Merge "Statically link libc++ into aapt2"
diff --git a/Android.bp b/Android.bp
index eb9cbbb..ea6a631 100644
--- a/Android.bp
+++ b/Android.bp
@@ -104,6 +104,11 @@
"core/java/android/app/backup/IRestoreObserver.aidl",
"core/java/android/app/backup/IRestoreSession.aidl",
"core/java/android/app/backup/ISelectBackupTransportCallback.aidl",
+ "core/java/android/app/contentsuggestions/IClassificationsCallback.aidl",
+ "core/java/android/app/contentsuggestions/IContentSuggestionsManager.aidl",
+ "core/java/android/app/contentsuggestions/ISelectionsCallback.aidl",
+ "core/java/android/app/prediction/IPredictionCallback.aidl",
+ "core/java/android/app/prediction/IPredictionManager.aidl",
"core/java/android/app/role/IOnRoleHoldersChangedListener.aidl",
"core/java/android/app/role/IRoleManager.aidl",
"core/java/android/app/role/IRoleManagerCallback.aidl",
@@ -273,6 +278,7 @@
"core/java/android/rolecontrollerservice/IRoleControllerService.aidl",
":keystore_aidl",
"core/java/android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl",
+ "core/java/android/service/appprediction/IPredictionService.aidl",
"core/java/android/service/autofill/augmented/IAugmentedAutofillService.aidl",
"core/java/android/service/autofill/augmented/IFillCallback.aidl",
"core/java/android/service/autofill/IAutoFillService.aidl",
@@ -282,6 +288,7 @@
"core/java/android/service/carrier/ICarrierService.aidl",
"core/java/android/service/carrier/ICarrierMessagingCallback.aidl",
"core/java/android/service/carrier/ICarrierMessagingService.aidl",
+ "core/java/android/service/contentsuggestions/IContentSuggestionsService.aidl",
"core/java/android/service/euicc/IDeleteSubscriptionCallback.aidl",
"core/java/android/service/euicc/IDownloadSubscriptionCallback.aidl",
"core/java/android/service/euicc/IEraseSubscriptionsCallback.aidl",
@@ -731,6 +738,7 @@
"android.hardware.contexthub-V1.0-java",
"android.hardware.health-V1.0-java-constants",
"android.hardware.thermal-V1.0-java-constants",
+ "android.hardware.thermal-V1.0-java",
"android.hardware.thermal-V1.1-java",
"android.hardware.thermal-V2.0-java",
"android.hardware.tv.input-V1.0-java-constants",
@@ -739,11 +747,13 @@
"android.hardware.vibrator-V1.0-java",
"android.hardware.vibrator-V1.1-java",
"android.hardware.vibrator-V1.2-java",
+ "android.hardware.vibrator-V1.3-java",
"android.hardware.wifi-V1.0-java-constants",
"android.hardware.radio-V1.0-java",
"android.hardware.radio-V1.3-java",
"android.hardware.radio-V1.4-java",
"android.hardware.usb.gadget-V1.0-java",
+ "networkstack-aidl-interfaces-java",
"netd_aidl_interface-java",
"devicepolicyprotosnano",
],
@@ -871,7 +881,16 @@
name: "networkstack-aidl-interfaces",
local_include_dir: "core/java",
srcs: [
+ "core/java/android/net/INetworkMonitor.aidl",
+ "core/java/android/net/INetworkMonitorCallbacks.aidl",
+ "core/java/android/net/IIpMemoryStore.aidl",
"core/java/android/net/INetworkStackConnector.aidl",
+ "core/java/android/net/INetworkStackStatusCallback.aidl",
+ "core/java/android/net/PrivateDnsConfigParcel.aidl",
+ "core/java/android/net/dhcp/DhcpServingParamsParcel.aidl",
+ "core/java/android/net/dhcp/IDhcpServer.aidl",
+ "core/java/android/net/dhcp/IDhcpServerCallbacks.aidl",
+ "core/java/android/net/ipmemorystore/**/*.aidl",
],
api_dir: "aidl/networkstack",
}
diff --git a/CleanSpec.mk b/CleanSpec.mk
index d01e183..30c2c69 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -252,6 +252,7 @@
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/vendor/overlay/ExperimentNavigationBarSlim)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/overlay/ExperimentNavigationBarSlim)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/SystemUI)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/media/audio)
# ******************************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER
# ******************************************************************
diff --git a/apct-tests/perftests/core/Android.mk b/apct-tests/perftests/core/Android.mk
index 77bacbe..3f87a1c 100644
--- a/apct-tests/perftests/core/Android.mk
+++ b/apct-tests/perftests/core/Android.mk
@@ -9,7 +9,7 @@
src/android/os/ISomeService.aidl
LOCAL_STATIC_JAVA_LIBRARIES := \
- android-support-test \
+ androidx.test.rules \
androidx.annotation_annotation \
apct-perftests-utils \
guava
diff --git a/apct-tests/perftests/core/AndroidManifest.xml b/apct-tests/perftests/core/AndroidManifest.xml
index 13c24d9..a564a4d 100644
--- a/apct-tests/perftests/core/AndroidManifest.xml
+++ b/apct-tests/perftests/core/AndroidManifest.xml
@@ -28,7 +28,7 @@
</application>
- <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.perftests.core"/>
</manifest>
diff --git a/apct-tests/perftests/core/src/android/accounts/AccountManagerPerfTest.java b/apct-tests/perftests/core/src/android/accounts/AccountManagerPerfTest.java
index b9411fa..e455e6a 100644
--- a/apct-tests/perftests/core/src/android/accounts/AccountManagerPerfTest.java
+++ b/apct-tests/perftests/core/src/android/accounts/AccountManagerPerfTest.java
@@ -23,10 +23,10 @@
import android.content.pm.PackageManager;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/apct-tests/perftests/core/src/android/app/PendingIntentPerfTest.java b/apct-tests/perftests/core/src/android/app/PendingIntentPerfTest.java
index f8fd51d..b3f8359 100644
--- a/apct-tests/perftests/core/src/android/app/PendingIntentPerfTest.java
+++ b/apct-tests/perftests/core/src/android/app/PendingIntentPerfTest.java
@@ -21,9 +21,10 @@
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.perftests.utils.StubActivity;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Rule;
@@ -33,7 +34,6 @@
// Due to b/71353150, you might get "java.lang.AssertionError: Binder ProxyMap has too many
// entries", but it's flaky. Adding "Runtime.getRuntime().gc()" between each iteration solves
// the problem, but it doesn't seem like it's currently needed.
-
@RunWith(AndroidJUnit4.class)
@LargeTest
public class PendingIntentPerfTest {
diff --git a/apct-tests/perftests/core/src/android/app/ResourcesPerfTest.java b/apct-tests/perftests/core/src/android/app/ResourcesPerfTest.java
index 9cdeb48..c3e43ee 100644
--- a/apct-tests/perftests/core/src/android/app/ResourcesPerfTest.java
+++ b/apct-tests/perftests/core/src/android/app/ResourcesPerfTest.java
@@ -23,7 +23,8 @@
import android.content.res.XmlResourceParser;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.filters.LargeTest;
+
+import androidx.test.filters.LargeTest;
import org.junit.After;
import org.junit.Before;
diff --git a/apct-tests/perftests/core/src/android/app/ResourcesThemePerfTest.java b/apct-tests/perftests/core/src/android/app/ResourcesThemePerfTest.java
index bb0627e5..1b07572 100644
--- a/apct-tests/perftests/core/src/android/app/ResourcesThemePerfTest.java
+++ b/apct-tests/perftests/core/src/android/app/ResourcesThemePerfTest.java
@@ -19,8 +19,9 @@
import android.content.res.Resources;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
import org.junit.Before;
import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/database/CursorWindowPerfTest.java b/apct-tests/perftests/core/src/android/database/CursorWindowPerfTest.java
index 897d0ae..c5ef80d 100644
--- a/apct-tests/perftests/core/src/android/database/CursorWindowPerfTest.java
+++ b/apct-tests/perftests/core/src/android/database/CursorWindowPerfTest.java
@@ -23,9 +23,10 @@
import android.database.sqlite.SQLiteDatabase;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
import org.junit.AfterClass;
import org.junit.BeforeClass;
diff --git a/apct-tests/perftests/core/src/android/database/SQLiteDatabaseIoPerfTest.java b/apct-tests/perftests/core/src/android/database/SQLiteDatabaseIoPerfTest.java
index 7c5316d..830302e 100644
--- a/apct-tests/perftests/core/src/android/database/SQLiteDatabaseIoPerfTest.java
+++ b/apct-tests/perftests/core/src/android/database/SQLiteDatabaseIoPerfTest.java
@@ -16,17 +16,20 @@
package android.database;
+import static org.junit.Assert.assertEquals;
+
import android.app.Activity;
import android.content.ContentValues;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
import android.util.ArrayMap;
import android.util.Log;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.internal.util.Preconditions;
import org.junit.After;
@@ -40,8 +43,6 @@
import java.util.List;
import java.util.Map;
-import static org.junit.Assert.assertEquals;
-
/**
* Performance tests for measuring amount of data written during typical DB operations
*
diff --git a/apct-tests/perftests/core/src/android/database/SQLiteDatabasePerfTest.java b/apct-tests/perftests/core/src/android/database/SQLiteDatabasePerfTest.java
index e2b75c3..973e996 100644
--- a/apct-tests/perftests/core/src/android/database/SQLiteDatabasePerfTest.java
+++ b/apct-tests/perftests/core/src/android/database/SQLiteDatabasePerfTest.java
@@ -16,14 +16,18 @@
package android.database;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
import android.content.ContentValues;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
@@ -33,9 +37,6 @@
import java.util.Random;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
/**
* Performance tests for typical CRUD operations and loading rows into the Cursor
*
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/CanvasPerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/CanvasPerfTest.java
index c742df3..9f09305 100644
--- a/apct-tests/perftests/core/src/android/graphics/perftests/CanvasPerfTest.java
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/CanvasPerfTest.java
@@ -24,7 +24,8 @@
import android.graphics.RenderNode;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.filters.LargeTest;
+
+import androidx.test.filters.LargeTest;
import org.junit.Rule;
import org.junit.Test;
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/OutlinePerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/OutlinePerfTest.java
index f9c3758..10a5128 100644
--- a/apct-tests/perftests/core/src/android/graphics/perftests/OutlinePerfTest.java
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/OutlinePerfTest.java
@@ -19,7 +19,8 @@
import android.graphics.Outline;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.filters.LargeTest;
+
+import androidx.test.filters.LargeTest;
import org.junit.Rule;
import org.junit.Test;
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 26b8309..3a80020 100644
--- a/apct-tests/perftests/core/src/android/graphics/perftests/PaintHasGlyphPerfTest.java
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/PaintHasGlyphPerfTest.java
@@ -20,17 +20,18 @@
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.perftests.utils.StubActivity;
-import android.support.test.filters.LargeTest;
-import android.support.test.rule.ActivityTestRule;
-import java.util.Arrays;
-import java.util.Collection;
+import androidx.test.filters.LargeTest;
+import androidx.test.rule.ActivityTestRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized.Parameters;
import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.Arrays;
+import java.util.Collection;
@LargeTest
@RunWith(Parameterized.class)
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/PaintMeasureTextTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/PaintMeasureTextTest.java
index b9ee613..fc8c673 100644
--- a/apct-tests/perftests/core/src/android/graphics/perftests/PaintMeasureTextTest.java
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/PaintMeasureTextTest.java
@@ -19,9 +19,10 @@
import android.graphics.Canvas;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.filters.LargeTest;
import android.text.TextPaint;
+import androidx.test.filters.LargeTest;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/PathPerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/PathPerfTest.java
index 7a49b4f..c6de9ec 100644
--- a/apct-tests/perftests/core/src/android/graphics/perftests/PathPerfTest.java
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/PathPerfTest.java
@@ -20,7 +20,8 @@
import android.graphics.RectF;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.filters.LargeTest;
+
+import androidx.test.filters.LargeTest;
import org.junit.Rule;
import org.junit.Test;
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/RenderNodePerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/RenderNodePerfTest.java
index 62dd124..e7fe235 100644
--- a/apct-tests/perftests/core/src/android/graphics/perftests/RenderNodePerfTest.java
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/RenderNodePerfTest.java
@@ -20,7 +20,8 @@
import android.graphics.RenderNode;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.filters.LargeTest;
+
+import androidx.test.filters.LargeTest;
import org.junit.Rule;
import org.junit.Test;
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceCreatePerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceCreatePerfTest.java
index 11ee599..d6e8ab2 100644
--- a/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceCreatePerfTest.java
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceCreatePerfTest.java
@@ -21,9 +21,14 @@
import android.graphics.Typeface;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.io.File;
import java.io.FileOutputStream;
@@ -31,10 +36,6 @@
import java.io.InputStream;
import java.io.OutputStream;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
@LargeTest
@RunWith(AndroidJUnit4.class)
public class TypefaceCreatePerfTest {
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 5533782..3b2b8a9 100644
--- a/apct-tests/perftests/core/src/android/graphics/perftests/VectorDrawablePerfTest.java
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/VectorDrawablePerfTest.java
@@ -16,6 +16,8 @@
package android.graphics.perftests;
+import static junit.framework.Assert.assertTrue;
+
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -25,21 +27,17 @@
import android.perftests.utils.BitmapUtils;
import android.perftests.utils.PerfStatusReporter;
import android.perftests.utils.StubActivity;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.rule.ActivityTestRule;
-import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.LargeTest;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.perftests.core.R;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.io.IOException;
-
-import static junit.framework.Assert.assertTrue;
-
@RunWith(AndroidJUnit4.class)
@LargeTest
public class VectorDrawablePerfTest {
diff --git a/apct-tests/perftests/core/src/android/os/BinderCallsStatsPerfTest.java b/apct-tests/perftests/core/src/android/os/BinderCallsStatsPerfTest.java
index 99e4ba1..12e49e3 100644
--- a/apct-tests/perftests/core/src/android/os/BinderCallsStatsPerfTest.java
+++ b/apct-tests/perftests/core/src/android/os/BinderCallsStatsPerfTest.java
@@ -18,8 +18,9 @@
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.BinderCallsStats;
import com.android.internal.os.BinderInternal.CallSession;
@@ -31,7 +32,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-
/**
* Performance tests for {@link BinderCallsStats}
*/
diff --git a/apct-tests/perftests/core/src/android/os/CpuUsageTrackingPerfTest.java b/apct-tests/perftests/core/src/android/os/CpuUsageTrackingPerfTest.java
index 4961b4f..0d7b7ca 100644
--- a/apct-tests/perftests/core/src/android/os/CpuUsageTrackingPerfTest.java
+++ b/apct-tests/perftests/core/src/android/os/CpuUsageTrackingPerfTest.java
@@ -18,8 +18,9 @@
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
@@ -28,7 +29,6 @@
import java.nio.file.Files;
import java.nio.file.Paths;
-
/**
* Performance tests collecting CPU data different mechanisms.
*/
diff --git a/apct-tests/perftests/core/src/android/os/KernelCpuThreadReaderPerfTest.java b/apct-tests/perftests/core/src/android/os/KernelCpuThreadReaderPerfTest.java
index 9034034..11c7599 100644
--- a/apct-tests/perftests/core/src/android/os/KernelCpuThreadReaderPerfTest.java
+++ b/apct-tests/perftests/core/src/android/os/KernelCpuThreadReaderPerfTest.java
@@ -20,8 +20,9 @@
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.KernelCpuThreadReader;
@@ -29,7 +30,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-
/**
* Performance tests collecting per-thread CPU data.
*/
diff --git a/apct-tests/perftests/core/src/android/os/LooperStatsPerfTest.java b/apct-tests/perftests/core/src/android/os/LooperStatsPerfTest.java
index 0f880b7..162167d 100644
--- a/apct-tests/perftests/core/src/android/os/LooperStatsPerfTest.java
+++ b/apct-tests/perftests/core/src/android/os/LooperStatsPerfTest.java
@@ -19,8 +19,9 @@
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.CachedDeviceState;
import com.android.internal.os.LooperStats;
@@ -31,7 +32,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-
/**
* Performance tests for {@link LooperStats}.
*/
diff --git a/apct-tests/perftests/core/src/android/os/PackageManagerPerfTest.java b/apct-tests/perftests/core/src/android/os/PackageManagerPerfTest.java
index 145fbcd..3aa6749 100644
--- a/apct-tests/perftests/core/src/android/os/PackageManagerPerfTest.java
+++ b/apct-tests/perftests/core/src/android/os/PackageManagerPerfTest.java
@@ -16,20 +16,16 @@
package android.os;
-import static android.content.pm.PackageManager.PERMISSION_DENIED;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-
import android.content.ComponentName;
-import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
-import org.junit.Assert;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/apct-tests/perftests/core/src/android/os/ParcelArrayPerfTest.java b/apct-tests/perftests/core/src/android/os/ParcelArrayPerfTest.java
index a67aeca..af6d6b0 100644
--- a/apct-tests/perftests/core/src/android/os/ParcelArrayPerfTest.java
+++ b/apct-tests/perftests/core/src/android/os/ParcelArrayPerfTest.java
@@ -18,7 +18,8 @@
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.filters.LargeTest;
+
+import androidx.test.filters.LargeTest;
import org.junit.After;
import org.junit.Before;
diff --git a/apct-tests/perftests/core/src/android/os/ParcelPerfTest.java b/apct-tests/perftests/core/src/android/os/ParcelPerfTest.java
index 6e4c9c5..4db9262 100644
--- a/apct-tests/perftests/core/src/android/os/ParcelPerfTest.java
+++ b/apct-tests/perftests/core/src/android/os/ParcelPerfTest.java
@@ -16,10 +16,15 @@
package android.os;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
@@ -27,10 +32,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
@RunWith(AndroidJUnit4.class)
@LargeTest
public class ParcelPerfTest {
diff --git a/apct-tests/perftests/core/src/android/os/PssPerfTest.java b/apct-tests/perftests/core/src/android/os/PssPerfTest.java
index 400115d..2cc294f 100644
--- a/apct-tests/perftests/core/src/android/os/PssPerfTest.java
+++ b/apct-tests/perftests/core/src/android/os/PssPerfTest.java
@@ -18,8 +18,9 @@
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/apct-tests/perftests/core/src/android/os/SharedPreferencesTest.java b/apct-tests/perftests/core/src/android/os/SharedPreferencesTest.java
index 099134f..dd479ac 100644
--- a/apct-tests/perftests/core/src/android/os/SharedPreferencesTest.java
+++ b/apct-tests/perftests/core/src/android/os/SharedPreferencesTest.java
@@ -20,9 +20,10 @@
import android.content.SharedPreferences;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/apct-tests/perftests/core/src/android/os/StrictModeTest.java b/apct-tests/perftests/core/src/android/os/StrictModeTest.java
index d973c20..60678e9 100644
--- a/apct-tests/perftests/core/src/android/os/StrictModeTest.java
+++ b/apct-tests/perftests/core/src/android/os/StrictModeTest.java
@@ -23,17 +23,21 @@
import android.net.Uri;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
import com.google.common.util.concurrent.SettableFuture;
-import java.io.File;
-import java.io.IOException;
-import java.util.concurrent.ExecutionException;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.File;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+
@RunWith(AndroidJUnit4.class)
@LargeTest
public class StrictModeTest {
diff --git a/apct-tests/perftests/core/src/android/os/TracePerfTest.java b/apct-tests/perftests/core/src/android/os/TracePerfTest.java
index 8e5cfaa..0d64c39 100644
--- a/apct-tests/perftests/core/src/android/os/TracePerfTest.java
+++ b/apct-tests/perftests/core/src/android/os/TracePerfTest.java
@@ -20,7 +20,8 @@
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.perftests.utils.ShellHelper;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.runner.AndroidJUnit4;
import org.junit.AfterClass;
import org.junit.Assert;
diff --git a/apct-tests/perftests/core/src/android/perftests/SystemPerfTest.java b/apct-tests/perftests/core/src/android/perftests/SystemPerfTest.java
index 95a7144..4f0d108 100644
--- a/apct-tests/perftests/core/src/android/perftests/SystemPerfTest.java
+++ b/apct-tests/perftests/core/src/android/perftests/SystemPerfTest.java
@@ -18,8 +18,9 @@
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
import dalvik.annotation.optimization.FastNative;
diff --git a/apct-tests/perftests/core/src/android/text/BoringLayoutCreateDrawPerfTest.java b/apct-tests/perftests/core/src/android/text/BoringLayoutCreateDrawPerfTest.java
index 9245c1b..c8121c5 100644
--- a/apct-tests/perftests/core/src/android/text/BoringLayoutCreateDrawPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/BoringLayoutCreateDrawPerfTest.java
@@ -22,9 +22,10 @@
import android.graphics.RenderNode;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.filters.LargeTest;
import android.text.NonEditableTextGenerator.TextType;
+import androidx.test.filters.LargeTest;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/apct-tests/perftests/core/src/android/text/BoringLayoutIsBoringPerfTest.java b/apct-tests/perftests/core/src/android/text/BoringLayoutIsBoringPerfTest.java
index 194a88c..ad1327c 100644
--- a/apct-tests/perftests/core/src/android/text/BoringLayoutIsBoringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/BoringLayoutIsBoringPerfTest.java
@@ -18,9 +18,10 @@
import android.graphics.Canvas;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.filters.LargeTest;
import android.text.NonEditableTextGenerator.TextType;
+import androidx.test.filters.LargeTest;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/apct-tests/perftests/core/src/android/text/CanvasDrawTextTest.java b/apct-tests/perftests/core/src/android/text/CanvasDrawTextTest.java
index ad9fb5f..bb6b691 100644
--- a/apct-tests/perftests/core/src/android/text/CanvasDrawTextTest.java
+++ b/apct-tests/perftests/core/src/android/text/CanvasDrawTextTest.java
@@ -19,8 +19,9 @@
import android.graphics.RenderNode;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/text/DynamicLayoutPerfTest.java b/apct-tests/perftests/core/src/android/text/DynamicLayoutPerfTest.java
index b4c7f54..5be99d9 100644
--- a/apct-tests/perftests/core/src/android/text/DynamicLayoutPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/DynamicLayoutPerfTest.java
@@ -24,11 +24,12 @@
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.perftests.utils.StubActivity;
-import android.support.test.filters.LargeTest;
-import android.support.test.rule.ActivityTestRule;
import android.text.style.ReplacementSpan;
import android.util.ArraySet;
+import androidx.test.filters.LargeTest;
+import androidx.test.rule.ActivityTestRule;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/apct-tests/perftests/core/src/android/text/PaintMeasureDrawPerfTest.java b/apct-tests/perftests/core/src/android/text/PaintMeasureDrawPerfTest.java
index a7972f5..bbe75b7 100644
--- a/apct-tests/perftests/core/src/android/text/PaintMeasureDrawPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/PaintMeasureDrawPerfTest.java
@@ -21,7 +21,8 @@
import android.graphics.RenderNode;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.filters.LargeTest;
+
+import androidx.test.filters.LargeTest;
import org.junit.Rule;
import org.junit.Test;
diff --git a/apct-tests/perftests/core/src/android/text/PrecomputedTextMemoryUsageTest.java b/apct-tests/perftests/core/src/android/text/PrecomputedTextMemoryUsageTest.java
index 00a6267..4ae2b93 100644
--- a/apct-tests/perftests/core/src/android/text/PrecomputedTextMemoryUsageTest.java
+++ b/apct-tests/perftests/core/src/android/text/PrecomputedTextMemoryUsageTest.java
@@ -18,9 +18,10 @@
import android.app.Activity;
import android.os.Bundle;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/apct-tests/perftests/core/src/android/text/PrecomputedTextPerfTest.java b/apct-tests/perftests/core/src/android/text/PrecomputedTextPerfTest.java
index 33b1a47..3be9114 100644
--- a/apct-tests/perftests/core/src/android/text/PrecomputedTextPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/PrecomputedTextPerfTest.java
@@ -18,8 +18,9 @@
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutCreateDrawPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutCreateDrawPerfTest.java
index b40dd6b..6c92229 100644
--- a/apct-tests/perftests/core/src/android/text/StaticLayoutCreateDrawPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/StaticLayoutCreateDrawPerfTest.java
@@ -22,9 +22,10 @@
import android.graphics.RenderNode;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.filters.LargeTest;
import android.text.NonEditableTextGenerator.TextType;
+import androidx.test.filters.LargeTest;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutGetOffsetForHorizontalPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutGetOffsetForHorizontalPerfTest.java
index 2768a7d..47e04ef 100644
--- a/apct-tests/perftests/core/src/android/text/StaticLayoutGetOffsetForHorizontalPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/StaticLayoutGetOffsetForHorizontalPerfTest.java
@@ -19,8 +19,8 @@
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutMultithreadPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutMultithreadPerfTest.java
index 60c6d89b..0b79834 100644
--- a/apct-tests/perftests/core/src/android/text/StaticLayoutMultithreadPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/StaticLayoutMultithreadPerfTest.java
@@ -19,17 +19,16 @@
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.Random;
import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
@LargeTest
@RunWith(AndroidJUnit4.class)
diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
index f60cbee6..bd7522d 100644
--- a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
@@ -21,8 +21,9 @@
import android.graphics.RenderNode;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/text/TextViewSetTextMeasurePerfTest.java b/apct-tests/perftests/core/src/android/text/TextViewSetTextMeasurePerfTest.java
index 25cc707..10bfa42 100644
--- a/apct-tests/perftests/core/src/android/text/TextViewSetTextMeasurePerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/TextViewSetTextMeasurePerfTest.java
@@ -23,11 +23,12 @@
import android.graphics.RenderNode;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
import android.text.NonEditableTextGenerator.TextType;
import android.widget.TextView;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java b/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java
index a482c4a..a7a81f2 100644
--- a/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java
+++ b/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java
@@ -18,13 +18,14 @@
import android.content.Context;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
import android.view.textclassifier.ConversationActions;
import android.view.textclassifier.TextClassificationManager;
import android.view.textclassifier.TextClassifier;
import android.view.textclassifier.TextLanguage;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
diff --git a/apct-tests/perftests/core/src/android/util/ArraySetPerfTest.java b/apct-tests/perftests/core/src/android/util/ArraySetPerfTest.java
index 0c1f289..b24bf42 100644
--- a/apct-tests/perftests/core/src/android/util/ArraySetPerfTest.java
+++ b/apct-tests/perftests/core/src/android/util/ArraySetPerfTest.java
@@ -18,8 +18,9 @@
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/apct-tests/perftests/core/src/android/util/perftests/LogPerfTest.java b/apct-tests/perftests/core/src/android/util/perftests/LogPerfTest.java
index 07cd33f..26cec95 100644
--- a/apct-tests/perftests/core/src/android/util/perftests/LogPerfTest.java
+++ b/apct-tests/perftests/core/src/android/util/perftests/LogPerfTest.java
@@ -18,11 +18,11 @@
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
-
import android.util.Log;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/apct-tests/perftests/core/src/android/view/ViewPerfTest.java b/apct-tests/perftests/core/src/android/view/ViewPerfTest.java
index 990be24..a1f8608 100644
--- a/apct-tests/perftests/core/src/android/view/ViewPerfTest.java
+++ b/apct-tests/perftests/core/src/android/view/ViewPerfTest.java
@@ -17,13 +17,13 @@
package android.view;
import android.content.Context;
-import android.content.res.Resources;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
import android.widget.FrameLayout;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+
import com.android.perftests.core.R;
import org.junit.Rule;
diff --git a/apct-tests/perftests/core/src/android/view/ViewShowHidePerfTest.java b/apct-tests/perftests/core/src/android/view/ViewShowHidePerfTest.java
index dc4d4bd..b34001d 100644
--- a/apct-tests/perftests/core/src/android/view/ViewShowHidePerfTest.java
+++ b/apct-tests/perftests/core/src/android/view/ViewShowHidePerfTest.java
@@ -24,14 +24,15 @@
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.perftests.utils.StubActivity;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.rule.ActivityTestRule;
import android.view.View.MeasureSpec;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.rule.ActivityTestRule;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/apct-tests/perftests/core/src/android/widget/EditTextBackspacePerfTest.java b/apct-tests/perftests/core/src/android/widget/EditTextBackspacePerfTest.java
index d219d3a..b3ea62a 100644
--- a/apct-tests/perftests/core/src/android/widget/EditTextBackspacePerfTest.java
+++ b/apct-tests/perftests/core/src/android/widget/EditTextBackspacePerfTest.java
@@ -19,14 +19,15 @@
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.perftests.utils.StubActivity;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.rule.ActivityTestRule;
import android.text.Selection;
import android.view.KeyEvent;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.rule.ActivityTestRule;
+
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
diff --git a/apct-tests/perftests/core/src/android/widget/EditTextCursorMovementPerfTest.java b/apct-tests/perftests/core/src/android/widget/EditTextCursorMovementPerfTest.java
index b6cf7d3..aa47d5b 100644
--- a/apct-tests/perftests/core/src/android/widget/EditTextCursorMovementPerfTest.java
+++ b/apct-tests/perftests/core/src/android/widget/EditTextCursorMovementPerfTest.java
@@ -19,14 +19,15 @@
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.perftests.utils.StubActivity;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.rule.ActivityTestRule;
import android.text.Selection;
import android.view.KeyEvent;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.rule.ActivityTestRule;
+
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
diff --git a/apct-tests/perftests/core/src/android/widget/EditTextLongTextPerfTest.java b/apct-tests/perftests/core/src/android/widget/EditTextLongTextPerfTest.java
index ce0c357..e50016c 100644
--- a/apct-tests/perftests/core/src/android/widget/EditTextLongTextPerfTest.java
+++ b/apct-tests/perftests/core/src/android/widget/EditTextLongTextPerfTest.java
@@ -16,10 +16,16 @@
package android.widget;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Locale;
-import java.util.Random;
+import android.app.Activity;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.perftests.utils.StubActivity;
+import android.view.KeyEvent;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.rule.ActivityTestRule;
import org.junit.Rule;
import org.junit.Test;
@@ -27,22 +33,9 @@
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
-import android.app.Activity;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.RenderNodeAnimator;
-import android.view.ViewGroup;
-import android.view.View.MeasureSpec;
-
-import android.perftests.utils.BenchmarkState;
-import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.StubActivity;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.test.rule.ActivityTestRule;
-import android.support.test.InstrumentationRegistry;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Random;
@LargeTest
@RunWith(Parameterized.class)
diff --git a/apct-tests/perftests/core/src/android/widget/LayoutPerfTest.java b/apct-tests/perftests/core/src/android/widget/LayoutPerfTest.java
index d570ef3..644095b 100644
--- a/apct-tests/perftests/core/src/android/widget/LayoutPerfTest.java
+++ b/apct-tests/perftests/core/src/android/widget/LayoutPerfTest.java
@@ -16,17 +16,26 @@
package android.widget;
+import static android.perftests.utils.LayoutUtils.gatherViewTree;
+import static android.perftests.utils.LayoutUtils.requestLayoutForAllNodes;
+import static android.view.View.MeasureSpec.AT_MOST;
+import static android.view.View.MeasureSpec.EXACTLY;
+import static android.view.View.MeasureSpec.UNSPECIFIED;
+
+import static org.junit.Assert.assertTrue;
+
import android.app.Activity;
import android.os.Looper;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.perftests.utils.StubActivity;
-import android.support.test.annotation.UiThreadTest;
-import android.support.test.filters.LargeTest;
-import android.support.test.rule.ActivityTestRule;
import android.view.View;
import android.view.ViewGroup;
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.filters.LargeTest;
+import androidx.test.rule.ActivityTestRule;
+
import com.android.perftests.core.R;
import org.junit.Rule;
@@ -38,13 +47,6 @@
import java.util.Collection;
import java.util.List;
-import static android.perftests.utils.LayoutUtils.gatherViewTree;
-import static android.perftests.utils.LayoutUtils.requestLayoutForAllNodes;
-import static android.view.View.MeasureSpec.AT_MOST;
-import static android.view.View.MeasureSpec.EXACTLY;
-import static android.view.View.MeasureSpec.UNSPECIFIED;
-import static org.junit.Assert.assertTrue;
-
@LargeTest
@RunWith(Parameterized.class)
public class LayoutPerfTest {
diff --git a/apct-tests/perftests/core/src/android/widget/TextViewAutoSizeLayoutPerfTest.java b/apct-tests/perftests/core/src/android/widget/TextViewAutoSizeLayoutPerfTest.java
index c310166..bed173b 100644
--- a/apct-tests/perftests/core/src/android/widget/TextViewAutoSizeLayoutPerfTest.java
+++ b/apct-tests/perftests/core/src/android/widget/TextViewAutoSizeLayoutPerfTest.java
@@ -16,33 +16,27 @@
package android.widget;
+import static org.junit.Assert.assertTrue;
+
import android.app.Activity;
import android.os.Looper;
-import android.os.Bundle;
-import android.perftests.utils.PerfStatusReporter;
-import android.util.Log;
-import android.view.View;
-
import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
import android.perftests.utils.StubActivity;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.test.rule.ActivityTestRule;
-import android.support.test.InstrumentationRegistry;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.rule.ActivityTestRule;
import com.android.perftests.core.R;
-import java.util.Locale;
-import java.util.Collection;
-import java.util.Arrays;
-
-import org.junit.Test;
import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
-import org.junit.runner.RunWith;
-import static org.junit.Assert.assertTrue;
+import java.util.Arrays;
+import java.util.Collection;
@LargeTest
@RunWith(Parameterized.class)
diff --git a/apct-tests/perftests/core/src/android/widget/TextViewFontFamilyLayoutPerfTest.java b/apct-tests/perftests/core/src/android/widget/TextViewFontFamilyLayoutPerfTest.java
index 4b6da6b..1f00838 100644
--- a/apct-tests/perftests/core/src/android/widget/TextViewFontFamilyLayoutPerfTest.java
+++ b/apct-tests/perftests/core/src/android/widget/TextViewFontFamilyLayoutPerfTest.java
@@ -19,23 +19,21 @@
import android.content.Context;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
import android.view.LayoutInflater;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+
import com.android.perftests.core.R;
-import java.util.Collection;
-import java.util.Arrays;
-
-import org.junit.Test;
import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
-import org.junit.runner.RunWith;
-import static org.junit.Assert.assertTrue;
+import java.util.Arrays;
+import java.util.Collection;
@LargeTest
@RunWith(Parameterized.class)
diff --git a/apct-tests/perftests/core/src/android/widget/TextViewOnMeasurePerfTest.java b/apct-tests/perftests/core/src/android/widget/TextViewOnMeasurePerfTest.java
index a14dd25..88acbba 100644
--- a/apct-tests/perftests/core/src/android/widget/TextViewOnMeasurePerfTest.java
+++ b/apct-tests/perftests/core/src/android/widget/TextViewOnMeasurePerfTest.java
@@ -25,24 +25,20 @@
import android.graphics.Typeface;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.TextAppearanceSpan;
-import android.view.LayoutInflater;
-import com.android.perftests.core.R;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
-import java.util.Random;
-import java.util.Locale;
-
-import org.junit.Test;
import org.junit.Rule;
+import org.junit.Test;
import org.junit.runner.RunWith;
-import static org.junit.Assert.assertTrue;
+import java.util.Locale;
+import java.util.Random;
@LargeTest
@RunWith(AndroidJUnit4.class)
diff --git a/apct-tests/perftests/core/src/android/widget/TextViewPrecomputedTextPerfTest.java b/apct-tests/perftests/core/src/android/widget/TextViewPrecomputedTextPerfTest.java
index bd91112..0bc9ee4e 100644
--- a/apct-tests/perftests/core/src/android/widget/TextViewPrecomputedTextPerfTest.java
+++ b/apct-tests/perftests/core/src/android/widget/TextViewPrecomputedTextPerfTest.java
@@ -24,9 +24,6 @@
import android.graphics.RenderNode;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
import android.text.BoringLayout;
import android.text.Layout;
import android.text.PrecomputedText;
@@ -34,6 +31,10 @@
import android.text.TextPerfUtils;
import android.view.View.MeasureSpec;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
diff --git a/apct-tests/perftests/core/src/android/widget/TextViewSetTextLocalePerfTest.java b/apct-tests/perftests/core/src/android/widget/TextViewSetTextLocalePerfTest.java
index e95676b..00bd8db 100644
--- a/apct-tests/perftests/core/src/android/widget/TextViewSetTextLocalePerfTest.java
+++ b/apct-tests/perftests/core/src/android/widget/TextViewSetTextLocalePerfTest.java
@@ -19,8 +19,9 @@
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.perftests.utils.StubActivity;
-import android.support.test.filters.LargeTest;
-import android.support.test.rule.ActivityTestRule;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.rule.ActivityTestRule;
import org.junit.Rule;
import org.junit.Test;
diff --git a/api/current.txt b/api/current.txt
index 29d459e..0ff1743 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5385,11 +5385,11 @@
method public android.graphics.drawable.Icon getIcon();
method public android.app.RemoteInput[] getRemoteInputs();
method public int getSemanticAction();
+ method public boolean isContextual();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.Notification.Action> CREATOR;
field public static final int SEMANTIC_ACTION_ARCHIVE = 5; // 0x5
field public static final int SEMANTIC_ACTION_CALL = 10; // 0xa
- field public static final int SEMANTIC_ACTION_CONTEXTUAL_SUGGESTION = 11; // 0xb
field public static final int SEMANTIC_ACTION_DELETE = 4; // 0x4
field public static final int SEMANTIC_ACTION_MARK_AS_READ = 2; // 0x2
field public static final int SEMANTIC_ACTION_MARK_AS_UNREAD = 3; // 0x3
@@ -5414,6 +5414,7 @@
method public android.app.Notification.Action.Builder extend(android.app.Notification.Action.Extender);
method public android.os.Bundle getExtras();
method public android.app.Notification.Action.Builder setAllowGeneratedReplies(boolean);
+ method public android.app.Notification.Action.Builder setContextual(boolean);
method public android.app.Notification.Action.Builder setSemanticAction(int);
}
@@ -8531,42 +8532,44 @@
field public static final java.lang.String VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY = "android.bluetooth.headset.intent.category.companyid";
}
- public final class BluetoothHealth implements android.bluetooth.BluetoothProfile {
- method public boolean connectChannelToSource(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothHealthAppConfiguration);
- method public boolean disconnectChannel(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothHealthAppConfiguration, int);
+ public final deprecated class BluetoothHealth implements android.bluetooth.BluetoothProfile {
+ ctor public BluetoothHealth();
+ method public deprecated boolean connectChannelToSource(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothHealthAppConfiguration);
+ method public deprecated boolean disconnectChannel(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothHealthAppConfiguration, int);
method public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
method public int getConnectionState(android.bluetooth.BluetoothDevice);
method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
- method public android.os.ParcelFileDescriptor getMainChannelFd(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothHealthAppConfiguration);
- method public boolean registerSinkAppConfiguration(java.lang.String, int, android.bluetooth.BluetoothHealthCallback);
- method public boolean unregisterAppConfiguration(android.bluetooth.BluetoothHealthAppConfiguration);
- field public static final int APP_CONFIG_REGISTRATION_FAILURE = 1; // 0x1
- field public static final int APP_CONFIG_REGISTRATION_SUCCESS = 0; // 0x0
- field public static final int APP_CONFIG_UNREGISTRATION_FAILURE = 3; // 0x3
- field public static final int APP_CONFIG_UNREGISTRATION_SUCCESS = 2; // 0x2
- field public static final int CHANNEL_TYPE_RELIABLE = 10; // 0xa
- field public static final int CHANNEL_TYPE_STREAMING = 11; // 0xb
- field public static final int SINK_ROLE = 2; // 0x2
- field public static final int SOURCE_ROLE = 1; // 0x1
- field public static final int STATE_CHANNEL_CONNECTED = 2; // 0x2
- field public static final int STATE_CHANNEL_CONNECTING = 1; // 0x1
- field public static final int STATE_CHANNEL_DISCONNECTED = 0; // 0x0
- field public static final int STATE_CHANNEL_DISCONNECTING = 3; // 0x3
+ method public deprecated android.os.ParcelFileDescriptor getMainChannelFd(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothHealthAppConfiguration);
+ method public deprecated boolean registerSinkAppConfiguration(java.lang.String, int, android.bluetooth.BluetoothHealthCallback);
+ method public deprecated boolean unregisterAppConfiguration(android.bluetooth.BluetoothHealthAppConfiguration);
+ field public static final deprecated int APP_CONFIG_REGISTRATION_FAILURE = 1; // 0x1
+ field public static final deprecated int APP_CONFIG_REGISTRATION_SUCCESS = 0; // 0x0
+ field public static final deprecated int APP_CONFIG_UNREGISTRATION_FAILURE = 3; // 0x3
+ field public static final deprecated int APP_CONFIG_UNREGISTRATION_SUCCESS = 2; // 0x2
+ field public static final deprecated int CHANNEL_TYPE_RELIABLE = 10; // 0xa
+ field public static final deprecated int CHANNEL_TYPE_STREAMING = 11; // 0xb
+ field public static final deprecated int SINK_ROLE = 2; // 0x2
+ field public static final deprecated int SOURCE_ROLE = 1; // 0x1
+ field public static final deprecated int STATE_CHANNEL_CONNECTED = 2; // 0x2
+ field public static final deprecated int STATE_CHANNEL_CONNECTING = 1; // 0x1
+ field public static final deprecated int STATE_CHANNEL_DISCONNECTED = 0; // 0x0
+ field public static final deprecated int STATE_CHANNEL_DISCONNECTING = 3; // 0x3
}
- public final class BluetoothHealthAppConfiguration implements android.os.Parcelable {
+ public final deprecated class BluetoothHealthAppConfiguration implements android.os.Parcelable {
+ ctor public BluetoothHealthAppConfiguration();
method public int describeContents();
- method public int getDataType();
- method public java.lang.String getName();
- method public int getRole();
+ method public deprecated int getDataType();
+ method public deprecated java.lang.String getName();
+ method public deprecated int getRole();
method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothHealthAppConfiguration> CREATOR;
+ field public static final deprecated android.os.Parcelable.Creator<android.bluetooth.BluetoothHealthAppConfiguration> CREATOR;
}
- public abstract class BluetoothHealthCallback {
+ public abstract deprecated class BluetoothHealthCallback {
ctor public BluetoothHealthCallback();
- method public void onHealthAppConfigurationStatusChange(android.bluetooth.BluetoothHealthAppConfiguration, int);
- method public void onHealthChannelStateChange(android.bluetooth.BluetoothHealthAppConfiguration, android.bluetooth.BluetoothDevice, int, int, android.os.ParcelFileDescriptor, int);
+ method public deprecated void onHealthAppConfigurationStatusChange(android.bluetooth.BluetoothHealthAppConfiguration, int);
+ method public deprecated void onHealthChannelStateChange(android.bluetooth.BluetoothHealthAppConfiguration, android.bluetooth.BluetoothDevice, int, int, android.os.ParcelFileDescriptor, int);
}
public final class BluetoothHidDevice implements android.bluetooth.BluetoothProfile {
@@ -8663,7 +8666,7 @@
field public static final int GATT = 7; // 0x7
field public static final int GATT_SERVER = 8; // 0x8
field public static final int HEADSET = 1; // 0x1
- field public static final int HEALTH = 3; // 0x3
+ field public static final deprecated int HEALTH = 3; // 0x3
field public static final int HID_DEVICE = 19; // 0x13
field public static final int SAP = 10; // 0xa
field public static final int STATE_CONNECTED = 2; // 0x2
@@ -10199,6 +10202,7 @@
field public static final java.lang.String ACTION_PACKAGE_RESTARTED = "android.intent.action.PACKAGE_RESTARTED";
field public static final java.lang.String ACTION_PACKAGE_VERIFIED = "android.intent.action.PACKAGE_VERIFIED";
field public static final java.lang.String ACTION_PASTE = "android.intent.action.PASTE";
+ field public static final java.lang.String ACTION_PERMISSION_USAGE_DETAILS = "android.intent.action.PERMISSION_USAGE_DETAILS";
field public static final java.lang.String ACTION_PICK = "android.intent.action.PICK";
field public static final java.lang.String ACTION_PICK_ACTIVITY = "android.intent.action.PICK_ACTIVITY";
field public static final java.lang.String ACTION_POWER_CONNECTED = "android.intent.action.ACTION_POWER_CONNECTED";
@@ -10323,6 +10327,7 @@
field public static final java.lang.String EXTRA_NOT_UNKNOWN_SOURCE = "android.intent.extra.NOT_UNKNOWN_SOURCE";
field public static final java.lang.String EXTRA_ORIGINATING_URI = "android.intent.extra.ORIGINATING_URI";
field public static final java.lang.String EXTRA_PACKAGE_NAME = "android.intent.extra.PACKAGE_NAME";
+ field public static final java.lang.String EXTRA_PERMISSION_USAGE_PERMISSIONS = "android.intent.extra.PERMISSION_USAGE_PERMISSIONS";
field public static final java.lang.String EXTRA_PHONE_NUMBER = "android.intent.extra.PHONE_NUMBER";
field public static final java.lang.String EXTRA_PROCESS_TEXT = "android.intent.extra.PROCESS_TEXT";
field public static final java.lang.String EXTRA_PROCESS_TEXT_READONLY = "android.intent.extra.PROCESS_TEXT_READONLY";
@@ -24942,6 +24947,25 @@
field public static final int TYPE_STRING = 4; // 0x4
}
+ public final class MediaItem2 implements android.os.Parcelable {
+ method public int describeContents();
+ method public long getEndPosition();
+ method public android.media.MediaMetadata getMetadata();
+ method public long getStartPosition();
+ method public void setMetadata(android.media.MediaMetadata);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.media.MediaItem2> CREATOR;
+ field public static final long POSITION_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
+ }
+
+ public static class MediaItem2.Builder {
+ ctor public MediaItem2.Builder();
+ method public android.media.MediaItem2 build();
+ method public android.media.MediaItem2.Builder setEndPosition(long);
+ method public android.media.MediaItem2.Builder setMetadata(android.media.MediaMetadata);
+ method public android.media.MediaItem2.Builder setStartPosition(long);
+ }
+
public final class MediaMetadata implements android.os.Parcelable {
method public boolean containsKey(java.lang.String);
method public int describeContents();
@@ -25340,12 +25364,15 @@
method public java.lang.Object clearNextDataSources();
method public void clearPendingCommands();
method public void close();
+ method public java.lang.Object deselectTrack(int);
method public java.lang.Object deselectTrack(android.media.DataSourceDesc, int);
method public android.media.AudioAttributes getAudioAttributes();
method public int getAudioSessionId();
+ method public long getBufferedPosition();
method public long getBufferedPosition(android.media.DataSourceDesc);
method public android.media.DataSourceDesc getCurrentDataSource();
method public long getCurrentPosition();
+ method public long getDuration();
method public long getDuration(android.media.DataSourceDesc);
method public float getMaxPlayerVolume();
method public android.os.PersistableBundle getMetrics();
@@ -25353,10 +25380,12 @@
method public float getPlayerVolume();
method public android.media.AudioDeviceInfo getPreferredDevice();
method public android.media.AudioDeviceInfo getRoutedDevice();
+ method public int getSelectedTrack(int);
method public int getSelectedTrack(android.media.DataSourceDesc, int);
method public int getState();
method public android.media.SyncParams getSyncParams();
method public android.media.MediaTimestamp getTimestamp();
+ method public java.util.List<android.media.MediaPlayer2.TrackInfo> getTrackInfo();
method public java.util.List<android.media.MediaPlayer2.TrackInfo> getTrackInfo(android.media.DataSourceDesc);
method public android.media.VideoSize getVideoSize();
method public boolean isLooping();
@@ -25370,6 +25399,7 @@
method public void reset();
method public java.lang.Object seekTo(long);
method public java.lang.Object seekTo(long, int);
+ method public java.lang.Object selectTrack(int);
method public java.lang.Object selectTrack(android.media.DataSourceDesc, int);
method public java.lang.Object setAudioAttributes(android.media.AudioAttributes);
method public java.lang.Object setAudioSessionId(int);
@@ -26046,6 +26076,27 @@
field public static final int URI_COLUMN_INDEX = 2; // 0x2
}
+ public final class Session2Command implements android.os.Parcelable {
+ ctor public Session2Command(int);
+ ctor public Session2Command(java.lang.String, android.os.Bundle);
+ method public int describeContents();
+ method public int getCommandCode();
+ method public java.lang.String getCustomCommand();
+ method public android.os.Bundle getExtras();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final int COMMAND_CODE_CUSTOM = 0; // 0x0
+ field public static final android.os.Parcelable.Creator<android.media.Session2Command> CREATOR;
+ field public static final int RESULT_ERROR_UNKNOWN_ERROR = -1; // 0xffffffff
+ field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
+ field public static final int RESULT_SUCCESS = 0; // 0x0
+ }
+
+ public static final class Session2Command.Result {
+ ctor public Session2Command.Result(int, android.os.Bundle);
+ method public int getResultCode();
+ method public android.os.Bundle getResultData();
+ }
+
public class SoundPool {
ctor public deprecated SoundPool(int, int, int);
method public final void autoPause();
@@ -27091,6 +27142,21 @@
package android.media.session {
+ public final class ControllerCallbackLink implements android.os.Parcelable {
+ method public int describeContents();
+ method public android.os.IBinder getBinder();
+ method public void notifyEvent(java.lang.String, android.os.Bundle);
+ method public void notifyExtrasChanged(android.os.Bundle);
+ method public void notifyMetadataChanged(android.media.MediaMetadata);
+ method public void notifyPlaybackStateChanged(android.media.session.PlaybackState);
+ method public void notifyQueueChanged(java.util.List<android.media.session.MediaSession.QueueItem>);
+ method public void notifyQueueTitleChanged(java.lang.CharSequence);
+ method public void notifySessionDestroyed();
+ method public void notifyVolumeInfoChanged(android.media.session.MediaController.PlaybackInfo);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.media.session.ControllerCallbackLink> CREATOR;
+ }
+
public final class MediaController {
ctor public MediaController(android.content.Context, android.media.session.MediaSession.Token);
method public void adjustVolume(int, int);
@@ -27126,12 +27192,15 @@
method public void onSessionEvent(java.lang.String, android.os.Bundle);
}
- public static final class MediaController.PlaybackInfo {
+ public static final class MediaController.PlaybackInfo implements android.os.Parcelable {
+ method public int describeContents();
method public android.media.AudioAttributes getAudioAttributes();
method public int getCurrentVolume();
method public int getMaxVolume();
method public int getPlaybackType();
method public int getVolumeControl();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.media.session.MediaController.PlaybackInfo> CREATOR;
field public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1
field public static final int PLAYBACK_TYPE_REMOTE = 2; // 0x2
}
@@ -27322,6 +27391,36 @@
method public android.media.session.PlaybackState.CustomAction.Builder setExtras(android.os.Bundle);
}
+ public final class SessionCallbackLink implements android.os.Parcelable {
+ method public int describeContents();
+ method public android.os.IBinder getBinder();
+ method public void notifyAdjustVolume(java.lang.String, int, int, android.media.session.ControllerCallbackLink, int);
+ method public void notifyCommand(java.lang.String, int, int, android.media.session.ControllerCallbackLink, java.lang.String, android.os.Bundle, android.os.ResultReceiver);
+ method public void notifyCustomAction(java.lang.String, int, int, android.media.session.ControllerCallbackLink, java.lang.String, android.os.Bundle);
+ method public void notifyFastForward(java.lang.String, int, int, android.media.session.ControllerCallbackLink);
+ method public void notifyMediaButton(java.lang.String, int, int, android.content.Intent, int, android.os.ResultReceiver);
+ method public void notifyMediaButtonFromController(java.lang.String, int, int, android.media.session.ControllerCallbackLink, android.content.Intent);
+ method public void notifyNext(java.lang.String, int, int, android.media.session.ControllerCallbackLink);
+ method public void notifyPause(java.lang.String, int, int, android.media.session.ControllerCallbackLink);
+ method public void notifyPlay(java.lang.String, int, int, android.media.session.ControllerCallbackLink);
+ method public void notifyPlayFromMediaId(java.lang.String, int, int, android.media.session.ControllerCallbackLink, java.lang.String, android.os.Bundle);
+ method public void notifyPlayFromSearch(java.lang.String, int, int, android.media.session.ControllerCallbackLink, java.lang.String, android.os.Bundle);
+ method public void notifyPlayFromUri(java.lang.String, int, int, android.media.session.ControllerCallbackLink, android.net.Uri, android.os.Bundle);
+ method public void notifyPrepare(java.lang.String, int, int, android.media.session.ControllerCallbackLink);
+ method public void notifyPrepareFromMediaId(java.lang.String, int, int, android.media.session.ControllerCallbackLink, java.lang.String, android.os.Bundle);
+ method public void notifyPrepareFromSearch(java.lang.String, int, int, android.media.session.ControllerCallbackLink, java.lang.String, android.os.Bundle);
+ method public void notifyPrepareFromUri(java.lang.String, int, int, android.media.session.ControllerCallbackLink, android.net.Uri, android.os.Bundle);
+ method public void notifyPrevious(java.lang.String, int, int, android.media.session.ControllerCallbackLink);
+ method public void notifyRate(java.lang.String, int, int, android.media.session.ControllerCallbackLink, android.media.Rating);
+ method public void notifyRewind(java.lang.String, int, int, android.media.session.ControllerCallbackLink);
+ method public void notifySeekTo(java.lang.String, int, int, android.media.session.ControllerCallbackLink, long);
+ method public void notifySetVolumeTo(java.lang.String, int, int, android.media.session.ControllerCallbackLink, int);
+ method public void notifySkipToTrack(java.lang.String, int, int, android.media.session.ControllerCallbackLink, long);
+ method public void notifyStop(java.lang.String, int, int, android.media.session.ControllerCallbackLink);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.media.session.SessionCallbackLink> CREATOR;
+ }
+
}
package android.media.tv {
@@ -29649,9 +29748,6 @@
method public void setReferenceCounted(boolean);
}
- public static abstract class WifiManager.NetworkSuggestionsStatusCode implements java.lang.annotation.Annotation {
- }
-
public class WifiManager.WifiLock {
method public void acquire();
method public boolean isHeld();
@@ -30016,6 +30112,7 @@
ctor public WifiP2pGroup(android.net.wifi.p2p.WifiP2pGroup);
method public int describeContents();
method public java.util.Collection<android.net.wifi.p2p.WifiP2pDevice> getClientList();
+ method public int getFrequency();
method public java.lang.String getInterface();
method public java.lang.String getNetworkName();
method public android.net.wifi.p2p.WifiP2pDevice getOwner();
@@ -33675,6 +33772,7 @@
method public static final void flushPendingCommands();
method public static final int getCallingPid();
method public static final int getCallingUid();
+ method public static final int getCallingUidOrThrow();
method public static final android.os.UserHandle getCallingUserHandle();
method public java.lang.String getInterfaceDescriptor();
method public boolean isBinderAlive();
@@ -34339,6 +34437,8 @@
method public java.util.ArrayList<java.lang.String> createStringArrayList();
method public <T> T[] createTypedArray(android.os.Parcelable.Creator<T>);
method public <T> java.util.ArrayList<T> createTypedArrayList(android.os.Parcelable.Creator<T>);
+ method public <T extends android.os.Parcelable> android.util.ArrayMap<java.lang.String, T> createTypedArrayMap(android.os.Parcelable.Creator<T>);
+ method public <T extends android.os.Parcelable> android.util.SparseArray<T> createTypedSparseArray(android.os.Parcelable.Creator<T>);
method public int dataAvail();
method public int dataCapacity();
method public int dataPosition();
@@ -34380,7 +34480,7 @@
method public java.io.Serializable readSerializable();
method public android.util.Size readSize();
method public android.util.SizeF readSizeF();
- method public android.util.SparseArray readSparseArray(java.lang.ClassLoader);
+ method public <T> android.util.SparseArray<T> readSparseArray(java.lang.ClassLoader);
method public android.util.SparseBooleanArray readSparseBooleanArray();
method public java.lang.String readString();
method public void readStringArray(java.lang.String[]);
@@ -34426,7 +34526,7 @@
method public void writeSerializable(java.io.Serializable);
method public void writeSize(android.util.Size);
method public void writeSizeF(android.util.SizeF);
- method public void writeSparseArray(android.util.SparseArray<java.lang.Object>);
+ method public <T> void writeSparseArray(android.util.SparseArray<T>);
method public void writeSparseBooleanArray(android.util.SparseBooleanArray);
method public void writeString(java.lang.String);
method public void writeStringArray(java.lang.String[]);
@@ -34434,8 +34534,10 @@
method public void writeStrongBinder(android.os.IBinder);
method public void writeStrongInterface(android.os.IInterface);
method public <T extends android.os.Parcelable> void writeTypedArray(T[], int);
+ method public <T extends android.os.Parcelable> void writeTypedArrayMap(android.util.ArrayMap<java.lang.String, T>, int);
method public <T extends android.os.Parcelable> void writeTypedList(java.util.List<T>);
method public <T extends android.os.Parcelable> void writeTypedObject(T, int);
+ method public <T extends android.os.Parcelable> void writeTypedSparseArray(android.util.SparseArray<T>, int);
method public void writeValue(java.lang.Object);
field public static final android.os.Parcelable.Creator<java.lang.String> STRING_CREATOR;
}
@@ -37864,7 +37966,11 @@
method public static java.lang.String getVolumeName(android.net.Uri);
method public static android.provider.MediaStore.PendingSession openPending(android.content.Context, android.net.Uri);
method public static android.net.Uri setIncludePending(android.net.Uri);
+ method public static android.net.Uri setIncludeTrashed(android.net.Uri);
method public static android.net.Uri setRequireOriginal(android.net.Uri);
+ method public static void trash(android.content.Context, android.net.Uri);
+ method public static void trash(android.content.Context, android.net.Uri, long);
+ method public static void untrash(android.content.Context, android.net.Uri);
field public static final java.lang.String ACTION_IMAGE_CAPTURE = "android.media.action.IMAGE_CAPTURE";
field public static final java.lang.String ACTION_IMAGE_CAPTURE_SECURE = "android.media.action.IMAGE_CAPTURE_SECURE";
field public static final java.lang.String ACTION_REVIEW = "android.provider.action.REVIEW";
@@ -38043,6 +38149,7 @@
}
public static abstract interface MediaStore.DownloadColumns implements android.provider.MediaStore.MediaColumns {
+ field public static final java.lang.String DESCRIPTION = "description";
field public static final java.lang.String DOWNLOAD_URI = "download_uri";
field public static final java.lang.String REFERER_URI = "referer_uri";
}
@@ -38087,6 +38194,7 @@
field public static final deprecated java.lang.String MINI_THUMB_MAGIC = "mini_thumb_magic";
field public static final java.lang.String ORIENTATION = "orientation";
field public static final deprecated java.lang.String PICASA_ID = "picasa_id";
+ field public static final java.lang.String SECONDARY_BUCKET_ID = "secondary_bucket_id";
}
public static final class MediaStore.Images.Media implements android.provider.MediaStore.Images.ImageColumns {
@@ -38131,11 +38239,13 @@
public static abstract interface MediaStore.MediaColumns implements android.provider.BaseColumns {
field public static final deprecated java.lang.String DATA = "_data";
field public static final java.lang.String DATE_ADDED = "date_added";
+ field public static final java.lang.String DATE_EXPIRES = "date_expires";
field public static final java.lang.String DATE_MODIFIED = "date_modified";
field public static final java.lang.String DISPLAY_NAME = "_display_name";
field public static final java.lang.String HASH = "_hash";
field public static final java.lang.String HEIGHT = "height";
field public static final java.lang.String IS_PENDING = "is_pending";
+ field public static final java.lang.String IS_TRASHED = "is_trashed";
field public static final java.lang.String MIME_TYPE = "mime_type";
field public static final java.lang.String OWNER_PACKAGE_NAME = "owner_package_name";
field public static final java.lang.String SIZE = "_size";
@@ -38211,6 +38321,7 @@
field public static final deprecated java.lang.String LONGITUDE = "longitude";
field public static final deprecated java.lang.String MINI_THUMB_MAGIC = "mini_thumb_magic";
field public static final java.lang.String RESOLUTION = "resolution";
+ field public static final java.lang.String SECONDARY_BUCKET_ID = "secondary_bucket_id";
field public static final java.lang.String TAGS = "tags";
}
@@ -42618,6 +42729,7 @@
method public static java.lang.String capabilitiesToString(int);
method public android.telecom.PhoneAccountHandle getAccountHandle();
method public int getCallCapabilities();
+ method public int getCallDirection();
method public android.telecom.CallIdentification getCallIdentification();
method public int getCallProperties();
method public java.lang.String getCallerDisplayName();
@@ -42654,6 +42766,9 @@
field public static final int CAPABILITY_SUPPORT_DEFLECT = 16777216; // 0x1000000
field public static final int CAPABILITY_SUPPORT_HOLD = 2; // 0x2
field public static final int CAPABILITY_SWAP_CONFERENCE = 8; // 0x8
+ field public static final int DIRECTION_INCOMING = 0; // 0x0
+ field public static final int DIRECTION_OUTGOING = 1; // 0x1
+ field public static final int DIRECTION_UNKNOWN = -1; // 0xffffffff
field public static final int PROPERTY_CONFERENCE = 1; // 0x1
field public static final int PROPERTY_EMERGENCY_CALLBACK_MODE = 4; // 0x4
field public static final int PROPERTY_ENTERPRISE_CALL = 32; // 0x20
@@ -42729,10 +42844,10 @@
ctor public CallRedirectionService();
method public final void cancelCall();
method public final android.os.IBinder onBind(android.content.Intent);
- method public abstract void onPlaceCall(android.net.Uri, android.telecom.PhoneAccountHandle);
+ method public abstract void onPlaceCall(android.net.Uri, android.telecom.PhoneAccountHandle, boolean);
method public final boolean onUnbind(android.content.Intent);
method public final void placeCallUnmodified();
- method public final void redirectCall(android.net.Uri, android.telecom.PhoneAccountHandle);
+ method public final void redirectCall(android.net.Uri, android.telecom.PhoneAccountHandle, boolean);
field public static final java.lang.String SERVICE_INTERFACE = "android.telecom.CallRedirectionService";
}
@@ -44574,6 +44689,7 @@
field public static final int DATA_CONNECTING = 1; // 0x1
field public static final int DATA_DISCONNECTED = 0; // 0x0
field public static final int DATA_SUSPENDED = 3; // 0x3
+ field public static final int DATA_UNKNOWN = -1; // 0xffffffff
field public static final java.lang.String EXTRA_CALL_VOICEMAIL_INTENT = "android.telephony.extra.CALL_VOICEMAIL_INTENT";
field public static final java.lang.String EXTRA_CARRIER_ID = "android.telephony.extra.CARRIER_ID";
field public static final java.lang.String EXTRA_CARRIER_NAME = "android.telephony.extra.CARRIER_NAME";
@@ -44803,16 +44919,21 @@
method public int compareTo(android.telephony.emergency.EmergencyNumber);
method public int describeContents();
method public java.lang.String getCountryIso();
+ method public int getEmergencyCallRouting();
method public int getEmergencyNumberSourceBitmask();
method public java.util.List<java.lang.Integer> getEmergencyNumberSources();
method public java.util.List<java.lang.Integer> getEmergencyServiceCategories();
method public int getEmergencyServiceCategoryBitmask();
+ method public java.util.List<java.lang.String> getEmergencyUrns();
method public java.lang.String getMnc();
method public java.lang.String getNumber();
method public boolean isFromSources(int);
method public boolean isInEmergencyServiceCategories(int);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.telephony.emergency.EmergencyNumber> CREATOR;
+ field public static final int EMERGENCY_CALL_ROUTING_EMERGENCY = 1; // 0x1
+ field public static final int EMERGENCY_CALL_ROUTING_NORMAL = 2; // 0x2
+ field public static final int EMERGENCY_CALL_ROUTING_UNKNOWN = 0; // 0x0
field public static final int EMERGENCY_NUMBER_SOURCE_DATABASE = 16; // 0x10
field public static final int EMERGENCY_NUMBER_SOURCE_DEFAULT = 8; // 0x8
field public static final int EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG = 4; // 0x4
@@ -44857,6 +44978,7 @@
method public boolean isEnabled();
method public void startResolutionActivity(android.app.Activity, int, android.content.Intent, android.app.PendingIntent) throws android.content.IntentSender.SendIntentException;
method public void switchToSubscription(int, android.app.PendingIntent);
+ method public void updateSubscriptionNickname(int, java.lang.String, android.app.PendingIntent);
field public static final java.lang.String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS = "android.telephony.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS";
field public static final java.lang.String ACTION_NOTIFY_CARRIER_SETUP_INCOMPLETE = "android.telephony.euicc.action.NOTIFY_CARRIER_SETUP_INCOMPLETE";
field public static final int EMBEDDED_SUBSCRIPTION_RESULT_ERROR = 2; // 0x2
@@ -49069,6 +49191,7 @@
method public float getAxisValue(int);
method public float getAxisValue(int, int);
method public int getButtonState();
+ method public int getClassification();
method public int getDeviceId();
method public long getDownTime();
method public int getEdgeFlags();
@@ -49220,6 +49343,9 @@
field public static final int BUTTON_STYLUS_PRIMARY = 32; // 0x20
field public static final int BUTTON_STYLUS_SECONDARY = 64; // 0x40
field public static final int BUTTON_TERTIARY = 4; // 0x4
+ field public static final int CLASSIFICATION_AMBIGUOUS_GESTURE = 1; // 0x1
+ field public static final int CLASSIFICATION_DEEP_PRESS = 2; // 0x2
+ field public static final int CLASSIFICATION_NONE = 0; // 0x0
field public static final android.os.Parcelable.Creator<android.view.MotionEvent> CREATOR;
field public static final int EDGE_BOTTOM = 2; // 0x2
field public static final int EDGE_LEFT = 4; // 0x4
@@ -52270,6 +52396,8 @@
method public final android.view.contentcapture.ContentCaptureSession createContentCaptureSession(android.view.contentcapture.ContentCaptureContext);
method public final void destroy();
method public final android.view.contentcapture.ContentCaptureSessionId getContentCaptureSessionId();
+ method public android.view.autofill.AutofillId newAutofillId(android.view.autofill.AutofillId, int);
+ method public final android.view.ViewStructure newVirtualViewStructure(android.view.autofill.AutofillId, int);
method public final void notifyViewAppeared(android.view.ViewStructure);
method public final void notifyViewDisappeared(android.view.autofill.AutofillId);
method public final void notifyViewTextChanged(android.view.autofill.AutofillId, java.lang.CharSequence, int);
@@ -53082,6 +53210,7 @@
field public static final int CATEGORY_SELECTION = 1; // 0x1
field public static final int CATEGORY_UNDEFINED = 0; // 0x0
field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextClassifierEvent> CREATOR;
+ field public static final int TYPE_ACTIONS_GENERATED = 20; // 0x14
field public static final int TYPE_ACTIONS_SHOWN = 6; // 0x6
field public static final int TYPE_AUTO_SELECTION = 5; // 0x5
field public static final int TYPE_COPY_ACTION = 9; // 0x9
@@ -55396,7 +55525,7 @@
method public int getSourceWidth();
method public int getWidth();
method public float getZoom();
- method public boolean isForcePositionWithinWindowSystemInsetsBounds();
+ method public boolean isClippingEnabled();
method public void setZoom(float);
method public void show(float, float);
method public void show(float, float, float, float);
@@ -55409,10 +55538,10 @@
public static class Magnifier.Builder {
ctor public Magnifier.Builder(android.view.View);
method public android.widget.Magnifier build();
+ method public android.widget.Magnifier.Builder setClippingEnabled(boolean);
method public android.widget.Magnifier.Builder setCornerRadius(float);
method public android.widget.Magnifier.Builder setDefaultSourceToMagnifierOffset(int, int);
method public android.widget.Magnifier.Builder setElevation(float);
- method public android.widget.Magnifier.Builder setForcePositionWithinWindowSystemInsetsBounds(boolean);
method public android.widget.Magnifier.Builder setOverlay(android.graphics.drawable.Drawable);
method public android.widget.Magnifier.Builder setSize(int, int);
method public android.widget.Magnifier.Builder setSourceBounds(int, int, int, int);
diff --git a/api/system-current.txt b/api/system-current.txt
index 10d483d..b190e9f 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -86,11 +86,13 @@
field public static final java.lang.String MANAGE_ACCESSIBILITY = "android.permission.MANAGE_ACCESSIBILITY";
field public static final java.lang.String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS";
field public static final java.lang.String MANAGE_APP_OPS_RESTRICTIONS = "android.permission.MANAGE_APP_OPS_RESTRICTIONS";
+ field public static final java.lang.String MANAGE_APP_PREDICTIONS = "android.permission.MANAGE_APP_PREDICTIONS";
field public static final java.lang.String MANAGE_APP_TOKENS = "android.permission.MANAGE_APP_TOKENS";
field public static final java.lang.String MANAGE_AUTO_FILL = "android.permission.MANAGE_AUTO_FILL";
field public static final java.lang.String MANAGE_CARRIER_OEM_UNLOCK_STATE = "android.permission.MANAGE_CARRIER_OEM_UNLOCK_STATE";
field public static final java.lang.String MANAGE_CA_CERTIFICATES = "android.permission.MANAGE_CA_CERTIFICATES";
field public static final java.lang.String MANAGE_CONTENT_CAPTURE = "android.permission.MANAGE_CONTENT_CAPTURE";
+ field public static final java.lang.String MANAGE_CONTENT_SUGGESTIONS = "android.permission.MANAGE_CONTENT_SUGGESTIONS";
field public static final java.lang.String MANAGE_DEBUGGING = "android.permission.MANAGE_DEBUGGING";
field public static final java.lang.String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS";
field public static final java.lang.String MANAGE_IPSEC_TUNNELS = "android.permission.MANAGE_IPSEC_TUNNELS";
@@ -284,10 +286,11 @@
}
public class AppOpsManager {
- method public java.util.List<android.app.AppOpsManager.HistoricalPackageOps> getAllHistoricPackagesOps(java.lang.String[], long, long);
- method public android.app.AppOpsManager.HistoricalPackageOps getHistoricalPackagesOps(int, java.lang.String, java.lang.String[], long, long);
+ method public void getHistoricalOps(int, java.lang.String, java.lang.String[], long, long, java.util.concurrent.Executor, java.util.function.Consumer<android.app.AppOpsManager.HistoricalOps>);
method public static java.lang.String[] getOpStrs();
- method public java.util.List<android.app.AppOpsManager.PackageOps> getOpsForPackage(int, java.lang.String, int[]);
+ method public deprecated java.util.List<android.app.AppOpsManager.PackageOps> getOpsForPackage(int, java.lang.String, int[]);
+ method public java.util.List<android.app.AppOpsManager.PackageOps> getOpsForPackage(int, java.lang.String, java.lang.String...);
+ method public java.util.List<android.app.AppOpsManager.PackageOps> getPackagesForOps(java.lang.String[]);
method public static int opToDefaultMode(java.lang.String);
method public static java.lang.String opToPermission(java.lang.String);
method public void setMode(java.lang.String, int, java.lang.String, int);
@@ -343,7 +346,7 @@
field public static final int UID_STATE_TOP = 1; // 0x1
}
- public static final class AppOpsManager.HistoricalOpEntry implements android.os.Parcelable {
+ public static final class AppOpsManager.HistoricalOp implements android.os.Parcelable {
method public int describeContents();
method public long getAccessCount(int);
method public long getAccessDuration(int);
@@ -353,23 +356,43 @@
method public long getForegroundAccessCount();
method public long getForegroundAccessDuration();
method public long getForegroundRejectCount();
- method public java.lang.String getOp();
+ method public java.lang.String getOpName();
method public long getRejectCount(int);
method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalOpEntry> CREATOR;
+ field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalOp> CREATOR;
+ }
+
+ public static final class AppOpsManager.HistoricalOps implements android.os.Parcelable {
+ method public int describeContents();
+ method public long getBeginTimeMillis();
+ method public long getEndTimeMillis();
+ method public int getUidCount();
+ method public android.app.AppOpsManager.HistoricalUidOps getUidOps(int);
+ method public android.app.AppOpsManager.HistoricalUidOps getUidOpsAt(int);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalOps> CREATOR;
}
public static final class AppOpsManager.HistoricalPackageOps implements android.os.Parcelable {
method public int describeContents();
- method public android.app.AppOpsManager.HistoricalOpEntry getEntry(java.lang.String);
- method public android.app.AppOpsManager.HistoricalOpEntry getEntryAt(int);
- method public int getEntryCount();
+ method public android.app.AppOpsManager.HistoricalOp getOp(java.lang.String);
+ method public android.app.AppOpsManager.HistoricalOp getOpAt(int);
+ method public int getOpCount();
method public java.lang.String getPackageName();
- method public int getUid();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalPackageOps> CREATOR;
}
+ public static final class AppOpsManager.HistoricalUidOps implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getPackageCount();
+ method public android.app.AppOpsManager.HistoricalPackageOps getPackageOps(java.lang.String);
+ method public android.app.AppOpsManager.HistoricalPackageOps getPackageOpsAt(int);
+ method public int getUid();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalUidOps> CREATOR;
+ }
+
public static final class AppOpsManager.OpEntry implements android.os.Parcelable {
method public int describeContents();
method public int getDuration();
@@ -537,6 +560,13 @@
method public void onVrStateChanged(boolean);
}
+ public final class WallpaperColors implements android.os.Parcelable {
+ ctor public WallpaperColors(android.graphics.Color, android.graphics.Color, android.graphics.Color, int);
+ method public int getColorHints();
+ field public static final int HINT_SUPPORTS_DARK_TEXT = 1; // 0x1
+ field public static final int HINT_SUPPORTS_DARK_THEME = 2; // 0x2
+ }
+
public final class WallpaperInfo implements android.os.Parcelable {
method public boolean supportsAmbientMode();
}
@@ -841,6 +871,73 @@
}
+package android.app.contentsuggestions {
+
+ public final class ClassificationsRequest implements android.os.Parcelable {
+ method public int describeContents();
+ method public android.os.Bundle getExtras();
+ method public java.util.List<android.app.contentsuggestions.ContentSelection> getSelections();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.contentsuggestions.ClassificationsRequest> CREATOR;
+ }
+
+ public static final class ClassificationsRequest.Builder {
+ ctor public ClassificationsRequest.Builder(java.util.List<android.app.contentsuggestions.ContentSelection>);
+ method public android.app.contentsuggestions.ClassificationsRequest build();
+ method public android.app.contentsuggestions.ClassificationsRequest.Builder setExtras(android.os.Bundle);
+ }
+
+ public final class ContentClassification implements android.os.Parcelable {
+ ctor public ContentClassification(java.lang.String, android.os.Bundle);
+ method public int describeContents();
+ method public android.os.Bundle getExtras();
+ method public java.lang.String getId();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.contentsuggestions.ContentClassification> CREATOR;
+ }
+
+ public final class ContentSelection implements android.os.Parcelable {
+ ctor public ContentSelection(java.lang.String, android.os.Bundle);
+ method public int describeContents();
+ method public android.os.Bundle getExtras();
+ method public java.lang.String getId();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.contentsuggestions.ContentSelection> CREATOR;
+ }
+
+ public final class ContentSuggestionsManager {
+ method public void classifyContentSelections(android.app.contentsuggestions.ClassificationsRequest, java.util.concurrent.Executor, android.app.contentsuggestions.ContentSuggestionsManager.ClassificationsCallback);
+ method public void notifyInteraction(java.lang.String, android.os.Bundle);
+ method public void provideContextImage(int, android.os.Bundle);
+ method public void suggestContentSelections(android.app.contentsuggestions.SelectionsRequest, java.util.concurrent.Executor, android.app.contentsuggestions.ContentSuggestionsManager.SelectionsCallback);
+ }
+
+ public static abstract interface ContentSuggestionsManager.ClassificationsCallback {
+ method public abstract void onContentClassificationsAvailable(int, java.util.List<android.app.contentsuggestions.ContentClassification>);
+ }
+
+ public static abstract interface ContentSuggestionsManager.SelectionsCallback {
+ method public abstract void onContentSelectionsAvailable(int, java.util.List<android.app.contentsuggestions.ContentSelection>);
+ }
+
+ public final class SelectionsRequest implements android.os.Parcelable {
+ method public int describeContents();
+ method public android.os.Bundle getExtras();
+ method public android.graphics.Point getInterestPoint();
+ method public int getTaskId();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.contentsuggestions.SelectionsRequest> CREATOR;
+ }
+
+ public static final class SelectionsRequest.Builder {
+ ctor public SelectionsRequest.Builder(int);
+ method public android.app.contentsuggestions.SelectionsRequest build();
+ method public android.app.contentsuggestions.SelectionsRequest.Builder setExtras(android.os.Bundle);
+ method public android.app.contentsuggestions.SelectionsRequest.Builder setInterestPoint(android.graphics.Point);
+ }
+
+}
+
package android.app.job {
public abstract class JobScheduler {
@@ -849,6 +946,87 @@
}
+package android.app.prediction {
+
+ public final class AppPredictionContext implements android.os.Parcelable {
+ method public int describeContents();
+ method public android.os.Bundle getExtras();
+ method public java.lang.String getPackageName();
+ method public int getPredictedTargetCount();
+ method public java.lang.String getUiSurface();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.prediction.AppPredictionContext> CREATOR;
+ }
+
+ public static final class AppPredictionContext.Builder {
+ method public android.app.prediction.AppPredictionContext build();
+ method public android.app.prediction.AppPredictionContext.Builder setExtras(android.os.Bundle);
+ method public android.app.prediction.AppPredictionContext.Builder setPredictedTargetCount(int);
+ method public android.app.prediction.AppPredictionContext.Builder setUiSurface(java.lang.String);
+ }
+
+ public final class AppPredictionManager {
+ method public android.app.prediction.AppPredictor createAppPredictionSession(android.app.prediction.AppPredictionContext);
+ }
+
+ public final class AppPredictionSessionId implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.prediction.AppPredictionSessionId> CREATOR;
+ }
+
+ public final class AppPredictor {
+ method public void destroy();
+ method public void notifyAppTargetEvent(android.app.prediction.AppTargetEvent);
+ method public void notifyLocationShown(java.lang.String, java.util.List<android.app.prediction.AppTargetId>);
+ method public void registerPredictionUpdates(java.util.concurrent.Executor, android.app.prediction.AppPredictor.Callback);
+ method public void requestPredictionUpdate();
+ method public void sortTargets(java.util.List<android.app.prediction.AppTarget>, java.util.concurrent.Executor, java.util.function.Consumer<java.util.List<android.app.prediction.AppTarget>>);
+ method public void unregisterPredictionUpdates(android.app.prediction.AppPredictor.Callback);
+ }
+
+ public static abstract interface AppPredictor.Callback {
+ method public abstract void onTargetsAvailable(java.util.List<android.app.prediction.AppTarget>);
+ }
+
+ public final class AppTarget implements android.os.Parcelable {
+ method public int describeContents();
+ method public java.lang.String getClassName();
+ method public android.app.prediction.AppTargetId getId();
+ method public java.lang.String getPackageName();
+ method public int getRank();
+ method public android.content.pm.ShortcutInfo getShortcutInfo();
+ method public android.os.UserHandle getUser();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.prediction.AppTarget> CREATOR;
+ }
+
+ public final class AppTargetEvent implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getAction();
+ method public java.lang.String getLaunchLocation();
+ method public android.app.prediction.AppTarget getTarget();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final int ACTION_DISMISS = 2; // 0x2
+ field public static final int ACTION_LAUNCH = 1; // 0x1
+ field public static final int ACTION_PIN = 3; // 0x3
+ field public static final android.os.Parcelable.Creator<android.app.prediction.AppTargetEvent> CREATOR;
+ }
+
+ public static final class AppTargetEvent.Builder {
+ ctor public AppTargetEvent.Builder(android.app.prediction.AppTarget, int);
+ method public android.app.prediction.AppTargetEvent build();
+ method public android.app.prediction.AppTargetEvent.Builder setLaunchLocation(java.lang.String);
+ }
+
+ public final class AppTargetId implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.prediction.AppTargetId> CREATOR;
+ }
+
+}
+
package android.app.role {
public abstract interface OnRoleHoldersChangedListener {
@@ -927,6 +1105,9 @@
method public java.util.Map<java.lang.String, java.lang.Integer> getAppStandbyBuckets();
method public void registerAppUsageObserver(int, java.lang.String[], long, java.util.concurrent.TimeUnit, android.app.PendingIntent);
method public void registerUsageSessionObserver(int, java.lang.String[], long, java.util.concurrent.TimeUnit, long, java.util.concurrent.TimeUnit, android.app.PendingIntent, android.app.PendingIntent);
+ method public void reportUsageStart(android.app.Activity, java.lang.String);
+ method public void reportUsageStart(android.app.Activity, java.lang.String, long);
+ method public void reportUsageStop(android.app.Activity, java.lang.String);
method public void setAppStandbyBucket(java.lang.String, int);
method public void setAppStandbyBuckets(java.util.Map<java.lang.String, java.lang.Integer>);
method public void unregisterAppUsageObserver(int);
@@ -1032,7 +1213,9 @@
method public abstract void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String, android.os.Bundle);
method public abstract void sendOrderedBroadcast(android.content.Intent, java.lang.String, android.os.Bundle, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
method public void startActivityAsUser(android.content.Intent, android.os.UserHandle);
+ field public static final java.lang.String APP_PREDICTION_SERVICE = "app_prediction";
field public static final java.lang.String BACKUP_SERVICE = "backup";
+ field public static final java.lang.String CONTENT_SUGGESTIONS_SERVICE = "content_suggestions";
field public static final java.lang.String CONTEXTHUB_SERVICE = "contexthub";
field public static final java.lang.String EUICC_CARD_SERVICE = "euicc_card";
field public static final java.lang.String HDMI_CONTROL_SERVICE = "hdmi_control";
@@ -1071,6 +1254,7 @@
field public static final java.lang.String ACTION_MANAGE_APP_PERMISSIONS = "android.intent.action.MANAGE_APP_PERMISSIONS";
field public static final java.lang.String ACTION_MANAGE_PERMISSIONS = "android.intent.action.MANAGE_PERMISSIONS";
field public static final java.lang.String ACTION_MANAGE_PERMISSION_APPS = "android.intent.action.MANAGE_PERMISSION_APPS";
+ field public static final java.lang.String ACTION_MANAGE_SPECIAL_APP_ACCESSES = "android.intent.action.MANAGE_SPECIAL_APP_ACCESSES";
field public static final java.lang.String ACTION_MASTER_CLEAR_NOTIFICATION = "android.intent.action.MASTER_CLEAR_NOTIFICATION";
field public static final java.lang.String ACTION_PACKAGE_ROLLBACK_EXECUTED = "android.intent.action.PACKAGE_ROLLBACK_EXECUTED";
field public static final java.lang.String ACTION_PRE_BOOT_COMPLETED = "android.intent.action.PRE_BOOT_COMPLETED";
@@ -1117,6 +1301,7 @@
package android.content.pm {
public class ApplicationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
+ method public boolean isEncryptionAware();
method public boolean isInstantApp();
field public java.lang.String credentialProtectedDataDir;
field public int targetSandboxVersion;
@@ -1537,6 +1722,10 @@
field public static final android.os.Parcelable.Creator<android.hardware.display.BrightnessCorrection> CREATOR;
}
+ public final class ColorDisplayManager {
+ method public boolean setSaturationLevel(int);
+ }
+
public final class DisplayManager {
method public java.util.List<android.hardware.display.AmbientBrightnessDayStats> getAmbientBrightnessStats();
method public android.hardware.display.BrightnessConfiguration getBrightnessConfiguration();
@@ -1545,7 +1734,7 @@
method public android.util.Pair<float[], float[]> getMinimumBrightnessCurve();
method public android.graphics.Point getStableDisplaySize();
method public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration);
- method public void setSaturationLevel(float);
+ method public deprecated void setSaturationLevel(float);
}
}
@@ -2943,7 +3132,7 @@
method public void setLocationControllerExtraPackage(java.lang.String);
method public void setLocationControllerExtraPackageEnabled(boolean);
method public void setLocationEnabledForUser(boolean, android.os.UserHandle);
- method public boolean setProviderEnabledForUser(java.lang.String, boolean, android.os.UserHandle);
+ method public deprecated boolean setProviderEnabledForUser(java.lang.String, boolean, android.os.UserHandle);
method public boolean unregisterGnssBatchedLocationCallback(android.location.BatchedLocationCallback);
}
@@ -3203,6 +3392,22 @@
package android.media.session {
+ public final class ControllerCallbackLink implements android.os.Parcelable {
+ ctor public ControllerCallbackLink(android.media.session.ControllerCallbackLink.CallbackStub);
+ }
+
+ public static abstract class ControllerCallbackLink.CallbackStub {
+ ctor public ControllerCallbackLink.CallbackStub();
+ method public void onEvent(java.lang.String, android.os.Bundle);
+ method public void onExtrasChanged(android.os.Bundle);
+ method public void onMetadataChanged(android.media.MediaMetadata);
+ method public void onPlaybackStateChanged(android.media.session.PlaybackState);
+ method public void onQueueChanged(java.util.List<android.media.session.MediaSession.QueueItem>);
+ method public void onQueueTitleChanged(java.lang.CharSequence);
+ method public void onSessionDestroyed();
+ method public void onVolumeInfoChanged(android.media.session.MediaController.PlaybackInfo);
+ }
+
public final class MediaSessionManager {
method public void setOnMediaKeyListener(android.media.session.MediaSessionManager.OnMediaKeyListener, android.os.Handler);
method public void setOnVolumeKeyLongPressListener(android.media.session.MediaSessionManager.OnVolumeKeyLongPressListener, android.os.Handler);
@@ -3909,7 +4114,6 @@
method public void disable(int, android.net.wifi.WifiManager.ActionListener);
method public void disableEphemeralNetwork(java.lang.String);
method public void forget(int, android.net.wifi.WifiManager.ActionListener);
- method public java.util.List<android.net.wifi.WifiConfiguration> getAllMatchingWifiConfigs(java.util.List<android.net.wifi.ScanResult>);
method public java.util.List<android.net.wifi.WifiConfiguration> getPrivilegedConfiguredNetworks();
method public android.net.wifi.WifiConfiguration getWifiApConfiguration();
method public int getWifiApState();
@@ -4506,6 +4710,7 @@
field public static final int PAYLOAD_HASH_MISMATCH_ERROR = 10; // 0xa
field public static final int PAYLOAD_MISMATCHED_TYPE_ERROR = 6; // 0x6
field public static final int PAYLOAD_SIZE_MISMATCH_ERROR = 11; // 0xb
+ field public static final int PAYLOAD_TIMESTAMP_ERROR = 51; // 0x33
field public static final int POST_INSTALL_RUNNER_ERROR = 5; // 0x5
field public static final int SUCCESS = 0; // 0x0
field public static final int UPDATED_BUT_NOT_ACTIVE = 52; // 0x34
@@ -4602,6 +4807,7 @@
method public void allocateBytes(java.util.UUID, long, int) throws java.io.IOException;
method public void allocateBytes(java.io.FileDescriptor, long, int) throws java.io.IOException;
method public long getAllocatableBytes(java.util.UUID, int) throws java.io.IOException;
+ method public static boolean hasIsolatedStorage();
field public static final int FLAG_ALLOCATE_AGGRESSIVE = 1; // 0x1
}
@@ -4609,6 +4815,17 @@
package android.permission {
+ public final class PermissionControllerManager {
+ method public void revokeRuntimePermissions(java.util.Map<java.lang.String, java.util.List<java.lang.String>>, boolean, int, java.util.concurrent.Executor, android.permission.PermissionControllerManager.OnRevokeRuntimePermissionsCallback);
+ field public static final int REASON_INSTALLER_POLICY_VIOLATION = 2; // 0x2
+ field public static final int REASON_MALWARE = 1; // 0x1
+ }
+
+ public static abstract class PermissionControllerManager.OnRevokeRuntimePermissionsCallback {
+ ctor public PermissionControllerManager.OnRevokeRuntimePermissionsCallback();
+ method public abstract void onRevokeRuntimePermissions(java.util.Map<java.lang.String, java.util.List<java.lang.String>>);
+ }
+
public abstract class PermissionControllerService extends android.app.Service {
ctor public PermissionControllerService();
method public final void attachBaseContext(android.content.Context);
@@ -4616,6 +4833,7 @@
method public abstract int onCountPermissionApps(java.util.List<java.lang.String>, boolean, boolean);
method public abstract java.util.List<android.permission.RuntimePermissionPresentationInfo> onGetAppPermissions(java.lang.String);
method public abstract void onRevokeRuntimePermission(java.lang.String, java.lang.String);
+ method public abstract java.util.Map<java.lang.String, java.util.List<java.lang.String>> onRevokeRuntimePermissions(java.util.Map<java.lang.String, java.util.List<java.lang.String>>, boolean, int, java.lang.String);
field public static final java.lang.String SERVICE_INTERFACE = "android.permission.PermissionControllerService";
}
@@ -5122,6 +5340,24 @@
}
+package android.service.appprediction {
+
+ public abstract class AppPredictionService extends android.app.Service {
+ ctor public AppPredictionService();
+ method public abstract void onAppTargetEvent(android.app.prediction.AppPredictionSessionId, android.app.prediction.AppTargetEvent);
+ method public final android.os.IBinder onBind(android.content.Intent);
+ method public void onCreatePredictionSession(android.app.prediction.AppPredictionContext, android.app.prediction.AppPredictionSessionId);
+ method public void onDestroyPredictionSession(android.app.prediction.AppPredictionSessionId);
+ method public abstract void onLocationShown(android.app.prediction.AppPredictionSessionId, java.lang.String, java.util.List<android.app.prediction.AppTargetId>);
+ method public abstract void onRequestPredictionUpdate(android.app.prediction.AppPredictionSessionId);
+ method public abstract void onSortAppTargets(android.app.prediction.AppPredictionSessionId, java.util.List<android.app.prediction.AppTarget>, android.os.CancellationSignal, java.util.function.Consumer<java.util.List<android.app.prediction.AppTarget>>);
+ method public void onStartPredictionUpdates();
+ method public void onStopPredictionUpdates();
+ method public final void updatePredictions(android.app.prediction.AppPredictionSessionId, java.util.List<android.app.prediction.AppTarget>);
+ }
+
+}
+
package android.service.autofill {
public abstract class AutofillFieldClassificationService extends android.app.Service {
@@ -5141,6 +5377,8 @@
public abstract class AugmentedAutofillService extends android.app.Service {
ctor public AugmentedAutofillService();
+ method protected final void dump(java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
+ method protected void dump(java.io.PrintWriter, java.lang.String[]);
method public void onFillRequest(android.service.autofill.augmented.FillRequest, android.os.CancellationSignal, android.service.autofill.augmented.FillController, android.service.autofill.augmented.FillCallback);
field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.augmented.AugmentedAutofillService";
}
@@ -5230,6 +5468,7 @@
method public void onCreateContentCaptureSession(android.view.contentcapture.ContentCaptureContext, android.view.contentcapture.ContentCaptureSessionId);
method public void onDestroyContentCaptureSession(android.view.contentcapture.ContentCaptureSessionId);
method public void onDisconnected();
+ method public void onUserDataRemovalRequest(android.view.contentcapture.UserDataRemovalRequest);
method public final void setActivityContentCaptureEnabled(android.content.ComponentName, boolean);
method public final void setContentCaptureWhitelist(java.util.List<java.lang.String>, java.util.List<android.content.ComponentName>);
method public final void setPackageContentCaptureEnabled(java.lang.String, boolean);
@@ -5247,6 +5486,19 @@
}
+package android.service.contentsuggestions {
+
+ public abstract class ContentSuggestionsService extends android.app.Service {
+ ctor public ContentSuggestionsService();
+ method public abstract void classifyContentSelections(android.app.contentsuggestions.ClassificationsRequest, android.app.contentsuggestions.ContentSuggestionsManager.ClassificationsCallback);
+ method public abstract void notifyInteraction(java.lang.String, android.os.Bundle);
+ method public abstract void processContextImage(int, android.graphics.Bitmap, android.os.Bundle);
+ method public abstract void suggestContentSelections(android.app.contentsuggestions.SelectionsRequest, android.app.contentsuggestions.ContentSuggestionsManager.SelectionsCallback);
+ field public static final java.lang.String SERVICE_INTERFACE = "android.service.contentsuggestions.ContentSuggestionsService";
+ }
+
+}
+
package android.service.euicc {
public final class DownloadSubscriptionResult implements android.os.Parcelable {
@@ -5411,7 +5663,7 @@
method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>);
method public void onActionClicked(java.lang.String, android.app.Notification.Action, int);
method public final android.os.IBinder onBind(android.content.Intent);
- method public void onNotificationDirectReply(java.lang.String);
+ method public void onNotificationDirectReplied(java.lang.String);
method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification);
method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, android.app.NotificationChannel);
method public void onNotificationExpansionChanged(java.lang.String, boolean, boolean);
@@ -5933,6 +6185,87 @@
field public static final java.lang.String KEY_CARRIER_SETUP_APP_STRING = "carrier_setup_app_string";
}
+ public final class DataFailCause {
+ field public static final int ACTIVATION_REJECT_GGSN = 30; // 0x1e
+ field public static final int ACTIVATION_REJECT_UNSPECIFIED = 31; // 0x1f
+ field public static final int ACTIVE_PDP_CONTEXT_MAX_NUMBER_REACHED = 65; // 0x41
+ field public static final int APN_TYPE_CONFLICT = 112; // 0x70
+ field public static final int AUTH_FAILURE_ON_EMERGENCY_CALL = 122; // 0x7a
+ field public static final int COMPANION_IFACE_IN_USE = 118; // 0x76
+ field public static final int CONDITIONAL_IE_ERROR = 100; // 0x64
+ field public static final int EMERGENCY_IFACE_ONLY = 116; // 0x74
+ field public static final int EMM_ACCESS_BARRED = 115; // 0x73
+ field public static final int EMM_ACCESS_BARRED_INFINITE_RETRY = 121; // 0x79
+ field public static final int ERROR_UNSPECIFIED = 65535; // 0xffff
+ field public static final int ESM_INFO_NOT_RECEIVED = 53; // 0x35
+ field public static final int FEATURE_NOT_SUPP = 40; // 0x28
+ field public static final int FILTER_SEMANTIC_ERROR = 44; // 0x2c
+ field public static final int FILTER_SYTAX_ERROR = 45; // 0x2d
+ field public static final int GPRS_REGISTRATION_FAIL = -2; // 0xfffffffe
+ field public static final int IFACE_AND_POL_FAMILY_MISMATCH = 120; // 0x78
+ field public static final int IFACE_MISMATCH = 117; // 0x75
+ field public static final int INSUFFICIENT_RESOURCES = 26; // 0x1a
+ field public static final int INTERNAL_CALL_PREEMPT_BY_HIGH_PRIO_APN = 114; // 0x72
+ field public static final int INVALID_MANDATORY_INFO = 96; // 0x60
+ field public static final int INVALID_PCSCF_ADDR = 113; // 0x71
+ field public static final int INVALID_TRANSACTION_ID = 81; // 0x51
+ field public static final int IP_ADDRESS_MISMATCH = 119; // 0x77
+ field public static final int LLC_SNDCP = 25; // 0x19
+ field public static final int LOST_CONNECTION = 65540; // 0x10004
+ field public static final int MESSAGE_INCORRECT_SEMANTIC = 95; // 0x5f
+ field public static final int MESSAGE_TYPE_UNSUPPORTED = 97; // 0x61
+ field public static final int MISSING_UNKNOWN_APN = 27; // 0x1b
+ field public static final int MSG_AND_PROTOCOL_STATE_UNCOMPATIBLE = 101; // 0x65
+ field public static final int MSG_TYPE_NONCOMPATIBLE_STATE = 98; // 0x62
+ field public static final int MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED = 55; // 0x37
+ field public static final int NAS_SIGNALLING = 14; // 0xe
+ field public static final int NETWORK_FAILURE = 38; // 0x26
+ field public static final int NONE = 0; // 0x0
+ field public static final int NSAPI_IN_USE = 35; // 0x23
+ field public static final int OEM_DCFAILCAUSE_1 = 4097; // 0x1001
+ field public static final int OEM_DCFAILCAUSE_10 = 4106; // 0x100a
+ field public static final int OEM_DCFAILCAUSE_11 = 4107; // 0x100b
+ field public static final int OEM_DCFAILCAUSE_12 = 4108; // 0x100c
+ field public static final int OEM_DCFAILCAUSE_13 = 4109; // 0x100d
+ field public static final int OEM_DCFAILCAUSE_14 = 4110; // 0x100e
+ field public static final int OEM_DCFAILCAUSE_15 = 4111; // 0x100f
+ field public static final int OEM_DCFAILCAUSE_2 = 4098; // 0x1002
+ field public static final int OEM_DCFAILCAUSE_3 = 4099; // 0x1003
+ field public static final int OEM_DCFAILCAUSE_4 = 4100; // 0x1004
+ field public static final int OEM_DCFAILCAUSE_5 = 4101; // 0x1005
+ field public static final int OEM_DCFAILCAUSE_6 = 4102; // 0x1006
+ field public static final int OEM_DCFAILCAUSE_7 = 4103; // 0x1007
+ field public static final int OEM_DCFAILCAUSE_8 = 4104; // 0x1008
+ field public static final int OEM_DCFAILCAUSE_9 = 4105; // 0x1009
+ field public static final int ONLY_IPV4_ALLOWED = 50; // 0x32
+ field public static final int ONLY_IPV6_ALLOWED = 51; // 0x33
+ field public static final int ONLY_SINGLE_BEARER_ALLOWED = 52; // 0x34
+ field public static final int OPERATOR_BARRED = 8; // 0x8
+ field public static final int PDN_CONN_DOES_NOT_EXIST = 54; // 0x36
+ field public static final int PDP_WITHOUT_ACTIVE_TFT = 46; // 0x2e
+ field public static final int PREF_RADIO_TECH_CHANGED = -4; // 0xfffffffc
+ field public static final int PROTOCOL_ERRORS = 111; // 0x6f
+ field public static final int QOS_NOT_ACCEPTED = 37; // 0x25
+ field public static final int RADIO_NOT_AVAILABLE = 65537; // 0x10001
+ field public static final int RADIO_POWER_OFF = -5; // 0xfffffffb
+ field public static final int REGISTRATION_FAIL = -1; // 0xffffffff
+ field public static final int REGULAR_DEACTIVATION = 36; // 0x24
+ field public static final int SERVICE_OPTION_NOT_SUBSCRIBED = 33; // 0x21
+ field public static final int SERVICE_OPTION_NOT_SUPPORTED = 32; // 0x20
+ field public static final int SERVICE_OPTION_OUT_OF_ORDER = 34; // 0x22
+ field public static final int SIGNAL_LOST = -3; // 0xfffffffd
+ field public static final int TETHERED_CALL_ACTIVE = -6; // 0xfffffffa
+ field public static final int TFT_SEMANTIC_ERROR = 41; // 0x29
+ field public static final int TFT_SYTAX_ERROR = 42; // 0x2a
+ field public static final int UMTS_REACTIVATION_REQ = 39; // 0x27
+ field public static final int UNKNOWN = 65536; // 0x10000
+ field public static final int UNKNOWN_INFO_ELEMENT = 99; // 0x63
+ field public static final int UNKNOWN_PDP_ADDRESS_TYPE = 28; // 0x1c
+ field public static final int UNKNOWN_PDP_CONTEXT = 43; // 0x2b
+ field public static final int UNSUPPORTED_APN_IN_CURRENT_PLMN = 66; // 0x42
+ field public static final int USER_AUTHENTICATION = 29; // 0x1d
+ }
+
public class DisconnectCause {
field public static final int ALREADY_DIALING = 72; // 0x48
field public static final int ANSWERED_ELSEWHERE = 52; // 0x34
@@ -6100,11 +6433,13 @@
public class PhoneStateListener {
method public void onCallDisconnectCauseChanged(int, int);
method public void onPreciseCallStateChanged(android.telephony.PreciseCallState);
+ method public void onPreciseDataConnectionStateChanged(android.telephony.PreciseDataConnectionState);
method public void onRadioPowerStateChanged(int);
method public void onSrvccStateChanged(int);
method public void onVoiceActivationStateChanged(int);
field public static final int LISTEN_CALL_DISCONNECT_CAUSES = 33554432; // 0x2000000
field public static final int LISTEN_PRECISE_CALL_STATE = 2048; // 0x800
+ field public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE = 4096; // 0x1000
field public static final int LISTEN_RADIO_POWER_STATE_CHANGED = 8388608; // 0x800000
field public static final int LISTEN_SRVCC_STATE_CHANGED = 16384; // 0x4000
field public static final int LISTEN_VOICE_ACTIVATION_STATE = 131072; // 0x20000
@@ -6129,6 +6464,16 @@
field public static final int PRECISE_CALL_STATE_WAITING = 6; // 0x6
}
+ public final class PreciseDataConnectionState implements android.os.Parcelable {
+ method public int describeContents();
+ method public java.lang.String getDataConnectionApn();
+ method public int getDataConnectionApnTypeBitMask();
+ method public int getDataConnectionFailCause();
+ method public int getDataConnectionState();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.telephony.PreciseDataConnectionState> CREATOR;
+ }
+
public class PreciseDisconnectCause {
field public static final int ACCESS_CLASS_BLOCKED = 260; // 0x104
field public static final int ACCESS_INFORMATION_DISCARDED = 43; // 0x2b
@@ -6264,6 +6609,7 @@
public class SubscriptionInfo implements android.os.Parcelable {
method public java.util.List<android.telephony.UiccAccessRule> getAccessRules();
method public int getCardId();
+ method public int getProfileClass();
}
public class SubscriptionManager {
@@ -6273,6 +6619,11 @@
method public void setDefaultDataSubId(int);
method public void setDefaultSmsSubId(int);
field public static final android.net.Uri ADVANCED_CALLING_ENABLED_CONTENT_URI;
+ field public static final int PROFILE_CLASS_DEFAULT = -1; // 0xffffffff
+ field public static final int PROFILE_CLASS_OPERATIONAL = 2; // 0x2
+ field public static final int PROFILE_CLASS_PROVISIONING = 1; // 0x1
+ field public static final int PROFILE_CLASS_TESTING = 0; // 0x0
+ field public static final int PROFILE_CLASS_UNSET = -1; // 0xffffffff
field public static final android.net.Uri VT_ENABLED_CONTENT_URI;
field public static final android.net.Uri WFC_ENABLED_CONTENT_URI;
field public static final android.net.Uri WFC_MODE_CONTENT_URI;
@@ -6331,6 +6682,7 @@
method public deprecated boolean getDataEnabled(int);
method public boolean getEmergencyCallbackMode();
method public java.lang.String getIsimDomain();
+ method public java.lang.String getIsimIst();
method public int getPreferredNetworkTypeBitmap();
method public int getRadioPowerState();
method public int getSimApplicationState();
@@ -6731,7 +7083,9 @@
method public android.os.Bundle getCallExtras();
method public int getCallType();
method public static int getCallTypeFromVideoState(int);
+ method public int getEmergencyCallRouting();
method public int getEmergencyServiceCategories();
+ method public java.util.List<java.lang.String> getEmergencyUrns();
method public android.telephony.ims.ImsStreamMediaProfile getMediaProfile();
method public int getRestrictCause();
method public int getServiceType();
@@ -6744,7 +7098,9 @@
method public void setCallExtraBoolean(java.lang.String, boolean);
method public void setCallExtraInt(java.lang.String, int);
method public void setCallRestrictCause(int);
+ method public void setEmergencyCallRouting(int);
method public void setEmergencyServiceCategories(int);
+ method public void setEmergencyUrns(java.util.List<java.lang.String>);
method public void updateCallExtras(android.telephony.ims.ImsCallProfile);
method public void updateCallType(android.telephony.ims.ImsCallProfile);
method public void updateMediaProfile(android.telephony.ims.ImsCallProfile);
@@ -6873,6 +7229,7 @@
public class ImsMmTelManager {
method public static android.telephony.ims.ImsMmTelManager createForSubscriptionId(android.content.Context, int);
method public int getVoWiFiModeSetting();
+ method public int getVoWiFiRoamingModeSetting();
method public boolean isAdvancedCallingSettingEnabled();
method public boolean isAvailable(int, int);
method public boolean isCapable(int, int);
diff --git a/api/test-current.txt b/api/test-current.txt
index 1401cbb..575875d 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -89,18 +89,26 @@
}
public class AppOpsManager {
- method public java.util.List<android.app.AppOpsManager.HistoricalPackageOps> getAllHistoricPackagesOps(java.lang.String[], long, long);
- method public android.app.AppOpsManager.HistoricalPackageOps getHistoricalPackagesOps(int, java.lang.String, java.lang.String[], long, long);
+ method public void addHistoricalOps(android.app.AppOpsManager.HistoricalOps);
+ method public void clearHistory();
+ method public void getHistoricalOps(int, java.lang.String, java.lang.String[], long, long, java.util.concurrent.Executor, java.util.function.Consumer<android.app.AppOpsManager.HistoricalOps>);
+ method public void getHistoricalOpsFromDiskRaw(int, java.lang.String, java.lang.String[], long, long, java.util.concurrent.Executor, java.util.function.Consumer<android.app.AppOpsManager.HistoricalOps>);
method public static int getNumOps();
method public static java.lang.String[] getOpStrs();
method public boolean isOperationActive(int, int, java.lang.String);
+ method public void offsetHistory(long);
method public static java.lang.String opToPermission(int);
method public static int permissionToOpCode(java.lang.String);
+ method public void resetHistoryParameters();
+ method public void setHistoryParameters(int, long, int);
method public void setMode(int, int, java.lang.String, int);
method public void setUidMode(java.lang.String, int, int);
method public void startWatchingActive(int[], android.app.AppOpsManager.OnOpActiveChangedListener);
method public void stopWatchingActive(android.app.AppOpsManager.OnOpActiveChangedListener);
method public static int strOpToOp(java.lang.String);
+ field public static final int HISTORICAL_MODE_DISABLED = 0; // 0x0
+ field public static final int HISTORICAL_MODE_ENABLED_ACTIVE = 1; // 0x1
+ field public static final int HISTORICAL_MODE_ENABLED_PASSIVE = 2; // 0x2
field public static final java.lang.String OPSTR_ACCEPT_HANDOVER = "android:accept_handover";
field public static final java.lang.String OPSTR_ACCESS_NOTIFICATIONS = "android:access_notifications";
field public static final java.lang.String OPSTR_ACTIVATE_VPN = "android:activate_vpn";
@@ -144,11 +152,18 @@
field public static final java.lang.String OPSTR_WRITE_ICC_SMS = "android:write_icc_sms";
field public static final java.lang.String OPSTR_WRITE_SMS = "android:write_sms";
field public static final java.lang.String OPSTR_WRITE_WALLPAPER = "android:write_wallpaper";
+ field public static final int OP_COARSE_LOCATION = 0; // 0x0
field public static final int OP_RECORD_AUDIO = 27; // 0x1b
field public static final int OP_SYSTEM_ALERT_WINDOW = 24; // 0x18
+ field public static final int UID_STATE_BACKGROUND = 4; // 0x4
+ field public static final int UID_STATE_CACHED = 5; // 0x5
+ field public static final int UID_STATE_FOREGROUND = 3; // 0x3
+ field public static final int UID_STATE_FOREGROUND_SERVICE = 2; // 0x2
+ field public static final int UID_STATE_PERSISTENT = 0; // 0x0
+ field public static final int UID_STATE_TOP = 1; // 0x1
}
- public static final class AppOpsManager.HistoricalOpEntry implements android.os.Parcelable {
+ public static final class AppOpsManager.HistoricalOp implements android.os.Parcelable {
method public int describeContents();
method public long getAccessCount(int);
method public long getAccessDuration(int);
@@ -158,23 +173,48 @@
method public long getForegroundAccessCount();
method public long getForegroundAccessDuration();
method public long getForegroundRejectCount();
- method public java.lang.String getOp();
+ method public java.lang.String getOpName();
method public long getRejectCount(int);
method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalOpEntry> CREATOR;
+ field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalOp> CREATOR;
+ }
+
+ public static final class AppOpsManager.HistoricalOps implements android.os.Parcelable {
+ ctor public AppOpsManager.HistoricalOps(long, long);
+ method public int describeContents();
+ method public long getBeginTimeMillis();
+ method public long getEndTimeMillis();
+ method public int getUidCount();
+ method public android.app.AppOpsManager.HistoricalUidOps getUidOps(int);
+ method public android.app.AppOpsManager.HistoricalUidOps getUidOpsAt(int);
+ method public void increaseAccessCount(int, int, java.lang.String, int, long);
+ method public void increaseAccessDuration(int, int, java.lang.String, int, long);
+ method public void increaseRejectCount(int, int, java.lang.String, int, long);
+ method public void offsetBeginAndEndTime(long);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalOps> CREATOR;
}
public static final class AppOpsManager.HistoricalPackageOps implements android.os.Parcelable {
method public int describeContents();
- method public android.app.AppOpsManager.HistoricalOpEntry getEntry(java.lang.String);
- method public android.app.AppOpsManager.HistoricalOpEntry getEntryAt(int);
- method public int getEntryCount();
+ method public android.app.AppOpsManager.HistoricalOp getOp(java.lang.String);
+ method public android.app.AppOpsManager.HistoricalOp getOpAt(int);
+ method public int getOpCount();
method public java.lang.String getPackageName();
- method public int getUid();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalPackageOps> CREATOR;
}
+ public static final class AppOpsManager.HistoricalUidOps implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getPackageCount();
+ method public android.app.AppOpsManager.HistoricalPackageOps getPackageOps(java.lang.String);
+ method public android.app.AppOpsManager.HistoricalPackageOps getPackageOpsAt(int);
+ method public int getUid();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalUidOps> CREATOR;
+ }
+
public static abstract interface AppOpsManager.OnOpActiveChangedListener {
method public abstract void onOpActiveChanged(int, int, java.lang.String, boolean);
}
@@ -394,6 +434,7 @@
package android.content.res {
public final class Configuration implements java.lang.Comparable android.os.Parcelable {
+ field public int assetsSeq;
field public final android.app.WindowConfiguration windowConfiguration;
}
@@ -985,6 +1026,21 @@
}
+package android.permission {
+
+ public final class PermissionControllerManager {
+ method public void revokeRuntimePermissions(java.util.Map<java.lang.String, java.util.List<java.lang.String>>, boolean, int, java.util.concurrent.Executor, android.permission.PermissionControllerManager.OnRevokeRuntimePermissionsCallback);
+ field public static final int REASON_INSTALLER_POLICY_VIOLATION = 2; // 0x2
+ field public static final int REASON_MALWARE = 1; // 0x1
+ }
+
+ public static abstract class PermissionControllerManager.OnRevokeRuntimePermissionsCallback {
+ ctor public PermissionControllerManager.OnRevokeRuntimePermissionsCallback();
+ method public abstract void onRevokeRuntimePermissions(java.util.Map<java.lang.String, java.util.List<java.lang.String>>);
+ }
+
+}
+
package android.print {
public final class PrintJobInfo implements android.os.Parcelable {
@@ -1221,7 +1277,7 @@
method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>);
method public void onActionClicked(java.lang.String, android.app.Notification.Action, int);
method public final android.os.IBinder onBind(android.content.Intent);
- method public void onNotificationDirectReply(java.lang.String);
+ method public void onNotificationDirectReplied(java.lang.String);
method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification);
method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, android.app.NotificationChannel);
method public void onNotificationExpansionChanged(java.lang.String, boolean, boolean);
diff --git a/cmds/idmap2/idmap2/Scan.cpp b/cmds/idmap2/idmap2/Scan.cpp
index 4918747..4f88127 100644
--- a/cmds/idmap2/idmap2/Scan.cpp
+++ b/cmds/idmap2/idmap2/Scan.cpp
@@ -39,6 +39,17 @@
using android::idmap2::utils::FindFiles;
namespace {
+
+struct InputOverlay {
+ bool operator<(InputOverlay const& rhs) const {
+ return priority < rhs.priority || (priority == rhs.priority && apk_path < rhs.apk_path);
+ }
+
+ std::string apk_path; // NOLINT(misc-non-private-member-variables-in-classes)
+ std::string idmap_path; // NOLINT(misc-non-private-member-variables-in-classes)
+ int priority; // NOLINT(misc-non-private-member-variables-in-classes)
+};
+
std::unique_ptr<std::vector<std::string>> FindApkFiles(const std::vector<std::string>& dirs,
bool recursive, std::ostream& out_error) {
const auto predicate = [](unsigned char type, const std::string& path) -> bool {
@@ -58,6 +69,7 @@
}
return std::make_unique<std::vector<std::string>>(paths.cbegin(), paths.cend());
}
+
} // namespace
bool Scan(const std::vector<std::string>& args, std::ostream& out_error) {
@@ -87,7 +99,7 @@
return false;
}
- std::vector<std::string> interesting_apks;
+ std::vector<InputOverlay> interesting_apks;
for (const std::string& path : *apk_paths) {
std::unique_ptr<const ZipFile> zip = ZipFile::Open(path);
if (!zip) {
@@ -132,27 +144,29 @@
continue;
}
+ // Sort the static overlays in ascending priority order
+ std::string idmap_path = Idmap::CanonicalIdmapPathFor(output_directory, path);
+ InputOverlay input{path, idmap_path, priority};
interesting_apks.insert(
- std::lower_bound(interesting_apks.begin(), interesting_apks.end(), path), path);
+ std::lower_bound(interesting_apks.begin(), interesting_apks.end(), input), input);
}
std::stringstream stream;
- for (const auto& apk : interesting_apks) {
- const std::string idmap_path = Idmap::CanonicalIdmapPathFor(output_directory, apk);
+ for (const auto& overlay : interesting_apks) {
std::stringstream dev_null;
- if (!Verify(std::vector<std::string>({"--idmap-path", idmap_path}), dev_null) &&
+ if (!Verify(std::vector<std::string>({"--idmap-path", overlay.idmap_path}), dev_null) &&
!Create(std::vector<std::string>({
"--target-apk-path",
target_apk_path,
"--overlay-apk-path",
- apk,
+ overlay.apk_path,
"--idmap-path",
- idmap_path,
+ overlay.idmap_path,
}),
out_error)) {
return false;
}
- stream << idmap_path << std::endl;
+ stream << overlay.idmap_path << std::endl;
}
std::cout << stream.str();
diff --git a/cmds/idmap2/static-checks.sh b/cmds/idmap2/static-checks.sh
index 560ccb6..ad9830b 100755
--- a/cmds/idmap2/static-checks.sh
+++ b/cmds/idmap2/static-checks.sh
@@ -70,7 +70,15 @@
function _cpplint()
{
local cpplint="${ANDROID_BUILD_TOP}/tools/repohooks/tools/cpplint.py"
- $cpplint --quiet $cpp_files
+ local output="$($cpplint --quiet $cpp_files 2>&1 >/dev/null | grep -v \
+ -e 'Found C system header after C++ system header.' \
+ -e 'Unknown NOLINT error category: misc-non-private-member-variables-in-classes' \
+ )"
+ if [[ "$output" ]]; then
+ echo "$output"
+ return 1
+ fi
+ return 0
}
function _parse_args()
diff --git a/cmds/input/src/com/android/commands/input/Input.java b/cmds/input/src/com/android/commands/input/Input.java
index 74edffb..0c861cf 100644
--- a/cmds/input/src/com/android/commands/input/Input.java
+++ b/cmds/input/src/com/android/commands/input/Input.java
@@ -16,15 +16,20 @@
package com.android.commands.input;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
+
import android.hardware.input.InputManager;
import android.os.SystemClock;
-import android.util.Log;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
+import com.android.internal.os.BaseCommand;
+
+import java.io.PrintStream;
import java.util.HashMap;
import java.util.Map;
@@ -33,9 +38,14 @@
* desired character output.
*/
-public class Input {
+public class Input extends BaseCommand {
private static final String TAG = "Input";
private static final String INVALID_ARGUMENTS = "Error: Invalid arguments for command: ";
+ private static final String INVALID_DISPLAY_ARGUMENTS =
+ "Error: Invalid arguments for display ID.";
+
+ private static final float DEFAULT_PRESSURE = 1.0f;
+ private static final float NO_PRESSURE = 0.0f;
private static final Map<String, Integer> SOURCES = new HashMap<String, Integer>() {{
put("keyboard", InputDevice.SOURCE_KEYBOARD);
@@ -50,6 +60,7 @@
put("joystick", InputDevice.SOURCE_JOYSTICK);
}};
+ private static final Map<String, InputCmd> COMMANDS = new HashMap<String, InputCmd>();
/**
* Command-line entry point.
@@ -60,217 +71,292 @@
(new Input()).run(args);
}
- private void run(String[] args) {
- if (args.length < 1) {
- showUsage();
- return;
- }
+ Input() {
+ COMMANDS.put("text", new InputText());
+ COMMANDS.put("keyevent", new InputKeyEvent());
+ COMMANDS.put("tap", new InputTap());
+ COMMANDS.put("swipe", new InputSwipe());
+ COMMANDS.put("draganddrop", new InputDragAndDrop());
+ COMMANDS.put("press", new InputPress());
+ COMMANDS.put("roll", new InputRoll());
+ COMMANDS.put("motionevent", new InputMotionEvent());
+ }
- int index = 0;
- String command = args[index];
+ @Override
+ public void onRun() throws Exception {
+ String arg = nextArgRequired();
int inputSource = InputDevice.SOURCE_UNKNOWN;
- if (SOURCES.containsKey(command)) {
- inputSource = SOURCES.get(command);
- index++;
- command = args[index];
- }
- final int length = args.length - index;
- try {
- if (command.equals("text")) {
- if (length == 2) {
- inputSource = getSource(inputSource, InputDevice.SOURCE_KEYBOARD);
- sendText(inputSource, args[index+1]);
- return;
+ // Get source (optional).
+ if (SOURCES.containsKey(arg)) {
+ inputSource = SOURCES.get(arg);
+ arg = nextArgRequired();
+ }
+
+ // Get displayId (optional).
+ int displayId = INVALID_DISPLAY;
+ if ("-d".equals(arg)) {
+ displayId = getDisplayId();
+ arg = nextArgRequired();
+ }
+
+ // Get command and run.
+ InputCmd cmd = COMMANDS.get(arg);
+ if (cmd != null) {
+ try {
+ cmd.run(inputSource, displayId);
+ return;
+ } catch (NumberFormatException ex) {
+ throw new IllegalArgumentException(INVALID_ARGUMENTS + arg);
+ }
+ }
+
+ throw new IllegalArgumentException("Error: Unknown command: " + arg);
+ }
+
+ private int getDisplayId() {
+ String displayArg = nextArgRequired();
+ if ("INVALID_DISPLAY".equalsIgnoreCase(displayArg)) {
+ return INVALID_DISPLAY;
+ } else if ("DEFAULT_DISPLAY".equalsIgnoreCase(displayArg)) {
+ return DEFAULT_DISPLAY;
+ } else {
+ try {
+ final int displayId = Integer.parseInt(displayArg);
+ if (displayId == INVALID_DISPLAY) {
+ return INVALID_DISPLAY;
}
- } else if (command.equals("keyevent")) {
- if (length >= 2) {
- final boolean longpress = "--longpress".equals(args[index + 1]);
- final int start = longpress ? index + 2 : index + 1;
- inputSource = getSource(inputSource, InputDevice.SOURCE_KEYBOARD);
- if (args.length > start) {
- for (int i = start; i < args.length; i++) {
- int keyCode = KeyEvent.keyCodeFromString(args[i]);
- sendKeyEvent(inputSource, keyCode, longpress);
- }
- return;
+ return Math.max(displayId, 0);
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException(INVALID_DISPLAY_ARGUMENTS);
+ }
+ }
+ }
+
+ class InputText implements InputCmd {
+ @Override
+ public void run(int inputSource, int displayId) {
+ inputSource = getSource(inputSource, InputDevice.SOURCE_KEYBOARD);
+ sendText(inputSource, nextArgRequired(), displayId);
+ }
+
+ /**
+ * Convert the characters of string text into key event's and send to
+ * device.
+ *
+ * @param text is a string of characters you want to input to the device.
+ */
+ private void sendText(int source, final String text, int displayId) {
+ final StringBuffer buff = new StringBuffer(text);
+ boolean escapeFlag = false;
+ for (int i = 0; i < buff.length(); i++) {
+ if (escapeFlag) {
+ escapeFlag = false;
+ if (buff.charAt(i) == 's') {
+ buff.setCharAt(i, ' ');
+ buff.deleteCharAt(--i);
}
}
- } else if (command.equals("tap")) {
- if (length == 3) {
- inputSource = getSource(inputSource, InputDevice.SOURCE_TOUCHSCREEN);
- sendTap(inputSource, Float.parseFloat(args[index+1]),
- Float.parseFloat(args[index+2]));
- return;
+ if (buff.charAt(i) == '%') {
+ escapeFlag = true;
}
- } else if (command.equals("swipe")) {
- int duration = -1;
- inputSource = getSource(inputSource, InputDevice.SOURCE_TOUCHSCREEN);
- switch (length) {
- case 6:
- duration = Integer.parseInt(args[index+5]);
- case 5:
- sendSwipe(inputSource,
- Float.parseFloat(args[index+1]), Float.parseFloat(args[index+2]),
- Float.parseFloat(args[index+3]), Float.parseFloat(args[index+4]),
- duration);
- return;
- }
- } else if (command.equals("draganddrop")) {
- int duration = -1;
- inputSource = getSource(inputSource, InputDevice.SOURCE_TOUCHSCREEN);
- switch (length) {
- case 6:
- duration = Integer.parseInt(args[index+5]);
- case 5:
- sendDragAndDrop(inputSource,
- Float.parseFloat(args[index+1]), Float.parseFloat(args[index+2]),
- Float.parseFloat(args[index+3]), Float.parseFloat(args[index+4]),
- duration);
- return;
- }
- } else if (command.equals("press")) {
- inputSource = getSource(inputSource, InputDevice.SOURCE_TRACKBALL);
- if (length == 1) {
- sendTap(inputSource, 0.0f, 0.0f);
- return;
- }
- } else if (command.equals("roll")) {
- inputSource = getSource(inputSource, InputDevice.SOURCE_TRACKBALL);
- if (length == 3) {
- sendMove(inputSource, Float.parseFloat(args[index+1]),
- Float.parseFloat(args[index+2]));
- return;
- }
- } else {
- System.err.println("Error: Unknown command: " + command);
- showUsage();
- return;
}
- } catch (NumberFormatException ex) {
+
+ final char[] chars = buff.toString().toCharArray();
+ final KeyCharacterMap kcm = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
+ final KeyEvent[] events = kcm.getEvents(chars);
+ for (int i = 0; i < events.length; i++) {
+ KeyEvent e = events[i];
+ if (source != e.getSource()) {
+ e.setSource(source);
+ }
+ e.setDisplayId(displayId);
+ injectKeyEvent(e);
+ }
}
- System.err.println(INVALID_ARGUMENTS + command);
- showUsage();
+ }
+
+ class InputKeyEvent implements InputCmd {
+ @Override
+ public void run(int inputSource, int displayId) {
+ String arg = nextArgRequired();
+ final boolean longpress = "--longpress".equals(arg);
+ if (longpress) {
+ arg = nextArgRequired();
+ }
+
+ do {
+ final int keycode = KeyEvent.keyCodeFromString(arg);
+ sendKeyEvent(inputSource, keycode, longpress, displayId);
+ } while ((arg = nextArg()) != null);
+ }
+
+ private void sendKeyEvent(int inputSource, int keyCode, boolean longpress, int displayId) {
+ final long now = SystemClock.uptimeMillis();
+ int repeatCount = 0;
+
+ KeyEvent event = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, repeatCount,
+ 0 /*metaState*/, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, 0/*flags*/,
+ inputSource);
+ event.setDisplayId(displayId);
+
+ injectKeyEvent(event);
+ if (longpress) {
+ repeatCount++;
+ injectKeyEvent(KeyEvent.changeTimeRepeat(event, now, repeatCount,
+ KeyEvent.FLAG_LONG_PRESS));
+ }
+ injectKeyEvent(KeyEvent.changeAction(event, KeyEvent.ACTION_UP));
+ }
+ }
+
+ class InputTap implements InputCmd {
+ @Override
+ public void run(int inputSource, int displayId) {
+ inputSource = getSource(inputSource, InputDevice.SOURCE_TOUCHSCREEN);
+ sendTap(inputSource, Float.parseFloat(nextArgRequired()),
+ Float.parseFloat(nextArgRequired()), displayId);
+ }
+
+ void sendTap(int inputSource, float x, float y, int displayId) {
+ final long now = SystemClock.uptimeMillis();
+ injectMotionEvent(inputSource, MotionEvent.ACTION_DOWN, now, now, x, y, 1.0f,
+ displayId);
+ injectMotionEvent(inputSource, MotionEvent.ACTION_UP, now, now, x, y, 0.0f, displayId);
+ }
+ }
+
+ class InputPress extends InputTap {
+ @Override
+ public void run(int inputSource, int displayId) {
+ inputSource = getSource(inputSource, InputDevice.SOURCE_TRACKBALL);
+ sendTap(inputSource, 0.0f, 0.0f, displayId);
+ }
+ }
+
+ class InputSwipe implements InputCmd {
+ @Override
+ public void run(int inputSource, int displayId) {
+ inputSource = getSource(inputSource, InputDevice.SOURCE_TOUCHSCREEN);
+ sendSwipe(inputSource, displayId, false);
+ }
+
+ void sendSwipe(int inputSource, int displayId, boolean isDragDrop) {
+ // Parse two points and duration.
+ final float x1 = Float.parseFloat(nextArgRequired());
+ final float y1 = Float.parseFloat(nextArgRequired());
+ final float x2 = Float.parseFloat(nextArgRequired());
+ final float y2 = Float.parseFloat(nextArgRequired());
+ String durationArg = nextArg();
+ int duration = durationArg != null ? Integer.parseInt(durationArg) : -1;
+ if (duration < 0) {
+ duration = 300;
+ }
+
+ final long down = SystemClock.uptimeMillis();
+ injectMotionEvent(inputSource, MotionEvent.ACTION_DOWN, down, down, x1, y1, 1.0f,
+ displayId);
+ if (isDragDrop) {
+ // long press until drag start.
+ try {
+ Thread.sleep(ViewConfiguration.getLongPressTimeout());
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ long now = SystemClock.uptimeMillis();
+ final long endTime = down + duration;
+ while (now < endTime) {
+ final long elapsedTime = now - down;
+ final float alpha = (float) elapsedTime / duration;
+ injectMotionEvent(inputSource, MotionEvent.ACTION_MOVE, down, now,
+ lerp(x1, x2, alpha), lerp(y1, y2, alpha), 1.0f, displayId);
+ now = SystemClock.uptimeMillis();
+ }
+ injectMotionEvent(inputSource, MotionEvent.ACTION_UP, down, now, x2, y2, 0.0f,
+ displayId);
+ }
+ }
+
+ class InputDragAndDrop extends InputSwipe {
+ @Override
+ public void run(int inputSource, int displayId) {
+ inputSource = getSource(inputSource, InputDevice.SOURCE_TOUCHSCREEN);
+ sendSwipe(inputSource, displayId, true);
+ }
+ }
+
+ class InputRoll implements InputCmd {
+ @Override
+ public void run(int inputSource, int displayId) {
+ inputSource = getSource(inputSource, InputDevice.SOURCE_TRACKBALL);
+ sendMove(inputSource, Float.parseFloat(nextArgRequired()),
+ Float.parseFloat(nextArgRequired()), displayId);
+ }
+
+ /**
+ * Sends a simple zero-pressure move event.
+ *
+ * @param inputSource the InputDevice.SOURCE_* sending the input event
+ * @param dx change in x coordinate due to move
+ * @param dy change in y coordinate due to move
+ */
+ private void sendMove(int inputSource, float dx, float dy, int displayId) {
+ final long now = SystemClock.uptimeMillis();
+ injectMotionEvent(inputSource, MotionEvent.ACTION_MOVE, now, now, dx, dy, 0.0f,
+ displayId);
+ }
+ }
+
+ class InputMotionEvent implements InputCmd {
+ @Override
+ public void run(int inputSource, int displayId) {
+ inputSource = getSource(inputSource, InputDevice.SOURCE_TOUCHSCREEN);
+ sendMotionEvent(inputSource, nextArgRequired(), Float.parseFloat(nextArgRequired()),
+ Float.parseFloat(nextArgRequired()), displayId);
+ }
+
+ private void sendMotionEvent(int inputSource, String motionEventType, float x, float y,
+ int displayId) {
+ final int action;
+ final float pressure;
+
+ switch (motionEventType.toUpperCase()) {
+ case "DOWN":
+ action = MotionEvent.ACTION_DOWN;
+ pressure = DEFAULT_PRESSURE;
+ break;
+ case "UP":
+ action = MotionEvent.ACTION_UP;
+ pressure = NO_PRESSURE;
+ break;
+ case "MOVE":
+ action = MotionEvent.ACTION_MOVE;
+ pressure = DEFAULT_PRESSURE;
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown motionevent " + motionEventType);
+ }
+
+ final long now = SystemClock.uptimeMillis();
+ injectMotionEvent(inputSource, action, now, now, x, y, pressure, displayId);
+ }
}
/**
- * Convert the characters of string text into key event's and send to
- * device.
- *
- * @param text is a string of characters you want to input to the device.
+ * Abstract class for command
+ * use nextArgRequired or nextArg to check next argument if necessary.
*/
- private void sendText(int source, String text) {
-
- StringBuffer buff = new StringBuffer(text);
-
- boolean escapeFlag = false;
- for (int i=0; i<buff.length(); i++) {
- if (escapeFlag) {
- escapeFlag = false;
- if (buff.charAt(i) == 's') {
- buff.setCharAt(i, ' ');
- buff.deleteCharAt(--i);
- }
- }
- if (buff.charAt(i) == '%') {
- escapeFlag = true;
- }
- }
-
- char[] chars = buff.toString().toCharArray();
-
- KeyCharacterMap kcm = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
- KeyEvent[] events = kcm.getEvents(chars);
- for(int i = 0; i < events.length; i++) {
- KeyEvent e = events[i];
- if (source != e.getSource()) {
- e.setSource(source);
- }
- injectKeyEvent(e);
- }
+ private interface InputCmd {
+ void run(int inputSource, int displayId);
}
- private void sendKeyEvent(int inputSource, int keyCode, boolean longpress) {
- long now = SystemClock.uptimeMillis();
- injectKeyEvent(new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 0, 0,
- KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, inputSource));
- if (longpress) {
- injectKeyEvent(new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 1, 0,
- KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_LONG_PRESS,
- inputSource));
- }
- injectKeyEvent(new KeyEvent(now, now, KeyEvent.ACTION_UP, keyCode, 0, 0,
- KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, inputSource));
- }
-
- private void sendTap(int inputSource, float x, float y) {
- long now = SystemClock.uptimeMillis();
- injectMotionEvent(inputSource, MotionEvent.ACTION_DOWN, now, x, y, 1.0f);
- injectMotionEvent(inputSource, MotionEvent.ACTION_UP, now, x, y, 0.0f);
- }
-
- private void sendSwipe(int inputSource, float x1, float y1, float x2, float y2, int duration) {
- if (duration < 0) {
- duration = 300;
- }
- long now = SystemClock.uptimeMillis();
- injectMotionEvent(inputSource, MotionEvent.ACTION_DOWN, now, x1, y1, 1.0f);
- long startTime = now;
- long endTime = startTime + duration;
- while (now < endTime) {
- long elapsedTime = now - startTime;
- float alpha = (float) elapsedTime / duration;
- injectMotionEvent(inputSource, MotionEvent.ACTION_MOVE, now, lerp(x1, x2, alpha),
- lerp(y1, y2, alpha), 1.0f);
- now = SystemClock.uptimeMillis();
- }
- injectMotionEvent(inputSource, MotionEvent.ACTION_UP, now, x2, y2, 0.0f);
- }
-
- private void sendDragAndDrop(int inputSource, float x1, float y1, float x2, float y2,
- int dragDuration) {
- if (dragDuration < 0) {
- dragDuration = 300;
- }
- long now = SystemClock.uptimeMillis();
- injectMotionEvent(inputSource, MotionEvent.ACTION_DOWN, now, x1, y1, 1.0f);
- try {
- Thread.sleep(ViewConfiguration.getLongPressTimeout());
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- now = SystemClock.uptimeMillis();
- long startTime = now;
- long endTime = startTime + dragDuration;
- while (now < endTime) {
- long elapsedTime = now - startTime;
- float alpha = (float) elapsedTime / dragDuration;
- injectMotionEvent(inputSource, MotionEvent.ACTION_MOVE, now, lerp(x1, x2, alpha),
- lerp(y1, y2, alpha), 1.0f);
- now = SystemClock.uptimeMillis();
- }
- injectMotionEvent(inputSource, MotionEvent.ACTION_UP, now, x2, y2, 0.0f);
- }
-
- /**
- * Sends a simple zero-pressure move event.
- *
- * @param inputSource the InputDevice.SOURCE_* sending the input event
- * @param dx change in x coordinate due to move
- * @param dy change in y coordinate due to move
- */
- private void sendMove(int inputSource, float dx, float dy) {
- long now = SystemClock.uptimeMillis();
- injectMotionEvent(inputSource, MotionEvent.ACTION_MOVE, now, dx, dy, 0.0f);
- }
-
- private void injectKeyEvent(KeyEvent event) {
- Log.i(TAG, "injectKeyEvent: " + event);
+ private static void injectKeyEvent(KeyEvent event) {
InputManager.getInstance().injectInputEvent(event,
InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
}
- private int getInputDeviceId(int inputSource) {
+ private static int getInputDeviceId(int inputSource) {
final int DEFAULT_DEVICE_ID = 0;
int[] devIds = InputDevice.getDeviceIds();
for (int devId : devIds) {
@@ -287,22 +373,27 @@
*
* @param inputSource the InputDevice.SOURCE_* sending the input event
* @param action the MotionEvent.ACTION_* for the event
+ * @param downTime the value of the ACTION_DOWN event happened
* @param when the value of SystemClock.uptimeMillis() at which the event happened
* @param x x coordinate of event
* @param y y coordinate of event
* @param pressure pressure of event
*/
- private void injectMotionEvent(int inputSource, int action, long when, float x, float y, float pressure) {
+ private static void injectMotionEvent(int inputSource, int action, long downTime, long when,
+ float x, float y, float pressure, int displayId) {
final float DEFAULT_SIZE = 1.0f;
final int DEFAULT_META_STATE = 0;
final float DEFAULT_PRECISION_X = 1.0f;
final float DEFAULT_PRECISION_Y = 1.0f;
final int DEFAULT_EDGE_FLAGS = 0;
- MotionEvent event = MotionEvent.obtain(when, when, action, x, y, pressure, DEFAULT_SIZE,
+ MotionEvent event = MotionEvent.obtain(downTime, when, action, x, y, pressure, DEFAULT_SIZE,
DEFAULT_META_STATE, DEFAULT_PRECISION_X, DEFAULT_PRECISION_Y,
getInputDeviceId(inputSource), DEFAULT_EDGE_FLAGS);
event.setSource(inputSource);
- Log.i(TAG, "injectMotionEvent: " + event);
+ if (displayId == INVALID_DISPLAY && (inputSource & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+ displayId = DEFAULT_DISPLAY;
+ }
+ event.setDisplayId(displayId);
InputManager.getInstance().injectInputEvent(event,
InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
}
@@ -315,24 +406,30 @@
return inputSource == InputDevice.SOURCE_UNKNOWN ? defaultSource : inputSource;
}
- private void showUsage() {
- System.err.println("Usage: input [<source>] <command> [<arg>...]");
- System.err.println();
- System.err.println("The sources are: ");
+ @Override
+ public void onShowUsage(PrintStream out) {
+ out.println("Usage: input [<source>] [-d DISPLAY_ID] <command> [<arg>...]");
+ out.println();
+ out.println("The sources are: ");
for (String src : SOURCES.keySet()) {
- System.err.println(" " + src);
+ out.println(" " + src);
}
- System.err.println();
- System.err.println("The commands and default sources are:");
- System.err.println(" text <string> (Default: touchscreen)");
- System.err.println(" keyevent [--longpress] <key code number or name> ..."
+ out.println();
+ out.printf("-d: specify the display ID.\n"
+ + " (Default: %d for key event, %d for motion event if not specified.)",
+ INVALID_DISPLAY, DEFAULT_DISPLAY);
+ out.println();
+ out.println("The commands and default sources are:");
+ out.println(" text <string> (Default: touchscreen)");
+ out.println(" keyevent [--longpress] <key code number or name> ..."
+ " (Default: keyboard)");
- System.err.println(" tap <x> <y> (Default: touchscreen)");
- System.err.println(" swipe <x1> <y1> <x2> <y2> [duration(ms)]"
+ out.println(" tap <x> <y> (Default: touchscreen)");
+ out.println(" swipe <x1> <y1> <x2> <y2> [duration(ms)]"
+ " (Default: touchscreen)");
- System.err.println(" draganddrop <x1> <y1> <x2> <y2> [duration(ms)]"
+ out.println(" draganddrop <x1> <y1> <x2> <y2> [duration(ms)]"
+ " (Default: touchscreen)");
- System.err.println(" press (Default: trackball)");
- System.err.println(" roll <dx> <dy> (Default: trackball)");
+ out.println(" press (Default: trackball)");
+ out.println(" roll <dx> <dy> (Default: trackball)");
+ out.println(" event <DOWN|UP|MOVE> <x> <y> (Default: touchscreen)");
}
}
diff --git a/cmds/media/src/com/android/commands/media/Media.java b/cmds/media/src/com/android/commands/media/Media.java
index c6d2bc8..d160b73 100644
--- a/cmds/media/src/com/android/commands/media/Media.java
+++ b/cmds/media/src/com/android/commands/media/Media.java
@@ -19,12 +19,12 @@
import android.app.ActivityManager;
import android.content.Context;
-import android.content.pm.ParceledListSlice;
import android.media.MediaMetadata;
+import android.media.session.ControllerCallbackLink;
import android.media.session.ISessionController;
-import android.media.session.ISessionControllerCallback;
import android.media.session.ISessionManager;
-import android.media.session.ParcelableVolumeInfo;
+import android.media.session.MediaController.PlaybackInfo;
+import android.media.session.MediaSession.QueueItem;
import android.media.session.PlaybackState;
import android.os.Bundle;
import android.os.HandlerThread;
@@ -178,13 +178,7 @@
KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD));
}
- class ControllerMonitor extends ISessionControllerCallback.Stub {
- private final ISessionController mController;
-
- public ControllerMonitor(ISessionController controller) {
- mController = controller;
- }
-
+ class ControllerCallbackStub extends ControllerCallbackLink.CallbackStub {
@Override
public void onSessionDestroyed() {
System.out.println("onSessionDestroyed. Enter q to quit.");
@@ -208,25 +202,35 @@
}
@Override
- public void onQueueChanged(ParceledListSlice queue) throws RemoteException {
+ public void onQueueChanged(List<QueueItem> queue) {
System.out.println("onQueueChanged, "
- + (queue == null ? "null queue" : " size=" + queue.getList().size()));
+ + (queue == null ? "null queue" : " size=" + queue.size()));
}
@Override
- public void onQueueTitleChanged(CharSequence title) throws RemoteException {
+ public void onQueueTitleChanged(CharSequence title) {
System.out.println("onQueueTitleChange " + title);
}
@Override
- public void onExtrasChanged(Bundle extras) throws RemoteException {
+ public void onExtrasChanged(Bundle extras) {
System.out.println("onExtrasChanged " + extras);
}
@Override
- public void onVolumeInfoChanged(ParcelableVolumeInfo info) throws RemoteException {
+ public void onVolumeInfoChanged(PlaybackInfo info) {
System.out.println("onVolumeInfoChanged " + info);
}
+ }
+
+ private class ControllerMonitor {
+ private final ISessionController mController;
+ private final ControllerCallbackLink mControllerCallbackLink;
+
+ ControllerMonitor(ISessionController controller) {
+ mController = controller;
+ mControllerCallbackLink = new ControllerCallbackLink(new ControllerCallbackStub());
+ }
void printUsageMessage() {
try {
@@ -244,7 +248,7 @@
@Override
protected void onLooperPrepared() {
try {
- mController.registerCallbackListener(PACKAGE_NAME, ControllerMonitor.this);
+ mController.registerCallbackListener(PACKAGE_NAME, mControllerCallbackLink);
} catch (RemoteException e) {
System.out.println("Error registering monitor callback");
}
@@ -287,7 +291,7 @@
} finally {
cbThread.getLooper().quit();
try {
- mController.unregisterCallbackListener(this);
+ mController.unregisterCallbackListener(mControllerCallbackLink);
} catch (Exception e) {
// ignoring
}
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 5c53a3a..fa3be26 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -173,6 +173,10 @@
WifiRunningStateChanged wifi_running_state_changed = 114;
AppCompacted app_compacted = 115;
NetworkDnsEventReported network_dns_event_Reported = 116;
+ DocsUIPickerLaunchedFromReported docs_ui_picker_launched_from_reported = 117;
+ DocsUIPickResultReported docs_ui_pick_result_reported = 118;
+ DocsUISearchModeReported docs_ui_search_mode_reported = 119;
+ DocsUISearchTypeReported docs_ui_search_type_reported = 120;
}
// Pulled events will start at field 10000.
@@ -3656,6 +3660,52 @@
}
/**
+ * Logs the package name that launches docsui picker mode.
+ *
+ * Logged from:
+ * package/app/DocumentsUI/src/com/android/documentsui/Metrics.java
+ */
+message DocsUIPickerLaunchedFromReported {
+ optional string package_name = 1;
+}
+
+/**
+ * Logs the search type.
+ *
+ * Logged from:
+ * package/app/DocumentsUI/src/com/android/documentsui/Metrics.java
+ */
+message DocsUISearchTypeReported {
+ optional android.stats.docsui.SearchType search_type = 1;
+}
+
+/**
+ * Logs the search mode.
+ *
+ * Logged from:
+ * package/app/DocumentsUI/src/com/android/documentsui/Metrics.java
+ */
+message DocsUISearchModeReported {
+ optional android.stats.docsui.SearchMode search_mode = 1;
+}
+
+/**
+ * Logs the pick result information.
+ *
+ * Logged from:
+ * package/app/DocumentsUI/src/com/android/documentsui/Metrics.java
+ */
+message DocsUIPickResultReported {
+ optional int32 total_action_count = 1;
+ optional int64 duration_millis = 2;
+ optional int32 file_count= 3;
+ optional bool is_searching = 4;
+ optional android.stats.docsui.Root picked_from = 5;
+ optional android.stats.docsui.MimeType mime_type = 6;
+ optional int32 repeatedly_pick_times = 7;
+}
+
+/**
* Logs when an app's memory is compacted.
*
* Logged from:
diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt
index 7a993fd..ed22df5 100644
--- a/config/hiddenapi-greylist.txt
+++ b/config/hiddenapi-greylist.txt
@@ -56,7 +56,6 @@
Landroid/app/IActivityManager$Stub$Proxy;->getLaunchedFromUid(Landroid/os/IBinder;)I
Landroid/app/IActivityManager$Stub$Proxy;->getProcessLimit()I
Landroid/app/IActivityManager$Stub$Proxy;->getProcessPss([I)[J
-Landroid/app/IActivityManager$Stub$Proxy;->isAppForeground(I)Z
Landroid/app/IActivityManager$Stub$Proxy;->mRemote:Landroid/os/IBinder;
Landroid/app/IActivityManager$Stub$Proxy;->setActivityController(Landroid/app/IActivityController;Z)V
Landroid/app/IActivityManager$Stub$Proxy;->updatePersistentConfiguration(Landroid/content/res/Configuration;)V
@@ -491,7 +490,6 @@
Landroid/location/ILocationManager$Stub;->TRANSACTION_getAllProviders:I
Landroid/location/ILocationManager;->getAllProviders()Ljava/util/List;
Landroid/location/ILocationManager;->getNetworkProviderPackage()Ljava/lang/String;
-Landroid/location/ILocationManager;->reportLocation(Landroid/location/Location;Z)V
Landroid/location/INetInitiatedListener$Stub;-><init>()V
Landroid/location/INetInitiatedListener;->sendNiResponse(II)Z
Landroid/location/LocationManager$ListenerTransport;-><init>(Landroid/location/LocationManager;Landroid/location/LocationListener;Landroid/os/Looper;)V
@@ -2768,94 +2766,6 @@
Lcom/android/internal/telephony/Connection;->mIsIncoming:Z
Lcom/android/internal/telephony/Connection;->mNumberPresentation:I
Lcom/android/internal/telephony/Connection;->setVideoState(I)V
-Lcom/android/internal/telephony/dataconnection/ApnContext;->getApnType()Ljava/lang/String;
-Lcom/android/internal/telephony/dataconnection/ApnContext;->getReason()Ljava/lang/String;
-Lcom/android/internal/telephony/dataconnection/ApnContext;->getState()Lcom/android/internal/telephony/DctConstants$State;
-Lcom/android/internal/telephony/dataconnection/ApnContext;->isConnectable()Z
-Lcom/android/internal/telephony/dataconnection/ApnContext;->isDisconnected()Z
-Lcom/android/internal/telephony/dataconnection/ApnContext;->isEnabled()Z
-Lcom/android/internal/telephony/dataconnection/ApnContext;->isReady()Z
-Lcom/android/internal/telephony/dataconnection/ApnContext;->log(Ljava/lang/String;)V
-Lcom/android/internal/telephony/dataconnection/ApnContext;->mApnType:Ljava/lang/String;
-Lcom/android/internal/telephony/dataconnection/ApnContext;->mRefCount:I
-Lcom/android/internal/telephony/dataconnection/ApnContext;->mRefCountLock:Ljava/lang/Object;
-Lcom/android/internal/telephony/dataconnection/ApnContext;->setState(Lcom/android/internal/telephony/DctConstants$State;)V
-Lcom/android/internal/telephony/dataconnection/DataConnection$ConnectionParams;->mApnContext:Lcom/android/internal/telephony/dataconnection/ApnContext;
-Lcom/android/internal/telephony/dataconnection/DataConnection;->clearSettings()V
-Lcom/android/internal/telephony/dataconnection/DataConnection;->dumpToLog()V
-Lcom/android/internal/telephony/dataconnection/DataConnection;->initConnection(Lcom/android/internal/telephony/dataconnection/DataConnection$ConnectionParams;)Z
-Lcom/android/internal/telephony/dataconnection/DataConnection;->log(Ljava/lang/String;)V
-Lcom/android/internal/telephony/dataconnection/DataConnection;->mActivatingState:Lcom/android/internal/telephony/dataconnection/DataConnection$DcActivatingState;
-Lcom/android/internal/telephony/dataconnection/DataConnection;->mActiveState:Lcom/android/internal/telephony/dataconnection/DataConnection$DcActiveState;
-Lcom/android/internal/telephony/dataconnection/DataConnection;->mConnectionParams:Lcom/android/internal/telephony/dataconnection/DataConnection$ConnectionParams;
-Lcom/android/internal/telephony/dataconnection/DataConnection;->mDataRegState:I
-Lcom/android/internal/telephony/dataconnection/DataConnection;->mDct:Lcom/android/internal/telephony/dataconnection/DcTracker;
-Lcom/android/internal/telephony/dataconnection/DataConnection;->mDisconnectingErrorCreatingConnection:Lcom/android/internal/telephony/dataconnection/DataConnection$DcDisconnectionErrorCreatingConnection;
-Lcom/android/internal/telephony/dataconnection/DataConnection;->mDisconnectingState:Lcom/android/internal/telephony/dataconnection/DataConnection$DcDisconnectingState;
-Lcom/android/internal/telephony/dataconnection/DataConnection;->mDisconnectParams:Lcom/android/internal/telephony/dataconnection/DataConnection$DisconnectParams;
-Lcom/android/internal/telephony/dataconnection/DataConnection;->mId:I
-Lcom/android/internal/telephony/dataconnection/DataConnection;->mInactiveState:Lcom/android/internal/telephony/dataconnection/DataConnection$DcInactiveState;
-Lcom/android/internal/telephony/dataconnection/DataConnection;->mLinkProperties:Landroid/net/LinkProperties;
-Lcom/android/internal/telephony/dataconnection/DataConnection;->mNetworkInfo:Landroid/net/NetworkInfo;
-Lcom/android/internal/telephony/dataconnection/DataConnection;->mPhone:Lcom/android/internal/telephony/Phone;
-Lcom/android/internal/telephony/dataconnection/DataConnection;->mRilRat:I
-Lcom/android/internal/telephony/dataconnection/DataConnection;->notifyAllOfConnected(Ljava/lang/String;)V
-Lcom/android/internal/telephony/dataconnection/DataConnection;->notifyDisconnectCompleted(Lcom/android/internal/telephony/dataconnection/DataConnection$DisconnectParams;Z)V
-Lcom/android/internal/telephony/dataconnection/DataConnection;->onConnect(Lcom/android/internal/telephony/dataconnection/DataConnection$ConnectionParams;)V
-Lcom/android/internal/telephony/dataconnection/DataConnection;->tearDownData(Ljava/lang/Object;)V
-Lcom/android/internal/telephony/dataconnection/DataConnection;->updateTcpBufferSizes(I)V
-Lcom/android/internal/telephony/dataconnection/DcController;->lr(Ljava/lang/String;)V
-Lcom/android/internal/telephony/dataconnection/DcController;->mDcListActiveByCid:Ljava/util/HashMap;
-Lcom/android/internal/telephony/dataconnection/DcController;->mDct:Lcom/android/internal/telephony/dataconnection/DcTracker;
-Lcom/android/internal/telephony/dataconnection/DcController;->mDcTesterDeactivateAll:Lcom/android/internal/telephony/dataconnection/DcTesterDeactivateAll;
-Lcom/android/internal/telephony/dataconnection/DcTracker$RecoveryAction;->isAggressiveRecovery(I)Z
-Lcom/android/internal/telephony/dataconnection/DcTracker;->cancelReconnectAlarm(Lcom/android/internal/telephony/dataconnection/ApnContext;)V
-Lcom/android/internal/telephony/dataconnection/DcTracker;->cleanUpAllConnections(Ljava/lang/String;)V
-Lcom/android/internal/telephony/dataconnection/DcTracker;->createAllApnList()V
-Lcom/android/internal/telephony/dataconnection/DcTracker;->getActiveApnTypes()[Ljava/lang/String;
-Lcom/android/internal/telephony/dataconnection/DcTracker;->getOverallState()Lcom/android/internal/telephony/DctConstants$State;
-Lcom/android/internal/telephony/dataconnection/DcTracker;->getUiccRecords(I)Lcom/android/internal/telephony/uicc/IccRecords;
-Lcom/android/internal/telephony/dataconnection/DcTracker;->isConnected()Z
-Lcom/android/internal/telephony/dataconnection/DcTracker;->isDisconnected()Z
-Lcom/android/internal/telephony/dataconnection/DcTracker;->isOnlySingleDcAllowed(I)Z
-Lcom/android/internal/telephony/dataconnection/DcTracker;->log(Ljava/lang/String;)V
-Lcom/android/internal/telephony/dataconnection/DcTracker;->loge(Ljava/lang/String;)V
-Lcom/android/internal/telephony/dataconnection/DcTracker;->mAllApnSettings:Ljava/util/ArrayList;
-Lcom/android/internal/telephony/dataconnection/DcTracker;->mApnContexts:Ljava/util/concurrent/ConcurrentHashMap;
-Lcom/android/internal/telephony/dataconnection/DcTracker;->mAttached:Ljava/util/concurrent/atomic/AtomicBoolean;
-Lcom/android/internal/telephony/dataconnection/DcTracker;->mAutoAttachOnCreation:Ljava/util/concurrent/atomic/AtomicBoolean;
-Lcom/android/internal/telephony/dataconnection/DcTracker;->mDataConnectionTracker:Landroid/os/Handler;
-Lcom/android/internal/telephony/dataconnection/DcTracker;->mDisconnectPendingCount:I
-Lcom/android/internal/telephony/dataconnection/DcTracker;->mIccRecords:Ljava/util/concurrent/atomic/AtomicReference;
-Lcom/android/internal/telephony/dataconnection/DcTracker;->mIsPsRestricted:Z
-Lcom/android/internal/telephony/dataconnection/DcTracker;->mIsScreenOn:Z
-Lcom/android/internal/telephony/dataconnection/DcTracker;->mNetStatPollEnabled:Z
-Lcom/android/internal/telephony/dataconnection/DcTracker;->mNetStatPollPeriod:I
-Lcom/android/internal/telephony/dataconnection/DcTracker;->mPhone:Lcom/android/internal/telephony/Phone;
-Lcom/android/internal/telephony/dataconnection/DcTracker;->mPrioritySortedApnContexts:Ljava/util/PriorityQueue;
-Lcom/android/internal/telephony/dataconnection/DcTracker;->mProvisioningSpinner:Landroid/app/ProgressDialog;
-Lcom/android/internal/telephony/dataconnection/DcTracker;->mResolver:Landroid/content/ContentResolver;
-Lcom/android/internal/telephony/dataconnection/DcTracker;->mState:Lcom/android/internal/telephony/DctConstants$State;
-Lcom/android/internal/telephony/dataconnection/DcTracker;->mSubscriptionManager:Landroid/telephony/SubscriptionManager;
-Lcom/android/internal/telephony/dataconnection/DcTracker;->notifyDataConnection(Ljava/lang/String;)V
-Lcom/android/internal/telephony/dataconnection/DcTracker;->notifyOffApnsOfAvailability(Ljava/lang/String;)V
-Lcom/android/internal/telephony/dataconnection/DcTracker;->onActionIntentDataStallAlarm(Landroid/content/Intent;)V
-Lcom/android/internal/telephony/dataconnection/DcTracker;->onActionIntentProvisioningApnAlarm(Landroid/content/Intent;)V
-Lcom/android/internal/telephony/dataconnection/DcTracker;->onRecordsLoadedOrSubIdChanged()V
-Lcom/android/internal/telephony/dataconnection/DcTracker;->onTrySetupData(Lcom/android/internal/telephony/dataconnection/ApnContext;)Z
-Lcom/android/internal/telephony/dataconnection/DcTracker;->onTrySetupData(Ljava/lang/String;)Z
-Lcom/android/internal/telephony/dataconnection/DcTracker;->registerSettingsObserver()V
-Lcom/android/internal/telephony/dataconnection/DcTracker;->resetPollStats()V
-Lcom/android/internal/telephony/dataconnection/DcTracker;->restartDataStallAlarm()V
-Lcom/android/internal/telephony/dataconnection/DcTracker;->setInitialAttachApn()V
-Lcom/android/internal/telephony/dataconnection/DcTracker;->setPreferredApn(I)V
-Lcom/android/internal/telephony/dataconnection/DcTracker;->setRadio(Z)V
-Lcom/android/internal/telephony/dataconnection/DcTracker;->setupDataOnConnectableApns(Ljava/lang/String;)V
-Lcom/android/internal/telephony/dataconnection/DcTracker;->startDataStallAlarm(Z)V
-Lcom/android/internal/telephony/dataconnection/DcTracker;->startNetStatPoll()V
-Lcom/android/internal/telephony/dataconnection/DcTracker;->stopDataStallAlarm()V
-Lcom/android/internal/telephony/dataconnection/DcTracker;->stopNetStatPoll()V
-Lcom/android/internal/telephony/dataconnection/DcTracker;->unregisterForAllDataDisconnected(Landroid/os/Handler;)V
Lcom/android/internal/telephony/DctConstants$Activity;->DATAIN:Lcom/android/internal/telephony/DctConstants$Activity;
Lcom/android/internal/telephony/DctConstants$Activity;->DATAINANDOUT:Lcom/android/internal/telephony/DctConstants$Activity;
Lcom/android/internal/telephony/DctConstants$Activity;->DATAOUT:Lcom/android/internal/telephony/DctConstants$Activity;
@@ -3290,7 +3200,7 @@
Lcom/android/internal/telephony/ITelephonyRegistry;->listen(Ljava/lang/String;Lcom/android/internal/telephony/IPhoneStateListener;IZ)V
Lcom/android/internal/telephony/ITelephonyRegistry;->notifyCallState(ILjava/lang/String;)V
Lcom/android/internal/telephony/ITelephonyRegistry;->notifyCellInfo(Ljava/util/List;)V
-Lcom/android/internal/telephony/ITelephonyRegistry;->notifyDataConnectionFailed(Ljava/lang/String;Ljava/lang/String;)V
+Lcom/android/internal/telephony/ITelephonyRegistry;->notifyDataConnectionFailed(Ljava/lang/String;)V
Lcom/android/internal/telephony/IWapPushManager$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/telephony/IWapPushManager;
Lcom/android/internal/telephony/IWapPushManager;->addPackage(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IZZ)Z
Lcom/android/internal/telephony/IWapPushManager;->deletePackage(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 5b8261e..c89848e 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -121,6 +121,7 @@
import android.view.autofill.AutofillManager.AutofillClient;
import android.view.autofill.AutofillPopupWindow;
import android.view.autofill.IAutofillWindowPresenter;
+import android.view.contentcapture.ContentCaptureContext;
import android.view.contentcapture.ContentCaptureManager;
import android.widget.AdapterView;
import android.widget.Toast;
@@ -1045,14 +1046,19 @@
private void notifyContentCaptureManagerIfNeeded(@ContentCaptureNotificationType int type) {
final ContentCaptureManager cm = getContentCaptureManager();
- if (cm == null || !cm.isContentCaptureEnabled()) {
+ if (cm == null) {
return;
}
switch (type) {
case CONTENT_CAPTURE_START:
//TODO(b/111276913): decide whether the InteractionSessionId should be
// saved / restored in the activity bundle - probably not
- cm.onActivityStarted(mToken, getComponentName());
+ int flags = 0;
+ if ((getWindow().getAttributes().flags
+ & WindowManager.LayoutParams.FLAG_SECURE) != 0) {
+ flags |= ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE;
+ }
+ cm.onActivityStarted(mToken, getComponentName(), flags);
break;
case CONTENT_CAPTURE_FLUSH:
cm.flush();
@@ -2158,6 +2164,7 @@
* for helping activities determine the proper time to cancel a notification.
*
* @see #onUserInteraction()
+ * @see android.content.Intent#FLAG_ACTIVITY_NO_USER_ACTION
*/
protected void onUserLeaveHint() {
}
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 0b50916..9d44e58 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -314,4 +314,7 @@
/** Returns mount mode for process running with given pid */
public abstract int getStorageMountMode(int pid, int uid);
+
+ /** Returns true if the given uid is the app in the foreground. */
+ public abstract boolean isAppForeground(int uid);
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 8faf08a..7767f04 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -271,6 +271,13 @@
@UnsupportedAppUsage
final H mH = new H();
final Executor mExecutor = new HandlerExecutor(mH);
+ /**
+ * Maps from activity token to local record of running activities in this process.
+ *
+ * This variable is readable if the code is running in activity thread or holding {@link
+ * #mResourcesManager}. It's only writable if the code is running in activity thread and holding
+ * {@link #mResourcesManager}.
+ */
@UnsupportedAppUsage
final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();
/** The activities to be truly destroyed (not include relaunch). */
@@ -434,6 +441,10 @@
Configuration newConfig;
Configuration createdConfig;
Configuration overrideConfig;
+ // Used to save the last reported configuration from server side so that activity
+ // configuration transactions can always use the latest configuration.
+ @GuardedBy("this")
+ private Configuration mPendingOverrideConfig;
// Used for consolidating configs before sending on to Activity.
private Configuration tmpConfig = new Configuration();
// Callback used for updating activity override config.
@@ -3064,7 +3075,12 @@
}
r.setState(ON_CREATE);
- mActivities.put(r.token, r);
+ // updatePendingActivityConfiguration() reads from mActivities to update
+ // ActivityClientRecord which runs in a different thread. Protect modifications to
+ // mActivities to avoid race.
+ synchronized (mResourcesManager) {
+ mActivities.put(r.token, r);
+ }
} catch (SuperNotCalledException e) {
throw e;
@@ -4639,7 +4655,12 @@
r.setState(ON_DESTROY);
}
schedulePurgeIdler();
- mActivities.remove(token);
+ // updatePendingActivityConfiguration() reads from mActivities to update
+ // ActivityClientRecord which runs in a different thread. Protect modifications to
+ // mActivities to avoid race.
+ synchronized (mResourcesManager) {
+ mActivities.remove(token);
+ }
StrictMode.decrementExpectedActivityCount(activityClass);
return r;
}
@@ -5382,6 +5403,26 @@
}
}
+ @Override
+ public void updatePendingActivityConfiguration(IBinder activityToken,
+ Configuration overrideConfig) {
+ final ActivityClientRecord r;
+ synchronized (mResourcesManager) {
+ r = mActivities.get(activityToken);
+ }
+
+ if (r == null) {
+ if (DEBUG_CONFIGURATION) {
+ Slog.w(TAG, "Not found target activity to update its pending config.");
+ }
+ return;
+ }
+
+ synchronized (r) {
+ r.mPendingOverrideConfig = overrideConfig;
+ }
+ }
+
/**
* Handle new activity configuration and/or move to a different display.
* @param activityToken Target activity token.
@@ -5401,6 +5442,24 @@
final boolean movedToDifferentDisplay = displayId != INVALID_DISPLAY
&& displayId != r.activity.getDisplayId();
+ synchronized (r) {
+ if (r.mPendingOverrideConfig != null
+ && !r.mPendingOverrideConfig.isOtherSeqNewer(overrideConfig)) {
+ overrideConfig = r.mPendingOverrideConfig;
+ }
+ r.mPendingOverrideConfig = null;
+ }
+
+ if (r.overrideConfig != null && !r.overrideConfig.isOtherSeqNewer(overrideConfig)
+ && !movedToDifferentDisplay) {
+ if (DEBUG_CONFIGURATION) {
+ Slog.v(TAG, "Activity already handled newer configuration so drop this"
+ + " transaction. overrideConfig=" + overrideConfig + " r.overrideConfig="
+ + r.overrideConfig);
+ }
+ return;
+ }
+
// Perform updates.
r.overrideConfig = overrideConfig;
final ViewRootImpl viewRoot = r.activity.mDecor != null
diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java
index 0b5776e..57132a7 100644
--- a/core/java/android/app/ActivityView.java
+++ b/core/java/android/app/ActivityView.java
@@ -37,6 +37,7 @@
import android.view.SurfaceHolder;
import android.view.SurfaceSession;
import android.view.SurfaceView;
+import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
@@ -320,7 +321,7 @@
public void surfaceCreated(SurfaceHolder surfaceHolder) {
mTmpSurface = new Surface();
if (mVirtualDisplay == null) {
- initVirtualDisplay(new SurfaceSession(surfaceHolder.getSurface()));
+ initVirtualDisplay(new SurfaceSession());
if (mVirtualDisplay != null && mActivityViewCallback != null) {
mActivityViewCallback.onActivityViewReady(ActivityView.this);
}
@@ -354,6 +355,12 @@
}
}
+ @Override
+ protected void onVisibilityChanged(View changedView, int visibility) {
+ super.onVisibilityChanged(changedView, visibility);
+ mSurfaceView.setVisibility(visibility);
+ }
+
private void initVirtualDisplay(SurfaceSession surfaceSession) {
if (mVirtualDisplay != null) {
throw new IllegalStateException("Trying to initialize for the second time.");
@@ -382,6 +389,7 @@
mRootSurfaceControl = new SurfaceControl.Builder(surfaceSession)
.setContainerLayer(true)
+ .setParent(mSurfaceView.getSurfaceControl())
.setName(DISPLAY_NAME)
.build();
diff --git a/core/java/android/app/AppOpsManager.aidl b/core/java/android/app/AppOpsManager.aidl
index 9329fbc..2240302 100644
--- a/core/java/android/app/AppOpsManager.aidl
+++ b/core/java/android/app/AppOpsManager.aidl
@@ -19,5 +19,7 @@
parcelable AppOpsManager.PackageOps;
parcelable AppOpsManager.OpEntry;
+parcelable AppOpsManager.HistoricalOp;
+parcelable AppOpsManager.HistoricalOps;
parcelable AppOpsManager.HistoricalPackageOps;
-parcelable AppOpsManager.HistoricalOpEntry;
+parcelable AppOpsManager.HistoricalUidOps;
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 78fe002..e155fe2 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -36,26 +36,33 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Process;
+import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.UserManager;
import android.provider.Settings;
import android.util.ArrayMap;
import com.android.internal.annotations.GuardedBy;
+import android.util.SparseArray;
import com.android.internal.app.IAppOpsActiveCallback;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsNotedCallback;
import com.android.internal.app.IAppOpsService;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* API for interacting with "application operation" tracking.
@@ -106,6 +113,51 @@
static IBinder sToken;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "HISTORICAL_MODE_" }, value = {
+ HISTORICAL_MODE_DISABLED,
+ HISTORICAL_MODE_ENABLED_ACTIVE,
+ HISTORICAL_MODE_ENABLED_PASSIVE
+ })
+ public @interface HistoricalMode {}
+
+ /**
+ * Mode in which app op history is completely disabled.
+ * @hide
+ */
+ @TestApi
+ public static final int HISTORICAL_MODE_DISABLED = 0;
+
+ /**
+ * Mode in which app op history is enabled and app ops performed by apps would
+ * be tracked. This is the mode in which the feature is completely enabled.
+ * @hide
+ */
+ @TestApi
+ public static final int HISTORICAL_MODE_ENABLED_ACTIVE = 1;
+
+ /**
+ * Mode in which app op history is enabled but app ops performed by apps would
+ * not be tracked and the only way to add ops to the history is via explicit calls
+ * to dedicated APIs. This mode is useful for testing to allow full control of
+ * the historical content.
+ * @hide
+ */
+ @TestApi
+ public static final int HISTORICAL_MODE_ENABLED_PASSIVE = 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "MODE_" }, value = {
+ MODE_ALLOWED,
+ MODE_IGNORED,
+ MODE_ERRORED,
+ MODE_DEFAULT,
+ MODE_FOREGROUND
+ })
+ public @interface Mode {}
+
/**
* Result from {@link #checkOp}, {@link #noteOp}, {@link #startOp}: the given caller is
* allowed to perform the given operation.
@@ -159,7 +211,6 @@
"foreground", // MODE_FOREGROUND
};
-
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, prefix = { "UID_STATE_" }, value = {
@@ -173,9 +224,16 @@
public @interface UidState {}
/**
+ * Invalid UID state.
+ * @hide
+ */
+ public static final int UID_STATE_INVALID = -1;
+
+ /**
* Metrics about an op when its uid is persistent.
* @hide
*/
+ @TestApi
@SystemApi
public static final int UID_STATE_PERSISTENT = 0;
@@ -183,6 +241,7 @@
* Metrics about an op when its uid is at the top.
* @hide
*/
+ @TestApi
@SystemApi
public static final int UID_STATE_TOP = 1;
@@ -190,6 +249,7 @@
* Metrics about an op when its uid is running a foreground service.
* @hide
*/
+ @TestApi
@SystemApi
public static final int UID_STATE_FOREGROUND_SERVICE = 2;
@@ -203,6 +263,7 @@
* Metrics about an op when its uid is in the foreground for any other reasons.
* @hide
*/
+ @TestApi
@SystemApi
public static final int UID_STATE_FOREGROUND = 3;
@@ -210,6 +271,7 @@
* Metrics about an op when its uid is in the background for any reason.
* @hide
*/
+ @TestApi
@SystemApi
public static final int UID_STATE_BACKGROUND = 4;
@@ -217,6 +279,7 @@
* Metrics about an op when its uid is cached.
* @hide
*/
+ @TestApi
@SystemApi
public static final int UID_STATE_CACHED = 5;
@@ -237,7 +300,7 @@
@UnsupportedAppUsage
public static final int OP_NONE = -1;
/** @hide Access to coarse location information. */
- @UnsupportedAppUsage
+ @TestApi
public static final int OP_COARSE_LOCATION = 0;
/** @hide Access to fine location information. */
@UnsupportedAppUsage
@@ -1645,6 +1708,9 @@
}
}
+ /** @hide */
+ public static final String KEY_HISTORICAL_OPS = "historical_ops";
+
/**
* Retrieve the op switch that controls the given operation.
* @hide
@@ -1731,7 +1797,7 @@
* Retrieve the default mode for the operation.
* @hide
*/
- public static int opToDefaultMode(int op) {
+ public static @Mode int opToDefaultMode(int op) {
// STOPSHIP b/118520006: Hardcode the default values once the feature is stable.
switch (op) {
// SMS permissions
@@ -1795,7 +1861,7 @@
* Retrieve the human readable mode.
* @hide
*/
- public static String modeToName(int mode) {
+ public static String modeToName(@Mode int mode) {
if (mode >= 0 && mode < MODE_NAMES.length) {
return MODE_NAMES[mode];
}
@@ -1885,7 +1951,7 @@
@SystemApi
public static final class OpEntry implements Parcelable {
private final int mOp;
- private final int mMode;
+ private final @Mode int mMode;
private final long[] mTimes;
private final long[] mRejectTimes;
private final int mDuration;
@@ -1896,7 +1962,7 @@
/**
* @hide
*/
- public OpEntry(int op, int mode, long time, long rejectTime, int duration,
+ public OpEntry(int op, @Mode int mode, long time, long rejectTime, int duration,
int proxyUid, String proxyPackage) {
mOp = op;
mMode = mode;
@@ -1913,7 +1979,7 @@
/**
* @hide
*/
- public OpEntry(int op, int mode, long[] times, long[] rejectTimes, int duration,
+ public OpEntry(int op, @Mode int mode, long[] times, long[] rejectTimes, int duration,
boolean running, int proxyUid, String proxyPackage) {
mOp = op;
mMode = mode;
@@ -1930,7 +1996,7 @@
/**
* @hide
*/
- public OpEntry(int op, int mode, long[] times, long[] rejectTimes, int duration,
+ public OpEntry(int op, @Mode int mode, long[] times, long[] rejectTimes, int duration,
int proxyUid, String proxyPackage) {
this(op, mode, times, rejectTimes, duration, duration == -1, proxyUid, proxyPackage);
}
@@ -1953,7 +2019,7 @@
/**
* Return this entry's current mode, such as {@link #MODE_ALLOWED}.
*/
- public int getMode() {
+ public @Mode int getMode() {
return mMode;
}
@@ -2086,41 +2152,774 @@
};
}
+ /** @hide */
+ public interface HistoricalOpsVisitor {
+ void visitHistoricalOps(@NonNull HistoricalOps ops);
+ void visitHistoricalUidOps(@NonNull HistoricalUidOps ops);
+ void visitHistoricalPackageOps(@NonNull HistoricalPackageOps ops);
+ void visitHistoricalOp(@NonNull HistoricalOp ops);
+ }
+
/**
- * This class represents historical app op information about a package. The history
- * is aggregated information about ops for a certain amount of time such
- * as the times the op was accessed, the times the op was rejected, the total
- * duration the app op has been accessed.
+ * This class represents historical app op state of all UIDs for a given time interval.
+ *
+ * @hide
+ */
+ @TestApi
+ @SystemApi
+ public static final class HistoricalOps implements Parcelable {
+ private long mBeginTimeMillis;
+ private long mEndTimeMillis;
+ private @Nullable SparseArray<HistoricalUidOps> mHistoricalUidOps;
+
+ /** @hide */
+ @TestApi
+ public HistoricalOps(long beginTimeMillis, long endTimeMillis) {
+ Preconditions.checkState(beginTimeMillis <= endTimeMillis);
+ mBeginTimeMillis = beginTimeMillis;
+ mEndTimeMillis = endTimeMillis;
+ }
+
+ /** @hide */
+ public HistoricalOps(@NonNull HistoricalOps other) {
+ mBeginTimeMillis = other.mBeginTimeMillis;
+ mEndTimeMillis = other.mEndTimeMillis;
+ Preconditions.checkState(mBeginTimeMillis <= mEndTimeMillis);
+ if (other.mHistoricalUidOps != null) {
+ final int opCount = other.getUidCount();
+ for (int i = 0; i < opCount; i++) {
+ final HistoricalUidOps origOps = other.getUidOpsAt(i);
+ final HistoricalUidOps clonedOps = new HistoricalUidOps(origOps);
+ if (mHistoricalUidOps == null) {
+ mHistoricalUidOps = new SparseArray<>(opCount);
+ }
+ mHistoricalUidOps.put(clonedOps.getUid(), clonedOps);
+ }
+ }
+ }
+
+ private HistoricalOps(Parcel parcel) {
+ mBeginTimeMillis = parcel.readLong();
+ mEndTimeMillis = parcel.readLong();
+ final int[] uids = parcel.createIntArray();
+ if (!ArrayUtils.isEmpty(uids)) {
+ final ParceledListSlice<HistoricalUidOps> listSlice = parcel.readParcelable(
+ HistoricalOps.class.getClassLoader());
+ final List<HistoricalUidOps> uidOps = (listSlice != null)
+ ? listSlice.getList() : null;
+ if (uidOps == null) {
+ return;
+ }
+ for (int i = 0; i < uids.length; i++) {
+ if (mHistoricalUidOps == null) {
+ mHistoricalUidOps = new SparseArray<>();
+ }
+ mHistoricalUidOps.put(uids[i], uidOps.get(i));
+ }
+ }
+ }
+
+ /**
+ * Splice a piece from the beginning of these ops.
+ *
+ * @param splicePoint The fraction of the data to be spliced off.
+ *
+ * @hide
+ */
+ public @NonNull HistoricalOps spliceFromBeginning(double splicePoint) {
+ return splice(splicePoint, true);
+ }
+
+ /**
+ * Splice a piece from the end of these ops.
+ *
+ * @param fractionToRemove The fraction of the data to be spliced off.
+ *
+ * @hide
+ */
+ public @NonNull HistoricalOps spliceFromEnd(double fractionToRemove) {
+ return splice(fractionToRemove, false);
+ }
+
+ /**
+ * Splice a piece from the beginning or end of these ops.
+ *
+ * @param fractionToRemove The fraction of the data to be spliced off.
+ * @param beginning Whether to splice off the beginning or the end.
+ *
+ * @return The spliced off part.
+ *
+ * @hide
+ */
+ private @Nullable HistoricalOps splice(double fractionToRemove, boolean beginning) {
+ final long spliceBeginTimeMills;
+ final long spliceEndTimeMills;
+ if (beginning) {
+ spliceBeginTimeMills = mBeginTimeMillis;
+ spliceEndTimeMills = (long) (mBeginTimeMillis
+ + getDurationMillis() * fractionToRemove);
+ mBeginTimeMillis = spliceEndTimeMills;
+ } else {
+ spliceBeginTimeMills = (long) (mEndTimeMillis
+ - getDurationMillis() * fractionToRemove);
+ spliceEndTimeMills = mEndTimeMillis;
+ mEndTimeMillis = spliceBeginTimeMills;
+ }
+
+ HistoricalOps splice = null;
+ final int uidCount = getUidCount();
+ for (int i = 0; i < uidCount; i++) {
+ final HistoricalUidOps origOps = getUidOpsAt(i);
+ final HistoricalUidOps spliceOps = origOps.splice(fractionToRemove);
+ if (spliceOps != null) {
+ if (splice == null) {
+ splice = new HistoricalOps(spliceBeginTimeMills, spliceEndTimeMills);
+ }
+ if (splice.mHistoricalUidOps == null) {
+ splice.mHistoricalUidOps = new SparseArray<>();
+ }
+ splice.mHistoricalUidOps.put(spliceOps.getUid(), spliceOps);
+ }
+ }
+ return splice;
+ }
+
+ /**
+ * Merge the passed ops into the current ones. The time interval is a
+ * union of the current and passed in one and the passed in data is
+ * folded into the data of this instance.
+ *
+ * @hide
+ */
+ public void merge(@NonNull HistoricalOps other) {
+ mBeginTimeMillis = Math.min(mBeginTimeMillis, other.mBeginTimeMillis);
+ mEndTimeMillis = Math.max(mEndTimeMillis, other.mEndTimeMillis);
+ final int uidCount = other.getUidCount();
+ for (int i = 0; i < uidCount; i++) {
+ final HistoricalUidOps otherUidOps = other.getUidOpsAt(i);
+ final HistoricalUidOps thisUidOps = getUidOps(otherUidOps.getUid());
+ if (thisUidOps != null) {
+ thisUidOps.merge(otherUidOps);
+ } else {
+ if (mHistoricalUidOps == null) {
+ mHistoricalUidOps = new SparseArray<>();
+ }
+ mHistoricalUidOps.put(otherUidOps.getUid(), otherUidOps);
+ }
+ }
+ }
+
+ /**
+ * AppPermissionUsage the ops to leave only the data we filter for.
+ *
+ * @param uid Uid to filter for or {@link android.os.Process#INCIDENTD_UID} for all.
+ * @param packageName Package to filter for or null for all.
+ * @param opNames Ops to filter for or null for all.
+ * @param beginTimeMillis The begin time to filter for or {@link Long#MIN_VALUE} for all.
+ * @param endTimeMillis The end time to filter for or {@link Long#MAX_VALUE} for all.
+ *
+ * @hide
+ */
+ public void filter(int uid, @Nullable String packageName, @Nullable String[] opNames,
+ long beginTimeMillis, long endTimeMillis) {
+ final long durationMillis = getDurationMillis();
+ mBeginTimeMillis = Math.max(mBeginTimeMillis, beginTimeMillis);
+ mEndTimeMillis = Math.min(mEndTimeMillis, endTimeMillis);
+ final double scaleFactor = Math.min((double) (endTimeMillis - beginTimeMillis)
+ / (double) durationMillis, 1);
+ final int uidCount = getUidCount();
+ for (int i = uidCount - 1; i >= 0; i--) {
+ final HistoricalUidOps uidOp = mHistoricalUidOps.valueAt(i);
+ if (uid != Process.INVALID_UID && uid != uidOp.getUid()) {
+ mHistoricalUidOps.removeAt(i);
+ } else {
+ uidOp.filter(packageName, opNames, scaleFactor);
+ }
+ }
+ }
+
+ /** @hide */
+ public boolean isEmpty() {
+ if (getBeginTimeMillis() >= getEndTimeMillis()) {
+ return true;
+ }
+ final int uidCount = getUidCount();
+ for (int i = uidCount - 1; i >= 0; i--) {
+ final HistoricalUidOps uidOp = mHistoricalUidOps.valueAt(i);
+ if (!uidOp.isEmpty()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /** @hide */
+ public long getDurationMillis() {
+ return mEndTimeMillis - mBeginTimeMillis;
+ }
+
+ /** @hide */
+ @TestApi
+ public void increaseAccessCount(int opCode, int uid, @NonNull String packageName,
+ @UidState int uidState, long increment) {
+ getOrCreateHistoricalUidOps(uid).increaseAccessCount(opCode,
+ packageName, uidState, increment);
+ }
+
+ /** @hide */
+ @TestApi
+ public void increaseRejectCount(int opCode, int uid, @NonNull String packageName,
+ @UidState int uidState, long increment) {
+ getOrCreateHistoricalUidOps(uid).increaseRejectCount(opCode,
+ packageName, uidState, increment);
+ }
+
+ /** @hide */
+ @TestApi
+ public void increaseAccessDuration(int opCode, int uid, @NonNull String packageName,
+ @UidState int uidState, long increment) {
+ getOrCreateHistoricalUidOps(uid).increaseAccessDuration(opCode,
+ packageName, uidState, increment);
+ }
+
+ /** @hide */
+ @TestApi
+ public void offsetBeginAndEndTime(long offsetMillis) {
+ mBeginTimeMillis += offsetMillis;
+ mEndTimeMillis += offsetMillis;
+ }
+
+ /** @hide */
+ public void setBeginAndEndTime(long beginTimeMillis, long endTimeMillis) {
+ mBeginTimeMillis = beginTimeMillis;
+ mEndTimeMillis = endTimeMillis;
+ }
+
+ /** @hide */
+ public void setBeginTime(long beginTimeMillis) {
+ mBeginTimeMillis = beginTimeMillis;
+ }
+
+ /** @hide */
+ public void setEndTime(long endTimeMillis) {
+ mEndTimeMillis = endTimeMillis;
+ }
+
+ /**
+ * @return The beginning of the interval in milliseconds since
+ * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian).
+ */
+ public long getBeginTimeMillis() {
+ return mBeginTimeMillis;
+ }
+
+ /**
+ * @return The end of the interval in milliseconds since
+ * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian).
+ */
+ public long getEndTimeMillis() {
+ return mEndTimeMillis;
+ }
+
+ /**
+ * Gets number of UIDs with historical ops.
+ *
+ * @return The number of UIDs with historical ops.
+ *
+ * @see #getUidOpsAt(int)
+ */
+ public int getUidCount() {
+ if (mHistoricalUidOps == null) {
+ return 0;
+ }
+ return mHistoricalUidOps.size();
+ }
+
+ /**
+ * Gets the historical UID ops at a given index.
+ *
+ * @param index The index.
+ *
+ * @return The historical UID ops at the given index.
+ *
+ * @see #getUidCount()
+ */
+ public @NonNull HistoricalUidOps getUidOpsAt(int index) {
+ if (mHistoricalUidOps == null) {
+ throw new IndexOutOfBoundsException();
+ }
+ return mHistoricalUidOps.valueAt(index);
+ }
+
+ /**
+ * Gets the historical UID ops for a given UID.
+ *
+ * @param uid The UID.
+ *
+ * @return The historical ops for the UID.
+ */
+ public @Nullable HistoricalUidOps getUidOps(int uid) {
+ if (mHistoricalUidOps == null) {
+ return null;
+ }
+ return mHistoricalUidOps.get(uid);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeLong(mBeginTimeMillis);
+ parcel.writeLong(mEndTimeMillis);
+ if (mHistoricalUidOps != null) {
+ final int uidCount = mHistoricalUidOps.size();
+ parcel.writeInt(uidCount);
+ for (int i = 0; i < uidCount; i++) {
+ parcel.writeInt(mHistoricalUidOps.keyAt(i));
+ }
+ final List<HistoricalUidOps> opsList = new ArrayList<>(uidCount);
+ for (int i = 0; i < uidCount; i++) {
+ opsList.add(mHistoricalUidOps.valueAt(i));
+ }
+ parcel.writeParcelable(new ParceledListSlice<>(opsList), flags);
+ } else {
+ parcel.writeInt(-1);
+ }
+ }
+
+ /**
+ * Accepts a visitor to traverse the ops tree.
+ *
+ * @param visitor The visitor.
+ *
+ * @hide
+ */
+ public void accept(@NonNull HistoricalOpsVisitor visitor) {
+ visitor.visitHistoricalOps(this);
+ final int uidCount = getUidCount();
+ for (int i = 0; i < uidCount; i++) {
+ getUidOpsAt(i).accept(visitor);
+ }
+ }
+
+ private @NonNull HistoricalUidOps getOrCreateHistoricalUidOps(int uid) {
+ if (mHistoricalUidOps == null) {
+ mHistoricalUidOps = new SparseArray<>();
+ }
+ HistoricalUidOps historicalUidOp = mHistoricalUidOps.get(uid);
+ if (historicalUidOp == null) {
+ historicalUidOp = new HistoricalUidOps(uid);
+ mHistoricalUidOps.put(uid, historicalUidOp);
+ }
+ return historicalUidOp;
+ }
+
+ /**
+ * @return Rounded value up at the 0.5 boundary.
+ *
+ * @hide
+ */
+ public static double round(double value) {
+ final BigDecimal decimalScale = new BigDecimal(value);
+ return decimalScale.setScale(0, RoundingMode.HALF_UP).doubleValue();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final HistoricalOps other = (HistoricalOps) obj;
+ if (mBeginTimeMillis != other.mBeginTimeMillis) {
+ return false;
+ }
+ if (mEndTimeMillis != other.mEndTimeMillis) {
+ return false;
+ }
+ if (mHistoricalUidOps == null) {
+ if (other.mHistoricalUidOps != null) {
+ return false;
+ }
+ } else if (!mHistoricalUidOps.equals(other.mHistoricalUidOps)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = (int) (mBeginTimeMillis ^ (mBeginTimeMillis >>> 32));
+ result = 31 * result + mHistoricalUidOps.hashCode();
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "[from:"
+ + mBeginTimeMillis + " to:" + mEndTimeMillis + "]";
+ }
+
+ public static final Creator<HistoricalOps> CREATOR = new Creator<HistoricalOps>() {
+ @Override
+ public @NonNull HistoricalOps createFromParcel(@NonNull Parcel parcel) {
+ return new HistoricalOps(parcel);
+ }
+
+ @Override
+ public @NonNull HistoricalOps[] newArray(int size) {
+ return new HistoricalOps[size];
+ }
+ };
+ }
+
+ /**
+ * This class represents historical app op state for a UID.
+ *
+ * @hide
+ */
+ @TestApi
+ @SystemApi
+ public static final class HistoricalUidOps implements Parcelable {
+ private final int mUid;
+ private @Nullable ArrayMap<String, HistoricalPackageOps> mHistoricalPackageOps;
+
+ /** @hide */
+ public HistoricalUidOps(int uid) {
+ mUid = uid;
+ }
+
+ private HistoricalUidOps(@NonNull HistoricalUidOps other) {
+ mUid = other.mUid;
+ final int opCount = other.getPackageCount();
+ for (int i = 0; i < opCount; i++) {
+ final HistoricalPackageOps origOps = other.getPackageOpsAt(i);
+ final HistoricalPackageOps cloneOps = new HistoricalPackageOps(origOps);
+ if (mHistoricalPackageOps == null) {
+ mHistoricalPackageOps = new ArrayMap<>(opCount);
+ }
+ mHistoricalPackageOps.put(cloneOps.getPackageName(), cloneOps);
+ }
+ }
+
+ private HistoricalUidOps(@NonNull Parcel parcel) {
+ // No arg check since we always read from a trusted source.
+ mUid = parcel.readInt();
+ mHistoricalPackageOps = parcel.createTypedArrayMap(HistoricalPackageOps.CREATOR);
+ }
+
+ private @Nullable HistoricalUidOps splice(double fractionToRemove) {
+ HistoricalUidOps splice = null;
+ final int packageCount = getPackageCount();
+ for (int i = 0; i < packageCount; i++) {
+ final HistoricalPackageOps origOps = getPackageOpsAt(i);
+ final HistoricalPackageOps spliceOps = origOps.splice(fractionToRemove);
+ if (spliceOps != null) {
+ if (splice == null) {
+ splice = new HistoricalUidOps(mUid);
+ }
+ if (splice.mHistoricalPackageOps == null) {
+ splice.mHistoricalPackageOps = new ArrayMap<>();
+ }
+ splice.mHistoricalPackageOps.put(spliceOps.getPackageName(), spliceOps);
+ }
+ }
+ return splice;
+ }
+
+ private void merge(@NonNull HistoricalUidOps other) {
+ final int packageCount = other.getPackageCount();
+ for (int i = 0; i < packageCount; i++) {
+ final HistoricalPackageOps otherPackageOps = other.getPackageOpsAt(i);
+ final HistoricalPackageOps thisPackageOps = getPackageOps(
+ otherPackageOps.getPackageName());
+ if (thisPackageOps != null) {
+ thisPackageOps.merge(otherPackageOps);
+ } else {
+ if (mHistoricalPackageOps == null) {
+ mHistoricalPackageOps = new ArrayMap<>();
+ }
+ mHistoricalPackageOps.put(otherPackageOps.getPackageName(), otherPackageOps);
+ }
+ }
+ }
+
+ private void filter(@Nullable String packageName, @Nullable String[] opNames,
+ double fractionToRemove) {
+ final int packageCount = getPackageCount();
+ for (int i = packageCount - 1; i >= 0; i--) {
+ final HistoricalPackageOps packageOps = getPackageOpsAt(i);
+ if (packageName != null && !packageName.equals(packageOps.getPackageName())) {
+ mHistoricalPackageOps.removeAt(i);
+ } else {
+ packageOps.filter(opNames, fractionToRemove);
+ }
+ }
+ }
+
+ private boolean isEmpty() {
+ final int packageCount = getPackageCount();
+ for (int i = packageCount - 1; i >= 0; i--) {
+ final HistoricalPackageOps packageOps = mHistoricalPackageOps.valueAt(i);
+ if (!packageOps.isEmpty()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void increaseAccessCount(int opCode, @NonNull String packageName,
+ @UidState int uidState, long increment) {
+ getOrCreateHistoricalPackageOps(packageName).increaseAccessCount(
+ opCode, uidState, increment);
+ }
+
+ private void increaseRejectCount(int opCode, @NonNull String packageName,
+ @UidState int uidState, long increment) {
+ getOrCreateHistoricalPackageOps(packageName).increaseRejectCount(
+ opCode, uidState, increment);
+ }
+
+ private void increaseAccessDuration(int opCode, @NonNull String packageName,
+ @UidState int uidState, long increment) {
+ getOrCreateHistoricalPackageOps(packageName).increaseAccessDuration(
+ opCode, uidState, increment);
+ }
+
+ /**
+ * @return The UID for which the data is related.
+ */
+ public int getUid() {
+ return mUid;
+ }
+
+ /**
+ * Gets number of packages with historical ops.
+ *
+ * @return The number of packages with historical ops.
+ *
+ * @see #getPackageOpsAt(int)
+ */
+ public int getPackageCount() {
+ if (mHistoricalPackageOps == null) {
+ return 0;
+ }
+ return mHistoricalPackageOps.size();
+ }
+
+ /**
+ * Gets the historical package ops at a given index.
+ *
+ * @param index The index.
+ *
+ * @return The historical package ops at the given index.
+ *
+ * @see #getPackageCount()
+ */
+ public @NonNull HistoricalPackageOps getPackageOpsAt(int index) {
+ if (mHistoricalPackageOps == null) {
+ throw new IndexOutOfBoundsException();
+ }
+ return mHistoricalPackageOps.valueAt(index);
+ }
+
+ /**
+ * Gets the historical package ops for a given package.
+ *
+ * @param packageName The package.
+ *
+ * @return The historical ops for the package.
+ */
+ public @Nullable HistoricalPackageOps getPackageOps(@NonNull String packageName) {
+ if (mHistoricalPackageOps == null) {
+ return null;
+ }
+ return mHistoricalPackageOps.get(packageName);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeInt(mUid);
+ parcel.writeTypedArrayMap(mHistoricalPackageOps, flags);
+ }
+
+ private void accept(@NonNull HistoricalOpsVisitor visitor) {
+ visitor.visitHistoricalUidOps(this);
+ final int packageCount = getPackageCount();
+ for (int i = 0; i < packageCount; i++) {
+ getPackageOpsAt(i).accept(visitor);
+ }
+ }
+
+ private @NonNull HistoricalPackageOps getOrCreateHistoricalPackageOps(
+ @NonNull String packageName) {
+ if (mHistoricalPackageOps == null) {
+ mHistoricalPackageOps = new ArrayMap<>();
+ }
+ HistoricalPackageOps historicalPackageOp = mHistoricalPackageOps.get(packageName);
+ if (historicalPackageOp == null) {
+ historicalPackageOp = new HistoricalPackageOps(packageName);
+ mHistoricalPackageOps.put(packageName, historicalPackageOp);
+ }
+ return historicalPackageOp;
+ }
+
+
+ public static final Creator<HistoricalUidOps> CREATOR = new Creator<HistoricalUidOps>() {
+ @Override
+ public @NonNull HistoricalUidOps createFromParcel(@NonNull Parcel parcel) {
+ return new HistoricalUidOps(parcel);
+ }
+
+ @Override
+ public @NonNull HistoricalUidOps[] newArray(int size) {
+ return new HistoricalUidOps[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final HistoricalUidOps other = (HistoricalUidOps) obj;
+ if (mUid != other.mUid) {
+ return false;
+ }
+ if (mHistoricalPackageOps == null) {
+ if (other.mHistoricalPackageOps != null) {
+ return false;
+ }
+ } else if (!mHistoricalPackageOps.equals(other.mHistoricalPackageOps)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mUid;
+ result = 31 * result + (mHistoricalPackageOps != null
+ ? mHistoricalPackageOps.hashCode() : 0);
+ return result;
+ }
+ }
+
+ /**
+ * This class represents historical app op information about a package.
*
* @hide
*/
@TestApi
@SystemApi
public static final class HistoricalPackageOps implements Parcelable {
- private final int mUid;
private final @NonNull String mPackageName;
- private final @NonNull List<HistoricalOpEntry> mEntries;
+ private @Nullable ArrayMap<String, HistoricalOp> mHistoricalOps;
- /**
- * @hide
- */
- public HistoricalPackageOps(int uid, @NonNull String packageName) {
- mUid = uid;
+ /** @hide */
+ public HistoricalPackageOps(@NonNull String packageName) {
mPackageName = packageName;
- mEntries = new ArrayList<>();
}
- HistoricalPackageOps(@NonNull Parcel parcel) {
- mUid = parcel.readInt();
+ private HistoricalPackageOps(@NonNull HistoricalPackageOps other) {
+ mPackageName = other.mPackageName;
+ final int opCount = other.getOpCount();
+ for (int i = 0; i < opCount; i++) {
+ final HistoricalOp origOp = other.getOpAt(i);
+ final HistoricalOp cloneOp = new HistoricalOp(origOp);
+ if (mHistoricalOps == null) {
+ mHistoricalOps = new ArrayMap<>(opCount);
+ }
+ mHistoricalOps.put(cloneOp.getOpName(), cloneOp);
+ }
+ }
+
+ private HistoricalPackageOps(@NonNull Parcel parcel) {
mPackageName = parcel.readString();
- mEntries = parcel.createTypedArrayList(HistoricalOpEntry.CREATOR);
+ mHistoricalOps = parcel.createTypedArrayMap(HistoricalOp.CREATOR);
}
- /**
- * @hide
- */
- public void addEntry(@NonNull HistoricalOpEntry entry) {
- mEntries.add(entry);
+ private @Nullable HistoricalPackageOps splice(double fractionToRemove) {
+ HistoricalPackageOps splice = null;
+ final int opCount = getOpCount();
+ for (int i = 0; i < opCount; i++) {
+ final HistoricalOp origOps = getOpAt(i);
+ final HistoricalOp spliceOps = origOps.splice(fractionToRemove);
+ if (spliceOps != null) {
+ if (splice == null) {
+ splice = new HistoricalPackageOps(mPackageName);
+ }
+ if (splice.mHistoricalOps == null) {
+ splice.mHistoricalOps = new ArrayMap<>();
+ }
+ splice.mHistoricalOps.put(spliceOps.getOpName(), spliceOps);
+ }
+ }
+ return splice;
+ }
+
+ private void merge(@NonNull HistoricalPackageOps other) {
+ final int opCount = other.getOpCount();
+ for (int i = 0; i < opCount; i++) {
+ final HistoricalOp otherOp = other.getOpAt(i);
+ final HistoricalOp thisOp = getOp(otherOp.getOpName());
+ if (thisOp != null) {
+ thisOp.merge(otherOp);
+ } else {
+ if (mHistoricalOps == null) {
+ mHistoricalOps = new ArrayMap<>();
+ }
+ mHistoricalOps.put(otherOp.getOpName(), otherOp);
+ }
+ }
+ }
+
+ private void filter(@Nullable String[] opNames, double scaleFactor) {
+ final int opCount = getOpCount();
+ for (int i = opCount - 1; i >= 0; i--) {
+ final HistoricalOp op = mHistoricalOps.valueAt(i);
+ if (opNames != null && !ArrayUtils.contains(opNames, op.getOpName())) {
+ mHistoricalOps.removeAt(i);
+ } else {
+ op.filter(scaleFactor);
+ }
+ }
+ }
+
+ private boolean isEmpty() {
+ final int opCount = getOpCount();
+ for (int i = opCount - 1; i >= 0; i--) {
+ final HistoricalOp op = mHistoricalOps.valueAt(i);
+ if (!op.isEmpty()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void increaseAccessCount(int opCode, @UidState int uidState, long increment) {
+ getOrCreateHistoricalOp(opCode).increaseAccessCount(uidState, increment);
+ }
+
+ private void increaseRejectCount(int opCode, @UidState int uidState, long increment) {
+ getOrCreateHistoricalOp(opCode).increaseRejectCount(uidState, increment);
+ }
+
+ private void increaseAccessDuration(int opCode, @UidState int uidState, long increment) {
+ getOrCreateHistoricalOp(opCode).increaseAccessDuration(uidState, increment);
}
/**
@@ -2133,36 +2932,33 @@
}
/**
- * Gets the UID which the data represents.
+ * Gets number historical app ops.
*
- * @return The UID which the data represents.
+ * @return The number historical app ops.
+ *
+ * @see #getOpAt(int)
*/
- public int getUid() {
- return mUid;
+ public int getOpCount() {
+ if (mHistoricalOps == null) {
+ return 0;
+ }
+ return mHistoricalOps.size();
}
/**
- * Gets number historical app op entries.
- *
- * @return The number historical app op entries.
- *
- * @see #getEntryAt(int)
- */
- public int getEntryCount() {
- return mEntries.size();
- }
-
- /**
- * Gets the historical at a given index.
+ * Gets the historical op at a given index.
*
* @param index The index to lookup.
*
- * @return The entry at the given index.
+ * @return The op at the given index.
*
- * @see #getEntryCount()
+ * @see #getOpCount()
*/
- public @NonNull HistoricalOpEntry getEntryAt(int index) {
- return mEntries.get(index);
+ public @NonNull HistoricalOp getOpAt(int index) {
+ if (mHistoricalOps == null) {
+ throw new IndexOutOfBoundsException();
+ }
+ return mHistoricalOps.valueAt(index);
}
/**
@@ -2172,15 +2968,11 @@
*
* @return The historical entry for that op name.
*/
- public @Nullable HistoricalOpEntry getEntry(@NonNull String opName) {
- final int entryCount = mEntries.size();
- for (int i = 0; i < entryCount; i++) {
- final HistoricalOpEntry entry = mEntries.get(i);
- if (entry.getOp().equals(opName)) {
- return entry;
- }
+ public @Nullable HistoricalOp getOp(@NonNull String opName) {
+ if (mHistoricalOps == null) {
+ return null;
}
- return null;
+ return mHistoricalOps.get(opName);
}
@Override
@@ -2190,9 +2982,29 @@
@Override
public void writeToParcel(@NonNull Parcel parcel, int flags) {
- parcel.writeInt(mUid);
parcel.writeString(mPackageName);
- parcel.writeTypedList(mEntries, flags);
+ parcel.writeTypedArrayMap(mHistoricalOps, flags);
+ }
+
+ private void accept(@NonNull HistoricalOpsVisitor visitor) {
+ visitor.visitHistoricalPackageOps(this);
+ final int opCount = getOpCount();
+ for (int i = 0; i < opCount; i++) {
+ getOpAt(i).accept(visitor);
+ }
+ }
+
+ private @NonNull HistoricalOp getOrCreateHistoricalOp(int opCode) {
+ if (mHistoricalOps == null) {
+ mHistoricalOps = new ArrayMap<>();
+ }
+ final String opStr = sOpToString[opCode];
+ HistoricalOp op = mHistoricalOps.get(opStr);
+ if (op == null) {
+ op = new HistoricalOp(opCode);
+ mHistoricalOps.put(opStr, op);
+ }
+ return op;
}
public static final Creator<HistoricalPackageOps> CREATOR =
@@ -2207,49 +3019,177 @@
return new HistoricalPackageOps[size];
}
};
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final HistoricalPackageOps other = (HistoricalPackageOps) obj;
+ if (!mPackageName.equals(other.mPackageName)) {
+ return false;
+ }
+ if (mHistoricalOps == null) {
+ if (other.mHistoricalOps != null) {
+ return false;
+ }
+ } else if (!mHistoricalOps.equals(other.mHistoricalOps)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mPackageName != null ? mPackageName.hashCode() : 0;
+ result = 31 * result + (mHistoricalOps != null ? mHistoricalOps.hashCode() : 0);
+ return result;
+ }
}
/**
- * This class represents historical information about an app op. The history
- * is aggregated information about the op for a certain amount of time such
- * as the times the op was accessed, the times the op was rejected, the total
- * duration the app op has been accessed.
+ * This class represents historical information about an app op.
*
* @hide
*/
@TestApi
@SystemApi
- public static final class HistoricalOpEntry implements Parcelable {
+ public static final class HistoricalOp implements Parcelable {
private final int mOp;
- private final long[] mAccessCount;
- private final long[] mRejectCount;
- private final long[] mAccessDuration;
+ private @Nullable long[] mAccessCount;
+ private @Nullable long[] mRejectCount;
+ private @Nullable long[] mAccessDuration;
- /**
- * @hide
- */
- public HistoricalOpEntry(int op) {
+ /** @hide */
+ public HistoricalOp(int op) {
mOp = op;
mAccessCount = new long[_NUM_UID_STATE];
mRejectCount = new long[_NUM_UID_STATE];
mAccessDuration = new long[_NUM_UID_STATE];
}
- HistoricalOpEntry(@NonNull Parcel parcel) {
+ private HistoricalOp(@NonNull HistoricalOp other) {
+ mOp = other.mOp;
+ if (other.mAccessCount != null) {
+ System.arraycopy(other.mAccessCount, 0, getOrCreateAccessCount(),
+ 0, other.mAccessCount.length);
+ }
+ if (other.mRejectCount != null) {
+ System.arraycopy(other.mRejectCount, 0, getOrCreateRejectCount(),
+ 0, other.mRejectCount.length);
+ }
+ if (other.mAccessDuration != null) {
+ System.arraycopy(other.mAccessDuration, 0, getOrCreateAccessDuration(),
+ 0, other.mAccessDuration.length);
+ }
+ }
+
+ private HistoricalOp(@NonNull Parcel parcel) {
mOp = parcel.readInt();
mAccessCount = parcel.createLongArray();
mRejectCount = parcel.createLongArray();
mAccessDuration = parcel.createLongArray();
}
- /**
- * @hide
- */
- public void addEntry(@UidState int uidState, long accessCount,
- long rejectCount, long accessDuration) {
- mAccessCount[uidState] = accessCount;
- mRejectCount[uidState] = rejectCount;
- mAccessDuration[uidState] = accessDuration;
+ private void filter(double scaleFactor) {
+ scale(mAccessCount, scaleFactor);
+ scale(mRejectCount, scaleFactor);
+ scale(mAccessDuration, scaleFactor);
+ }
+
+ private boolean isEmpty() {
+ return !hasData(mAccessCount)
+ && !hasData(mRejectCount)
+ && !hasData(mAccessDuration);
+ }
+
+ private boolean hasData(@NonNull long[] array) {
+ for (long value : array) {
+ if (value != 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private @Nullable HistoricalOp splice(double fractionToRemove) {
+ HistoricalOp splice = null;
+ if (mAccessCount != null) {
+ for (int i = 0; i < _NUM_UID_STATE; i++) {
+ final long spliceAccessCount = Math.round(
+ mAccessCount[i] * fractionToRemove);
+ if (spliceAccessCount > 0) {
+ if (splice == null) {
+ splice = new HistoricalOp(mOp);
+ }
+ splice.getOrCreateAccessCount()[i] = spliceAccessCount;
+ mAccessCount[i] -= spliceAccessCount;
+ }
+ }
+ }
+
+ if (mRejectCount != null) {
+ for (int i = 0; i < _NUM_UID_STATE; i++) {
+ final long spliceRejectCount = Math.round(
+ mRejectCount[i] * fractionToRemove);
+
+ if (spliceRejectCount > 0) {
+ if (splice == null) {
+ splice = new HistoricalOp(mOp);
+ }
+ splice.getOrCreateRejectCount()[i] = spliceRejectCount;
+ mRejectCount[i] -= spliceRejectCount;
+ }
+ }
+ }
+
+ if (mAccessDuration != null) {
+ for (int i = 0; i < _NUM_UID_STATE; i++) {
+ final long spliceAccessDuration = Math.round(
+ mAccessDuration[i] * fractionToRemove);
+ if (spliceAccessDuration > 0) {
+ if (splice == null) {
+ splice = new HistoricalOp(mOp);
+ }
+ splice.getOrCreateAccessDuration()[i] = spliceAccessDuration;
+ mAccessDuration[i] -= spliceAccessDuration;
+ }
+ }
+ }
+ return splice;
+ }
+
+ private void merge(@NonNull HistoricalOp other) {
+ if (other.mAccessCount != null) {
+ for (int i = 0; i < _NUM_UID_STATE; i++) {
+ getOrCreateAccessCount()[i] += other.mAccessCount[i];
+ }
+ }
+ if (other.mRejectCount != null) {
+ for (int i = 0; i < _NUM_UID_STATE; i++) {
+ getOrCreateRejectCount()[i] += other.mRejectCount[i];
+ }
+ }
+ if (other.mAccessDuration != null) {
+ for (int i = 0; i < _NUM_UID_STATE; i++) {
+ getOrCreateAccessDuration()[i] += other.mAccessDuration[i];
+ }
+ }
+ }
+
+ private void increaseAccessCount(@UidState int uidState, long increment) {
+ getOrCreateAccessCount()[uidState] += increment;
+ }
+
+ private void increaseRejectCount(@UidState int uidState, long increment) {
+ getOrCreateRejectCount()[uidState] += increment;
+ }
+
+ private void increaseAccessDuration(@UidState int uidState, long increment) {
+ getOrCreateAccessDuration()[uidState] += increment;
}
/**
@@ -2257,10 +3197,15 @@
*
* @return The op name.
*/
- public @NonNull String getOp() {
+ public @NonNull String getOpName() {
return sOpToString[mOp];
}
+ /** @hide */
+ public int getOpCode() {
+ return mOp;
+ }
+
/**
* Gets the number times the op was accessed (performed) in the foreground.
*
@@ -2270,6 +3215,9 @@
* @see #getAccessCount(int)
*/
public long getForegroundAccessCount() {
+ if (mAccessCount == null) {
+ return 0;
+ }
return sum(mAccessCount, UID_STATE_PERSISTENT, UID_STATE_LAST_NON_RESTRICTED + 1);
}
@@ -2282,6 +3230,9 @@
* @see #getAccessCount(int)
*/
public long getBackgroundAccessCount() {
+ if (mAccessCount == null) {
+ return 0;
+ }
return sum(mAccessCount, UID_STATE_LAST_NON_RESTRICTED + 1, _NUM_UID_STATE);
}
@@ -2299,6 +3250,9 @@
* @see #getBackgroundAccessCount()
*/
public long getAccessCount(@UidState int uidState) {
+ if (mAccessCount == null) {
+ return 0;
+ }
return mAccessCount[uidState];
}
@@ -2311,6 +3265,9 @@
* @see #getRejectCount(int)
*/
public long getForegroundRejectCount() {
+ if (mRejectCount == null) {
+ return 0;
+ }
return sum(mRejectCount, UID_STATE_PERSISTENT, UID_STATE_LAST_NON_RESTRICTED + 1);
}
@@ -2323,6 +3280,9 @@
* @see #getRejectCount(int)
*/
public long getBackgroundRejectCount() {
+ if (mRejectCount == null) {
+ return 0;
+ }
return sum(mRejectCount, UID_STATE_LAST_NON_RESTRICTED + 1, _NUM_UID_STATE);
}
@@ -2340,6 +3300,9 @@
* @see #getBackgroundRejectCount()
*/
public long getRejectCount(@UidState int uidState) {
+ if (mRejectCount == null) {
+ return 0;
+ }
return mRejectCount[uidState];
}
@@ -2352,6 +3315,9 @@
* @see #getAccessDuration(int)
*/
public long getForegroundAccessDuration() {
+ if (mAccessDuration == null) {
+ return 0;
+ }
return sum(mAccessDuration, UID_STATE_PERSISTENT, UID_STATE_LAST_NON_RESTRICTED + 1);
}
@@ -2364,6 +3330,9 @@
* @see #getAccessDuration(int)
*/
public long getBackgroundAccessDuration() {
+ if (mAccessDuration == null) {
+ return 0;
+ }
return sum(mAccessDuration, UID_STATE_LAST_NON_RESTRICTED + 1, _NUM_UID_STATE);
}
@@ -2381,6 +3350,9 @@
* @see #getBackgroundAccessDuration()
*/
public long getAccessDuration(@UidState int uidState) {
+ if (mAccessDuration == null) {
+ return 0;
+ }
return mAccessDuration[uidState];
}
@@ -2397,34 +3369,29 @@
parcel.writeLongArray(mAccessDuration);
}
- @Override
- public boolean equals(Object other) {
- if (this == other) {
- return true;
- }
- if (other == null || getClass() != other.getClass()) {
- return false;
- }
- final HistoricalOpEntry otherInstance = (HistoricalOpEntry) other;
- if (mOp != otherInstance.mOp) {
- return false;
- }
- if (!Arrays.equals(mAccessCount, otherInstance.mAccessCount)) {
- return false;
- }
- if (!Arrays.equals(mRejectCount, otherInstance.mRejectCount)) {
- return false;
- }
- return Arrays.equals(mAccessDuration, otherInstance.mAccessDuration);
+ private void accept(@NonNull HistoricalOpsVisitor visitor) {
+ visitor.visitHistoricalOp(this);
}
- @Override
- public int hashCode() {
- int result = mOp;
- result = 31 * result + Arrays.hashCode(mAccessCount);
- result = 31 * result + Arrays.hashCode(mRejectCount);
- result = 31 * result + Arrays.hashCode(mAccessDuration);
- return result;
+ private @NonNull long[] getOrCreateAccessCount() {
+ if (mAccessCount == null) {
+ mAccessCount = new long[_NUM_UID_STATE];
+ }
+ return mAccessCount;
+ }
+
+ private @NonNull long[] getOrCreateRejectCount() {
+ if (mRejectCount == null) {
+ mRejectCount = new long[_NUM_UID_STATE];
+ }
+ return mRejectCount;
+ }
+
+ private @NonNull long[] getOrCreateAccessDuration() {
+ if (mAccessDuration == null) {
+ mAccessDuration = new long[_NUM_UID_STATE];
+ }
+ return mAccessDuration;
}
/**
@@ -2444,17 +3411,62 @@
return totalCount;
}
- public static final Creator<HistoricalOpEntry> CREATOR = new Creator<HistoricalOpEntry>() {
+ /**
+ * Multiplies the entries in the array with the passed in scale factor and
+ * rounds the result at up 0.5 boundary.
+ *
+ * @param data The data to scale.
+ * @param scaleFactor The scale factor.
+ */
+ private static void scale(@NonNull long[] data, double scaleFactor) {
+ if (data != null) {
+ for (int i = 0; i < _NUM_UID_STATE; i++) {
+ data[i] = (long) HistoricalOps.round((double) data[i] * scaleFactor);
+ }
+ }
+ }
+
+ public static final Creator<HistoricalOp> CREATOR = new Creator<HistoricalOp>() {
@Override
- public @NonNull HistoricalOpEntry createFromParcel(@NonNull Parcel source) {
- return new HistoricalOpEntry(source);
+ public @NonNull HistoricalOp createFromParcel(@NonNull Parcel source) {
+ return new HistoricalOp(source);
}
@Override
- public @NonNull HistoricalOpEntry[] newArray(int size) {
- return new HistoricalOpEntry[size];
+ public @NonNull HistoricalOp[] newArray(int size) {
+ return new HistoricalOp[size];
}
};
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final HistoricalOp other = (HistoricalOp) obj;
+ if (mOp != other.mOp) {
+ return false;
+ }
+ if (!Arrays.equals(mAccessCount, other.mAccessCount)) {
+ return false;
+ }
+ if (!Arrays.equals(mRejectCount, other.mRejectCount)) {
+ return false;
+ }
+ return Arrays.equals(mAccessDuration, other.mAccessDuration);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mOp;
+ result = 31 * result + Arrays.hashCode(mAccessCount);
+ result = 31 * result + Arrays.hashCode(mRejectCount);
+ result = 31 * result + Arrays.hashCode(mAccessDuration);
+ return result;
+ }
}
/**
@@ -2520,6 +3532,24 @@
* @param ops The set of operations you are interested in, or null if you want all of them.
* @hide
*/
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS)
+ public @NonNull List<AppOpsManager.PackageOps> getPackagesForOps(@Nullable String[] ops) {
+ final int opCount = ops.length;
+ final int[] opCodes = new int[opCount];
+ for (int i = 0; i < opCount; i++) {
+ opCodes[i] = sOpStrToOp.get(ops[i]);
+ }
+ final List<AppOpsManager.PackageOps> result = getPackagesForOps(opCodes);
+ return (result != null) ? result : Collections.emptyList();
+ }
+
+ /**
+ * Retrieve current operation state for all applications.
+ *
+ * @param ops The set of operations you are interested in, or null if you want all of them.
+ * @hide
+ */
@RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS)
@UnsupportedAppUsage
public List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops) {
@@ -2536,11 +3566,17 @@
* @param uid The uid of the application of interest.
* @param packageName The name of the application of interest.
* @param ops The set of operations you are interested in, or null if you want all of them.
+ *
+ * @deprecated The int op codes are not stable and you should use the string based op
+ * names which are stable and namespaced. Use
+ * {@link #getOpsForPackage(int, String, String...)})}.
+ *
* @hide
*/
+ @Deprecated
@SystemApi
@RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS)
- public List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName, int[] ops) {
+ public List<PackageOps> getOpsForPackage(int uid, String packageName, int[] ops) {
try {
return mService.getOpsForPackage(uid, packageName, ops);
} catch (RemoteException e) {
@@ -2549,7 +3585,49 @@
}
/**
- * Retrieve historical app op stats for a package.
+ * Retrieve current operation state for one application. The UID and the
+ * package must match.
+ *
+ * @param uid The uid of the application of interest.
+ * @param packageName The name of the application of interest.
+ * @param ops The set of operations you are interested in, or null if you want all of them.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS)
+ public @NonNull List<AppOpsManager.PackageOps> getOpsForPackage(int uid,
+ @NonNull String packageName, @Nullable String... ops) {
+ int[] opCodes = null;
+ if (ops != null) {
+ opCodes = new int[ops.length];
+ for (int i = 0; i < ops.length; i++) {
+ opCodes[i] = strOpToOp(ops[i]);
+ }
+ }
+ try {
+ final List<PackageOps> result = mService.getOpsForPackage(uid, packageName, opCodes);
+ if (result == null) {
+ return Collections.emptyList();
+ }
+ return result;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Retrieve historical app op stats for a period.
+ *
+ * <p>Historical data can be obtained
+ * for a specific package by specifying the <code>packageName</code> argument,
+ * for a specific UID if specifying the <code>uid</code> argument, for a
+ * specific package in a UID by specifying the <code>packageName</code>
+ * and the <code>uid</code> arguments, for all packages by passing
+ * {@link android.os.Process#INVALID_UID} and <code>null</code> for the
+ * <code>uid</code> and <code>packageName</code> arguments, respectively.
+ * Similarly, you can specify the <code>opNames</code> argument to get
+ * data only for these ops or <code>null</code> for all ops.
*
* @param uid The UID to query for.
* @param packageName The package to query for.
@@ -2558,10 +3636,12 @@
* negative.
* @param endTimeMillis The end of the interval in milliseconds since
* epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian). Must be after
- * {@code beginTimeMillis}.
+ * {@code beginTimeMillis}. Pass {@link Long#MAX_VALUE} to get the most recent
+ * history including ops that happen while this call is in flight.
* @param opNames The ops to query for. Pass {@code null} for all ops.
- *
- * @return The historical ops or {@code null} if there are no ops for this package.
+ * @param executor Executor on which to run the callback. If <code>null</code>
+ * the callback is executed on the default executor running on the main thread.
+ * @param callback Callback on which to deliver the result.
*
* @throws IllegalArgumentException If any of the argument contracts is violated.
*
@@ -2570,47 +3650,78 @@
@TestApi
@SystemApi
@RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS)
- public @Nullable HistoricalPackageOps getHistoricalPackagesOps(
- int uid, @NonNull String packageName, @Nullable String[] opNames,
- long beginTimeMillis, long endTimeMillis) {
+ public void getHistoricalOps(int uid, @Nullable String packageName,
+ @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis,
+ @NonNull Executor executor, @NonNull Consumer<HistoricalOps> callback) {
+ Preconditions.checkNotNull(executor, "executor cannot be null");
+ Preconditions.checkNotNull(callback, "callback cannot be null");
try {
- return mService.getHistoricalPackagesOps(uid, packageName, opNames,
- beginTimeMillis, endTimeMillis);
+ mService.getHistoricalOps(uid, packageName, opNames, beginTimeMillis, endTimeMillis,
+ new RemoteCallback((result) -> {
+ final HistoricalOps ops = result.getParcelable(KEY_HISTORICAL_OPS);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> callback.accept(ops));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
- * Retrieve historical app op stats for all packages.
+ * Retrieve historical app op stats for a period.
*
+ * <p>Historical data can be obtained
+ * for a specific package by specifying the <code>packageName</code> argument,
+ * for a specific UID if specifying the <code>uid</code> argument, for a
+ * specific package in a UID by specifying the <code>packageName</code>
+ * and the <code>uid</code> arguments, for all packages by passing
+ * {@link android.os.Process#INVALID_UID} and <code>null</code> for the
+ * <code>uid</code> and <code>packageName</code> arguments, respectively.
+ * Similarly, you can specify the <code>opNames</code> argument to get
+ * data only for these ops or <code>null</code> for all ops.
+ * <p>
+ * This method queries only the on disk state and the returned ops are raw,
+ * which is their times are relative to the history start as opposed to the
+ * epoch start.
+ *
+ * @param uid The UID to query for.
+ * @param packageName The package to query for.
* @param beginTimeMillis The beginning of the interval in milliseconds since
- * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian). Must be non
- * negative.
+ * history start. History time grows as one goes into the past.
* @param endTimeMillis The end of the interval in milliseconds since
- * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian). Must be after
+ * history start. History time grows as one goes into the past. Must be after
* {@code beginTimeMillis}.
* @param opNames The ops to query for. Pass {@code null} for all ops.
- *
- * @return The historical ops or an empty list if there are no ops for any package.
+ * @param executor Executor on which to run the callback. If <code>null</code>
+ * the callback is executed on the default executor running on the main thread.
+ * @param callback Callback on which to deliver the result.
*
* @throws IllegalArgumentException If any of the argument contracts is violated.
*
* @hide
*/
@TestApi
- @SystemApi
@RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS)
- public @NonNull List<HistoricalPackageOps> getAllHistoricPackagesOps(
- @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis) {
+ public void getHistoricalOpsFromDiskRaw(int uid, @Nullable String packageName,
+ @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis,
+ @Nullable Executor executor, @NonNull Consumer<HistoricalOps> callback) {
+ Preconditions.checkNotNull(executor, "executor cannot be null");
+ Preconditions.checkNotNull(callback, "callback cannot be null");
try {
- @SuppressWarnings("unchecked")
- final ParceledListSlice<HistoricalPackageOps> payload =
- mService.getAllHistoricalPackagesOps(opNames, beginTimeMillis, endTimeMillis);
- if (payload != null) {
- return payload.getList();
- }
- return Collections.emptyList();
+ mService.getHistoricalOpsFromDiskRaw(uid, packageName, opNames, beginTimeMillis,
+ endTimeMillis, new RemoteCallback((result) -> {
+ final HistoricalOps ops = result.getParcelable(KEY_HISTORICAL_OPS);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> callback.accept(ops));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2627,7 +3738,7 @@
* @hide
*/
@RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
- public void setUidMode(int code, int uid, int mode) {
+ public void setUidMode(int code, int uid, @Mode int mode) {
try {
mService.setUidMode(code, uid, mode);
} catch (RemoteException e) {
@@ -2648,7 +3759,7 @@
@SystemApi
@TestApi
@RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
- public void setUidMode(String appOp, int uid, int mode) {
+ public void setUidMode(String appOp, int uid, @Mode int mode) {
try {
mService.setUidMode(AppOpsManager.strOpToOp(appOp), uid, mode);
} catch (RemoteException e) {
@@ -2680,7 +3791,7 @@
/** @hide */
@TestApi
@RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
- public void setMode(int code, int uid, String packageName, int mode) {
+ public void setMode(int code, int uid, String packageName, @Mode int mode) {
try {
mService.setMode(code, uid, packageName, mode);
} catch (RemoteException e) {
@@ -2701,7 +3812,7 @@
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
- public void setMode(String op, int uid, String packageName, int mode) {
+ public void setMode(String op, int uid, String packageName, @Mode int mode) {
try {
mService.setMode(strOpToOp(op), uid, packageName, mode);
} catch (RemoteException e) {
@@ -2722,7 +3833,7 @@
*/
@RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
@UnsupportedAppUsage
- public void setRestriction(int code, @AttributeUsage int usage, int mode,
+ public void setRestriction(int code, @AttributeUsage int usage, @Mode int mode,
String[] exceptionPackages) {
try {
final int uid = Binder.getCallingUid();
@@ -3507,6 +4618,104 @@
}
/**
+ * Configures the app ops persistence for testing.
+ *
+ * @param mode The mode in which the historical registry operates.
+ * @param baseSnapshotInterval The base interval on which we would be persisting a snapshot of
+ * the historical data. The history is recursive where every subsequent step encompasses
+ * {@code compressionStep} longer interval with {@code compressionStep} distance between
+ * snapshots.
+ * @param compressionStep The compression step in every iteration.
+ *
+ * @see #HISTORICAL_MODE_DISABLED
+ * @see #HISTORICAL_MODE_ENABLED_ACTIVE
+ * @see #HISTORICAL_MODE_ENABLED_PASSIVE
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(Manifest.permission.MANAGE_APPOPS)
+ public void setHistoryParameters(@HistoricalMode int mode, long baseSnapshotInterval,
+ int compressionStep) {
+ try {
+ mService.setHistoryParameters(mode, baseSnapshotInterval, compressionStep);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Offsets the history by the given duration.
+ *
+ * @param offsetMillis The offset duration.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(Manifest.permission.MANAGE_APPOPS)
+ public void offsetHistory(long offsetMillis) {
+ try {
+ mService.offsetHistory(offsetMillis);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Adds ops to the history directly. This could be useful for testing especially
+ * when the historical registry operates in {@link #HISTORICAL_MODE_ENABLED_PASSIVE}
+ * mode.
+ *
+ * @param ops The ops to add to the history.
+ *
+ * @see #setHistoryParameters(int, long, int)
+ * @see #HISTORICAL_MODE_ENABLED_PASSIVE
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(Manifest.permission.MANAGE_APPOPS)
+ public void addHistoricalOps(@NonNull HistoricalOps ops) {
+ try {
+ mService.addHistoricalOps(ops);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Resets the app ops persistence for testing.
+ *
+ * @see #setHistoryParameters(int, long, int)
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(Manifest.permission.MANAGE_APPOPS)
+ public void resetHistoryParameters() {
+ try {
+ mService.resetHistoryParameters();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Clears all app ops history.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(Manifest.permission.MANAGE_APPOPS)
+ public void clearHistory() {
+ try {
+ mService.clearHistory();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns all supported operation names.
* @hide
*/
@@ -3538,4 +4747,64 @@
}
return time;
}
+
+ /** @hide */
+ public static String uidStateToString(@UidState int uidState) {
+ switch (uidState) {
+ case UID_STATE_PERSISTENT: {
+ return "UID_STATE_PERSISTENT";
+ }
+ case UID_STATE_TOP: {
+ return "UID_STATE_TOP";
+ }
+ case UID_STATE_FOREGROUND_SERVICE: {
+ return "UID_STATE_FOREGROUND_SERVICE";
+ }
+ case UID_STATE_FOREGROUND: {
+ return "UID_STATE_FOREGROUND";
+ }
+ case UID_STATE_BACKGROUND: {
+ return "UID_STATE_BACKGROUND";
+ }
+ case UID_STATE_CACHED: {
+ return "UID_STATE_CACHED";
+ }
+ default: {
+ return "UNKNOWN";
+ }
+ }
+ }
+
+ /** @hide */
+ public static int parseHistoricalMode(@NonNull String mode) {
+ switch (mode) {
+ case "HISTORICAL_MODE_ENABLED_ACTIVE": {
+ return HISTORICAL_MODE_ENABLED_ACTIVE;
+ }
+ case "HISTORICAL_MODE_ENABLED_PASSIVE": {
+ return HISTORICAL_MODE_ENABLED_PASSIVE;
+ }
+ default: {
+ return HISTORICAL_MODE_DISABLED;
+ }
+ }
+ }
+
+ /** @hide */
+ public static String historicalModeToString(@HistoricalMode int mode) {
+ switch (mode) {
+ case HISTORICAL_MODE_DISABLED: {
+ return "HISTORICAL_MODE_DISABLED";
+ }
+ case HISTORICAL_MODE_ENABLED_ACTIVE: {
+ return "HISTORICAL_MODE_ENABLED_ACTIVE";
+ }
+ case HISTORICAL_MODE_ENABLED_PASSIVE: {
+ return "HISTORICAL_MODE_ENABLED_PASSIVE";
+ }
+ default: {
+ return "UNKNOWN";
+ }
+ }
+ }
}
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index b5295d1..07dbb6b 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -124,6 +124,10 @@
/** Restart the activity after it was stopped. */
public abstract void performRestartActivity(IBinder token, boolean start);
+ /** Set pending activity configuration in case it will be updated by other transaction item. */
+ public abstract void updatePendingActivityConfiguration(IBinder activityToken,
+ Configuration overrideConfig);
+
/** Deliver activity (override) configuration change. */
public abstract void handleActivityConfigurationChanged(IBinder activityToken,
Configuration overrideConfig, int displayId);
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index fb519b6..347973e 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -421,7 +421,6 @@
void resizeDockedStack(in Rect dockedBounds, in Rect tempDockedTaskBounds,
in Rect tempDockedTaskInsetBounds,
in Rect tempOtherTaskBounds, in Rect tempOtherTaskInsetBounds);
- boolean isAppForeground(int uid);
void removeStack(int stackId);
void makePackageIdle(String packageName, int userId);
int getMemoryTrimLevel();
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index 3a2038d..666f721 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -142,19 +142,20 @@
*
* @param which either {@link WallpaperManager#FLAG_LOCK}
* or {@link WallpaperManager#FLAG_SYSTEM}
+ * @param displayId Which display is interested
* @return colors of chosen wallpaper
*/
- WallpaperColors getWallpaperColors(int which, int userId);
+ WallpaperColors getWallpaperColors(int which, int userId, int displayId);
/**
- * Register a callback to receive color updates
+ * Register a callback to receive color updates from a display
*/
- void registerWallpaperColorsCallback(IWallpaperManagerCallback cb, int userId);
+ void registerWallpaperColorsCallback(IWallpaperManagerCallback cb, int userId, int displayId);
/**
- * Unregister a callback that was receiving color updates
+ * Unregister a callback that was receiving color updates from a display
*/
- void unregisterWallpaperColorsCallback(IWallpaperManagerCallback cb, int userId);
+ void unregisterWallpaperColorsCallback(IWallpaperManagerCallback cb, int userId, int displayId);
/**
* Called from SystemUI when it shows the AoD UI.
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index e066f06..b657a91 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1427,18 +1427,13 @@
*/
public static final int SEMANTIC_ACTION_CALL = 10;
- /**
- * {@code SemanticAction}: Contextual action - dependent on the current notification. E.g.
- * open a Map application with an address shown in the notification.
- */
- public static final int SEMANTIC_ACTION_CONTEXTUAL_SUGGESTION = 11;
-
private final Bundle mExtras;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private Icon mIcon;
private final RemoteInput[] mRemoteInputs;
private boolean mAllowGeneratedReplies = true;
private final @SemanticAction int mSemanticAction;
+ private final boolean mIsContextual;
/**
* Small icon representing the action.
@@ -1474,6 +1469,7 @@
mRemoteInputs = in.createTypedArray(RemoteInput.CREATOR);
mAllowGeneratedReplies = in.readInt() == 1;
mSemanticAction = in.readInt();
+ mIsContextual = in.readInt() == 1;
}
/**
@@ -1482,13 +1478,13 @@
@Deprecated
public Action(int icon, CharSequence title, PendingIntent intent) {
this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, true,
- SEMANTIC_ACTION_NONE);
+ SEMANTIC_ACTION_NONE, false /* isContextual */);
}
/** Keep in sync with {@link Notification.Action.Builder#Builder(Action)}! */
private Action(Icon icon, CharSequence title, PendingIntent intent, Bundle extras,
RemoteInput[] remoteInputs, boolean allowGeneratedReplies,
- @SemanticAction int semanticAction) {
+ @SemanticAction int semanticAction, boolean isContextual) {
this.mIcon = icon;
if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
this.icon = icon.getResId();
@@ -1499,6 +1495,7 @@
this.mRemoteInputs = remoteInputs;
this.mAllowGeneratedReplies = allowGeneratedReplies;
this.mSemanticAction = semanticAction;
+ this.mIsContextual = isContextual;
}
/**
@@ -1546,6 +1543,15 @@
}
/**
+ * Returns whether this is a contextual Action, i.e. whether the action is dependent on the
+ * notification message body. An example of a contextual action could be an action opening a
+ * map application with an address shown in the notification.
+ */
+ public boolean isContextual() {
+ return mIsContextual;
+ }
+
+ /**
* Get the list of inputs to be collected from the user that ONLY accept data when this
* action is sent. These remote inputs are guaranteed to return true on a call to
* {@link RemoteInput#isDataOnly}.
@@ -1570,6 +1576,7 @@
private final Bundle mExtras;
private ArrayList<RemoteInput> mRemoteInputs;
private @SemanticAction int mSemanticAction;
+ private boolean mIsContextual;
/**
* Construct a new builder for {@link Action} object.
@@ -1684,6 +1691,16 @@
}
/**
+ * Sets whether this {@link Action} is a contextual action, i.e. whether the action is
+ * dependent on the notification message body. An example of a contextual action could
+ * be an action opening a map application with an address shown in the notification.
+ */
+ public Builder setContextual(boolean isContextual) {
+ mIsContextual = isContextual;
+ return this;
+ }
+
+ /**
* Apply an extender to this action builder. Extenders may be used to add
* metadata or change options on this builder.
*/
@@ -1697,7 +1714,7 @@
* necessary to display the action.
*/
private void checkContextualActionNullFields() {
- if (mSemanticAction != SEMANTIC_ACTION_CONTEXTUAL_SUGGESTION) return;
+ if (!mIsContextual) return;
if (mIcon == null) {
throw new NullPointerException("Contextual Actions must contain a valid icon");
@@ -1743,7 +1760,7 @@
RemoteInput[] textInputsArr = textInputs.isEmpty()
? null : textInputs.toArray(new RemoteInput[textInputs.size()]);
return new Action(mIcon, mTitle, mIntent, mExtras, textInputsArr,
- mAllowGeneratedReplies, mSemanticAction);
+ mAllowGeneratedReplies, mSemanticAction, mIsContextual);
}
}
@@ -1756,7 +1773,8 @@
mExtras == null ? new Bundle() : new Bundle(mExtras),
getRemoteInputs(),
getAllowGeneratedReplies(),
- getSemanticAction());
+ getSemanticAction(),
+ isContextual());
}
@Override
@@ -1784,6 +1802,7 @@
out.writeTypedArray(mRemoteInputs, flags);
out.writeInt(mAllowGeneratedReplies ? 1 : 0);
out.writeInt(mSemanticAction);
+ out.writeInt(mIsContextual ? 1 : 0);
}
public static final Parcelable.Creator<Action> CREATOR =
@@ -2073,8 +2092,7 @@
SEMANTIC_ACTION_UNMUTE,
SEMANTIC_ACTION_THUMBS_UP,
SEMANTIC_ACTION_THUMBS_DOWN,
- SEMANTIC_ACTION_CALL,
- SEMANTIC_ACTION_CONTEXTUAL_SUGGESTION
+ SEMANTIC_ACTION_CALL
})
@Retention(RetentionPolicy.SOURCE)
public @interface SemanticAction {}
@@ -3230,8 +3248,7 @@
}
/**
- * Returns the actions that are contextual (marked as SEMANTIC_ACTION_CONTEXTUAL_SUGGESTION) out
- * of the actions in this notification.
+ * Returns the actions that are contextual out of the actions in this notification.
*
* @hide
*/
@@ -3240,8 +3257,7 @@
List<Notification.Action> contextualActions = new ArrayList<>();
for (Notification.Action action : actions) {
- if (action.getSemanticAction()
- == Notification.Action.SEMANTIC_ACTION_CONTEXTUAL_SUGGESTION) {
+ if (action.isContextual()) {
contextualActions.add(action);
}
}
@@ -5062,8 +5078,7 @@
List<Notification.Action> actions) {
List<Notification.Action> nonContextualActions = new ArrayList<>();
for (Notification.Action action : actions) {
- if (action.getSemanticAction()
- != Action.SEMANTIC_ACTION_CONTEXTUAL_SUGGESTION) {
+ if (!action.isContextual()) {
nonContextualActions.add(action);
}
}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 9ddf4bd..d827f6c 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -21,8 +21,11 @@
import android.app.ContextImpl.ServiceInitializationState;
import android.app.admin.DevicePolicyManager;
import android.app.admin.IDevicePolicyManager;
+import android.app.contentsuggestions.ContentSuggestionsManager;
+import android.app.contentsuggestions.IContentSuggestionsManager;
import android.app.job.IJobScheduler;
import android.app.job.JobScheduler;
+import android.app.prediction.AppPredictionManager;
import android.app.role.RoleManager;
import android.app.slice.SliceManager;
import android.app.timedetector.TimeDetector;
@@ -95,8 +98,10 @@
import android.net.EthernetManager;
import android.net.IConnectivityManager;
import android.net.IEthernetManager;
+import android.net.IIpMemoryStore;
import android.net.IIpSecService;
import android.net.INetworkPolicyManager;
+import android.net.IpMemoryStore;
import android.net.IpSecManager;
import android.net.NetworkPolicyManager;
import android.net.NetworkScoreManager;
@@ -120,6 +125,7 @@
import android.nfc.NfcManager;
import android.os.BatteryManager;
import android.os.BatteryStats;
+import android.os.BugreportManager;
import android.os.Build;
import android.os.DeviceIdleManager;
import android.os.DropBoxManager;
@@ -127,6 +133,7 @@
import android.os.IBatteryPropertiesRegistrar;
import android.os.IBinder;
import android.os.IDeviceIdleController;
+import android.os.IDumpstate;
import android.os.IHardwarePropertiesManager;
import android.os.IPowerManager;
import android.os.IRecoverySystem;
@@ -321,10 +328,21 @@
registerService(Context.NETWORK_STACK_SERVICE, NetworkStack.class,
new StaticServiceFetcher<NetworkStack>() {
- @Override
- public NetworkStack createService() {
- return new NetworkStack();
- }});
+ @Override
+ public NetworkStack createService() {
+ return new NetworkStack();
+ }});
+
+ registerService(Context.IP_MEMORY_STORE_SERVICE, IpMemoryStore.class,
+ new CachedServiceFetcher<IpMemoryStore>() {
+ @Override
+ public IpMemoryStore createService(final ContextImpl ctx)
+ throws ServiceNotFoundException {
+ IBinder b = ServiceManager.getServiceOrThrow(
+ Context.IP_MEMORY_STORE_SERVICE);
+ IIpMemoryStore service = IIpMemoryStore.Stub.asInterface(b);
+ return new IpMemoryStore(ctx, service);
+ }});
registerService(Context.IPSEC_SERVICE, IpSecManager.class,
new CachedServiceFetcher<IpSecManager>() {
@@ -1069,6 +1087,16 @@
return new IncidentManager(ctx);
}});
+ registerService(Context.BUGREPORT_SERVICE, BugreportManager.class,
+ new CachedServiceFetcher<BugreportManager>() {
+ @Override
+ public BugreportManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ IBinder b = ServiceManager.getServiceOrThrow(Context.BUGREPORT_SERVICE);
+ return new BugreportManager(ctx.getOuterContext(),
+ IDumpstate.Stub.asInterface(b));
+ }});
+
registerService(Context.AUTOFILL_MANAGER_SERVICE, AutofillManager.class,
new CachedServiceFetcher<AutofillManager>() {
@Override
@@ -1095,6 +1123,29 @@
return null;
}});
+ registerService(Context.APP_PREDICTION_SERVICE, AppPredictionManager.class,
+ new CachedServiceFetcher<AppPredictionManager>() {
+ @Override
+ public AppPredictionManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ return new AppPredictionManager(ctx);
+ }
+ });
+
+ registerService(Context.CONTENT_SUGGESTIONS_SERVICE,
+ ContentSuggestionsManager.class,
+ new CachedServiceFetcher<ContentSuggestionsManager>() {
+ @Override
+ public ContentSuggestionsManager createService(ContextImpl ctx) {
+ // No throw as this is an optional service
+ IBinder b = ServiceManager.getService(
+ Context.CONTENT_SUGGESTIONS_SERVICE);
+ IContentSuggestionsManager service =
+ IContentSuggestionsManager.Stub.asInterface(b);
+ return new ContentSuggestionsManager(service);
+ }
+ });
+
registerService(Context.VR_SERVICE, VrManager.class, new CachedServiceFetcher<VrManager>() {
@Override
public VrManager createService(ContextImpl ctx) throws ServiceNotFoundException {
diff --git a/core/java/android/app/WallpaperColors.java b/core/java/android/app/WallpaperColors.java
index ace814a..38a98d3c8 100644
--- a/core/java/android/app/WallpaperColors.java
+++ b/core/java/android/app/WallpaperColors.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.annotation.SystemApi;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -56,6 +56,7 @@
* eg. A launcher may set its text color to black if this flag is specified.
* @hide
*/
+ @SystemApi
public static final int HINT_SUPPORTS_DARK_TEXT = 1 << 0;
/**
@@ -64,6 +65,7 @@
* eg. A launcher may set its drawer color to black if this flag is specified.
* @hide
*/
+ @SystemApi
public static final int HINT_SUPPORTS_DARK_THEME = 1 << 1;
/**
@@ -234,7 +236,7 @@
* @see WallpaperColors#fromDrawable(Drawable)
* @hide
*/
- @UnsupportedAppUsage
+ @SystemApi
public WallpaperColors(@NonNull Color primaryColor, @Nullable Color secondaryColor,
@Nullable Color tertiaryColor, int colorHints) {
@@ -349,7 +351,7 @@
* @return True if dark text is supported.
* @hide
*/
- @UnsupportedAppUsage
+ @SystemApi
public int getColorHints() {
return mColorHints;
}
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 27ae0b0..a929fe0 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -307,13 +307,14 @@
* @param callback Listener
* @param handler Thread to call it from. Main thread if null.
* @param userId Owner of the wallpaper or UserHandle.USER_ALL
+ * @param displayId Caller comes from which display
*/
public void addOnColorsChangedListener(@NonNull OnColorsChangedListener callback,
- @Nullable Handler handler, int userId) {
+ @Nullable Handler handler, int userId, int displayId) {
synchronized (this) {
if (!mColorCallbackRegistered) {
try {
- mService.registerWallpaperColorsCallback(this, userId);
+ mService.registerWallpaperColorsCallback(this, userId, displayId);
mColorCallbackRegistered = true;
} catch (RemoteException e) {
// Failed, service is gone
@@ -329,16 +330,17 @@
*
* @param callback listener
* @param userId Owner of the wallpaper or UserHandle.USER_ALL
+ * @param displayId Which display is interested
*/
public void removeOnColorsChangedListener(@NonNull OnColorsChangedListener callback,
- int userId) {
+ int userId, int displayId) {
synchronized (this) {
mColorListeners.removeIf(pair -> pair.first == callback);
if (mColorListeners.size() == 0 && mColorCallbackRegistered) {
mColorCallbackRegistered = false;
try {
- mService.unregisterWallpaperColorsCallback(this, userId);
+ mService.unregisterWallpaperColorsCallback(this, userId, displayId);
} catch (RemoteException e) {
// Failed, service is gone
Log.w(TAG, "Can't unregister color updates", e);
@@ -370,14 +372,14 @@
}
}
- WallpaperColors getWallpaperColors(int which, int userId) {
+ WallpaperColors getWallpaperColors(int which, int userId, int displayId) {
if (which != FLAG_LOCK && which != FLAG_SYSTEM) {
throw new IllegalArgumentException(
"Must request colors for exactly one kind of wallpaper");
}
try {
- return mService.getWallpaperColors(which, userId);
+ return mService.getWallpaperColors(which, userId, displayId);
} catch (RemoteException e) {
// Can't get colors, connection lost.
}
@@ -894,7 +896,7 @@
@UnsupportedAppUsage
public void addOnColorsChangedListener(@NonNull OnColorsChangedListener listener,
@NonNull Handler handler, int userId) {
- sGlobals.addOnColorsChangedListener(listener, handler, userId);
+ sGlobals.addOnColorsChangedListener(listener, handler, userId, mContext.getDisplayId());
}
/**
@@ -913,7 +915,7 @@
*/
public void removeOnColorsChangedListener(@NonNull OnColorsChangedListener callback,
int userId) {
- sGlobals.removeOnColorsChangedListener(callback, userId);
+ sGlobals.removeOnColorsChangedListener(callback, userId, mContext.getDisplayId());
}
/**
@@ -947,7 +949,7 @@
*/
@UnsupportedAppUsage
public @Nullable WallpaperColors getWallpaperColors(int which, int userId) {
- return sGlobals.getWallpaperColors(which, userId);
+ return sGlobals.getWallpaperColors(which, userId, mContext.getDisplayId());
}
/**
diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index 2990b57..e0a15a5 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -19,6 +19,7 @@
import static android.app.ActivityThread.isSystem;
import static android.app.WindowConfigurationProto.ACTIVITY_TYPE;
import static android.app.WindowConfigurationProto.APP_BOUNDS;
+import static android.app.WindowConfigurationProto.BOUNDS;
import static android.app.WindowConfigurationProto.WINDOWING_MODE;
import static android.view.Surface.rotationToString;
@@ -585,6 +586,9 @@
}
protoOutputStream.write(WINDOWING_MODE, mWindowingMode);
protoOutputStream.write(ACTIVITY_TYPE, mActivityType);
+ if (mBounds != null) {
+ mBounds.writeToProto(protoOutputStream, BOUNDS);
+ }
protoOutputStream.end(token);
}
@@ -606,6 +610,10 @@
mAppBounds = new Rect();
mAppBounds.readFromProto(proto, APP_BOUNDS);
break;
+ case (int) BOUNDS:
+ mBounds = new Rect();
+ mBounds.readFromProto(proto, BOUNDS);
+ break;
case (int) WINDOWING_MODE:
mWindowingMode = proto.readInt(WINDOWING_MODE);
break;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 7da67d9..9247486 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -6815,8 +6815,12 @@
}
/**
- * Returns the list of input methods permitted by the device or profiles
- * owners of the current user. (*Not* calling user, due to a limitation in InputMethodManager.)
+ * Returns the list of input methods permitted by the device or profiles owners.
+ *
+ * <p>On {@link android.os.Build.VERSION_CODES#Q} and later devices, this method returns the
+ * result for the calling user.</p>
+ *
+ * <p>On Android P and prior devices, this method returns the result for the current user.</p>
*
* <p>Null means all input methods are allowed, if a non-null list is returned
* it will contain the intersection of the permitted lists for any device or profile
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index cc4d4b1a..7d03f00 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -1699,7 +1699,8 @@
@Override
public void setVisibility(int visibility) {
- mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_VISIBILITY_MASK) | visibility;
+ mNode.mFlags = (mNode.mFlags & ~ViewNode.FLAGS_VISIBILITY_MASK)
+ | (visibility & ViewNode.FLAGS_VISIBILITY_MASK);
}
@Override
diff --git a/core/java/android/app/contentsuggestions/ClassificationsRequest.aidl b/core/java/android/app/contentsuggestions/ClassificationsRequest.aidl
new file mode 100644
index 0000000..a66413b
--- /dev/null
+++ b/core/java/android/app/contentsuggestions/ClassificationsRequest.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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.
+ */
+
+package android.app.contentsuggestions;
+
+parcelable ClassificationsRequest;
diff --git a/core/java/android/app/contentsuggestions/ClassificationsRequest.java b/core/java/android/app/contentsuggestions/ClassificationsRequest.java
new file mode 100644
index 0000000..3f71518
--- /dev/null
+++ b/core/java/android/app/contentsuggestions/ClassificationsRequest.java
@@ -0,0 +1,113 @@
+/*
+ * 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.
+ */
+
+package android.app.contentsuggestions;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.List;
+
+/**
+ * @hide
+ */
+@SystemApi
+public final class ClassificationsRequest implements Parcelable {
+ @NonNull
+ private final List<ContentSelection> mSelections;
+ @Nullable
+ private final Bundle mExtras;
+
+ private ClassificationsRequest(@NonNull List<ContentSelection> selections,
+ @Nullable Bundle extras) {
+ mSelections = selections;
+ mExtras = extras;
+ }
+
+ /**
+ * Return request selections.
+ */
+ public List<ContentSelection> getSelections() {
+ return mSelections;
+ }
+
+ /**
+ * Return the request extras.
+ */
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeTypedList(mSelections);
+ dest.writeBundle(mExtras);
+ }
+
+ public static final Creator<ClassificationsRequest> CREATOR =
+ new Creator<ClassificationsRequest>() {
+ @Override
+ public ClassificationsRequest createFromParcel(Parcel source) {
+ return new ClassificationsRequest(
+ source.createTypedArrayList(ContentSelection.CREATOR),
+ source.readBundle());
+ }
+
+ @Override
+ public ClassificationsRequest[] newArray(int size) {
+ return new ClassificationsRequest[size];
+ }
+ };
+
+ /**
+ * A builder for classifications request events.
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder {
+
+ private final List<ContentSelection> mSelections;
+ private Bundle mExtras;
+
+ public Builder(@NonNull List<ContentSelection> selections) {
+ mSelections = selections;
+ }
+
+ /**
+ * Sets the request extras.
+ */
+ public Builder setExtras(@NonNull Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Builds a new request instance.
+ */
+ public ClassificationsRequest build() {
+ return new ClassificationsRequest(mSelections, mExtras);
+ }
+ }
+}
diff --git a/core/java/android/app/contentsuggestions/ContentClassification.aidl b/core/java/android/app/contentsuggestions/ContentClassification.aidl
new file mode 100644
index 0000000..f67b2c8
--- /dev/null
+++ b/core/java/android/app/contentsuggestions/ContentClassification.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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.
+ */
+
+package android.app.contentsuggestions;
+
+parcelable ContentClassification;
diff --git a/core/java/android/app/contentsuggestions/ContentClassification.java b/core/java/android/app/contentsuggestions/ContentClassification.java
new file mode 100644
index 0000000..f18520f
--- /dev/null
+++ b/core/java/android/app/contentsuggestions/ContentClassification.java
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+package android.app.contentsuggestions;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+@SystemApi
+public final class ContentClassification implements Parcelable {
+ @NonNull
+ private final String mClassificationId;
+ @NonNull
+ private final Bundle mExtras;
+
+ public ContentClassification(@NonNull String classificationId, @NonNull Bundle extras) {
+ mClassificationId = classificationId;
+ mExtras = extras;
+ }
+
+ /**
+ * Return the classification id.
+ */
+ public String getId() {
+ return mClassificationId;
+ }
+
+ /**
+ * Return the classification extras.
+ */
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mClassificationId);
+ dest.writeBundle(mExtras);
+ }
+
+ public static final Creator<ContentClassification> CREATOR =
+ new Creator<ContentClassification>() {
+ @Override
+ public ContentClassification createFromParcel(Parcel source) {
+ return new ContentClassification(
+ source.readString(),
+ source.readBundle());
+ }
+
+ @Override
+ public ContentClassification[] newArray(int size) {
+ return new ContentClassification[size];
+ }
+ };
+}
diff --git a/core/java/android/app/contentsuggestions/ContentSelection.aidl b/core/java/android/app/contentsuggestions/ContentSelection.aidl
new file mode 100644
index 0000000..e626d69
--- /dev/null
+++ b/core/java/android/app/contentsuggestions/ContentSelection.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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.
+ */
+
+package android.app.contentsuggestions;
+
+parcelable ContentSelection;
diff --git a/core/java/android/app/contentsuggestions/ContentSelection.java b/core/java/android/app/contentsuggestions/ContentSelection.java
new file mode 100644
index 0000000..a8917f7
--- /dev/null
+++ b/core/java/android/app/contentsuggestions/ContentSelection.java
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+package android.app.contentsuggestions;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+@SystemApi
+public final class ContentSelection implements Parcelable {
+ @NonNull
+ private final String mSelectionId;
+ @NonNull
+ private final Bundle mExtras;
+
+ public ContentSelection(@NonNull String selectionId, @NonNull Bundle extras) {
+ mSelectionId = selectionId;
+ mExtras = extras;
+ }
+
+ /**
+ * Return the selection id.
+ */
+ public String getId() {
+ return mSelectionId;
+ }
+
+ /**
+ * Return the selection extras.
+ */
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mSelectionId);
+ dest.writeBundle(mExtras);
+ }
+
+ public static final Creator<ContentSelection> CREATOR =
+ new Creator<ContentSelection>() {
+ @Override
+ public ContentSelection createFromParcel(Parcel source) {
+ return new ContentSelection(
+ source.readString(),
+ source.readBundle());
+ }
+
+ @Override
+ public ContentSelection[] newArray(int size) {
+ return new ContentSelection[size];
+ }
+ };
+}
diff --git a/core/java/android/app/contentsuggestions/ContentSuggestionsManager.java b/core/java/android/app/contentsuggestions/ContentSuggestionsManager.java
new file mode 100644
index 0000000..b4d8977
--- /dev/null
+++ b/core/java/android/app/contentsuggestions/ContentSuggestionsManager.java
@@ -0,0 +1,230 @@
+/*
+ * 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.
+ */
+
+package android.app.contentsuggestions;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * When provided with content from an app, can suggest selections and classifications of that
+ * content.
+ *
+ * <p>The content is mainly a snapshot of a running task, the selections will be text and image
+ * selections with that image content. These mSelections can then be classified to find actions and
+ * entities on those selections.
+ *
+ * <p>Only accessible to blessed components such as Overview.
+ *
+ * @hide
+ */
+@SystemApi
+public final class ContentSuggestionsManager {
+ private static final String TAG = ContentSuggestionsManager.class.getSimpleName();
+
+ @Nullable
+ private final IContentSuggestionsManager mService;
+
+ /** @hide */
+ public ContentSuggestionsManager(@Nullable IContentSuggestionsManager service) {
+ mService = service;
+ }
+
+ /**
+ * Hints to the system that a new context image for the provided task should be sent to the
+ * system content suggestions service.
+ *
+ * @param taskId of the task to snapshot.
+ * @param imageContextRequestExtras sent with with request to provide implementation specific
+ * extra information.
+ */
+ public void provideContextImage(int taskId, @NonNull Bundle imageContextRequestExtras) {
+ if (mService == null) {
+ Log.e(TAG, "provideContextImage called, but no ContentSuggestionsManager configured");
+ return;
+ }
+
+ try {
+ mService.provideContextImage(taskId, imageContextRequestExtras);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Suggest content selections, based on the provided task id and optional
+ * location on screen provided in the request. Called after provideContextImage().
+ * The result can be passed to
+ * {@link #classifyContentSelections(ClassificationsRequest, Executor, ClassificationsCallback)}
+ * to classify actions and entities on these selections.
+ *
+ * @param request containing the task and point location.
+ * @param callbackExecutor to execute the provided callback on.
+ * @param callback to receive the selections.
+ */
+ public void suggestContentSelections(
+ @NonNull SelectionsRequest request,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull SelectionsCallback callback) {
+ if (mService == null) {
+ Log.e(TAG,
+ "suggestContentSelections called, but no ContentSuggestionsManager configured");
+ return;
+ }
+
+ try {
+ mService.suggestContentSelections(
+ request, new SelectionsCallbackWrapper(callback, callbackExecutor));
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Classify actions and entities in content selections, as returned from
+ * suggestContentSelections. Note these selections may be modified by the
+ * caller before being passed here.
+ *
+ * @param request containing the selections to classify.
+ * @param callbackExecutor to execute the provided callback on.
+ * @param callback to receive the classifications.
+ */
+ public void classifyContentSelections(
+ @NonNull ClassificationsRequest request,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull ClassificationsCallback callback) {
+ if (mService == null) {
+ Log.e(TAG, "classifyContentSelections called, "
+ + "but no ContentSuggestionsManager configured");
+ return;
+ }
+
+ try {
+ mService.classifyContentSelections(
+ request, new ClassificationsCallbackWrapper(callback, callbackExecutor));
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Report telemetry for interaction with suggestions / classifications.
+ *
+ * @param requestId the id for the associated interaction
+ * @param interaction to report back to the system content suggestions service.
+ */
+ public void notifyInteraction(@NonNull String requestId, @NonNull Bundle interaction) {
+ if (mService == null) {
+ Log.e(TAG, "notifyInteraction called, but no ContentSuggestionsManager configured");
+ return;
+ }
+
+ try {
+ mService.notifyInteraction(requestId, interaction);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Callback to receive content selections from
+ * {@link #suggestContentSelections(SelectionsRequest, Executor, SelectionsCallback)}.
+ */
+ public interface SelectionsCallback {
+ /**
+ * Async callback called when the content suggestions service has selections available.
+ * These can be modified and sent back to the manager for classification. The contents of
+ * the selection is implementation dependent.
+ *
+ * @param statusCode as defined by the implementation of content suggestions service.
+ * @param selections not {@code null}, but can be size {@code 0}.
+ */
+ void onContentSelectionsAvailable(
+ int statusCode, @NonNull List<ContentSelection> selections);
+ }
+
+ /**
+ * Callback to receive classifications from
+ * {@link #classifyContentSelections(ClassificationsRequest, Executor, ClassificationsCallback)}
+ */
+ public interface ClassificationsCallback {
+ /**
+ * Async callback called when the content suggestions service has classified selections. The
+ * contents of the classification is implementation dependent.
+ *
+ * @param statusCode as defined by the implementation of content suggestions service.
+ * @param classifications not {@code null}, but can be size {@code 0}.
+ */
+ void onContentClassificationsAvailable(int statusCode,
+ @NonNull List<ContentClassification> classifications);
+ }
+
+ private static class SelectionsCallbackWrapper extends ISelectionsCallback.Stub {
+ private final SelectionsCallback mCallback;
+ private final Executor mExecutor;
+
+ SelectionsCallbackWrapper(
+ @NonNull SelectionsCallback callback, @NonNull Executor executor) {
+ mCallback = callback;
+ mExecutor = executor;
+ }
+
+ @Override
+ public void onContentSelectionsAvailable(
+ int statusCode, List<ContentSelection> selections) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() ->
+ mCallback.onContentSelectionsAvailable(statusCode, selections));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ private static final class ClassificationsCallbackWrapper extends
+ IClassificationsCallback.Stub {
+ private final ClassificationsCallback mCallback;
+ private final Executor mExecutor;
+
+ ClassificationsCallbackWrapper(@NonNull ClassificationsCallback callback,
+ @NonNull Executor executor) {
+ mCallback = callback;
+ mExecutor = executor;
+ }
+
+ @Override
+ public void onContentClassificationsAvailable(
+ int statusCode, List<ContentClassification> classifications) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() ->
+ mCallback.onContentClassificationsAvailable(statusCode, classifications));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+}
diff --git a/core/java/android/app/contentsuggestions/IClassificationsCallback.aidl b/core/java/android/app/contentsuggestions/IClassificationsCallback.aidl
new file mode 100644
index 0000000..69f5d55
--- /dev/null
+++ b/core/java/android/app/contentsuggestions/IClassificationsCallback.aidl
@@ -0,0 +1,25 @@
+/**
+ * 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.
+ */
+
+package android.app.contentsuggestions;
+
+import android.app.contentsuggestions.ContentClassification;
+
+/** @hide */
+oneway interface IClassificationsCallback {
+ void onContentClassificationsAvailable(
+ int statusCode, in List<ContentClassification> classifications);
+}
diff --git a/core/java/android/app/contentsuggestions/IContentSuggestionsManager.aidl b/core/java/android/app/contentsuggestions/IContentSuggestionsManager.aidl
new file mode 100644
index 0000000..24f5ad8
--- /dev/null
+++ b/core/java/android/app/contentsuggestions/IContentSuggestionsManager.aidl
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+package android.app.contentsuggestions;
+
+import android.app.contentsuggestions.IClassificationsCallback;
+import android.app.contentsuggestions.ISelectionsCallback;
+import android.app.contentsuggestions.ClassificationsRequest;
+import android.app.contentsuggestions.SelectionsRequest;
+import android.os.Bundle;
+
+/** @hide */
+oneway interface IContentSuggestionsManager {
+ void provideContextImage(
+ int taskId,
+ in Bundle imageContextRequestExtras);
+ void suggestContentSelections(
+ in SelectionsRequest request,
+ in ISelectionsCallback callback);
+ void classifyContentSelections(
+ in ClassificationsRequest request,
+ in IClassificationsCallback callback);
+ void notifyInteraction(in String requestId, in Bundle interaction);
+}
diff --git a/core/java/android/app/contentsuggestions/ISelectionsCallback.aidl b/core/java/android/app/contentsuggestions/ISelectionsCallback.aidl
new file mode 100644
index 0000000..1952583
--- /dev/null
+++ b/core/java/android/app/contentsuggestions/ISelectionsCallback.aidl
@@ -0,0 +1,25 @@
+/**
+ * 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.
+ */
+
+package android.app.contentsuggestions;
+
+import android.app.contentsuggestions.ContentSelection;
+import android.os.Bundle;
+
+/** @hide */
+oneway interface ISelectionsCallback {
+ void onContentSelectionsAvailable(int statusCode, in List<ContentSelection> selections);
+}
diff --git a/core/java/android/app/contentsuggestions/SelectionsRequest.aidl b/core/java/android/app/contentsuggestions/SelectionsRequest.aidl
new file mode 100644
index 0000000..f5ce7c3
--- /dev/null
+++ b/core/java/android/app/contentsuggestions/SelectionsRequest.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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.
+ */
+
+package android.app.contentsuggestions;
+
+parcelable SelectionsRequest;
diff --git a/core/java/android/app/contentsuggestions/SelectionsRequest.java b/core/java/android/app/contentsuggestions/SelectionsRequest.java
new file mode 100644
index 0000000..16f3e6b
--- /dev/null
+++ b/core/java/android/app/contentsuggestions/SelectionsRequest.java
@@ -0,0 +1,129 @@
+/*
+ * 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.
+ */
+
+package android.app.contentsuggestions;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.graphics.Point;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+@SystemApi
+public final class SelectionsRequest implements Parcelable {
+ private final int mTaskId;
+ @Nullable
+ private final Point mInterestPoint;
+ @Nullable
+ private final Bundle mExtras;
+
+ private SelectionsRequest(int taskId, @Nullable Point interestPoint, @Nullable Bundle extras) {
+ mTaskId = taskId;
+ mInterestPoint = interestPoint;
+ mExtras = extras;
+ }
+
+ /**
+ * Return the request task id.
+ */
+ public int getTaskId() {
+ return mTaskId;
+ }
+
+ /**
+ * Return the request point of interest.
+ */
+ public Point getInterestPoint() {
+ return mInterestPoint;
+ }
+
+ /**
+ * Return the request extras.
+ */
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mTaskId);
+ dest.writeTypedObject(mInterestPoint, flags);
+ dest.writeBundle(mExtras);
+ }
+
+ public static final Creator<SelectionsRequest> CREATOR =
+ new Creator<SelectionsRequest>() {
+ @Override
+ public SelectionsRequest createFromParcel(Parcel source) {
+ return new SelectionsRequest(
+ source.readInt(), source.readTypedObject(Point.CREATOR), source.readBundle());
+ }
+
+ @Override
+ public SelectionsRequest[] newArray(int size) {
+ return new SelectionsRequest[size];
+ }
+ };
+
+ /**
+ * A builder for selections requests events.
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder {
+
+ private final int mTaskId;
+ private Point mInterestPoint;
+ private Bundle mExtras;
+
+ public Builder(int taskId) {
+ mTaskId = taskId;
+ }
+
+ /**
+ * Sets the request extras.
+ */
+ public Builder setExtras(@NonNull Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Sets the request interest point.
+ */
+ public Builder setInterestPoint(@NonNull Point interestPoint) {
+ mInterestPoint = interestPoint;
+ return this;
+ }
+
+ /**
+ * Builds a new request instance.
+ */
+ public SelectionsRequest build() {
+ return new SelectionsRequest(mTaskId, mInterestPoint, mExtras);
+ }
+ }
+}
diff --git a/core/java/android/app/prediction/AppPredictionContext.aidl b/core/java/android/app/prediction/AppPredictionContext.aidl
new file mode 100644
index 0000000..5767bf4
--- /dev/null
+++ b/core/java/android/app/prediction/AppPredictionContext.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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.
+ */
+
+package android.app.prediction;
+
+parcelable AppPredictionContext;
diff --git a/core/java/android/app/prediction/AppPredictionContext.java b/core/java/android/app/prediction/AppPredictionContext.java
new file mode 100644
index 0000000..87ccb66
--- /dev/null
+++ b/core/java/android/app/prediction/AppPredictionContext.java
@@ -0,0 +1,158 @@
+/*
+ * 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.
+ */
+package android.app.prediction;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * TODO(b/111701043): Add java docs
+ *
+ * @hide
+ */
+@SystemApi
+public final class AppPredictionContext implements Parcelable {
+
+ private final int mPredictedTargetCount;
+ @NonNull
+ private final String mUiSurface;
+ @NonNull
+ private final String mPackageName;
+ @Nullable
+ private final Bundle mExtras;
+
+ private AppPredictionContext(@NonNull String uiSurface, int numPredictedTargets,
+ @NonNull String packageName, @Nullable Bundle extras) {
+ mUiSurface = uiSurface;
+ mPredictedTargetCount = numPredictedTargets;
+ mPackageName = packageName;
+ mExtras = extras;
+ }
+
+ private AppPredictionContext(Parcel parcel) {
+ mUiSurface = parcel.readString();
+ mPredictedTargetCount = parcel.readInt();
+ mPackageName = parcel.readString();
+ mExtras = parcel.readBundle();
+ }
+
+ public String getUiSurface() {
+ return mUiSurface;
+ }
+
+ public int getPredictedTargetCount() {
+ return mPredictedTargetCount;
+ }
+
+ @NonNull
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ @Nullable
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mUiSurface);
+ dest.writeInt(mPredictedTargetCount);
+ dest.writeString(mPackageName);
+ dest.writeBundle(mExtras);
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ public static final Parcelable.Creator<AppPredictionContext> CREATOR =
+ new Parcelable.Creator<AppPredictionContext>() {
+ public AppPredictionContext createFromParcel(Parcel parcel) {
+ return new AppPredictionContext(parcel);
+ }
+
+ public AppPredictionContext[] newArray(int size) {
+ return new AppPredictionContext[size];
+ }
+ };
+
+ /**
+ * A builder for app prediction contexts.
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder {
+
+ @NonNull
+ private final String mPackageName;
+
+ private int mPredictedTargetCount;
+ @Nullable
+ private String mUiSurface;
+ @Nullable
+ private Bundle mExtras;
+
+ /**
+ * @hide
+ */
+ public Builder(@NonNull Context context) {
+ mPackageName = context.getPackageName();
+ }
+
+
+ /**
+ * Sets the number of prediction targets as a hint.
+ */
+ public Builder setPredictedTargetCount(int predictedTargetCount) {
+ mPredictedTargetCount = predictedTargetCount;
+ return this;
+ }
+
+ /**
+ * Sets the UI surface.
+ */
+ public Builder setUiSurface(@Nullable String uiSurface) {
+ mUiSurface = uiSurface;
+ return this;
+ }
+
+ /**
+ * Sets the extras.
+ */
+ public Builder setExtras(@Nullable Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Builds a new context instance.
+ */
+ public AppPredictionContext build() {
+ return new AppPredictionContext(mUiSurface, mPredictedTargetCount, mPackageName,
+ mExtras);
+ }
+ }
+}
diff --git a/core/java/android/app/prediction/AppPredictionManager.java b/core/java/android/app/prediction/AppPredictionManager.java
new file mode 100644
index 0000000..f8578d4
--- /dev/null
+++ b/core/java/android/app/prediction/AppPredictionManager.java
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+package android.app.prediction;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.content.Context;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * TODO (b/111701043) : Add java doc
+ * @hide
+ */
+@SystemApi
+public final class AppPredictionManager {
+
+ private final Context mContext;
+
+ /**
+ * @hide
+ */
+ public AppPredictionManager(Context context) {
+ mContext = Preconditions.checkNotNull(context);
+ }
+
+ /**
+ * Creates a new app prediction session.
+ */
+ public AppPredictor createAppPredictionSession(
+ @NonNull AppPredictionContext predictionContext) {
+ return new AppPredictor(mContext, predictionContext);
+ }
+}
diff --git a/core/java/android/app/prediction/AppPredictionSessionId.aidl b/core/java/android/app/prediction/AppPredictionSessionId.aidl
new file mode 100644
index 0000000..e829526
--- /dev/null
+++ b/core/java/android/app/prediction/AppPredictionSessionId.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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.
+ */
+
+package android.app.prediction;
+
+parcelable AppPredictionSessionId;
diff --git a/core/java/android/app/prediction/AppPredictionSessionId.java b/core/java/android/app/prediction/AppPredictionSessionId.java
new file mode 100644
index 0000000..1d7308e
--- /dev/null
+++ b/core/java/android/app/prediction/AppPredictionSessionId.java
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ */
+package android.app.prediction;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * TODO (b/111701043) : Add java doc
+ *
+ * @hide
+ */
+@SystemApi
+public final class AppPredictionSessionId implements Parcelable {
+
+ private final String mId;
+
+ /**
+ * @hide
+ */
+ public AppPredictionSessionId(@NonNull String id) {
+ mId = id;
+ }
+
+ private AppPredictionSessionId(Parcel p) {
+ mId = p.readString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!getClass().equals(o != null ? o.getClass() : null)) return false;
+
+ AppPredictionSessionId other = (AppPredictionSessionId) o;
+ return mId.equals(other.mId);
+ }
+
+ @Override
+ public @NonNull String toString() {
+ return mId;
+ }
+
+ @Override
+ public int hashCode() {
+ // Ensure that the id has a consistent hash
+ return mId.hashCode();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mId);
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ public static final Parcelable.Creator<AppPredictionSessionId> CREATOR =
+ new Parcelable.Creator<AppPredictionSessionId>() {
+ public AppPredictionSessionId createFromParcel(Parcel parcel) {
+ return new AppPredictionSessionId(parcel);
+ }
+
+ public AppPredictionSessionId[] newArray(int size) {
+ return new AppPredictionSessionId[size];
+ }
+ };
+}
diff --git a/core/java/android/app/prediction/AppPredictor.java b/core/java/android/app/prediction/AppPredictor.java
new file mode 100644
index 0000000..2ddbd08c
--- /dev/null
+++ b/core/java/android/app/prediction/AppPredictor.java
@@ -0,0 +1,257 @@
+/*
+ * 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.
+ */
+package android.app.prediction;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.prediction.IPredictionCallback.Stub;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import dalvik.system.CloseGuard;
+
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+
+/**
+ * TODO (b/111701043) : Add java doc
+ *
+ * <p>
+ * Usage: <pre> {@code
+ *
+ * class MyActivity {
+ * private AppPredictor mClient
+ *
+ * void onCreate() {
+ * mClient = new AppPredictor(...)
+ * }
+ *
+ * void onStart() {
+ * mClient.requestPredictionUpdate();
+ * }
+ *
+ * void onDestroy() {
+ * mClient.close();
+ * }
+ *
+ * }</pre>
+ *
+ * @hide
+ */
+@SystemApi
+public final class AppPredictor {
+
+ private static final String TAG = AppPredictor.class.getSimpleName();
+
+
+ private final IPredictionManager mPredictionManager;
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+ private final AtomicBoolean mIsClosed = new AtomicBoolean(false);
+
+ private final AppPredictionSessionId mSessionId;
+ private final ArrayMap<Callback, CallbackWrapper> mRegisteredCallbacks = new ArrayMap<>();
+
+ /**
+ * Creates a new Prediction client.
+ * <p>
+ * The caller should call {@link AppPredictor#destroy()} to dispose the client once it
+ * no longer used.
+ *
+ * @param predictionContext The prediction context
+ */
+ AppPredictor(@NonNull Context context, @NonNull AppPredictionContext predictionContext) {
+ IBinder b = ServiceManager.getService(Context.APP_PREDICTION_SERVICE);
+ mPredictionManager = IPredictionManager.Stub.asInterface(b);
+ mSessionId = new AppPredictionSessionId(
+ context.getPackageName() + ":" + UUID.randomUUID().toString());
+ try {
+ mPredictionManager.createPredictionSession(predictionContext, mSessionId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to create predictor", e);
+ return;
+ }
+
+ mCloseGuard.open("close");
+ }
+
+ /**
+ * Notifies the prediction service of an app target event.
+ */
+ public void notifyAppTargetEvent(@NonNull AppTargetEvent event) {
+ try {
+ mPredictionManager.notifyAppTargetEvent(mSessionId, event);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to notify app target event", e);
+ }
+ }
+
+ /**
+ * Notifies the prediction service when the targets in a launch location are shown to the user.
+ */
+ public void notifyLocationShown(@NonNull String launchLocation,
+ @NonNull List<AppTargetId> targetIds) {
+ try {
+ mPredictionManager.notifyLocationShown(mSessionId, launchLocation,
+ new ParceledListSlice<>(targetIds));
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to notify location shown event", e);
+ }
+ }
+
+ /**
+ * Requests the prediction service provide continuous updates of App predictions via the
+ * provided callback, until the given callback is unregistered.
+ *
+ * @see Callback#onTargetsAvailable(List)
+ */
+ public void registerPredictionUpdates(@NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull AppPredictor.Callback callback) {
+ if (mRegisteredCallbacks.containsKey(callback)) {
+ // Skip if this callback is already registered
+ return;
+ }
+ try {
+ final CallbackWrapper callbackWrapper = new CallbackWrapper(callbackExecutor,
+ callback::onTargetsAvailable);
+ mPredictionManager.registerPredictionUpdates(mSessionId, callbackWrapper);
+ mRegisteredCallbacks.put(callback, callbackWrapper);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to register for prediction updates", e);
+ }
+ }
+
+ /**
+ * Requests the prediction service to stop providing continuous updates to the provided
+ * callback until the callback is re-registered.
+ */
+ public void unregisterPredictionUpdates(@NonNull AppPredictor.Callback callback) {
+ if (!mRegisteredCallbacks.containsKey(callback)) {
+ // Skip if this callback was never registered
+ return;
+ }
+ try {
+ final CallbackWrapper callbackWrapper = mRegisteredCallbacks.remove(callback);
+ mPredictionManager.unregisterPredictionUpdates(mSessionId, callbackWrapper);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to unregister for prediction updates", e);
+ }
+ }
+
+ /**
+ * Requests the prediction service to dispatch a new set of App predictions via the provided
+ * callback.
+ *
+ * @see Callback#onTargetsAvailable(List)
+ */
+ public void requestPredictionUpdate() {
+ try {
+ mPredictionManager.requestPredictionUpdate(mSessionId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to request prediction update", e);
+ }
+ }
+
+ /**
+ * Returns a new list of AppTargets sorted based on prediction rank or {@code null} if the
+ * ranker is not available.
+ */
+ @Nullable
+ public void sortTargets(@NonNull List<AppTarget> targets,
+ @NonNull Executor callbackExecutor, @NonNull Consumer<List<AppTarget>> callback) {
+ try {
+ mPredictionManager.sortAppTargets(mSessionId, new ParceledListSlice(targets),
+ new CallbackWrapper(callbackExecutor, callback));
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to sort targets", e);
+ }
+ }
+
+ /**
+ * Destroys the client and unregisters the callback. Any method on this class after this call
+ * with throw {@link IllegalStateException}.
+ *
+ * TODO(b/111701043): Add state check in other methods.
+ */
+ public void destroy() {
+ if (!mIsClosed.getAndSet(true)) {
+ mCloseGuard.close();
+
+ // Do destroy;
+ try {
+ mPredictionManager.onDestroyPredictionSession(mSessionId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to notify app target event", e);
+ }
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+ destroy();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Callback for receiving prediction updates.
+ */
+ public interface Callback {
+
+ /**
+ * Called when a new set of predicted app targets are available.
+ * @param targets Sorted list of predicted targets
+ */
+ void onTargetsAvailable(@NonNull List<AppTarget> targets);
+ }
+
+ static class CallbackWrapper extends Stub {
+
+ private final Consumer<List<AppTarget>> mCallback;
+ private final Executor mExecutor;
+
+ CallbackWrapper(@NonNull Executor callbackExecutor,
+ @NonNull Consumer<List<AppTarget>> callback) {
+ mCallback = callback;
+ mExecutor = callbackExecutor;
+ }
+
+ @Override
+ public void onResult(ParceledListSlice result) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.accept(result.getList()));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+}
diff --git a/core/java/android/app/prediction/AppTarget.aidl b/core/java/android/app/prediction/AppTarget.aidl
new file mode 100644
index 0000000..e4e2bc2
--- /dev/null
+++ b/core/java/android/app/prediction/AppTarget.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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.
+ */
+
+package android.app.prediction;
+
+parcelable AppTarget;
diff --git a/core/java/android/app/prediction/AppTarget.java b/core/java/android/app/prediction/AppTarget.java
new file mode 100644
index 0000000..99c1c44
--- /dev/null
+++ b/core/java/android/app/prediction/AppTarget.java
@@ -0,0 +1,166 @@
+/*
+ * 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.
+ */
+package android.app.prediction;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.pm.ShortcutInfo;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * A representation of a launchable target.
+ * @hide
+ */
+@SystemApi
+public final class AppTarget implements Parcelable {
+
+ private final AppTargetId mId;
+ private final String mPackageName;
+ private final String mClassName;
+ private final UserHandle mUser;
+
+ private final ShortcutInfo mShortcutInfo;
+
+ private int mRank;
+
+ /**
+ * @hide
+ */
+ public AppTarget(@NonNull AppTargetId id, @NonNull String packageName,
+ @Nullable String className, @NonNull UserHandle user) {
+ mId = id;
+ mShortcutInfo = null;
+
+ mPackageName = Preconditions.checkNotNull(packageName);
+ mClassName = className;
+ mUser = Preconditions.checkNotNull(user);
+ }
+
+ /**
+ * @hide
+ */
+ public AppTarget(@NonNull AppTargetId id, @NonNull ShortcutInfo shortcutInfo) {
+ mId = id;
+ mShortcutInfo = Preconditions.checkNotNull(shortcutInfo);
+
+ mPackageName = mShortcutInfo.getPackage();
+ mUser = mShortcutInfo.getUserHandle();
+ mClassName = null;
+ }
+
+ private AppTarget(Parcel parcel) {
+ mId = parcel.readTypedObject(AppTargetId.CREATOR);
+ mShortcutInfo = parcel.readTypedObject(ShortcutInfo.CREATOR);
+ if (mShortcutInfo == null) {
+ mPackageName = parcel.readString();
+ mClassName = parcel.readString();
+ mUser = UserHandle.of(parcel.readInt());
+ } else {
+ mPackageName = mShortcutInfo.getPackage();
+ mUser = mShortcutInfo.getUserHandle();
+ mClassName = null;
+ }
+ mRank = parcel.readInt();
+ }
+
+ /**
+ * Returns the target id.
+ */
+ @NonNull
+ public AppTargetId getId() {
+ return mId;
+ }
+
+ /**
+ * Returns the class name for the app target.
+ */
+ @Nullable
+ public String getClassName() {
+ return mClassName;
+ }
+
+ /**
+ * Returns the package name for the app target.
+ */
+ @NonNull
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Returns the user for the app target.
+ */
+ @NonNull
+ public UserHandle getUser() {
+ return mUser;
+ }
+
+ /**
+ * Returns the shortcut info for the target.
+ */
+ @Nullable
+ public ShortcutInfo getShortcutInfo() {
+ return mShortcutInfo;
+ }
+
+ /**
+ * Sets the rank of the for the target.
+ * @hide
+ */
+ public void setRank(int rank) {
+ mRank = rank;
+ }
+
+ public int getRank() {
+ return mRank;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeTypedObject(mId, flags);
+ dest.writeTypedObject(mShortcutInfo, flags);
+ if (mShortcutInfo == null) {
+ dest.writeString(mPackageName);
+ dest.writeString(mClassName);
+ dest.writeInt(mUser.getIdentifier());
+ }
+ dest.writeInt(mRank);
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ public static final Parcelable.Creator<AppTarget> CREATOR =
+ new Parcelable.Creator<AppTarget>() {
+ public AppTarget createFromParcel(Parcel parcel) {
+ return new AppTarget(parcel);
+ }
+
+ public AppTarget[] newArray(int size) {
+ return new AppTarget[size];
+ }
+ };
+}
diff --git a/core/java/android/app/prediction/AppTargetEvent.aidl b/core/java/android/app/prediction/AppTargetEvent.aidl
new file mode 100644
index 0000000..ebed2da
--- /dev/null
+++ b/core/java/android/app/prediction/AppTargetEvent.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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.
+ */
+
+package android.app.prediction;
+
+parcelable AppTargetEvent;
diff --git a/core/java/android/app/prediction/AppTargetEvent.java b/core/java/android/app/prediction/AppTargetEvent.java
new file mode 100644
index 0000000..18317e1
--- /dev/null
+++ b/core/java/android/app/prediction/AppTargetEvent.java
@@ -0,0 +1,154 @@
+/*
+ * 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.
+ */
+package android.app.prediction;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A representation of an app target event.
+ * @hide
+ */
+@SystemApi
+public final class AppTargetEvent implements Parcelable {
+
+ /**
+ * @hide
+ */
+ @IntDef({ACTION_LAUNCH, ACTION_DISMISS, ACTION_PIN})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ActionType {}
+
+ /**
+ * Event type constant indicating an app target has been launched.
+ */
+ public static final int ACTION_LAUNCH = 1;
+
+ /**
+ * Event type constant indicating an app target has been dismissed.
+ */
+ public static final int ACTION_DISMISS = 2;
+
+ /**
+ * Event type constant indicating an app target has been pinned.
+ */
+ public static final int ACTION_PIN = 3;
+
+ private final AppTarget mTarget;
+ private final String mLocation;
+ private final int mAction;
+
+ private AppTargetEvent(@Nullable AppTarget target, @Nullable String location,
+ @ActionType int actionType) {
+ mTarget = target;
+ mLocation = location;
+ mAction = actionType;
+ }
+
+ private AppTargetEvent(Parcel parcel) {
+ mTarget = parcel.readParcelable(null);
+ mLocation = parcel.readString();
+ mAction = parcel.readInt();
+ }
+
+ /**
+ * Returns the app target.
+ */
+ @Nullable
+ public AppTarget getTarget() {
+ return mTarget;
+ }
+
+ /**
+ * Returns the launch location.
+ */
+ @NonNull
+ public String getLaunchLocation() {
+ return mLocation;
+ }
+
+ /**
+ * Returns the action type.
+ */
+ @NonNull
+ public int getAction() {
+ return mAction;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(mTarget, 0);
+ dest.writeString(mLocation);
+ dest.writeInt(mAction);
+ }
+
+ /**
+ * @see Creator
+ */
+ public static final Creator<AppTargetEvent> CREATOR =
+ new Creator<AppTargetEvent>() {
+ public AppTargetEvent createFromParcel(Parcel parcel) {
+ return new AppTargetEvent(parcel);
+ }
+
+ public AppTargetEvent[] newArray(int size) {
+ return new AppTargetEvent[size];
+ }
+ };
+
+ /**
+ * A builder for app target events.
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder {
+ private AppTarget mTarget;
+ private String mLocation;
+ private @ActionType int mAction;
+
+ public Builder(@Nullable AppTarget target, @ActionType int actionType) {
+ mTarget = target;
+ mAction = actionType;
+ }
+
+ /**
+ * Sets the launch location.
+ */
+ public Builder setLaunchLocation(String location) {
+ mLocation = location;
+ return this;
+ }
+
+ /**
+ * Builds a new event instance.
+ */
+ public AppTargetEvent build() {
+ return new AppTargetEvent(mTarget, mLocation, mAction);
+ }
+ }
+}
diff --git a/core/java/android/app/prediction/AppTargetId.aidl b/core/java/android/app/prediction/AppTargetId.aidl
new file mode 100644
index 0000000..bf69eea
--- /dev/null
+++ b/core/java/android/app/prediction/AppTargetId.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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.
+ */
+
+package android.app.prediction;
+
+parcelable AppTargetId;
diff --git a/core/java/android/app/prediction/AppTargetId.java b/core/java/android/app/prediction/AppTargetId.java
new file mode 100644
index 0000000..0b8fb47
--- /dev/null
+++ b/core/java/android/app/prediction/AppTargetId.java
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+package android.app.prediction;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * The id for a prediction target.
+ * @hide
+ */
+@SystemApi
+public final class AppTargetId implements Parcelable {
+
+ @NonNull
+ private final String mId;
+
+ /**
+ * @hide
+ */
+ public AppTargetId(@NonNull String id) {
+ mId = id;
+ }
+
+ private AppTargetId(Parcel parcel) {
+ mId = parcel.readString();
+ }
+
+ /**
+ * Returns the id.
+ * @hide
+ */
+ @NonNull
+ public String getId() {
+ return mId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!getClass().equals(o != null ? o.getClass() : null)) return false;
+
+ AppTargetId other = (AppTargetId) o;
+ return mId.equals(other.mId);
+ }
+
+ @Override
+ public int hashCode() {
+ // Ensure that the id has a consistent hash
+ return mId.hashCode();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mId);
+ }
+
+ /**
+ * @see Creator
+ */
+ public static final Creator<AppTargetId> CREATOR =
+ new Creator<AppTargetId>() {
+ public AppTargetId createFromParcel(Parcel parcel) {
+ return new AppTargetId(parcel);
+ }
+
+ public AppTargetId[] newArray(int size) {
+ return new AppTargetId[size];
+ }
+ };
+}
diff --git a/core/java/android/app/prediction/IPredictionCallback.aidl b/core/java/android/app/prediction/IPredictionCallback.aidl
new file mode 100644
index 0000000..f6f241e
--- /dev/null
+++ b/core/java/android/app/prediction/IPredictionCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+package android.app.prediction;
+
+import android.content.pm.ParceledListSlice;
+
+/**
+ * @hide
+ */
+oneway interface IPredictionCallback {
+
+ void onResult(in ParceledListSlice result);
+}
diff --git a/core/java/android/app/prediction/IPredictionManager.aidl b/core/java/android/app/prediction/IPredictionManager.aidl
new file mode 100644
index 0000000..114a1ff
--- /dev/null
+++ b/core/java/android/app/prediction/IPredictionManager.aidl
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+package android.app.prediction;
+
+import android.app.prediction.AppTarget;
+import android.app.prediction.AppTargetEvent;
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionSessionId;
+import android.app.prediction.IPredictionCallback;
+import android.content.pm.ParceledListSlice;
+
+/**
+ * @hide
+ */
+interface IPredictionManager {
+
+ void createPredictionSession(in AppPredictionContext context,
+ in AppPredictionSessionId sessionId);
+
+ void notifyAppTargetEvent(in AppPredictionSessionId sessionId, in AppTargetEvent event);
+
+ void notifyLocationShown(in AppPredictionSessionId sessionId, in String launchLocation,
+ in ParceledListSlice targetIds);
+
+ void sortAppTargets(in AppPredictionSessionId sessionId, in ParceledListSlice targets,
+ in IPredictionCallback callback);
+
+ void registerPredictionUpdates(in AppPredictionSessionId sessionId,
+ in IPredictionCallback callback);
+
+ void unregisterPredictionUpdates(in AppPredictionSessionId sessionId,
+ in IPredictionCallback callback);
+
+ void requestPredictionUpdate(in AppPredictionSessionId sessionId);
+
+ void onDestroyPredictionSession(in AppPredictionSessionId sessionId);
+}
diff --git a/core/java/android/app/role/IRoleManager.aidl b/core/java/android/app/role/IRoleManager.aidl
index 0c9b41bf..2964fbc 100644
--- a/core/java/android/app/role/IRoleManager.aidl
+++ b/core/java/android/app/role/IRoleManager.aidl
@@ -50,4 +50,6 @@
boolean removeRoleHolderFromController(in String roleName, in String packageName);
List<String> getHeldRolesFromController(in String packageName);
+
+ String getDefaultSmsPackage(int userId);
}
diff --git a/core/java/android/app/role/RoleManager.java b/core/java/android/app/role/RoleManager.java
index 2d630a6..f14c568 100644
--- a/core/java/android/app/role/RoleManager.java
+++ b/core/java/android/app/role/RoleManager.java
@@ -19,6 +19,7 @@
import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
@@ -67,6 +68,56 @@
private static final String LOG_TAG = RoleManager.class.getSimpleName();
/**
+ * The name of the proxy calling role.
+ * <p>
+ * A proxy calling app implements the {@link android.telecom.CallRedirectionService} API and
+ * provides a means to re-write the phone number for an outgoing call to place the call through
+ * a proxy calling service.
+ * <p>
+ * A single app may fill this role at any one time.
+ * @hide
+ */
+ public static final String ROLE_PROXY_CALLING_APP = "android.app.role.PROXY_CALLING_APP";
+
+ /**
+ * The name of the call screening and caller id role.
+ * <p>
+ * A call screening and caller id app implements the
+ * {@link android.telecom.CallScreeningService} API.
+ * <p>
+ * A single app may fill this role at any one time.
+ * @hide
+ */
+ public static final String ROLE_CALL_SCREENING_APP = "android.app.role.CALL_SCREENING_APP";
+
+ /**
+ * The name of the call companion app role.
+ * <p>
+ * A call companion app provides no user interface for calls, but will be bound to by Telecom
+ * when there are active calls on the device. Companion apps for wearable devices are an
+ * acceptable use-case. A call companion app implements the
+ * {@link android.telecom.InCallService} API.
+ * <p>
+ * Multiple apps app may fill this role at any one time.
+ * @hide
+ */
+ public static final String ROLE_CALL_COMPANION_APP = "android.app.role.CALL_COMPANION_APP";
+
+ /**
+ * The name of the car mode dialer app role.
+ * <p>
+ * Similar to the {@link #ROLE_DIALER} role, this role determines which app is responsible for
+ * showing the user interface for ongoing calls on the device. This app filling this role is
+ * only used when the device is in car mode (see
+ * {@link android.app.UiModeManager#ACTION_ENTER_CAR_MODE} for more information). An app
+ * filling this role must implement the {@link android.telecom.InCallService} API.
+ * <p>
+ * A single app may fill this role at any one time.
+ * @hide
+ */
+ public static final String ROLE_CAR_MODE_DIALER_APP = "android.app.role.CAR_MODE_DIALER_APP";
+
+ /**
* The name of the dialer role.
*/
public static final String ROLE_DIALER = "android.app.role.DIALER";
@@ -542,7 +593,6 @@
}
}
-
/**
* Returns the list of all roles that the given package is currently holding
*
@@ -563,6 +613,22 @@
}
}
+ /**
+ * Allows getting the role holder for {@link #ROLE_SMS} without
+ * {@link Manifest.permission#OBSERVE_ROLE_HOLDERS}, as required by
+ * {@link android.provider.Telephony.Sms#getDefaultSmsPackage(Context)}
+ *
+ * @hide
+ */
+ @Nullable
+ public String getDefaultSmsPackage(@UserIdInt int userId) {
+ try {
+ return mService.getDefaultSmsPackage(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
private static class RoleManagerCallbackDelegate extends IRoleManagerCallback.Stub {
@NonNull
diff --git a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
index a2b7d58..8ee9e53 100644
--- a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
+++ b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
@@ -36,6 +36,11 @@
private Configuration mConfiguration;
@Override
+ public void preExecute(android.app.ClientTransactionHandler client, IBinder token) {
+ client.updatePendingActivityConfiguration(token, mConfiguration);
+ }
+
+ @Override
public void execute(ClientTransactionHandler client, IBinder token,
PendingTransactionActions pendingActions) {
// TODO(lifecycler): detect if PIP or multi-window mode changed and report it here.
diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl
index 4d52263..bbae7d3 100644
--- a/core/java/android/app/usage/IUsageStatsManager.aidl
+++ b/core/java/android/app/usage/IUsageStatsManager.aidl
@@ -55,4 +55,8 @@
long sessionThresholdTimeMs, in PendingIntent limitReachedCallbackIntent,
in PendingIntent sessionEndCallbackIntent, String callingPackage);
void unregisterUsageSessionObserver(int sessionObserverId, String callingPackage);
+ void reportUsageStart(in IBinder activity, String token, String callingPackage);
+ void reportPastUsageStart(in IBinder activity, String token, long timeAgoMs,
+ String callingPackage);
+ void reportUsageStop(in IBinder activity, String token, String callingPackage);
}
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index 26beb45..605deac 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -23,6 +23,7 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.UnsupportedAppUsage;
+import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
import android.content.pm.ParceledListSlice;
@@ -579,15 +580,18 @@
/**
* @hide
* Register an app usage limit observer that receives a callback on the provided intent when
- * the sum of usages of apps in the packages array exceeds the {@code timeLimit} specified. The
- * observer will automatically be unregistered when the time limit is reached and the intent
- * is delivered. Registering an {@code observerId} that was already registered will override
- * the previous one. No more than 1000 unique {@code observerId} may be registered by a single
- * uid at any one time.
+ * the sum of usages of apps and tokens in the {@code observed} array exceeds the
+ * {@code timeLimit} specified. The structure of a token is a String with the reporting
+ * package's name and a token the reporting app will use, separated by the forward slash
+ * character. Example: com.reporting.package/5OM3*0P4QU3-7OK3N
+ * The observer will automatically be unregistered when the time limit is reached and the
+ * intent is delivered. Registering an {@code observerId} that was already registered will
+ * override the previous one. No more than 1000 unique {@code observerId} may be registered by
+ * a single uid at any one time.
* @param observerId A unique id associated with the group of apps to be monitored. There can
* be multiple groups with common packages and different time limits.
- * @param packages The list of packages to observe for foreground activity time. Cannot be null
- * and must include at least one package.
+ * @param observedEntities The list of packages and token to observe for usage time. Cannot be
+ * null and must include at least one package or token.
* @param timeLimit The total time the set of apps can be in the foreground before the
* callbackIntent is delivered. Must be at least one minute.
* @param timeUnit The unit for time specified in {@code timeLimit}. Cannot be null.
@@ -600,11 +604,11 @@
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE)
- public void registerAppUsageObserver(int observerId, @NonNull String[] packages, long timeLimit,
- @NonNull TimeUnit timeUnit, @NonNull PendingIntent callbackIntent) {
+ public void registerAppUsageObserver(int observerId, @NonNull String[] observedEntities,
+ long timeLimit, @NonNull TimeUnit timeUnit, @NonNull PendingIntent callbackIntent) {
try {
- mService.registerAppUsageObserver(observerId, packages, timeUnit.toMillis(timeLimit),
- callbackIntent, mContext.getOpPackageName());
+ mService.registerAppUsageObserver(observerId, observedEntities,
+ timeUnit.toMillis(timeLimit), callbackIntent, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -631,18 +635,21 @@
/**
* Register a usage session observer that receives a callback on the provided {@code
- * limitReachedCallbackIntent} when the sum of usages of apps in the packages array exceeds
- * the {@code timeLimit} specified within a usage session. After the {@code timeLimit} has
- * been reached, the usage session observer will receive a callback on the provided {@code
- * sessionEndCallbackIntent} when the usage session ends. Registering another session
- * observer against a {@code sessionObserverId} that has already been registered will
- * override the previous session observer.
+ * limitReachedCallbackIntent} when the sum of usages of apps and tokens in the {@code
+ * observed} array exceeds the {@code timeLimit} specified within a usage session. The
+ * structure of a token is a String with the reporting packages' name and a token the
+ * reporting app will use, separated by the forward slash character.
+ * Example: com.reporting.package/5OM3*0P4QU3-7OK3N
+ * After the {@code timeLimit} has been reached, the usage session observer will receive a
+ * callback on the provided {@code sessionEndCallbackIntent} when the usage session ends.
+ * Registering another session observer against a {@code sessionObserverId} that has already
+ * been registered will override the previous session observer.
*
* @param sessionObserverId A unique id associated with the group of apps to be
* monitored. There can be multiple groups with common
* packages and different time limits.
- * @param packages The list of packages to observe for foreground activity time. Cannot be null
- * and must include at least one package.
+ * @param observedEntities The list of packages and token to observe for usage time. Cannot be
+ * null and must include at least one package or token.
* @param timeLimit The total time the set of apps can be used continuously before the {@code
* limitReachedCallbackIntent} is delivered. Must be at least one minute.
* @param timeUnit The unit for time specified in {@code timeLimit}. Cannot be null.
@@ -668,13 +675,13 @@
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE)
- public void registerUsageSessionObserver(int sessionObserverId, @NonNull String[] packages,
- long timeLimit, @NonNull TimeUnit timeUnit, long sessionThresholdTime,
- @NonNull TimeUnit sessionThresholdTimeUnit,
+ public void registerUsageSessionObserver(int sessionObserverId,
+ @NonNull String[] observedEntities, long timeLimit, @NonNull TimeUnit timeUnit,
+ long sessionThresholdTime, @NonNull TimeUnit sessionThresholdTimeUnit,
@NonNull PendingIntent limitReachedCallbackIntent,
@Nullable PendingIntent sessionEndCallbackIntent) {
try {
- mService.registerUsageSessionObserver(sessionObserverId, packages,
+ mService.registerUsageSessionObserver(sessionObserverId, observedEntities,
timeUnit.toMillis(timeLimit),
sessionThresholdTimeUnit.toMillis(sessionThresholdTime),
limitReachedCallbackIntent, sessionEndCallbackIntent,
@@ -704,6 +711,71 @@
}
}
+ /**
+ * Report usage associated with a particular {@code token} has started. Tokens are app defined
+ * strings used to represent usage of in-app features. Apps with the {@link
+ * android.Manifest.permission#OBSERVE_APP_USAGE} permission can register time limit observers
+ * to monitor the usage of a token. In app usage can only associated with an {@code activity}
+ * and usage will be considered stopped if the activity stops or crashes.
+ * @see #registerAppUsageObserver
+ * @see #registerUsageSessionObserver
+ *
+ * @param activity The activity {@code token} is associated with.
+ * @param token The token to report usage against.
+ * @hide
+ */
+ @SystemApi
+ public void reportUsageStart(@NonNull Activity activity, @NonNull String token) {
+ try {
+ mService.reportUsageStart(activity.getActivityToken(), token,
+ mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Report usage associated with a particular {@code token} had started some amount of time in
+ * the past. Tokens are app defined strings used to represent usage of in-app features. Apps
+ * with the {@link android.Manifest.permission#OBSERVE_APP_USAGE} permission can register time
+ * limit observers to monitor the usage of a token. In app usage can only associated with an
+ * {@code activity} and usage will be considered stopped if the activity stops or crashes.
+ * @see #registerAppUsageObserver
+ * @see #registerUsageSessionObserver
+ *
+ * @param activity The activity {@code token} is associated with.
+ * @param token The token to report usage against.
+ * @param timeAgoMs How long ago the start of usage took place
+ * @hide
+ */
+ @SystemApi
+ public void reportUsageStart(@NonNull Activity activity, @NonNull String token,
+ long timeAgoMs) {
+ try {
+ mService.reportPastUsageStart(activity.getActivityToken(), token, timeAgoMs,
+ mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Report the usage associated with a particular {@code token} has stopped.
+ *
+ * @param activity The activity {@code token} is associated with.
+ * @param token The token to report usage against.
+ * @hide
+ */
+ @SystemApi
+ public void reportUsageStop(@NonNull Activity activity, @NonNull String token) {
+ try {
+ mService.reportUsageStop(activity.getActivityToken(), token,
+ mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/** @hide */
public static String reasonToString(int standbyReason) {
StringBuilder sb = new StringBuilder();
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index c740c42..3e9dd28 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -375,12 +375,12 @@
}
/**
- * Sets whether the widget should is being displayed on a light/white background and use an
+ * Sets whether the widget is being displayed on a light/white background and use an
* alternate UI if available.
* @see RemoteViews#setLightBackgroundLayoutId(int)
*/
- public void setOnLightBackground(boolean useDarkTextLayout) {
- mOnLightBackground = useDarkTextLayout;
+ public void setOnLightBackground(boolean onLightBackground) {
+ mOnLightBackground = onLightBackground;
}
/**
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 87b64797..1945b2f 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -2066,8 +2066,7 @@
* Get the current connection state of a profile.
* This function can be used to check whether the local Bluetooth adapter
* is connected to any remote device for a specific profile.
- * Profile can be one of {@link BluetoothProfile#HEALTH}, {@link BluetoothProfile#HEADSET},
- * {@link BluetoothProfile#A2DP}.
+ * Profile can be one of {@link BluetoothProfile#HEADSET}, {@link BluetoothProfile#A2DP}.
*
* <p> Return value can be one of
* {@link BluetoothProfile#STATE_DISCONNECTED},
@@ -2441,16 +2440,15 @@
/**
* Get the profile proxy object associated with the profile.
*
- * <p>Profile can be one of {@link BluetoothProfile#HEALTH}, {@link BluetoothProfile#HEADSET},
- * {@link BluetoothProfile#A2DP}, {@link BluetoothProfile#GATT}, or
- * {@link BluetoothProfile#GATT_SERVER}. Clients must implement
- * {@link BluetoothProfile.ServiceListener} to get notified of
- * the connection status and to get the proxy object.
+ * <p>Profile can be one of {@link BluetoothProfile#HEADSET}, {@link BluetoothProfile#A2DP},
+ * {@link BluetoothProfile#GATT}, or {@link BluetoothProfile#GATT_SERVER}. Clients must
+ * implement {@link BluetoothProfile.ServiceListener} to get notified of the connection status
+ * and to get the proxy object.
*
* @param context Context of the application
* @param listener The service Listener for connection callbacks.
- * @param profile The Bluetooth profile; either {@link BluetoothProfile#HEALTH}, {@link
- * BluetoothProfile#HEADSET}, {@link BluetoothProfile#A2DP}. {@link BluetoothProfile#GATT} or
+ * @param profile The Bluetooth profile; either {@link BluetoothProfile#HEADSET},
+ * {@link BluetoothProfile#A2DP}. {@link BluetoothProfile#GATT} or
* {@link BluetoothProfile#GATT_SERVER}.
* @return true on success, false on error
*/
@@ -2479,8 +2477,8 @@
BluetoothPan pan = new BluetoothPan(context, listener);
return true;
} else if (profile == BluetoothProfile.HEALTH) {
- BluetoothHealth health = new BluetoothHealth(context, listener);
- return true;
+ Log.e(TAG, "getProfileProxy(): BluetoothHealth is deprecated");
+ return false;
} else if (profile == BluetoothProfile.MAP) {
BluetoothMap map = new BluetoothMap(context, listener);
return true;
@@ -2512,8 +2510,7 @@
*
* <p> Clients should call this when they are no longer using
* the proxy obtained from {@link #getProfileProxy}.
- * Profile can be one of {@link BluetoothProfile#HEALTH}, {@link BluetoothProfile#HEADSET} or
- * {@link BluetoothProfile#A2DP}
+ * Profile can be one of {@link BluetoothProfile#HEADSET} or {@link BluetoothProfile#A2DP}
*
* @param profile
* @param proxy Profile proxy object
@@ -2548,10 +2545,6 @@
BluetoothPan pan = (BluetoothPan) proxy;
pan.close();
break;
- case BluetoothProfile.HEALTH:
- BluetoothHealth health = (BluetoothHealth) proxy;
- health.close();
- break;
case BluetoothProfile.GATT:
BluetoothGatt gatt = (BluetoothGatt) proxy;
gatt.close();
diff --git a/core/java/android/bluetooth/BluetoothHealth.java b/core/java/android/bluetooth/BluetoothHealth.java
index 22d41d9..e2e56fd 100644
--- a/core/java/android/bluetooth/BluetoothHealth.java
+++ b/core/java/android/bluetooth/BluetoothHealth.java
@@ -16,15 +16,7 @@
package android.bluetooth;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Binder;
-import android.os.IBinder;
import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.os.UserHandle;
import android.util.Log;
import java.util.ArrayList;
@@ -54,79 +46,59 @@
* <li> When done, close the health channel by calling {@link #disconnectChannel}
* and unregister the application configuration calling
* {@link #unregisterAppConfiguration}
+ *
+ * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New apps
+ * should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+ * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+ * {@link BluetoothDevice#createL2capChannel(int)}
*/
+@Deprecated
public final class BluetoothHealth implements BluetoothProfile {
private static final String TAG = "BluetoothHealth";
- private static final boolean DBG = true;
- private static final boolean VDBG = false;
-
/**
* Health Profile Source Role - the health device.
+ *
+ * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+ * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+ * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+ * {@link BluetoothDevice#createL2capChannel(int)}
*/
+ @Deprecated
public static final int SOURCE_ROLE = 1 << 0;
/**
* Health Profile Sink Role the device talking to the health device.
+ *
+ * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+ * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+ * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+ * {@link BluetoothDevice#createL2capChannel(int)}
*/
+ @Deprecated
public static final int SINK_ROLE = 1 << 1;
/**
* Health Profile - Channel Type used - Reliable
+ *
+ * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+ * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+ * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+ * {@link BluetoothDevice#createL2capChannel(int)}
*/
+ @Deprecated
public static final int CHANNEL_TYPE_RELIABLE = 10;
/**
* Health Profile - Channel Type used - Streaming
+ *
+ * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+ * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+ * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+ * {@link BluetoothDevice#createL2capChannel(int)}
*/
+ @Deprecated
public static final int CHANNEL_TYPE_STREAMING = 11;
- /**
- * @hide
- */
- public static final int CHANNEL_TYPE_ANY = 12;
-
- /** @hide */
- public static final int HEALTH_OPERATION_SUCCESS = 6000;
- /** @hide */
- public static final int HEALTH_OPERATION_ERROR = 6001;
- /** @hide */
- public static final int HEALTH_OPERATION_INVALID_ARGS = 6002;
- /** @hide */
- public static final int HEALTH_OPERATION_GENERIC_FAILURE = 6003;
- /** @hide */
- public static final int HEALTH_OPERATION_NOT_FOUND = 6004;
- /** @hide */
- public static final int HEALTH_OPERATION_NOT_ALLOWED = 6005;
-
- private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
- new IBluetoothStateChangeCallback.Stub() {
- public void onBluetoothStateChange(boolean up) {
- if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
- if (!up) {
- if (VDBG) Log.d(TAG, "Unbinding service...");
- synchronized (mConnection) {
- try {
- mService = null;
- mContext.unbindService(mConnection);
- } catch (Exception re) {
- Log.e(TAG, "", re);
- }
- }
- } else {
- synchronized (mConnection) {
- try {
- if (mService == null) {
- if (VDBG) Log.d(TAG, "Binding service...");
- doBind();
- }
- } catch (Exception re) {
- Log.e(TAG, "", re);
- }
- }
- }
- }
- };
-
/**
* Register an application configuration that acts as a Health SINK.
@@ -142,53 +114,17 @@
* @param callback A callback to indicate success or failure of the registration and all
* operations done on this application configuration.
* @return If true, callback will be called.
+ *
+ * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+ * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+ * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+ * {@link BluetoothDevice#createL2capChannel(int)}
*/
+ @Deprecated
public boolean registerSinkAppConfiguration(String name, int dataType,
BluetoothHealthCallback callback) {
- if (!isEnabled() || name == null) return false;
-
- if (VDBG) log("registerSinkApplication(" + name + ":" + dataType + ")");
- return registerAppConfiguration(name, dataType, SINK_ROLE,
- CHANNEL_TYPE_ANY, callback);
- }
-
- /**
- * Register an application configuration that acts as a Health SINK or in a Health
- * SOURCE role.This is an asynchronous call and so
- * the callback is used to notify success or failure if the function returns true.
- *
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
- * @param name The friendly name associated with the application or configuration.
- * @param dataType The dataType of the Source role of Health Profile.
- * @param channelType The channel type. Will be one of {@link #CHANNEL_TYPE_RELIABLE} or {@link
- * #CHANNEL_TYPE_STREAMING}
- * @param callback - A callback to indicate success or failure.
- * @return If true, callback will be called.
- * @hide
- */
- public boolean registerAppConfiguration(String name, int dataType, int role,
- int channelType, BluetoothHealthCallback callback) {
- boolean result = false;
- if (!isEnabled() || !checkAppParam(name, role, channelType, callback)) return result;
-
- if (VDBG) log("registerApplication(" + name + ":" + dataType + ")");
- BluetoothHealthCallbackWrapper wrapper = new BluetoothHealthCallbackWrapper(callback);
- BluetoothHealthAppConfiguration config =
- new BluetoothHealthAppConfiguration(name, dataType, role, channelType);
-
- final IBluetoothHealth service = mService;
- if (service != null) {
- try {
- result = service.registerAppConfiguration(config, wrapper);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
- Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
- }
- return result;
+ Log.e(TAG, "registerSinkAppConfiguration(): BluetoothHealth is deprecated");
+ return false;
}
/**
@@ -199,22 +135,16 @@
*
* @param config The health app configuration
* @return Success or failure.
+ *
+ * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+ * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+ * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+ * {@link BluetoothDevice#createL2capChannel(int)}
*/
+ @Deprecated
public boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) {
- boolean result = false;
- final IBluetoothHealth service = mService;
- if (service != null && isEnabled() && config != null) {
- try {
- result = service.unregisterAppConfiguration(config);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
- Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
- }
-
- return result;
+ Log.e(TAG, "unregisterAppConfiguration(): BluetoothHealth is deprecated");
+ return false;
}
/**
@@ -228,49 +158,16 @@
* @param config The application configuration which has been registered using {@link
* #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) }
* @return If true, the callback associated with the application config will be called.
+ *
+ * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+ * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+ * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+ * {@link BluetoothDevice#createL2capChannel(int)}
*/
+ @Deprecated
public boolean connectChannelToSource(BluetoothDevice device,
BluetoothHealthAppConfiguration config) {
- final IBluetoothHealth service = mService;
- if (service != null && isEnabled() && isValidDevice(device) && config != null) {
- try {
- return service.connectChannelToSource(device, config);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
- Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
- }
- return false;
- }
-
- /**
- * Connect to a health device which has the {@link #SINK_ROLE}.
- * This is an asynchronous call. If this function returns true, the callback
- * associated with the application configuration will be called.
- *
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
- * @param device The remote Bluetooth device.
- * @param config The application configuration which has been registered using {@link
- * #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) }
- * @return If true, the callback associated with the application config will be called.
- * @hide
- */
- public boolean connectChannelToSink(BluetoothDevice device,
- BluetoothHealthAppConfiguration config, int channelType) {
- final IBluetoothHealth service = mService;
- if (service != null && isEnabled() && isValidDevice(device) && config != null) {
- try {
- return service.connectChannelToSink(device, config, channelType);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
- Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
- }
+ Log.e(TAG, "connectChannelToSource(): BluetoothHealth is deprecated");
return false;
}
@@ -286,20 +183,16 @@
* #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) }
* @param channelId The channel id associated with the channel
* @return If true, the callback associated with the application config will be called.
+ *
+ * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+ * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+ * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+ * {@link BluetoothDevice#createL2capChannel(int)}
*/
+ @Deprecated
public boolean disconnectChannel(BluetoothDevice device,
BluetoothHealthAppConfiguration config, int channelId) {
- final IBluetoothHealth service = mService;
- if (service != null && isEnabled() && isValidDevice(device) && config != null) {
- try {
- return service.disconnectChannel(device, config, channelId);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
- Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
- }
+ Log.e(TAG, "disconnectChannel(): BluetoothHealth is deprecated");
return false;
}
@@ -315,20 +208,16 @@
* @param device The remote Bluetooth health device
* @param config The application configuration
* @return null on failure, ParcelFileDescriptor on success.
+ *
+ * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+ * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+ * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+ * {@link BluetoothDevice#createL2capChannel(int)}
*/
+ @Deprecated
public ParcelFileDescriptor getMainChannelFd(BluetoothDevice device,
BluetoothHealthAppConfiguration config) {
- final IBluetoothHealth service = mService;
- if (service != null && isEnabled() && isValidDevice(device) && config != null) {
- try {
- return service.getMainChannelFd(device, config);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
- Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
- }
+ Log.e(TAG, "getMainChannelFd(): BluetoothHealth is deprecated");
return null;
}
@@ -348,17 +237,7 @@
*/
@Override
public int getConnectionState(BluetoothDevice device) {
- final IBluetoothHealth service = mService;
- if (service != null && isEnabled() && isValidDevice(device)) {
- try {
- return service.getHealthDeviceConnectionState(device);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
- Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
- }
+ Log.e(TAG, "getConnectionState(): BluetoothHealth is deprecated");
return STATE_DISCONNECTED;
}
@@ -378,17 +257,8 @@
*/
@Override
public List<BluetoothDevice> getConnectedDevices() {
- final IBluetoothHealth service = mService;
- if (service != null && isEnabled()) {
- try {
- return service.getConnectedHealthDevices();
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return new ArrayList<BluetoothDevice>();
- }
- }
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return new ArrayList<BluetoothDevice>();
+ Log.e(TAG, "getConnectedDevices(): BluetoothHealth is deprecated");
+ return new ArrayList<>();
}
/**
@@ -410,163 +280,81 @@
*/
@Override
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
- final IBluetoothHealth service = mService;
- if (service != null && isEnabled()) {
- try {
- return service.getHealthDevicesMatchingConnectionStates(states);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return new ArrayList<BluetoothDevice>();
- }
- }
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return new ArrayList<BluetoothDevice>();
+ Log.e(TAG, "getDevicesMatchingConnectionStates(): BluetoothHealth is deprecated");
+ return new ArrayList<>();
}
- private static class BluetoothHealthCallbackWrapper extends IBluetoothHealthCallback.Stub {
- private BluetoothHealthCallback mCallback;
-
- public BluetoothHealthCallbackWrapper(BluetoothHealthCallback callback) {
- mCallback = callback;
- }
-
- @Override
- public void onHealthAppConfigurationStatusChange(BluetoothHealthAppConfiguration config,
- int status) {
- mCallback.onHealthAppConfigurationStatusChange(config, status);
- }
-
- @Override
- public void onHealthChannelStateChange(BluetoothHealthAppConfiguration config,
- BluetoothDevice device, int prevState, int newState,
- ParcelFileDescriptor fd, int channelId) {
- mCallback.onHealthChannelStateChange(config, device, prevState, newState, fd,
- channelId);
- }
- }
-
- /** Health Channel Connection State - Disconnected */
+ /** Health Channel Connection State - Disconnected
+ *
+ * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+ * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+ * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+ * {@link BluetoothDevice#createL2capChannel(int)}
+ */
+ @Deprecated
public static final int STATE_CHANNEL_DISCONNECTED = 0;
- /** Health Channel Connection State - Connecting */
+ /** Health Channel Connection State - Connecting
+ *
+ * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+ * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+ * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+ * {@link BluetoothDevice#createL2capChannel(int)}
+ */
+ @Deprecated
public static final int STATE_CHANNEL_CONNECTING = 1;
- /** Health Channel Connection State - Connected */
+ /** Health Channel Connection State - Connected
+ *
+ * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+ * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+ * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+ * {@link BluetoothDevice#createL2capChannel(int)}
+ */
+ @Deprecated
public static final int STATE_CHANNEL_CONNECTED = 2;
- /** Health Channel Connection State - Disconnecting */
+ /** Health Channel Connection State - Disconnecting
+ *
+ * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+ * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+ * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+ * {@link BluetoothDevice#createL2capChannel(int)}
+ */
+ @Deprecated
public static final int STATE_CHANNEL_DISCONNECTING = 3;
- /** Health App Configuration registration success */
- public static final int APP_CONFIG_REGISTRATION_SUCCESS = 0;
- /** Health App Configuration registration failure */
- public static final int APP_CONFIG_REGISTRATION_FAILURE = 1;
- /** Health App Configuration un-registration success */
- public static final int APP_CONFIG_UNREGISTRATION_SUCCESS = 2;
- /** Health App Configuration un-registration failure */
- public static final int APP_CONFIG_UNREGISTRATION_FAILURE = 3;
-
- private Context mContext;
- private ServiceListener mServiceListener;
- private volatile IBluetoothHealth mService;
- BluetoothAdapter mAdapter;
-
- /**
- * Create a BluetoothHealth proxy object.
+ /** Health App Configuration registration success
+ *
+ * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+ * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+ * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+ * {@link BluetoothDevice#createL2capChannel(int)}
*/
- /*package*/ BluetoothHealth(Context context, ServiceListener l) {
- mContext = context;
- mServiceListener = l;
- mAdapter = BluetoothAdapter.getDefaultAdapter();
- IBluetoothManager mgr = mAdapter.getBluetoothManager();
- if (mgr != null) {
- try {
- mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
- } catch (RemoteException e) {
- Log.e(TAG, "", e);
- }
- }
-
- doBind();
- }
-
- boolean doBind() {
- Intent intent = new Intent(IBluetoothHealth.class.getName());
- ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
- intent.setComponent(comp);
- if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
- UserHandle.CURRENT_OR_SELF)) {
- Log.e(TAG, "Could not bind to Bluetooth Health Service with " + intent);
- return false;
- }
- return true;
- }
-
- /*package*/ void close() {
- if (VDBG) log("close()");
- IBluetoothManager mgr = mAdapter.getBluetoothManager();
- if (mgr != null) {
- try {
- mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
- } catch (Exception e) {
- Log.e(TAG, "", e);
- }
- }
-
- synchronized (mConnection) {
- if (mService != null) {
- try {
- mService = null;
- mContext.unbindService(mConnection);
- } catch (Exception re) {
- Log.e(TAG, "", re);
- }
- }
- }
- mServiceListener = null;
- }
-
- private final ServiceConnection mConnection = new ServiceConnection() {
- public void onServiceConnected(ComponentName className, IBinder service) {
- if (DBG) Log.d(TAG, "Proxy object connected");
- mService = IBluetoothHealth.Stub.asInterface(Binder.allowBlocking(service));
-
- if (mServiceListener != null) {
- mServiceListener.onServiceConnected(BluetoothProfile.HEALTH, BluetoothHealth.this);
- }
- }
-
- public void onServiceDisconnected(ComponentName className) {
- if (DBG) Log.d(TAG, "Proxy object disconnected");
- mService = null;
- if (mServiceListener != null) {
- mServiceListener.onServiceDisconnected(BluetoothProfile.HEALTH);
- }
- }
- };
-
- private boolean isEnabled() {
- BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-
- if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) return true;
- log("Bluetooth is Not enabled");
- return false;
- }
-
- private static boolean isValidDevice(BluetoothDevice device) {
- return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
- }
-
- private boolean checkAppParam(String name, int role, int channelType,
- BluetoothHealthCallback callback) {
- if (name == null || (role != SOURCE_ROLE && role != SINK_ROLE)
- || (channelType != CHANNEL_TYPE_RELIABLE && channelType != CHANNEL_TYPE_STREAMING
- && channelType != CHANNEL_TYPE_ANY)
- || callback == null) {
- return false;
- }
- if (role == SOURCE_ROLE && channelType == CHANNEL_TYPE_ANY) return false;
- return true;
- }
-
- private static void log(String msg) {
- Log.d(TAG, msg);
- }
+ @Deprecated
+ public static final int APP_CONFIG_REGISTRATION_SUCCESS = 0;
+ /** Health App Configuration registration failure
+ *
+ * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+ * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+ * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+ * {@link BluetoothDevice#createL2capChannel(int)}
+ */
+ @Deprecated
+ public static final int APP_CONFIG_REGISTRATION_FAILURE = 1;
+ /** Health App Configuration un-registration success
+ *
+ * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+ * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+ * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+ * {@link BluetoothDevice#createL2capChannel(int)}
+ */
+ @Deprecated
+ public static final int APP_CONFIG_UNREGISTRATION_SUCCESS = 2;
+ /** Health App Configuration un-registration failure
+ *
+ * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+ * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+ * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+ * {@link BluetoothDevice#createL2capChannel(int)}
+ */
+ @Deprecated
+ public static final int APP_CONFIG_UNREGISTRATION_FAILURE = 3;
}
diff --git a/core/java/android/bluetooth/BluetoothHealthAppConfiguration.java b/core/java/android/bluetooth/BluetoothHealthAppConfiguration.java
index 7c9db6f..9788bbf 100644
--- a/core/java/android/bluetooth/BluetoothHealthAppConfiguration.java
+++ b/core/java/android/bluetooth/BluetoothHealthAppConfiguration.java
@@ -25,72 +25,14 @@
* the {@link BluetoothHealth} class. This class represents an application configuration
* that the Bluetooth Health third party application will register to communicate with the
* remote Bluetooth health device.
+ *
+ * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+ * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+ * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+ * {@link BluetoothDevice#createL2capChannel(int)}
*/
+@Deprecated
public final class BluetoothHealthAppConfiguration implements Parcelable {
- private final String mName;
- private final int mDataType;
- private final int mRole;
- private final int mChannelType;
-
- /**
- * Constructor to register the SINK role
- *
- * @param name Friendly name associated with the application configuration
- * @param dataType Data Type of the remote Bluetooth Health device
- * @hide
- */
- BluetoothHealthAppConfiguration(String name, int dataType) {
- mName = name;
- mDataType = dataType;
- mRole = BluetoothHealth.SINK_ROLE;
- mChannelType = BluetoothHealth.CHANNEL_TYPE_ANY;
- }
-
- /**
- * Constructor to register the application configuration.
- *
- * @param name Friendly name associated with the application configuration
- * @param dataType Data Type of the remote Bluetooth Health device
- * @param role {@link BluetoothHealth#SOURCE_ROLE} or {@link BluetoothHealth#SINK_ROLE}
- * @hide
- */
- BluetoothHealthAppConfiguration(String name, int dataType, int role, int
- channelType) {
- mName = name;
- mDataType = dataType;
- mRole = role;
- mChannelType = channelType;
- }
-
- @Override
- public boolean equals(Object o) {
- if (o instanceof BluetoothHealthAppConfiguration) {
- BluetoothHealthAppConfiguration config = (BluetoothHealthAppConfiguration) o;
-
- if (mName == null) return false;
-
- return mName.equals(config.getName()) && mDataType == config.getDataType()
- && mRole == config.getRole() && mChannelType == config.getChannelType();
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- int result = 17;
- result = 31 * result + (mName != null ? mName.hashCode() : 0);
- result = 31 * result + mDataType;
- result = 31 * result + mRole;
- result = 31 * result + mChannelType;
- return result;
- }
-
- @Override
- public String toString() {
- return "BluetoothHealthAppConfiguration [mName = " + mName + ",mDataType = " + mDataType
- + ", mRole = " + mRole + ",mChannelType = " + mChannelType + "]";
- }
-
@Override
public int describeContents() {
return 0;
@@ -100,50 +42,59 @@
* Return the data type associated with this application configuration.
*
* @return dataType
+ *
+ * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+ * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+ * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+ * {@link BluetoothDevice#createL2capChannel(int)}
*/
+ @Deprecated
public int getDataType() {
- return mDataType;
+ return 0;
}
/**
* Return the name of the application configuration.
*
* @return String name
+ *
+ * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+ * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+ * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+ * {@link BluetoothDevice#createL2capChannel(int)}
*/
+ @Deprecated
public String getName() {
- return mName;
+ return null;
}
/**
* Return the role associated with this application configuration.
*
* @return One of {@link BluetoothHealth#SOURCE_ROLE} or {@link BluetoothHealth#SINK_ROLE}
+ *
+ * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+ * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+ * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+ * {@link BluetoothDevice#createL2capChannel(int)}
*/
+ @Deprecated
public int getRole() {
- return mRole;
+ return 0;
}
/**
- * Return the channel type associated with this application configuration.
- *
- * @return One of {@link BluetoothHealth#CHANNEL_TYPE_RELIABLE} or {@link
- * BluetoothHealth#CHANNEL_TYPE_STREAMING} or {@link BluetoothHealth#CHANNEL_TYPE_ANY}.
- * @hide
+ * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+ * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+ * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+ * {@link BluetoothDevice#createL2capChannel(int)}
*/
- public int getChannelType() {
- return mChannelType;
- }
-
+ @Deprecated
public static final Parcelable.Creator<BluetoothHealthAppConfiguration> CREATOR =
new Parcelable.Creator<BluetoothHealthAppConfiguration>() {
@Override
public BluetoothHealthAppConfiguration createFromParcel(Parcel in) {
- String name = in.readString();
- int type = in.readInt();
- int role = in.readInt();
- int channelType = in.readInt();
- return new BluetoothHealthAppConfiguration(name, type, role,
- channelType);
+ return new BluetoothHealthAppConfiguration();
}
@Override
@@ -153,10 +104,5 @@
};
@Override
- public void writeToParcel(Parcel out, int flags) {
- out.writeString(mName);
- out.writeInt(mDataType);
- out.writeInt(mRole);
- out.writeInt(mChannelType);
- }
+ public void writeToParcel(Parcel out, int flags) {}
}
diff --git a/core/java/android/bluetooth/BluetoothHealthCallback.java b/core/java/android/bluetooth/BluetoothHealthCallback.java
index 4023485..4769212 100644
--- a/core/java/android/bluetooth/BluetoothHealthCallback.java
+++ b/core/java/android/bluetooth/BluetoothHealthCallback.java
@@ -23,7 +23,13 @@
/**
* This abstract class is used to implement {@link BluetoothHealth} callbacks.
+ *
+ * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+ * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+ * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+ * {@link BluetoothDevice#createL2capChannel(int)}
*/
+@Deprecated
public abstract class BluetoothHealthCallback {
private static final String TAG = "BluetoothHealthCallback";
@@ -38,8 +44,14 @@
* BluetoothHealth#APP_CONFIG_REGISTRATION_FAILURE} or
* {@link BluetoothHealth#APP_CONFIG_UNREGISTRATION_SUCCESS}
* or {@link BluetoothHealth#APP_CONFIG_UNREGISTRATION_FAILURE}
+ *
+ * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+ * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+ * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+ * {@link BluetoothDevice#createL2capChannel(int)}
*/
@BinderThread
+ @Deprecated
public void onHealthAppConfigurationStatusChange(BluetoothHealthAppConfiguration config,
int status) {
Log.d(TAG, "onHealthAppConfigurationStatusChange: " + config + "Status: " + status);
@@ -58,8 +70,14 @@
* @param fd The Parcel File Descriptor when the channel state is connected.
* @param channelId The id associated with the channel. This id will be used in future calls
* like when disconnecting the channel.
+ *
+ * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+ * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+ * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+ * {@link BluetoothDevice#createL2capChannel(int)}
*/
@BinderThread
+ @Deprecated
public void onHealthChannelStateChange(BluetoothHealthAppConfiguration config,
BluetoothDevice device, int prevState, int newState, ParcelFileDescriptor fd,
int channelId) {
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index 3c3a01b..3c87c73 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -72,7 +72,13 @@
/**
* Health Profile
+ *
+ * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+ * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+ * {@link BluetoothAdapter#listenUsingL2capChannel()}, or
+ * {@link BluetoothDevice#createL2capChannel(int)}
*/
+ @Deprecated
int HEALTH = 3;
/**
@@ -269,9 +275,8 @@
* Called to notify the client when the proxy object has been
* connected to the service.
*
- * @param profile - One of {@link #HEALTH}, {@link #HEADSET} or {@link #A2DP}
- * @param proxy - One of {@link BluetoothHealth}, {@link BluetoothHeadset} or {@link
- * BluetoothA2dp}
+ * @param profile - One of {@link #HEADSET} or {@link #A2DP}
+ * @param proxy - One of {@link BluetoothHeadset} or {@link BluetoothA2dp}
*/
public void onServiceConnected(int profile, BluetoothProfile proxy);
@@ -279,7 +284,7 @@
* Called to notify the client that this proxy object has been
* disconnected from the service.
*
- * @param profile - One of {@link #HEALTH}, {@link #HEADSET} or {@link #A2DP}
+ * @param profile - One of {@link #HEADSET} or {@link #A2DP}
*/
public void onServiceDisconnected(int profile);
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index eb7be6f..cefc700 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -337,6 +337,15 @@
public static final int BIND_ADJUST_BELOW_PERCEPTIBLE = 0x0100;
/**
+ * @hide Flag for {@link #bindService}: the service being bound to represents a
+ * protected system component, so must have association restrictions applied to it.
+ * That is, a system config must have one or more allow-association tags limiting
+ * which packages it can interact with. If it does not have any such association
+ * restrictions, a default empty set will be created.
+ */
+ public static final int BIND_RESTRICT_ASSOCIATIONS = 0x00200000;
+
+ /**
* @hide Flag for {@link #bindService}: allows binding to a service provided
* by an instant app. Note that the caller may not have access to the instant
* app providing the service which is a violation of the instant app sandbox.
@@ -3093,6 +3102,7 @@
VIBRATOR_SERVICE,
//@hide: STATUS_BAR_SERVICE,
CONNECTIVITY_SERVICE,
+ //@hide: IP_MEMORY_STORE_SERVICE,
IPSEC_SERVICE,
//@hide: UPDATE_LOCK_SERVICE,
//@hide: NETWORKMANAGEMENT_SERVICE,
@@ -3630,6 +3640,14 @@
/**
* Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.net.IpMemoryStore} to store and read information about
+ * known networks.
+ * @hide
+ */
+ public static final String IP_MEMORY_STORE_SERVICE = "ipmemorystore";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
* {@link android.net.IpSecManager} for encrypting Sockets or Networks with
* IPSec.
*
@@ -3975,6 +3993,24 @@
public static final String CONTENT_CAPTURE_MANAGER_SERVICE = "content_capture";
/**
+ * Used for getting content selections and classifications for task snapshots.
+ *
+ * @hide
+ * @see #getSystemService(String)
+ */
+ @SystemApi
+ public static final String CONTENT_SUGGESTIONS_SERVICE = "content_suggestions";
+
+ /**
+ * Official published name of the app prediction service.
+ *
+ * @hide
+ * @see #getSystemService(String)
+ */
+ @SystemApi
+ public static final String APP_PREDICTION_SERVICE = "app_prediction";
+
+ /**
* Use with {@link #getSystemService(String)} to access the
* {@link com.android.server.voiceinteraction.SoundTriggerService}.
*
@@ -4427,6 +4463,16 @@
public static final String STATS_MANAGER = "stats";
/**
+ * Service to capture a bugreport.
+ * @see #getSystemService(String)
+ * @see android.os.BugreportManager
+ * @hide
+ */
+ // TODO: Expose API when the implementation is more complete.
+ // @SystemApi
+ public static final String BUGREPORT_SERVICE = "bugreport";
+
+ /**
* Use with {@link #getSystemService(String)} to retrieve a {@link
* android.content.om.OverlayManager} for managing overlay packages.
*
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 7b3497b..de810e8 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -816,6 +816,28 @@
= "android.intent.action.SHOW_APP_INFO";
/**
+ * Activity Action: Start an activity to show the app's detailed usage information for
+ * permission protected data.
+ *
+ * The Intent contains an extra {@link #EXTRA_PERMISSION_USAGE_PERMISSIONS} that is of
+ * type {@code String[]} and contains the specific permissions to show information for.
+ *
+ * Apps should handle this intent if they want to provide more information about permission
+ * usage to users beyond the information provided in the manifest.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_PERMISSION_USAGE_DETAILS =
+ "android.intent.action.PERMISSION_USAGE_DETAILS";
+
+ /**
+ * The name of the extra used to contain the permissions in
+ * {@link #ACTION_PERMISSION_USAGE_DETAILS}.
+ * @see #ACTION_PERMISSION_USAGE_DETAILS
+ */
+ public static final String EXTRA_PERMISSION_USAGE_PERMISSIONS =
+ "android.intent.extra.PERMISSION_USAGE_PERMISSIONS";
+
+ /**
* Represents a shortcut/live folder icon resource.
*
* @see Intent#ACTION_CREATE_SHORTCUT
@@ -1811,6 +1833,23 @@
"android.intent.action.REVIEW_PERMISSIONS";
/**
+ * Activity action: Launch UI to manage special app accesses.
+ * <p>
+ * Input: Nothing.
+ * </p>
+ * <p>
+ * Output: Nothing.
+ * </p>
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS)
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ @SystemApi
+ public static final String ACTION_MANAGE_SPECIAL_APP_ACCESSES =
+ "android.intent.action.MANAGE_SPECIAL_APP_ACCESSES";
+
+ /**
* Intent extra: A callback for reporting remote result as a bundle.
* <p>
* Type: IRemoteCallback
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 2978058..576466f 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1866,7 +1866,15 @@
return (privateFlags & ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE) != 0;
}
- /** @hide */
+ /**
+ * Check whether the application is encryption aware.
+ *
+ * @see #isDirectBootAware()
+ * @see #isPartiallyDirectBootAware()
+ *
+ * @hide
+ */
+ @SystemApi
public boolean isEncryptionAware() {
return isDirectBootAware() || isPartiallyDirectBootAware();
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 2b266b7..c984651 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1621,7 +1621,7 @@
}
final AttributeSet attrs = parser;
- return parseApkLite(apkPath, parser, attrs, signingDetails);
+ return parseApkLite(apkPath, parser, attrs, signingDetails, flags);
} catch (XmlPullParserException | IOException | RuntimeException e) {
Slog.w(TAG, "Failed to parse " + apkPath, e);
@@ -1708,7 +1708,7 @@
}
private static ApkLite parseApkLite(String codePath, XmlPullParser parser, AttributeSet attrs,
- SigningDetails signingDetails)
+ SigningDetails signingDetails, int flags)
throws IOException, XmlPullParserException, PackageParserException {
final Pair<String, String> packageSplit = parsePackageSplitNames(parser, attrs);
@@ -1716,11 +1716,12 @@
int versionCode = 0;
int versionCodeMajor = 0;
int revisionCode = 0;
+ int targetSdkVersion = 0;
boolean coreApp = false;
boolean debuggable = false;
boolean multiArch = false;
boolean use32bitAbi = false;
- boolean extractNativeLibs = true;
+ Boolean extractNativeLibsProvided = null;
boolean isolatedSplits = false;
boolean isFeatureSplit = false;
boolean isSplitRequired = false;
@@ -1785,7 +1786,8 @@
use32bitAbi = attrs.getAttributeBooleanValue(i, false);
}
if ("extractNativeLibs".equals(attr)) {
- extractNativeLibs = attrs.getAttributeBooleanValue(i, true);
+ extractNativeLibsProvided = Boolean.valueOf(
+ attrs.getAttributeBooleanValue(i, true));
}
if ("preferCodeIntegrity".equals(attr)) {
preferCodeIntegrity = attrs.getAttributeBooleanValue(i, false);
@@ -1803,9 +1805,51 @@
PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
"<uses-split> tag requires 'android:name' attribute");
}
+ } else if (TAG_USES_SDK.equals(parser.getName())) {
+ final String[] errorMsg = new String[1];
+ Pair<Integer, Integer> versions = deriveSdkVersions(new AbstractVersionsAccessor() {
+ @Override public String getMinSdkVersionCode() {
+ return getAttributeAsString("minSdkVersion");
+ }
+
+ @Override public int getMinSdkVersion() {
+ return getAttributeAsInt("minSdkVersion");
+ }
+
+ @Override public String getTargetSdkVersionCode() {
+ return getAttributeAsString("targetSdkVersion");
+ }
+
+ @Override public int getTargetSdkVersion() {
+ return getAttributeAsInt("targetSdkVersion");
+ }
+
+ private String getAttributeAsString(String name) {
+ return attrs.getAttributeValue(ANDROID_RESOURCES, name);
+ }
+
+ private int getAttributeAsInt(String name) {
+ try {
+ return attrs.getAttributeIntValue(ANDROID_RESOURCES, name, -1);
+ } catch (NumberFormatException e) {
+ return -1;
+ }
+ }
+ }, flags, errorMsg);
+
+ if (versions == null) {
+ throw new PackageParserException(
+ PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, errorMsg[0]);
+ }
+
+ targetSdkVersion = versions.second;
}
}
+ final boolean extractNativeLibsDefault = targetSdkVersion < Build.VERSION_CODES.Q;
+ final boolean extractNativeLibs = (extractNativeLibsProvided != null)
+ ? extractNativeLibsProvided : extractNativeLibsDefault;
+
if (preferCodeIntegrity && extractNativeLibs) {
throw new PackageParserException(
PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
@@ -2215,65 +2259,60 @@
} else if (tagName.equals(TAG_USES_SDK)) {
if (SDK_VERSION > 0) {
- sa = res.obtainAttributes(parser,
- com.android.internal.R.styleable.AndroidManifestUsesSdk);
+ sa = res.obtainAttributes(parser, R.styleable.AndroidManifestUsesSdk);
+ final TypedArray saFinal = sa;
+ Pair<Integer, Integer> versions = deriveSdkVersions(
+ new AbstractVersionsAccessor() {
+ @Override public String getMinSdkVersionCode() {
+ return getAttributeAsString(
+ R.styleable.AndroidManifestUsesSdk_minSdkVersion);
+ }
- int minVers = 1;
- String minCode = null;
- int targetVers = 0;
- String targetCode = null;
+ @Override public int getMinSdkVersion() {
+ return getAttributeAsInt(
+ R.styleable.AndroidManifestUsesSdk_minSdkVersion);
+ }
- TypedValue val = sa.peekValue(
- com.android.internal.R.styleable.AndroidManifestUsesSdk_minSdkVersion);
- if (val != null) {
- if (val.type == TypedValue.TYPE_STRING && val.string != null) {
- minCode = val.string.toString();
- } else {
- // If it's not a string, it's an integer.
- minVers = val.data;
- }
+ @Override public String getTargetSdkVersionCode() {
+ return getAttributeAsString(
+ R.styleable.AndroidManifestUsesSdk_targetSdkVersion);
+ }
+
+ @Override public int getTargetSdkVersion() {
+ return getAttributeAsInt(
+ R.styleable.AndroidManifestUsesSdk_targetSdkVersion);
+ }
+
+ private String getAttributeAsString(int index) {
+ TypedValue val = saFinal.peekValue(index);
+ if (val != null && val.type == TypedValue.TYPE_STRING
+ && val.string != null) {
+ return val.string.toString();
+ }
+ return null;
+ }
+
+ private int getAttributeAsInt(int index) {
+ TypedValue val = saFinal.peekValue(index);
+ if (val != null && val.type != TypedValue.TYPE_STRING) {
+ // If it's not a string, it's an integer.
+ return val.data;
+ }
+ return -1;
+ }
+ }, flags, outError);
+
+ if (versions == null) {
+ mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK;
+ return null;
}
- val = sa.peekValue(
- com.android.internal.R.styleable.AndroidManifestUsesSdk_targetSdkVersion);
- if (val != null) {
- if (val.type == TypedValue.TYPE_STRING && val.string != null) {
- targetCode = val.string.toString();
- if (minCode == null) {
- minCode = targetCode;
- }
- } else {
- // If it's not a string, it's an integer.
- targetVers = val.data;
- }
- } else {
- targetVers = minVers;
- targetCode = minCode;
- }
+ pkg.applicationInfo.minSdkVersion = versions.first;
+ pkg.applicationInfo.targetSdkVersion = versions.second;
sa.recycle();
-
- final int minSdkVersion = PackageParser.computeMinSdkVersion(minVers, minCode,
- SDK_VERSION, SDK_CODENAMES, outError);
- if (minSdkVersion < 0) {
- mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK;
- return null;
- }
-
- boolean defaultToCurrentDevBranch = (flags & PARSE_FORCE_SDK) != 0;
- final int targetSdkVersion = PackageParser.computeTargetSdkVersion(targetVers,
- targetCode, SDK_CODENAMES, outError, defaultToCurrentDevBranch);
- if (targetSdkVersion < 0) {
- mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK;
- return null;
- }
-
- pkg.applicationInfo.minSdkVersion = minSdkVersion;
- pkg.applicationInfo.targetSdkVersion = targetSdkVersion;
}
-
XmlUtils.skipCurrentTag(parser);
-
} else if (tagName.equals(TAG_SUPPORT_SCREENS)) {
sa = res.obtainAttributes(parser,
com.android.internal.R.styleable.AndroidManifestSupportsScreens);
@@ -2675,6 +2714,67 @@
return -1;
}
+ private interface AbstractVersionsAccessor {
+ /** Returns minimum SDK version code string, or null if absent. */
+ String getMinSdkVersionCode();
+
+ /** Returns minimum SDK version code, or -1 if absent. */
+ int getMinSdkVersion();
+
+ /** Returns target SDK version code string, or null if absent. */
+ String getTargetSdkVersionCode();
+
+ /** Returns target SDK version code, or -1 if absent. */
+ int getTargetSdkVersion();
+ }
+
+ private static @Nullable Pair<Integer, Integer> deriveSdkVersions(
+ @NonNull AbstractVersionsAccessor accessor, int flags, String[] outError) {
+ int minVers = 1;
+ String minCode = null;
+ int targetVers = 0;
+ String targetCode = null;
+
+ String code = accessor.getMinSdkVersionCode();
+ int version = accessor.getMinSdkVersion();
+ // Check integer first since code is almost never a null string (e.g. "28").
+ if (version >= 0) {
+ minVers = version;
+ } else if (code != null) {
+ minCode = code;
+ }
+
+ code = accessor.getTargetSdkVersionCode();
+ version = accessor.getTargetSdkVersion();
+ // Check integer first since code is almost never a null string (e.g. "28").
+ if (version >= 0) {
+ targetVers = version;
+ } else if (code != null) {
+ targetCode = code;
+ if (minCode == null) {
+ minCode = targetCode;
+ }
+ } else {
+ targetVers = minVers;
+ targetCode = minCode;
+ }
+
+ final int minSdkVersion = computeMinSdkVersion(minVers, minCode,
+ SDK_VERSION, SDK_CODENAMES, outError);
+ if (minSdkVersion < 0) {
+ return null;
+ }
+
+ boolean defaultToCurrentDevBranch = (flags & PARSE_FORCE_SDK) != 0;
+ final int targetSdkVersion = computeTargetSdkVersion(targetVers,
+ targetCode, SDK_CODENAMES, outError, defaultToCurrentDevBranch);
+ if (targetSdkVersion < 0) {
+ return null;
+ }
+
+ return Pair.create(minSdkVersion, targetSdkVersion);
+ }
+
/**
* Computes the minSdkVersion to use at runtime. If the package is not
* compatible with this platform, populates {@code outError[0]} with an
diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java
index be6ed51..74dd08f 100644
--- a/core/java/android/content/pm/PackageUserState.java
+++ b/core/java/android/content/pm/PackageUserState.java
@@ -29,8 +29,11 @@
import android.annotation.UnsupportedAppUsage;
import android.os.BaseBundle;
+import android.os.Debug;
import android.os.PersistableBundle;
import android.util.ArraySet;
+import android.util.DebugUtils;
+import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
@@ -43,6 +46,9 @@
* @hide
*/
public class PackageUserState {
+ private static final boolean DEBUG = false;
+ private static final String LOG_TAG = "PackageUserState";
+
public long ceDataInode;
public boolean installed;
public boolean stopped;
@@ -132,12 +138,12 @@
final boolean isSystemApp = componentInfo.applicationInfo.isSystemApp();
final boolean matchUninstalled = (flags & PackageManager.MATCH_KNOWN_PACKAGES) != 0;
if (!isAvailable(flags)
- && !(isSystemApp && matchUninstalled)) return false;
- if (!isEnabled(componentInfo, flags)) return false;
+ && !(isSystemApp && matchUninstalled)) return reportIfDebug(false, flags);
+ if (!isEnabled(componentInfo, flags)) return reportIfDebug(false, flags);
if ((flags & MATCH_SYSTEM_ONLY) != 0) {
if (!isSystemApp) {
- return false;
+ return reportIfDebug(false, flags);
}
}
@@ -145,7 +151,16 @@
&& !componentInfo.directBootAware;
final boolean matchesAware = ((flags & MATCH_DIRECT_BOOT_AWARE) != 0)
&& componentInfo.directBootAware;
- return matchesUnaware || matchesAware;
+ return reportIfDebug(matchesUnaware || matchesAware, flags);
+ }
+
+ private boolean reportIfDebug(boolean result, int flags) {
+ if (DEBUG && !result) {
+ Slog.i(LOG_TAG, "No match!; flags: "
+ + DebugUtils.flagsToString(PackageManager.class, "MATCH_", flags) + " "
+ + Debug.getCaller());
+ }
+ return result;
}
/**
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index a8c3b88..6e519c1 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -475,6 +475,14 @@
final List<ResolveInfo> resolveInfos = queryIntentServices(userId);
for (ResolveInfo resolveInfo : resolveInfos) {
try {
+ // if this package is not one of those changedUids, we don't need to scan it,
+ // since nothing in it changed, so save a call to parseServiceInfo, which
+ // can cause a large amount of the package apk to be loaded into memory.
+ // if this is the initial scan, changedUids will be null, and containsUid will
+ // trivially return true, and will call parseServiceInfo
+ if (!containsUid(changedUids, resolveInfo.serviceInfo.applicationInfo.uid)) {
+ continue;
+ }
ServiceInfo<V> info = parseServiceInfo(resolveInfo);
if (info == null) {
Log.w(TAG, "Unable to load service info " + resolveInfo.toString());
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 799f8e5..536a1b7 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -794,6 +794,7 @@
* {@link ActivityInfo#CONFIG_ASSETS_PATHS}.
* @hide
*/
+ @TestApi
public int assetsSeq;
/**
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index b238d77..f652f85 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -73,6 +73,10 @@
* @hide
*/
public static final String KEY_NEGATIVE_TEXT = "negative_text";
+ /**
+ * @hide
+ */
+ public static final String KEY_REQUIRE_CONFIRMATION = "require_confirmation";
/**
* Error/help message will show for this amount of time.
@@ -215,6 +219,30 @@
}
/**
+ * Optional: A hint to the system to require user confirmation after a biometric has been
+ * authenticated. For example, implicit modalities like Face and Iris authentication are
+ * passive, meaning they don't require an explicit user action to complete. When set to
+ * 'false', the user action (e.g. pressing a button) will not be required. BiometricPrompt
+ * will require confirmation by default.
+ *
+ * A typical use case for not requiring confirmation would be for low-risk transactions,
+ * such as re-authenticating a recently authenticated application. A typical use case for
+ * requiring confirmation would be for authorizing a purchase.
+ *
+ * Note that this is a hint to the system. The system may choose to ignore the flag. For
+ * example, if the user disables implicit authentication in Settings, or if it does not
+ * apply to a modality (e.g. Fingerprint). When ignored, the system will default to
+ * requiring confirmation.
+ *
+ * @param requireConfirmation
+ * @hide
+ */
+ public Builder setRequireConfirmation(boolean requireConfirmation) {
+ mBundle.putBoolean(KEY_REQUIRE_CONFIRMATION, requireConfirmation);
+ return this;
+ }
+
+ /**
* Creates a {@link BiometricPrompt}.
* @return a {@link BiometricPrompt}
* @throws IllegalArgumentException if any of the required fields are not set.
diff --git a/core/java/android/hardware/display/ColorDisplayManager.java b/core/java/android/hardware/display/ColorDisplayManager.java
index dd782ec..0cf2d18 100644
--- a/core/java/android/hardware/display/ColorDisplayManager.java
+++ b/core/java/android/hardware/display/ColorDisplayManager.java
@@ -16,7 +16,9 @@
package android.hardware.display;
+import android.Manifest;
import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.content.Context;
import android.os.IBinder;
@@ -31,6 +33,7 @@
*
* @hide
*/
+@SystemApi
@SystemService(Context.COLOR_DISPLAY_SERVICE)
public final class ColorDisplayManager {
@@ -48,13 +51,29 @@
*
* @hide
*/
- @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
+ @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
public boolean isDeviceColorManaged() {
return mManager.isDeviceColorManaged();
}
/**
+ * Set the level of color saturation to apply to the display.
+ *
+ * @param saturationLevel 0-100 (inclusive), where 100 is full saturation
+ * @return whether the saturation level change was applied successfully
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
+ public boolean setSaturationLevel(int saturationLevel) {
+ return mManager.setSaturationLevel(saturationLevel);
+ }
+
+ /**
* Returns {@code true} if Night Display is supported by the device.
+ *
+ * @hide
*/
public static boolean isNightDisplayAvailable(Context context) {
return context.getResources().getBoolean(R.bool.config_nightDisplayAvailable);
@@ -62,6 +81,8 @@
/**
* Returns {@code true} if display white balance is supported by the device.
+ *
+ * @hide
*/
public static boolean isDisplayWhiteBalanceAvailable(Context context) {
return context.getResources().getBoolean(R.bool.config_displayWhiteBalanceAvailable);
@@ -99,5 +120,13 @@
throw e.rethrowFromSystemServer();
}
}
+
+ boolean setSaturationLevel(int saturationLevel) {
+ try {
+ return mCdm.setSaturationLevel(saturationLevel);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
}
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 82e765d..44b653c 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -563,11 +563,16 @@
* 0 produces a grayscale image, 1 is normal.
*
* @hide
+ * @deprecated use {@link ColorDisplayManager#setSaturationLevel(int)}.
*/
@SystemApi
@RequiresPermission(Manifest.permission.CONTROL_DISPLAY_SATURATION)
public void setSaturationLevel(float level) {
- mGlobal.setSaturationLevel(level);
+ if (level < 0f || level > 1f) {
+ throw new IllegalArgumentException("Saturation level must be between 0 and 1");
+ }
+ final ColorDisplayManager cdm = mContext.getSystemService(ColorDisplayManager.class);
+ cdm.setSaturationLevel(Math.round(level * 100f));
}
/**
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 7304ab4..cda8498 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -394,17 +394,6 @@
}
}
- /**
- * Set the level of color saturation to apply to the display.
- */
- public void setSaturationLevel(float level) {
- try {
- mDm.setSaturationLevel(level);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
- }
-
public VirtualDisplay createVirtualDisplay(Context context, MediaProjection projection,
String name, int width, int height, int densityDpi, Surface surface, int flags,
VirtualDisplay.Callback callback, Handler handler, String uniqueId) {
diff --git a/core/java/android/hardware/display/IColorDisplayManager.aidl b/core/java/android/hardware/display/IColorDisplayManager.aidl
index f786589..81b82c6 100644
--- a/core/java/android/hardware/display/IColorDisplayManager.aidl
+++ b/core/java/android/hardware/display/IColorDisplayManager.aidl
@@ -19,4 +19,6 @@
/** @hide */
interface IColorDisplayManager {
boolean isDeviceColorManaged();
+
+ boolean setSaturationLevel(int saturationLevel);
}
\ No newline at end of file
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index b575997..2d81cdf 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -66,9 +66,6 @@
// Requires CONFIGURE_DISPLAY_COLOR_MODE
void requestColorMode(int displayId, int colorMode);
- // Requires CONTROL_DISPLAY_SATURATION
- void setSaturationLevel(float level);
-
// Requires CAPTURE_VIDEO_OUTPUT, CAPTURE_SECURE_VIDEO_OUTPUT, or an appropriate
// MediaProjection token for certain combinations of flags.
int createVirtualDisplay(in IVirtualDisplayCallback callback,
diff --git a/core/java/android/hardware/hdmi/HdmiAudioSystemClient.java b/core/java/android/hardware/hdmi/HdmiAudioSystemClient.java
index 9bebbd2..d382eb9 100644
--- a/core/java/android/hardware/hdmi/HdmiAudioSystemClient.java
+++ b/core/java/android/hardware/hdmi/HdmiAudioSystemClient.java
@@ -17,6 +17,7 @@
import android.annotation.Nullable;
import android.os.Handler;
+import android.os.Looper;
import android.os.RemoteException;
import com.android.internal.annotations.VisibleForTesting;
@@ -52,7 +53,7 @@
@VisibleForTesting(visibility = Visibility.PACKAGE)
public HdmiAudioSystemClient(IHdmiControlService service, @Nullable Handler handler) {
super(service);
- mHandler = handler == null ? new Handler() : handler;
+ mHandler = handler == null ? new Handler(Looper.getMainLooper()) : handler;
}
/** @hide */
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index 72a6ffe..b520d2c 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -388,6 +388,19 @@
}
/**
+ * Gets whether the system is in system audio mode.
+ *
+ * @hide
+ */
+ public boolean getSystemAudioMode() {
+ try {
+ return mService.getSystemAudioMode();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Listener used to get hotplug event from HDMI port.
*/
public interface HotplugEventListener {
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 61d5a91..abc00fe 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -16,6 +16,7 @@
package android.net;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
@@ -909,6 +910,7 @@
*/
@Deprecated
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @Nullable
public NetworkInfo getActiveNetworkInfo() {
try {
return mService.getActiveNetworkInfo();
@@ -928,6 +930,7 @@
* {@code null} if no default network is currently active
*/
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @Nullable
public Network getActiveNetwork() {
try {
return mService.getActiveNetwork();
@@ -949,6 +952,7 @@
* @hide
*/
@RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL)
+ @Nullable
public Network getActiveNetworkForUid(int uid) {
return getActiveNetworkForUid(uid, false);
}
@@ -1074,6 +1078,7 @@
*/
@Deprecated
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @Nullable
public NetworkInfo getNetworkInfo(int networkType) {
try {
return mService.getNetworkInfo(networkType);
@@ -1095,7 +1100,8 @@
*/
@Deprecated
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
- public NetworkInfo getNetworkInfo(Network network) {
+ @Nullable
+ public NetworkInfo getNetworkInfo(@Nullable Network network) {
return getNetworkInfoForUid(network, Process.myUid(), false);
}
@@ -1121,6 +1127,7 @@
*/
@Deprecated
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @NonNull
public NetworkInfo[] getAllNetworkInfo() {
try {
return mService.getAllNetworkInfo();
@@ -1156,6 +1163,7 @@
* @return an array of {@link Network} objects.
*/
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @NonNull
public Network[] getAllNetworks() {
try {
return mService.getAllNetworks();
@@ -1230,7 +1238,8 @@
* @return The {@link LinkProperties} for the network, or {@code null}.
*/
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
- public LinkProperties getLinkProperties(Network network) {
+ @Nullable
+ public LinkProperties getLinkProperties(@Nullable Network network) {
try {
return mService.getLinkProperties(network);
} catch (RemoteException e) {
@@ -1246,7 +1255,8 @@
* @return The {@link android.net.NetworkCapabilities} for the network, or {@code null}.
*/
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
- public NetworkCapabilities getNetworkCapabilities(Network network) {
+ @Nullable
+ public NetworkCapabilities getNetworkCapabilities(@Nullable Network network) {
try {
return mService.getNetworkCapabilities(network);
} catch (RemoteException e) {
@@ -2000,7 +2010,7 @@
*
* @param l Previously registered listener.
*/
- public void removeDefaultNetworkActiveListener(OnNetworkActiveListener l) {
+ public void removeDefaultNetworkActiveListener(@NonNull OnNetworkActiveListener l) {
INetworkActivityListener rl = mNetworkActivityListeners.get(l);
Preconditions.checkArgument(rl != null, "Listener was not registered.");
try {
@@ -2041,6 +2051,16 @@
return (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
}
+ /** @hide */
+ public NetworkRequest getDefaultRequest() {
+ try {
+ // This is not racy as the default request is final in ConnectivityService.
+ return mService.getDefaultRequest();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/* TODO: These permissions checks don't belong in client-side code. Move them to
* services.jar, possibly in com.android.server.net. */
@@ -2475,6 +2495,8 @@
public static final int TETHER_ERROR_IFACE_CFG_ERROR = 10;
/** {@hide} */
public static final int TETHER_ERROR_PROVISION_FAILED = 11;
+ /** {@hide} */
+ public static final int TETHER_ERROR_DHCPSERVER_ERROR = 12;
/**
* Get a more detailed error code after a Tethering or Untethering
@@ -2528,7 +2550,7 @@
* working and non-working connectivity.
*/
@Deprecated
- public void reportBadNetwork(Network network) {
+ public void reportBadNetwork(@Nullable Network network) {
printStackTrace();
try {
// One of these will be ignored because it matches system's current state.
@@ -2551,7 +2573,7 @@
* @param hasConnectivity {@code true} if the application was able to successfully access the
* Internet using {@code network} or {@code false} if not.
*/
- public void reportNetworkConnectivity(Network network, boolean hasConnectivity) {
+ public void reportNetworkConnectivity(@Nullable Network network, boolean hasConnectivity) {
printStackTrace();
try {
mService.reportNetworkConnectivity(network, hasConnectivity);
@@ -2625,6 +2647,7 @@
* @return the {@link ProxyInfo} for the current HTTP proxy, or {@code null} if no
* HTTP proxy is active.
*/
+ @Nullable
public ProxyInfo getDefaultProxy() {
return getProxyForNetwork(getBoundNetworkForProcess());
}
@@ -3156,8 +3179,9 @@
*
* @hide
*/
- public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback,
- int timeoutMs, int legacyType, Handler handler) {
+ public void requestNetwork(@NonNull NetworkRequest request,
+ @NonNull NetworkCallback networkCallback, int timeoutMs, int legacyType,
+ @NonNull Handler handler) {
CallbackHandler cbHandler = new CallbackHandler(handler);
NetworkCapabilities nc = request.networkCapabilities;
sendRequestForNetwork(nc, networkCallback, timeoutMs, REQUEST, legacyType, cbHandler);
@@ -3194,7 +3218,8 @@
* @throws IllegalArgumentException if {@code request} specifies any mutable
* {@code NetworkCapabilities}.
*/
- public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback) {
+ public void requestNetwork(@NonNull NetworkRequest request,
+ @NonNull NetworkCallback networkCallback) {
requestNetwork(request, networkCallback, getDefaultHandler());
}
@@ -3229,8 +3254,8 @@
* @throws IllegalArgumentException if {@code request} specifies any mutable
* {@code NetworkCapabilities}.
*/
- public void requestNetwork(
- NetworkRequest request, NetworkCallback networkCallback, Handler handler) {
+ public void requestNetwork(@NonNull NetworkRequest request,
+ @NonNull NetworkCallback networkCallback, @NonNull Handler handler) {
int legacyType = inferLegacyTypeForNetworkCapabilities(request.networkCapabilities);
CallbackHandler cbHandler = new CallbackHandler(handler);
requestNetwork(request, networkCallback, 0, legacyType, cbHandler);
@@ -3264,8 +3289,8 @@
* before {@link NetworkCallback#onUnavailable()} is called. The timeout must
* be a positive value (i.e. >0).
*/
- public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback,
- int timeoutMs) {
+ public void requestNetwork(@NonNull NetworkRequest request,
+ @NonNull NetworkCallback networkCallback, int timeoutMs) {
checkTimeout(timeoutMs);
int legacyType = inferLegacyTypeForNetworkCapabilities(request.networkCapabilities);
requestNetwork(request, networkCallback, timeoutMs, legacyType, getDefaultHandler());
@@ -3298,8 +3323,8 @@
* @param timeoutMs The time in milliseconds to attempt looking for a suitable network
* before {@link NetworkCallback#onUnavailable} is called.
*/
- public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback,
- Handler handler, int timeoutMs) {
+ public void requestNetwork(@NonNull NetworkRequest request,
+ @NonNull NetworkCallback networkCallback, @NonNull Handler handler, int timeoutMs) {
checkTimeout(timeoutMs);
int legacyType = inferLegacyTypeForNetworkCapabilities(request.networkCapabilities);
CallbackHandler cbHandler = new CallbackHandler(handler);
@@ -3371,7 +3396,8 @@
* {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED} or
* {@link NetworkCapabilities#NET_CAPABILITY_CAPTIVE_PORTAL}.
*/
- public void requestNetwork(NetworkRequest request, PendingIntent operation) {
+ public void requestNetwork(@NonNull NetworkRequest request,
+ @NonNull PendingIntent operation) {
printStackTrace();
checkPendingIntentNotNull(operation);
try {
@@ -3395,7 +3421,7 @@
* {@link #requestNetwork(NetworkRequest, android.app.PendingIntent)} with the
* corresponding NetworkRequest you'd like to remove. Cannot be null.
*/
- public void releaseNetworkRequest(PendingIntent operation) {
+ public void releaseNetworkRequest(@NonNull PendingIntent operation) {
printStackTrace();
checkPendingIntentNotNull(operation);
try {
@@ -3428,7 +3454,8 @@
* The callback is invoked on the default internal Handler.
*/
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
- public void registerNetworkCallback(NetworkRequest request, NetworkCallback networkCallback) {
+ public void registerNetworkCallback(@NonNull NetworkRequest request,
+ @NonNull NetworkCallback networkCallback) {
registerNetworkCallback(request, networkCallback, getDefaultHandler());
}
@@ -3443,8 +3470,8 @@
* @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
*/
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
- public void registerNetworkCallback(
- NetworkRequest request, NetworkCallback networkCallback, Handler handler) {
+ public void registerNetworkCallback(@NonNull NetworkRequest request,
+ @NonNull NetworkCallback networkCallback, @NonNull Handler handler) {
CallbackHandler cbHandler = new CallbackHandler(handler);
NetworkCapabilities nc = request.networkCapabilities;
sendRequestForNetwork(nc, networkCallback, 0, LISTEN, TYPE_NONE, cbHandler);
@@ -3480,7 +3507,8 @@
* comes from {@link PendingIntent#getBroadcast}. Cannot be null.
*/
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
- public void registerNetworkCallback(NetworkRequest request, PendingIntent operation) {
+ public void registerNetworkCallback(@NonNull NetworkRequest request,
+ @NonNull PendingIntent operation) {
printStackTrace();
checkPendingIntentNotNull(operation);
try {
@@ -3502,7 +3530,7 @@
* The callback is invoked on the default internal Handler.
*/
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
- public void registerDefaultNetworkCallback(NetworkCallback networkCallback) {
+ public void registerDefaultNetworkCallback(@NonNull NetworkCallback networkCallback) {
registerDefaultNetworkCallback(networkCallback, getDefaultHandler());
}
@@ -3516,7 +3544,8 @@
* @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
*/
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
- public void registerDefaultNetworkCallback(NetworkCallback networkCallback, Handler handler) {
+ public void registerDefaultNetworkCallback(@NonNull NetworkCallback networkCallback,
+ @NonNull Handler handler) {
// This works because if the NetworkCapabilities are null,
// ConnectivityService takes them from the default request.
//
@@ -3541,7 +3570,7 @@
* @param network {@link Network} specifying which network you're interested.
* @return {@code true} on success, {@code false} if the {@link Network} is no longer valid.
*/
- public boolean requestBandwidthUpdate(Network network) {
+ public boolean requestBandwidthUpdate(@NonNull Network network) {
try {
return mService.requestBandwidthUpdate(network);
} catch (RemoteException e) {
@@ -3562,7 +3591,7 @@
*
* @param networkCallback The {@link NetworkCallback} used when making the request.
*/
- public void unregisterNetworkCallback(NetworkCallback networkCallback) {
+ public void unregisterNetworkCallback(@NonNull NetworkCallback networkCallback) {
printStackTrace();
checkCallbackNotNull(networkCallback);
final List<NetworkRequest> reqs = new ArrayList<>();
@@ -3601,7 +3630,7 @@
* {@link #registerNetworkCallback(NetworkRequest, android.app.PendingIntent)}.
* Cannot be null.
*/
- public void unregisterNetworkCallback(PendingIntent operation) {
+ public void unregisterNetworkCallback(@NonNull PendingIntent operation) {
checkPendingIntentNotNull(operation);
releaseNetworkRequest(operation);
}
@@ -3723,7 +3752,7 @@
* @return a bitwise OR of zero or more of the {@code MULTIPATH_PREFERENCE_*} constants.
*/
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
- public @MultipathPreference int getMultipathPreference(Network network) {
+ public @MultipathPreference int getMultipathPreference(@Nullable Network network) {
try {
return mService.getMultipathPreference(network);
} catch (RemoteException e) {
@@ -3761,7 +3790,7 @@
* the current binding.
* @return {@code true} on success, {@code false} if the {@link Network} is no longer valid.
*/
- public boolean bindProcessToNetwork(Network network) {
+ public boolean bindProcessToNetwork(@Nullable Network network) {
// Forcing callers to call through non-static function ensures ConnectivityManager
// instantiated.
return setProcessDefaultNetwork(network);
@@ -3789,7 +3818,7 @@
* is a direct replacement.
*/
@Deprecated
- public static boolean setProcessDefaultNetwork(Network network) {
+ public static boolean setProcessDefaultNetwork(@Nullable Network network) {
int netId = (network == null) ? NETID_UNSET : network.netId;
if (netId == NetworkUtils.getBoundNetworkForProcess()) {
return true;
@@ -3820,6 +3849,7 @@
*
* @return {@code Network} to which this process is bound, or {@code null}.
*/
+ @Nullable
public Network getBoundNetworkForProcess() {
// Forcing callers to call thru non-static function ensures ConnectivityManager
// instantiated.
@@ -3836,6 +3866,7 @@
* {@code getBoundNetworkForProcess} is a direct replacement.
*/
@Deprecated
+ @Nullable
public static Network getProcessDefaultNetwork() {
int netId = NetworkUtils.getBoundNetworkForProcess();
if (netId == NETID_UNSET) return null;
@@ -3962,6 +3993,7 @@
*
* @return Hash of network watchlist config file. Null if config does not exist.
*/
+ @Nullable
public byte[] getNetworkWatchlistConfigHash() {
try {
return mService.getNetworkWatchlistConfigHash();
@@ -3983,8 +4015,8 @@
* (e.g., if it is associated with the calling VPN app's tunnel) or
* {@link android.os.Process#INVALID_UID} if the connection is not found.
*/
- public int getConnectionOwnerUid(int protocol, InetSocketAddress local,
- InetSocketAddress remote) {
+ public int getConnectionOwnerUid(int protocol, @NonNull InetSocketAddress local,
+ @NonNull InetSocketAddress remote) {
ConnectionInfo connectionInfo = new ConnectionInfo(protocol, local, remote);
try {
return mService.getConnectionOwnerUid(connectionInfo);
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index e7d441d..da5d96e 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -167,6 +167,8 @@
int getMultipathPreference(in Network Network);
+ NetworkRequest getDefaultRequest();
+
int getRestoreDefaultNetworkDelay(int networkType);
boolean addVpnAddress(String address, int prefixLength);
diff --git a/core/java/android/net/IIpMemoryStore.aidl b/core/java/android/net/IIpMemoryStore.aidl
new file mode 100644
index 0000000..6f88dec
--- /dev/null
+++ b/core/java/android/net/IIpMemoryStore.aidl
@@ -0,0 +1,113 @@
+/*
+ * 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.
+ */
+
+package android.net;
+
+import android.net.ipmemorystore.Blob;
+import android.net.ipmemorystore.NetworkAttributesParcelable;
+import android.net.ipmemorystore.IOnBlobRetrievedListener;
+import android.net.ipmemorystore.IOnL2KeyResponseListener;
+import android.net.ipmemorystore.IOnNetworkAttributesRetrieved;
+import android.net.ipmemorystore.IOnSameNetworkResponseListener;
+import android.net.ipmemorystore.IOnStatusListener;
+
+/** {@hide} */
+oneway interface IIpMemoryStore {
+ /**
+ * Store network attributes for a given L2 key.
+ * If L2Key is null, choose automatically from the attributes ; passing null is equivalent to
+ * calling findL2Key with the attributes and storing in the returned value.
+ *
+ * @param l2Key The L2 key for the L2 network. Clients that don't know or care about the L2
+ * key and only care about grouping can pass a unique ID here like the ones
+ * generated by {@code java.util.UUID.randomUUID()}, but keep in mind the low
+ * relevance of such a network will lead to it being evicted soon if it's not
+ * refreshed. Use findL2Key to try and find a similar L2Key to these attributes.
+ * @param attributes The attributes for this network.
+ * @param listener A listener that will be invoked to inform of the completion of this call,
+ * or null if the client is not interested in learning about success/failure.
+ * @return (through the listener) The L2 key. This is useful if the L2 key was not specified.
+ * If the call failed, the L2 key will be null.
+ */
+ void storeNetworkAttributes(String l2Key, in NetworkAttributesParcelable attributes,
+ IOnStatusListener listener);
+
+ /**
+ * Store a binary blob associated with an L2 key and a name.
+ *
+ * @param l2Key The L2 key for this network.
+ * @param clientId The ID of the client.
+ * @param name The name of this data.
+ * @param data The data to store.
+ * @param listener A listener to inform of the completion of this call, or null if the client
+ * is not interested in learning about success/failure.
+ * @return (through the listener) A status to indicate success or failure.
+ */
+ void storeBlob(String l2Key, String clientId, String name, in Blob data,
+ IOnStatusListener listener);
+
+ /**
+ * Returns the best L2 key associated with the attributes.
+ *
+ * This will find a record that would be in the same group as the passed attributes. This is
+ * useful to choose the key for storing a sample or private data when the L2 key is not known.
+ * If multiple records are group-close to these attributes, the closest match is returned.
+ * If multiple records have the same closeness, the one with the smaller (unicode codepoint
+ * order) L2 key is returned.
+ * If no record matches these attributes, null is returned.
+ *
+ * @param attributes The attributes of the network to find.
+ * @param listener The listener that will be invoked to return the answer.
+ * @return (through the listener) The L2 key if one matched, or null.
+ */
+ void findL2Key(in NetworkAttributesParcelable attributes, IOnL2KeyResponseListener listener);
+
+ /**
+ * Returns whether, to the best of the store's ability to tell, the two specified L2 keys point
+ * to the same L3 network. Group-closeness is used to determine this.
+ *
+ * @param l2Key1 The key for the first network.
+ * @param l2Key2 The key for the second network.
+ * @param listener The listener that will be invoked to return the answer.
+ * @return (through the listener) A SameL3NetworkResponse containing the answer and confidence.
+ */
+ void isSameNetwork(String l2Key1, String l2Key2, IOnSameNetworkResponseListener listener);
+
+ /**
+ * Retrieve the network attributes for a key.
+ * If no record is present for this key, this will return null attributes.
+ *
+ * @param l2Key The key of the network to query.
+ * @param listener The listener that will be invoked to return the answer.
+ * @return (through the listener) The network attributes and the L2 key associated with
+ * the query.
+ */
+ void retrieveNetworkAttributes(String l2Key, IOnNetworkAttributesRetrieved listener);
+
+ /**
+ * Retrieve previously stored private data.
+ * If no data was stored for this L2 key and name this will return null.
+ *
+ * @param l2Key The L2 key.
+ * @param clientId The id of the client that stored this data.
+ * @param name The name of the data.
+ * @param listener The listener that will be invoked to return the answer.
+ * @return (through the listener) The private data (or null), with the L2 key
+ * and the name of the data associated with the query.
+ */
+ void retrieveBlob(String l2Key, String clientId, String name,
+ IOnBlobRetrievedListener listener);
+}
diff --git a/core/java/android/net/INetworkMonitor.aidl b/core/java/android/net/INetworkMonitor.aidl
new file mode 100644
index 0000000..41f969a
--- /dev/null
+++ b/core/java/android/net/INetworkMonitor.aidl
@@ -0,0 +1,45 @@
+/**
+ * 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 perNmissions and
+ * limitations under the License.
+ */
+package android.net;
+
+import android.net.PrivateDnsConfigParcel;
+
+/** @hide */
+oneway interface INetworkMonitor {
+ // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED.
+ // The network should be used as a default internet connection. It was found to be:
+ // 1. a functioning network providing internet access, or
+ // 2. a captive portal and the user decided to use it as is.
+ const int NETWORK_TEST_RESULT_VALID = 0;
+
+ // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED.
+ // The network should not be used as a default internet connection. It was found to be:
+ // 1. a captive portal and the user is prompted to sign-in, or
+ // 2. a captive portal and the user did not want to use it, or
+ // 3. a broken network (e.g. DNS failed, connect failed, HTTP request failed).
+ const int NETWORK_TEST_RESULT_INVALID = 1;
+
+ void start();
+ void launchCaptivePortalApp();
+ void forceReevaluation(int uid);
+ void notifyPrivateDnsChanged(in PrivateDnsConfigParcel config);
+ void notifyDnsResponse(int returnCode);
+ void notifySystemReady();
+ void notifyNetworkConnected();
+ void notifyNetworkDisconnected();
+ void notifyLinkPropertiesChanged();
+ void notifyNetworkCapabilitiesChanged();
+}
\ No newline at end of file
diff --git a/core/java/android/net/INetworkMonitorCallbacks.aidl b/core/java/android/net/INetworkMonitorCallbacks.aidl
new file mode 100644
index 0000000..0bc2575
--- /dev/null
+++ b/core/java/android/net/INetworkMonitorCallbacks.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+package android.net;
+
+import android.net.INetworkMonitor;
+import android.net.PrivateDnsConfigParcel;
+
+/** @hide */
+oneway interface INetworkMonitorCallbacks {
+ void onNetworkMonitorCreated(in INetworkMonitor networkMonitor);
+ void notifyNetworkTested(int testResult, @nullable String redirectUrl);
+ void notifyPrivateDnsConfigResolved(in PrivateDnsConfigParcel config);
+ void showProvisioningNotification(String action);
+ void hideProvisioningNotification();
+}
\ No newline at end of file
diff --git a/core/java/android/net/INetworkStackConnector.aidl b/core/java/android/net/INetworkStackConnector.aidl
index 29f8828..2df8ab7 100644
--- a/core/java/android/net/INetworkStackConnector.aidl
+++ b/core/java/android/net/INetworkStackConnector.aidl
@@ -15,7 +15,13 @@
*/
package android.net;
+import android.net.INetworkMonitorCallbacks;
+import android.net.dhcp.DhcpServingParamsParcel;
+import android.net.dhcp.IDhcpServerCallbacks;
+
/** @hide */
oneway interface INetworkStackConnector {
- // TODO: requestDhcpServer(), etc. will go here
+ void makeDhcpServer(in String ifName, in DhcpServingParamsParcel params,
+ in IDhcpServerCallbacks cb);
+ void makeNetworkMonitor(int netId, String name, in INetworkMonitorCallbacks cb);
}
\ No newline at end of file
diff --git a/core/java/android/net/INetworkStackStatusCallback.aidl b/core/java/android/net/INetworkStackStatusCallback.aidl
new file mode 100644
index 0000000..51032d8
--- /dev/null
+++ b/core/java/android/net/INetworkStackStatusCallback.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+package android.net;
+
+/** @hide */
+oneway interface INetworkStackStatusCallback {
+ void onStatusAvailable(int statusCode);
+}
\ No newline at end of file
diff --git a/core/java/android/net/IpMemoryStore.java b/core/java/android/net/IpMemoryStore.java
new file mode 100644
index 0000000..b35f097
--- /dev/null
+++ b/core/java/android/net/IpMemoryStore.java
@@ -0,0 +1,174 @@
+/*
+ * 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.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.net.ipmemorystore.Blob;
+import android.net.ipmemorystore.IOnBlobRetrievedListener;
+import android.net.ipmemorystore.IOnL2KeyResponseListener;
+import android.net.ipmemorystore.IOnNetworkAttributesRetrieved;
+import android.net.ipmemorystore.IOnSameNetworkResponseListener;
+import android.net.ipmemorystore.IOnStatusListener;
+import android.net.ipmemorystore.NetworkAttributes;
+import android.os.RemoteException;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * The interface for system components to access the IP memory store.
+ * @see com.android.server.net.ipmemorystore.IpMemoryStoreService
+ * @hide
+ */
+@SystemService(Context.IP_MEMORY_STORE_SERVICE)
+public class IpMemoryStore {
+ @NonNull final Context mContext;
+ @NonNull final IIpMemoryStore mService;
+
+ public IpMemoryStore(@NonNull final Context context, @NonNull final IIpMemoryStore service) {
+ mContext = Preconditions.checkNotNull(context, "missing context");
+ mService = Preconditions.checkNotNull(service, "missing IIpMemoryStore");
+ }
+
+ /**
+ * Store network attributes for a given L2 key.
+ * If L2Key is null, choose automatically from the attributes ; passing null is equivalent to
+ * calling findL2Key with the attributes and storing in the returned value.
+ *
+ * @param l2Key The L2 key for the L2 network. Clients that don't know or care about the L2
+ * key and only care about grouping can pass a unique ID here like the ones
+ * generated by {@code java.util.UUID.randomUUID()}, but keep in mind the low
+ * relevance of such a network will lead to it being evicted soon if it's not
+ * refreshed. Use findL2Key to try and find a similar L2Key to these attributes.
+ * @param attributes The attributes for this network.
+ * @param listener A listener that will be invoked to inform of the completion of this call,
+ * or null if the client is not interested in learning about success/failure.
+ * Through the listener, returns the L2 key. This is useful if the L2 key was not specified.
+ * If the call failed, the L2 key will be null.
+ */
+ public void storeNetworkAttributes(@NonNull final String l2Key,
+ @NonNull final NetworkAttributes attributes,
+ @Nullable final IOnStatusListener listener) {
+ try {
+ mService.storeNetworkAttributes(l2Key, attributes.toParcelable(), listener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Store a binary blob associated with an L2 key and a name.
+ *
+ * @param l2Key The L2 key for this network.
+ * @param clientId The ID of the client.
+ * @param name The name of this data.
+ * @param data The data to store.
+ * @param listener A listener to inform of the completion of this call, or null if the client
+ * is not interested in learning about success/failure.
+ * Through the listener, returns a status to indicate success or failure.
+ */
+ public void storeBlob(@NonNull final String l2Key, @NonNull final String clientId,
+ @NonNull final String name, @NonNull final Blob data,
+ @Nullable final IOnStatusListener listener) {
+ try {
+ mService.storeBlob(l2Key, clientId, name, data, listener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the best L2 key associated with the attributes.
+ *
+ * This will find a record that would be in the same group as the passed attributes. This is
+ * useful to choose the key for storing a sample or private data when the L2 key is not known.
+ * If multiple records are group-close to these attributes, the closest match is returned.
+ * If multiple records have the same closeness, the one with the smaller (unicode codepoint
+ * order) L2 key is returned.
+ * If no record matches these attributes, null is returned.
+ *
+ * @param attributes The attributes of the network to find.
+ * @param listener The listener that will be invoked to return the answer.
+ * Through the listener, returns the L2 key if one matched, or null.
+ */
+ public void findL2Key(@NonNull final NetworkAttributes attributes,
+ @NonNull final IOnL2KeyResponseListener listener) {
+ try {
+ mService.findL2Key(attributes.toParcelable(), listener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns whether, to the best of the store's ability to tell, the two specified L2 keys point
+ * to the same L3 network. Group-closeness is used to determine this.
+ *
+ * @param l2Key1 The key for the first network.
+ * @param l2Key2 The key for the second network.
+ * @param listener The listener that will be invoked to return the answer.
+ * Through the listener, a SameL3NetworkResponse containing the answer and confidence.
+ */
+ public void isSameNetwork(@NonNull final String l2Key1, @NonNull final String l2Key2,
+ @NonNull final IOnSameNetworkResponseListener listener) {
+ try {
+ mService.isSameNetwork(l2Key1, l2Key2, listener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Retrieve the network attributes for a key.
+ * If no record is present for this key, this will return null attributes.
+ *
+ * @param l2Key The key of the network to query.
+ * @param listener The listener that will be invoked to return the answer.
+ * Through the listener, returns the network attributes and the L2 key associated with
+ * the query.
+ */
+ public void retrieveNetworkAttributes(@NonNull final String l2Key,
+ @NonNull final IOnNetworkAttributesRetrieved listener) {
+ try {
+ mService.retrieveNetworkAttributes(l2Key, listener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Retrieve previously stored private data.
+ * If no data was stored for this L2 key and name this will return null.
+ *
+ * @param l2Key The L2 key.
+ * @param clientId The id of the client that stored this data.
+ * @param name The name of the data.
+ * @param listener The listener that will be invoked to return the answer.
+ * Through the listener, returns the private data (or null), with the L2 key
+ * and the name of the data associated with the query.
+ */
+ public void retrieveBlob(@NonNull final String l2Key, @NonNull final String clientId,
+ @NonNull final String name, @NonNull final IOnBlobRetrievedListener listener) {
+ try {
+ mService.retrieveBlob(l2Key, clientId, name, listener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 3a79206..4a466f3 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -55,6 +55,8 @@
private String mIfaceName;
private ArrayList<LinkAddress> mLinkAddresses = new ArrayList<>();
private ArrayList<InetAddress> mDnses = new ArrayList<>();
+ // PCSCF addresses are addresses of SIP proxies that only exist for the IMS core service.
+ private ArrayList<InetAddress> mPcscfs = new ArrayList<InetAddress>();
private ArrayList<InetAddress> mValidatedPrivateDnses = new ArrayList<>();
private boolean mUsePrivateDns;
private String mPrivateDnsServerName;
@@ -179,6 +181,7 @@
mValidatedPrivateDnses.addAll(source.mValidatedPrivateDnses);
mUsePrivateDns = source.mUsePrivateDns;
mPrivateDnsServerName = source.mPrivateDnsServerName;
+ mPcscfs.addAll(source.mPcscfs);
mDomains = source.mDomains;
mRoutes.addAll(source.mRoutes);
mHttpProxy = (source.mHttpProxy == null) ? null : new ProxyInfo(source.mHttpProxy);
@@ -525,6 +528,60 @@
}
/**
+ * Adds the given {@link InetAddress} to the list of PCSCF servers, if not present.
+ *
+ * @param pcscfServer The {@link InetAddress} to add to the list of PCSCF servers.
+ * @return true if the PCSCF server was added, false otherwise.
+ * @hide
+ */
+ public boolean addPcscfServer(InetAddress pcscfServer) {
+ if (pcscfServer != null && !mPcscfs.contains(pcscfServer)) {
+ mPcscfs.add(pcscfServer);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Removes the given {@link InetAddress} from the list of PCSCF servers.
+ *
+ * @param pcscf Server The {@link InetAddress} to remove from the list of PCSCF servers.
+ * @return true if the PCSCF server was removed, false otherwise.
+ * @hide
+ */
+ public boolean removePcscfServer(InetAddress pcscfServer) {
+ if (pcscfServer != null) {
+ return mPcscfs.remove(pcscfServer);
+ }
+ return false;
+ }
+
+ /**
+ * Replaces the PCSCF servers in this {@code LinkProperties} with
+ * the given {@link Collection} of {@link InetAddress} objects.
+ *
+ * @param addresses The {@link Collection} of PCSCF servers to set in this object.
+ * @hide
+ */
+ public void setPcscfServers(Collection<InetAddress> pcscfServers) {
+ mPcscfs.clear();
+ for (InetAddress pcscfServer: pcscfServers) {
+ addPcscfServer(pcscfServer);
+ }
+ }
+
+ /**
+ * Returns all the {@link InetAddress} for PCSCF servers on this link.
+ *
+ * @return An unmodifiable {@link List} of {@link InetAddress} for PCSCF servers on
+ * this link.
+ * @hide
+ */
+ public List<InetAddress> getPcscfServers() {
+ return Collections.unmodifiableList(mPcscfs);
+ }
+
+ /**
* Sets the DNS domain search path used on this link.
*
* @param domains A {@link String} listing in priority order the comma separated
@@ -767,6 +824,7 @@
mDnses.clear();
mUsePrivateDns = false;
mPrivateDnsServerName = null;
+ mPcscfs.clear();
mDomains = null;
mRoutes.clear();
mHttpProxy = null;
@@ -813,6 +871,12 @@
resultJoiner.add(mPrivateDnsServerName);
}
+ if (!mPcscfs.isEmpty()) {
+ resultJoiner.add("PcscfAddresses: [");
+ resultJoiner.add(TextUtils.join(",", mPcscfs));
+ resultJoiner.add("]");
+ }
+
if (!mValidatedPrivateDnses.isEmpty()) {
final StringJoiner validatedPrivateDnsesJoiner =
new StringJoiner(",", "ValidatedPrivateDnsAddresses: [", "]");
@@ -965,6 +1029,36 @@
}
/**
+ * Returns true if this link has an IPv4 PCSCF server.
+ *
+ * @return {@code true} if there is an IPv4 PCSCF server, {@code false} otherwise.
+ * @hide
+ */
+ public boolean hasIPv4PcscfServer() {
+ for (InetAddress ia : mPcscfs) {
+ if (ia instanceof Inet4Address) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if this link has an IPv6 PCSCF server.
+ *
+ * @return {@code true} if there is an IPv6 PCSCF server, {@code false} otherwise.
+ * @hide
+ */
+ public boolean hasIPv6PcscfServer() {
+ for (InetAddress ia : mPcscfs) {
+ if (ia instanceof Inet6Address) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
* Returns true if this link is provisioned for global IPv4 connectivity.
* This requires an IP address, default route, and DNS server.
*
@@ -1117,6 +1211,19 @@
}
/**
+ * Compares this {@code LinkProperties} PCSCF addresses against the target
+ *
+ * @param target LinkProperties to compare.
+ * @return {@code true} if both are identical, {@code false} otherwise.
+ * @hide
+ */
+ public boolean isIdenticalPcscfs(LinkProperties target) {
+ Collection<InetAddress> targetPcscfs = target.getPcscfServers();
+ return (mPcscfs.size() == targetPcscfs.size()) ?
+ mPcscfs.containsAll(targetPcscfs) : false;
+ }
+
+ /**
* Compares this {@code LinkProperties} Routes against the target
*
* @param target LinkProperties to compare.
@@ -1218,6 +1325,7 @@
&& isIdenticalDnses(target)
&& isIdenticalPrivateDns(target)
&& isIdenticalValidatedPrivateDnses(target)
+ && isIdenticalPcscfs(target)
&& isIdenticalRoutes(target)
&& isIdenticalHttpProxy(target)
&& isIdenticalStackedLinks(target)
@@ -1334,6 +1442,7 @@
+ mMtu * 51
+ ((null == mTcpBufferSizes) ? 0 : mTcpBufferSizes.hashCode())
+ (mUsePrivateDns ? 57 : 0)
+ + mPcscfs.size() * 67
+ ((null == mPrivateDnsServerName) ? 0 : mPrivateDnsServerName.hashCode());
}
@@ -1357,6 +1466,10 @@
}
dest.writeBoolean(mUsePrivateDns);
dest.writeString(mPrivateDnsServerName);
+ dest.writeInt(mPcscfs.size());
+ for (InetAddress d : mPcscfs) {
+ dest.writeByteArray(d.getAddress());
+ }
dest.writeString(mDomains);
dest.writeInt(mMtu);
dest.writeString(mTcpBufferSizes);
@@ -1406,6 +1519,12 @@
}
netProp.setUsePrivateDns(in.readBoolean());
netProp.setPrivateDnsServerName(in.readString());
+ addressCount = in.readInt();
+ for (int i = 0; i < addressCount; i++) {
+ try {
+ netProp.addPcscfServer(InetAddress.getByAddress(in.createByteArray()));
+ } catch (UnknownHostException e) { }
+ }
netProp.setDomains(in.readString());
netProp.setMtu(in.readInt());
netProp.setTcpBufferSizes(in.readString());
diff --git a/core/java/android/net/NetworkStack.java b/core/java/android/net/NetworkStack.java
index 82a4e31..2eac6de 100644
--- a/core/java/android/net/NetworkStack.java
+++ b/core/java/android/net/NetworkStack.java
@@ -25,9 +25,12 @@
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.net.dhcp.DhcpServingParamsParcel;
+import android.net.dhcp.IDhcpServerCallbacks;
import android.os.Binder;
import android.os.IBinder;
import android.os.Process;
+import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.Slog;
@@ -45,19 +48,52 @@
public class NetworkStack {
private static final String TAG = NetworkStack.class.getSimpleName();
+ public static final String NETWORKSTACK_PACKAGE_NAME = "com.android.mainline.networkstack";
+
@NonNull
@GuardedBy("mPendingNetStackRequests")
- private final ArrayList<NetworkStackRequest> mPendingNetStackRequests = new ArrayList<>();
+ private final ArrayList<NetworkStackCallback> mPendingNetStackRequests = new ArrayList<>();
@Nullable
@GuardedBy("mPendingNetStackRequests")
private INetworkStackConnector mConnector;
- private interface NetworkStackRequest {
+ private interface NetworkStackCallback {
void onNetworkStackConnected(INetworkStackConnector connector);
}
public NetworkStack() { }
+ /**
+ * Create a DHCP server according to the specified parameters.
+ *
+ * <p>The server will be returned asynchronously through the provided callbacks.
+ */
+ public void makeDhcpServer(final String ifName, final DhcpServingParamsParcel params,
+ final IDhcpServerCallbacks cb) {
+ requestConnector(connector -> {
+ try {
+ connector.makeDhcpServer(ifName, params, cb);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ });
+ }
+
+ /**
+ * Create a NetworkMonitor.
+ *
+ * <p>The INetworkMonitor will be returned asynchronously through the provided callbacks.
+ */
+ public void makeNetworkMonitor(Network network, String name, INetworkMonitorCallbacks cb) {
+ requestConnector(connector -> {
+ try {
+ connector.makeNetworkMonitor(network.netId, name, cb);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ });
+ }
+
private class NetworkStackConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
@@ -77,14 +113,14 @@
ServiceManager.addService(Context.NETWORK_STACK_SERVICE, service, false /* allowIsolated */,
DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL);
- final ArrayList<NetworkStackRequest> requests;
+ final ArrayList<NetworkStackCallback> requests;
synchronized (mPendingNetStackRequests) {
requests = new ArrayList<>(mPendingNetStackRequests);
mPendingNetStackRequests.clear();
mConnector = connector;
}
- for (NetworkStackRequest r : requests) {
+ for (NetworkStackCallback r : requests) {
r.onNetworkStackConnected(connector);
}
}
@@ -105,7 +141,8 @@
"com.android.server.NetworkStackService",
true /* initialize */,
context.getClassLoader());
- connector = (IBinder) service.getMethod("makeConnector").invoke(null);
+ connector = (IBinder) service.getMethod("makeConnector", Context.class)
+ .invoke(null, context);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
Slog.wtf(TAG, "Could not create network stack connector from NetworkStackService");
// TODO: crash/reboot system here ?
@@ -134,7 +171,7 @@
}
// TODO: use this method to obtain the connector when implementing network stack operations
- private void requestConnector(@NonNull NetworkStackRequest request) {
+ private void requestConnector(@NonNull NetworkStackCallback request) {
// TODO: PID check.
if (Binder.getCallingUid() != Process.SYSTEM_UID) {
// Don't even attempt to obtain the connector and give a nice error message
diff --git a/core/java/android/net/PrivateDnsConfigParcel.aidl b/core/java/android/net/PrivateDnsConfigParcel.aidl
new file mode 100644
index 0000000..b52fce6
--- /dev/null
+++ b/core/java/android/net/PrivateDnsConfigParcel.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+package android.net;
+
+parcelable PrivateDnsConfigParcel {
+ String hostname;
+ String[] ips;
+}
diff --git a/core/java/android/net/dhcp/DhcpServerCallbacks.java b/core/java/android/net/dhcp/DhcpServerCallbacks.java
new file mode 100644
index 0000000..bb56876
--- /dev/null
+++ b/core/java/android/net/dhcp/DhcpServerCallbacks.java
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+package android.net.dhcp;
+
+/**
+ * Convenience wrapper around IDhcpServerCallbacks.Stub that implements getInterfaceVersion().
+ * @hide
+ */
+public abstract class DhcpServerCallbacks extends IDhcpServerCallbacks.Stub {
+ // TODO: add @Override here once the API is versioned
+
+ /**
+ * Get the version of the aidl interface implemented by the callbacks.
+ */
+ public int getInterfaceVersion() {
+ // TODO: return IDhcpServerCallbacks.VERSION;
+ return 0;
+ }
+}
diff --git a/core/java/android/net/dhcp/DhcpServingParamsParcel.aidl b/core/java/android/net/dhcp/DhcpServingParamsParcel.aidl
new file mode 100644
index 0000000..7b8b9ee
--- /dev/null
+++ b/core/java/android/net/dhcp/DhcpServingParamsParcel.aidl
@@ -0,0 +1,30 @@
+/**
+ *
+ * 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.
+ */
+
+package android.net.dhcp;
+
+parcelable DhcpServingParamsParcel {
+ int serverAddr;
+ int serverAddrPrefixLength;
+ int[] defaultRouters;
+ int[] dnsServers;
+ int[] excludedAddrs;
+ long dhcpLeaseTimeSecs;
+ int linkMtu;
+ boolean metered;
+}
+
diff --git a/core/java/android/net/dhcp/IDhcpServer.aidl b/core/java/android/net/dhcp/IDhcpServer.aidl
new file mode 100644
index 0000000..559433b
--- /dev/null
+++ b/core/java/android/net/dhcp/IDhcpServer.aidl
@@ -0,0 +1,32 @@
+/**
+ * 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 perNmissions and
+ * limitations under the License.
+ */
+
+package android.net.dhcp;
+
+import android.net.INetworkStackStatusCallback;
+import android.net.dhcp.DhcpServingParamsParcel;
+
+/** @hide */
+oneway interface IDhcpServer {
+ const int STATUS_UNKNOWN = 0;
+ const int STATUS_SUCCESS = 1;
+ const int STATUS_INVALID_ARGUMENT = 2;
+ const int STATUS_UNKNOWN_ERROR = 3;
+
+ void start(in INetworkStackStatusCallback cb);
+ void updateParams(in DhcpServingParamsParcel params, in INetworkStackStatusCallback cb);
+ void stop(in INetworkStackStatusCallback cb);
+}
diff --git a/core/java/android/net/dhcp/IDhcpServerCallbacks.aidl b/core/java/android/net/dhcp/IDhcpServerCallbacks.aidl
new file mode 100644
index 0000000..7ab4dcd
--- /dev/null
+++ b/core/java/android/net/dhcp/IDhcpServerCallbacks.aidl
@@ -0,0 +1,24 @@
+/**
+ * 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 perNmissions and
+ * limitations under the License.
+ */
+
+package android.net.dhcp;
+
+import android.net.dhcp.IDhcpServer;
+
+/** @hide */
+oneway interface IDhcpServerCallbacks {
+ void onDhcpServerCreated(int statusCode, in IDhcpServer server);
+}
diff --git a/core/java/android/net/ipmemorystore/Blob.aidl b/core/java/android/net/ipmemorystore/Blob.aidl
new file mode 100644
index 0000000..9dbef11
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/Blob.aidl
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+
+package android.net.ipmemorystore;
+
+/**
+ * A blob of data opaque to the memory store. The client mutates this at its own risk,
+ * and it is strongly suggested to never do it at all and treat this as immutable.
+ * {@hide}
+ */
+parcelable Blob {
+ byte[] data;
+}
diff --git a/core/java/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl b/core/java/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl
new file mode 100644
index 0000000..4926feb
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+package android.net.ipmemorystore;
+
+import android.net.ipmemorystore.Blob;
+import android.net.ipmemorystore.StatusParcelable;
+
+/** {@hide} */
+oneway interface IOnBlobRetrievedListener {
+ /**
+ * Private data was retrieved for the L2 key and name specified.
+ * Note this does not return the client ID, as clients are expected to only ever use one ID.
+ */
+ void onBlobRetrieved(in StatusParcelable status, in String l2Key, in String name,
+ in Blob data);
+}
diff --git a/core/java/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl b/core/java/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl
new file mode 100644
index 0000000..dea0cc4
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+package android.net.ipmemorystore;
+
+import android.net.ipmemorystore.StatusParcelable;
+
+/** {@hide} */
+oneway interface IOnL2KeyResponseListener {
+ /**
+ * The operation completed with the specified L2 key.
+ */
+ void onL2KeyResponse(in StatusParcelable status, in String l2Key);
+}
diff --git a/core/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl b/core/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl
new file mode 100644
index 0000000..57f59a1
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+package android.net.ipmemorystore;
+
+import android.net.ipmemorystore.NetworkAttributesParcelable;
+import android.net.ipmemorystore.StatusParcelable;
+
+/** {@hide} */
+oneway interface IOnNetworkAttributesRetrieved {
+ /**
+ * Network attributes were fetched for the specified L2 key. While the L2 key will never
+ * be null, the attributes may be if no data is stored about this L2 key.
+ */
+ void onL2KeyResponse(in StatusParcelable status, in String l2Key,
+ in NetworkAttributesParcelable attributes);
+}
diff --git a/core/java/android/net/ipmemorystore/IOnSameNetworkResponseListener.aidl b/core/java/android/net/ipmemorystore/IOnSameNetworkResponseListener.aidl
new file mode 100644
index 0000000..294bd3b
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/IOnSameNetworkResponseListener.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+package android.net.ipmemorystore;
+
+import android.net.ipmemorystore.SameL3NetworkResponseParcelable;
+import android.net.ipmemorystore.StatusParcelable;
+
+/** {@hide} */
+oneway interface IOnSameNetworkResponseListener {
+ /**
+ * The memory store has come up with the answer to a query that was sent.
+ */
+ void onSameNetworkResponse(in StatusParcelable status,
+ in SameL3NetworkResponseParcelable response);
+}
diff --git a/core/java/android/net/ipmemorystore/IOnStatusListener.aidl b/core/java/android/net/ipmemorystore/IOnStatusListener.aidl
new file mode 100644
index 0000000..5d07504
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/IOnStatusListener.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+package android.net.ipmemorystore;
+
+import android.net.ipmemorystore.StatusParcelable;
+
+/** {@hide} */
+oneway interface IOnStatusListener {
+ /**
+ * The operation has completed with the specified status.
+ */
+ void onComplete(in StatusParcelable status);
+}
diff --git a/core/java/android/net/ipmemorystore/NetworkAttributes.java b/core/java/android/net/ipmemorystore/NetworkAttributes.java
new file mode 100644
index 0000000..d7e5b27
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/NetworkAttributes.java
@@ -0,0 +1,210 @@
+/*
+ * 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.
+ */
+
+package android.net.ipmemorystore;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A POD object to represent attributes of a single L2 network entry.
+ * @hide
+ */
+public class NetworkAttributes {
+ private static final boolean DBG = true;
+
+ // The v4 address that was assigned to this device the last time it joined this network.
+ // This typically comes from DHCP but could be something else like static configuration.
+ // This does not apply to IPv6.
+ // TODO : add a list of v6 prefixes for the v6 case.
+ @Nullable
+ public final Inet4Address assignedV4Address;
+
+ // Optionally supplied by the client if it has an opinion on L3 network. For example, this
+ // could be a hash of the SSID + security type on WiFi.
+ @Nullable
+ public final String groupHint;
+
+ // The list of DNS server addresses.
+ @Nullable
+ public final List<InetAddress> dnsAddresses;
+
+ // The mtu on this network.
+ @Nullable
+ public final Integer mtu;
+
+ NetworkAttributes(
+ @Nullable final Inet4Address assignedV4Address,
+ @Nullable final String groupHint,
+ @Nullable final List<InetAddress> dnsAddresses,
+ @Nullable final Integer mtu) {
+ if (mtu != null && mtu < 0) throw new IllegalArgumentException("MTU can't be negative");
+ this.assignedV4Address = assignedV4Address;
+ this.groupHint = groupHint;
+ this.dnsAddresses = null == dnsAddresses ? null :
+ Collections.unmodifiableList(new ArrayList<>(dnsAddresses));
+ this.mtu = mtu;
+ }
+
+ @VisibleForTesting
+ public NetworkAttributes(@NonNull final NetworkAttributesParcelable parcelable) {
+ // The call to the other constructor must be the first statement of this constructor,
+ // so everything has to be inline
+ this((Inet4Address) getByAddressOrNull(parcelable.assignedV4Address),
+ parcelable.groupHint,
+ blobArrayToInetAddressList(parcelable.dnsAddresses),
+ parcelable.mtu >= 0 ? parcelable.mtu : null);
+ }
+
+ @Nullable
+ private static InetAddress getByAddressOrNull(@Nullable final byte[] address) {
+ try {
+ return InetAddress.getByAddress(address);
+ } catch (UnknownHostException e) {
+ return null;
+ }
+ }
+
+ @Nullable
+ private static List<InetAddress> blobArrayToInetAddressList(@Nullable final Blob[] blobs) {
+ if (null == blobs) return null;
+ final ArrayList<InetAddress> list = new ArrayList<>(blobs.length);
+ for (final Blob b : blobs) {
+ final InetAddress addr = getByAddressOrNull(b.data);
+ if (null != addr) list.add(addr);
+ }
+ return list;
+ }
+
+ @Nullable
+ private static Blob[] inetAddressListToBlobArray(@Nullable final List<InetAddress> addresses) {
+ if (null == addresses) return null;
+ final ArrayList<Blob> blobs = new ArrayList<>();
+ for (int i = 0; i < addresses.size(); ++i) {
+ final InetAddress addr = addresses.get(i);
+ if (null == addr) continue;
+ final Blob b = new Blob();
+ b.data = addr.getAddress();
+ blobs.add(b);
+ }
+ return blobs.toArray(new Blob[0]);
+ }
+
+ /** Converts this NetworkAttributes to a parcelable object */
+ @NonNull
+ public NetworkAttributesParcelable toParcelable() {
+ final NetworkAttributesParcelable parcelable = new NetworkAttributesParcelable();
+ parcelable.assignedV4Address =
+ (null == assignedV4Address) ? null : assignedV4Address.getAddress();
+ parcelable.groupHint = groupHint;
+ parcelable.dnsAddresses = inetAddressListToBlobArray(dnsAddresses);
+ parcelable.mtu = (null == mtu) ? -1 : mtu;
+ return parcelable;
+ }
+
+ /** @hide */
+ public static class Builder {
+ @Nullable
+ private Inet4Address mAssignedAddress;
+ @Nullable
+ private String mGroupHint;
+ @Nullable
+ private List<InetAddress> mDnsAddresses;
+ @Nullable
+ private Integer mMtu;
+
+ /**
+ * Set the assigned address.
+ * @param assignedV4Address The assigned address.
+ * @return This builder.
+ */
+ public Builder setAssignedV4Address(@Nullable final Inet4Address assignedV4Address) {
+ mAssignedAddress = assignedV4Address;
+ return this;
+ }
+
+ /**
+ * Set the group hint.
+ * @param groupHint The group hint.
+ * @return This builder.
+ */
+ public Builder setGroupHint(@Nullable final String groupHint) {
+ mGroupHint = groupHint;
+ return this;
+ }
+
+ /**
+ * Set the DNS addresses.
+ * @param dnsAddresses The DNS addresses.
+ * @return This builder.
+ */
+ public Builder setDnsAddresses(@Nullable final List<InetAddress> dnsAddresses) {
+ if (DBG && null != dnsAddresses) {
+ // Parceling code crashes if one of the addresses is null, therefore validate
+ // them when running in debug.
+ for (final InetAddress address : dnsAddresses) {
+ if (null == address) throw new IllegalArgumentException("Null DNS address");
+ }
+ }
+ this.mDnsAddresses = dnsAddresses;
+ return this;
+ }
+
+ /**
+ * Set the MTU.
+ * @param mtu The MTU.
+ * @return This builder.
+ */
+ public Builder setMtu(@Nullable final Integer mtu) {
+ if (null != mtu && mtu < 0) throw new IllegalArgumentException("MTU can't be negative");
+ mMtu = mtu;
+ return this;
+ }
+
+ /**
+ * Return the built NetworkAttributes object.
+ * @return The built NetworkAttributes object.
+ */
+ public NetworkAttributes build() {
+ return new NetworkAttributes(mAssignedAddress, mGroupHint, mDnsAddresses, mMtu);
+ }
+ }
+
+ @Override
+ public boolean equals(@Nullable final Object o) {
+ if (!(o instanceof NetworkAttributes)) return false;
+ final NetworkAttributes other = (NetworkAttributes) o;
+ return Objects.equals(assignedV4Address, other.assignedV4Address)
+ && Objects.equals(groupHint, other.groupHint)
+ && Objects.equals(dnsAddresses, other.dnsAddresses)
+ && Objects.equals(mtu, other.mtu);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(assignedV4Address, groupHint, dnsAddresses, mtu);
+ }
+}
diff --git a/core/java/android/net/ipmemorystore/NetworkAttributesParcelable.aidl b/core/java/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
new file mode 100644
index 0000000..0894d72
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+package android.net.ipmemorystore;
+
+// Blob[] is used to represent an array of byte[], as structured AIDL does not support arrays
+// of arrays.
+import android.net.ipmemorystore.Blob;
+
+/**
+ * An object to represent attributes of a single L2 network entry.
+ * See NetworkAttributes.java for a description of each field. The types used in this class
+ * are structured parcelable types instead of the richer types of the NetworkAttributes object,
+ * but they have the same purpose. The NetworkAttributes.java file also contains the code
+ * to convert the richer types to the parcelable types and back.
+ * @hide
+ */
+parcelable NetworkAttributesParcelable {
+ byte[] assignedV4Address;
+ String groupHint;
+ Blob[] dnsAddresses;
+ int mtu;
+}
diff --git a/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java b/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java
new file mode 100644
index 0000000..0cb37e9
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java
@@ -0,0 +1,131 @@
+/*
+ * 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.
+ */
+
+package android.net.ipmemorystore;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * An object representing the answer to a query whether two given L2 networks represent the
+ * same L3 network. Parcels as a SameL3NetworkResponseParceled object.
+ * @hide
+ */
+public class SameL3NetworkResponse {
+ @IntDef(prefix = "NETWORK_",
+ value = {NETWORK_SAME, NETWORK_DIFFERENT, NETWORK_NEVER_CONNECTED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface NetworkSameness {}
+
+ /**
+ * Both L2 networks represent the same L3 network.
+ */
+ public static final int NETWORK_SAME = 1;
+
+ /**
+ * The two L2 networks represent a different L3 network.
+ */
+ public static final int NETWORK_DIFFERENT = 2;
+
+ /**
+ * The device has never connected to at least one of these two L2 networks, or data
+ * has been wiped. Therefore the device has never seen the L3 network behind at least
+ * one of these two L2 networks, and can't evaluate whether it's the same as the other.
+ */
+ public static final int NETWORK_NEVER_CONNECTED = 3;
+
+ /**
+ * The first L2 key specified in the query.
+ */
+ @NonNull
+ public final String l2Key1;
+
+ /**
+ * The second L2 key specified in the query.
+ */
+ @NonNull
+ public final String l2Key2;
+
+ /**
+ * A confidence value indicating whether the two L2 networks represent the same L3 network.
+ *
+ * If both L2 networks were known, this value will be between 0.0 and 1.0, with 0.0
+ * representing complete confidence that the given L2 networks represent a different
+ * L3 network, and 1.0 representing complete confidence that the given L2 networks
+ * represent the same L3 network.
+ * If at least one of the L2 networks was not known, this value will be outside of the
+ * 0.0~1.0 range.
+ *
+ * Most apps should not be interested in this, and are encouraged to use the collapsing
+ * {@link #getNetworkSameness()} function below.
+ */
+ public final float confidence;
+
+ /**
+ * @return whether the two L2 networks represent the same L3 network. Either
+ * {@code NETWORK_SAME}, {@code NETWORK_DIFFERENT} or {@code NETWORK_NEVER_CONNECTED}.
+ */
+ @NetworkSameness
+ public final int getNetworkSameness() {
+ if (confidence > 1.0 || confidence < 0.0) return NETWORK_NEVER_CONNECTED;
+ return confidence > 0.5 ? NETWORK_SAME : NETWORK_DIFFERENT;
+ }
+
+ SameL3NetworkResponse(@NonNull final String l2Key1, @NonNull final String l2Key2,
+ final float confidence) {
+ this.l2Key1 = l2Key1;
+ this.l2Key2 = l2Key2;
+ this.confidence = confidence;
+ }
+
+ /** Builds a SameL3NetworkResponse from a parcelable object */
+ @VisibleForTesting
+ public SameL3NetworkResponse(@NonNull final SameL3NetworkResponseParcelable parceled) {
+ this(parceled.l2Key1, parceled.l2Key2, parceled.confidence);
+ }
+
+ /** Converts this SameL3NetworkResponse to a parcelable object */
+ @NonNull
+ public SameL3NetworkResponseParcelable toParcelable() {
+ final SameL3NetworkResponseParcelable parcelable = new SameL3NetworkResponseParcelable();
+ parcelable.l2Key1 = l2Key1;
+ parcelable.l2Key2 = l2Key2;
+ parcelable.confidence = confidence;
+ return parcelable;
+ }
+
+ // Note key1 and key2 have to match each other for this to return true. If
+ // key1 matches o.key2 and the other way around this returns false.
+ @Override
+ public boolean equals(@Nullable final Object o) {
+ if (!(o instanceof SameL3NetworkResponse)) return false;
+ final SameL3NetworkResponse other = (SameL3NetworkResponse) o;
+ return l2Key1.equals(other.l2Key1) && l2Key2.equals(other.l2Key2)
+ && confidence == other.confidence;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(l2Key1, l2Key2, confidence);
+ }
+}
diff --git a/core/java/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl b/core/java/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl
new file mode 100644
index 0000000..7196699
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+package android.net.ipmemorystore;
+
+/** {@hide} */
+parcelable SameL3NetworkResponseParcelable {
+ String l2Key1;
+ String l2Key2;
+ float confidence;
+}
diff --git a/core/java/android/net/ipmemorystore/Status.java b/core/java/android/net/ipmemorystore/Status.java
new file mode 100644
index 0000000..5b016ec
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/Status.java
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+package android.net.ipmemorystore;
+
+import android.annotation.NonNull;
+
+/**
+ * A parcelable status representing the result of an operation.
+ * Parcels as StatusParceled.
+ * @hide
+ */
+public class Status {
+ public static final int SUCCESS = 0;
+
+ public final int resultCode;
+
+ public Status(final int resultCode) {
+ this.resultCode = resultCode;
+ }
+
+ Status(@NonNull final StatusParcelable parcelable) {
+ this(parcelable.resultCode);
+ }
+
+ /** Converts this Status to a parcelable object */
+ @NonNull
+ public StatusParcelable toParcelable() {
+ final StatusParcelable parcelable = new StatusParcelable();
+ parcelable.resultCode = resultCode;
+ return parcelable;
+ }
+
+ public boolean isSuccess() {
+ return SUCCESS == resultCode;
+ }
+}
diff --git a/core/java/android/net/ipmemorystore/StatusParcelable.aidl b/core/java/android/net/ipmemorystore/StatusParcelable.aidl
new file mode 100644
index 0000000..fb36ef4
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/StatusParcelable.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+package android.net.ipmemorystore;
+
+/** {@hide} */
+parcelable StatusParcelable {
+ int resultCode;
+}
diff --git a/core/java/android/os/AppZygote.java b/core/java/android/os/AppZygote.java
new file mode 100644
index 0000000..40cbaf7
--- /dev/null
+++ b/core/java/android/os/AppZygote.java
@@ -0,0 +1,119 @@
+/*
+ * 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.
+ */
+
+package android.os;
+
+import android.content.pm.ApplicationInfo;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * AppZygote is responsible for interfacing with an application-specific zygote.
+ *
+ * Application zygotes can pre-load app-specific code and data, and this interface can
+ * be used to spawn isolated services from such an application zygote.
+ *
+ * Note that we'll have only one instance of this per application / uid combination.
+ *
+ * @hide
+ */
+public class AppZygote {
+ private static final String LOG_TAG = "AppZygote";
+
+ private final int mZygoteUid;
+
+ private final Object mLock = new Object();
+
+ /**
+ * Instance that maintains the socket connection to the zygote. This is {@code null} if the
+ * zygote is not running or is not connected.
+ */
+ @GuardedBy("mLock")
+ private ChildZygoteProcess mZygote;
+
+ private final ApplicationInfo mAppInfo;
+
+ public AppZygote(ApplicationInfo appInfo, int zygoteUid) {
+ mAppInfo = appInfo;
+ mZygoteUid = zygoteUid;
+ }
+
+ /**
+ * Returns the zygote process associated with this app zygote.
+ * Creates the process if it's not already running.
+ */
+ public ChildZygoteProcess getProcess() {
+ synchronized (mLock) {
+ if (mZygote != null) return mZygote;
+
+ connectToZygoteIfNeededLocked();
+ return mZygote;
+ }
+ }
+
+ /**
+ * Stops the Zygote and kills the zygote process.
+ */
+ public void stopZygote() {
+ synchronized (mLock) {
+ stopZygoteLocked();
+ }
+ }
+
+ public ApplicationInfo getAppInfo() {
+ return mAppInfo;
+ }
+
+ @GuardedBy("mLock")
+ private void stopZygoteLocked() {
+ if (mZygote != null) {
+ // Close the connection and kill the zygote process. This will not cause
+ // child processes to be killed by itself.
+ mZygote.close();
+ Process.killProcess(mZygote.getPid());
+ mZygote = null;
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void connectToZygoteIfNeededLocked() {
+ String abi = mAppInfo.primaryCpuAbi != null ? mAppInfo.primaryCpuAbi :
+ Build.SUPPORTED_ABIS[0];
+ try {
+ mZygote = Process.zygoteProcess.startChildZygote(
+ "com.android.internal.os.AppZygoteInit",
+ mAppInfo.processName + "_zygote",
+ mZygoteUid,
+ mZygoteUid,
+ null, // gids
+ 0, // runtimeFlags
+ "app_zygote", // seInfo
+ abi, // abi
+ abi, // acceptedAbiList
+ null); // instructionSet
+
+ ZygoteProcess.waitForConnectionToZygote(mZygote.getPrimarySocketAddress());
+ // preload application code in the zygote
+ Log.i(LOG_TAG, "Starting application preload.");
+ mZygote.preloadApp(mAppInfo, abi);
+ Log.i(LOG_TAG, "Application preload done.");
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Error connecting to app zygote", e);
+ stopZygoteLocked();
+ }
+ }
+}
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 9939a3c..1ebb551 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -273,6 +273,30 @@
public static final native int getCallingUid();
/**
+ * Returns {@code true} if the current thread is currently executing an
+ * incoming transaction.
+ *
+ * @hide
+ */
+ @CriticalNative
+ public static final native boolean isHandlingTransaction();
+
+ /**
+ * Return the Linux uid assigned to the process that sent the transaction
+ * currently being processed.
+ *
+ * @throws IllegalStateException if the current thread is not currently
+ * executing an incoming transaction.
+ */
+ public static final int getCallingUidOrThrow() {
+ if (!isHandlingTransaction()) {
+ throw new IllegalStateException(
+ "Thread is not in a binder transcation");
+ }
+ return getCallingUid();
+ }
+
+ /**
* Return the UserHandle assigned to the process that sent you the
* current transaction that is being processed. This is the user
* of the caller. It is distinct from {@link #getCallingUid()} in that a
diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java
new file mode 100644
index 0000000..1343d24
--- /dev/null
+++ b/core/java/android/os/BugreportManager.java
@@ -0,0 +1,148 @@
+/*
+ * 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.os;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.IBinder.DeathRecipient;
+
+import java.io.FileDescriptor;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Class that provides a privileged API to capture and consume bugreports.
+ *
+ * @hide
+ */
+// TODO: Expose API when the implementation is more complete.
+// @SystemApi
+@SystemService(Context.BUGREPORT_SERVICE)
+public class BugreportManager {
+ private final Context mContext;
+ private final IDumpstate mBinder;
+
+ /** @hide */
+ public BugreportManager(@NonNull Context context, IDumpstate binder) {
+ mContext = context;
+ mBinder = binder;
+ }
+
+ /**
+ * An interface describing the listener for bugreport progress and status.
+ */
+ public interface BugreportListener {
+ /**
+ * Called when there is a progress update.
+ * @param progress the progress in [0.0, 100.0]
+ */
+ void onProgress(float progress);
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "BUGREPORT_ERROR_" }, value = {
+ BUGREPORT_ERROR_INVALID_INPUT,
+ BUGREPORT_ERROR_RUNTIME
+ })
+
+ /** Possible error codes taking a bugreport can encounter */
+ @interface BugreportErrorCode {}
+
+ /** The input options were invalid */
+ int BUGREPORT_ERROR_INVALID_INPUT = 1;
+
+ /** A runtime error occured */
+ int BUGREPORT_ERROR_RUNTIME = 2;
+
+ /**
+ * Called when taking bugreport resulted in an error.
+ *
+ * @param errorCode the error that occurred. Possible values are
+ * {@code BUGREPORT_ERROR_INVALID_INPUT}, {@code BUGREPORT_ERROR_RUNTIME}.
+ */
+ void onError(@BugreportErrorCode int errorCode);
+
+ /**
+ * Called when taking bugreport finishes successfully
+ *
+ * @param durationMs time capturing bugreport took in milliseconds
+ * @param title title for the bugreport; helpful in reminding the user why they took it
+ * @param description detailed description for the bugreport
+ */
+ void onFinished(long durationMs, @NonNull String title,
+ @NonNull String description);
+ }
+
+ /**
+ * Starts a bugreport asynchronously.
+ *
+ * @param bugreportFd file to write the bugreport. This should be opened in write-only,
+ * append mode.
+ * @param screenshotFd file to write the screenshot, if necessary. This should be opened
+ * in write-only, append mode.
+ * @param params options that specify what kind of a bugreport should be taken
+ * @param listener callback for progress and status updates
+ */
+ @RequiresPermission(android.Manifest.permission.DUMP)
+ public void startBugreport(@NonNull FileDescriptor bugreportFd,
+ @Nullable FileDescriptor screenshotFd,
+ @NonNull BugreportParams params, @Nullable BugreportListener listener) {
+ // TODO(b/111441001): Enforce android.Manifest.permission.DUMP if necessary.
+ DumpstateListener dsListener = new DumpstateListener(listener);
+
+ try {
+ mBinder.startBugreport(bugreportFd, screenshotFd, params.getMode(), dsListener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+
+ // TODO(b/111441001) Connect up with BugreportListener methods.
+ private final class DumpstateListener extends IDumpstateListener.Stub
+ implements DeathRecipient {
+ private final BugreportListener mListener;
+
+ DumpstateListener(@Nullable BugreportListener listener) {
+ mListener = listener;
+ }
+
+ @Override
+ public void binderDied() {
+ // TODO(b/111441001): implement
+ }
+
+ @Override
+ public void onProgressUpdated(int progress) throws RemoteException {
+ // TODO(b/111441001): implement
+ }
+
+ @Override
+ public void onMaxProgressUpdated(int maxProgress) throws RemoteException {
+ // TODO(b/111441001): implement
+ }
+
+ @Override
+ public void onSectionComplete(String title, int status, int size, int durationMs)
+ throws RemoteException {
+ // TODO(b/111441001): implement
+ }
+ }
+}
diff --git a/core/java/android/os/BugreportParams.java b/core/java/android/os/BugreportParams.java
new file mode 100644
index 0000000..4e696ae
--- /dev/null
+++ b/core/java/android/os/BugreportParams.java
@@ -0,0 +1,90 @@
+/*
+ * 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.os;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Parameters that specify what kind of bugreport should be taken.
+ *
+ * @hide
+ */
+// TODO: Expose API when the implementation is more complete.
+// @SystemApi
+public final class BugreportParams {
+ private final int mMode;
+
+ public BugreportParams(@BugreportMode int mode) {
+ mMode = mode;
+ }
+
+ public int getMode() {
+ return mMode;
+ }
+
+ /**
+ * Defines acceptable types of bugreports.
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "BUGREPORT_MODE_" }, value = {
+ BUGREPORT_MODE_FULL,
+ BUGREPORT_MODE_INTERACTIVE,
+ BUGREPORT_MODE_REMOTE,
+ BUGREPORT_MODE_WEAR,
+ BUGREPORT_MODE_TELEPHONY,
+ BUGREPORT_MODE_WIFI
+ })
+ public @interface BugreportMode {}
+
+ /**
+ * Options for a bugreport without user interference (and hence causing less
+ * interference to the system), but includes all sections.
+ */
+ public static final int BUGREPORT_MODE_FULL = IDumpstate.BUGREPORT_MODE_FULL;
+
+ /**
+ * Options that allow user to monitor progress and enter additional data; might not
+ * include all sections.
+ */
+ public static final int BUGREPORT_MODE_INTERACTIVE = IDumpstate.BUGREPORT_MODE_INTERACTIVE;
+
+ /**
+ * Options for a bugreport requested remotely by administrator of the Device Owner app,
+ * not the device's user.
+ */
+ public static final int BUGREPORT_MODE_REMOTE = IDumpstate.BUGREPORT_MODE_REMOTE;
+
+ /**
+ * Options for a bugreport on a wearable device.
+ */
+ public static final int BUGREPORT_MODE_WEAR = IDumpstate.BUGREPORT_MODE_WEAR;
+
+ /**
+ * Options for a lightweight version of bugreport that only includes a few, urgent
+ * sections used to report telephony bugs.
+ */
+ public static final int BUGREPORT_MODE_TELEPHONY = IDumpstate.BUGREPORT_MODE_TELEPHONY;
+
+ /**
+ * Options for a lightweight bugreport that only includes a few sections related to
+ * Wifi.
+ */
+ public static final int BUGREPORT_MODE_WIFI = IDumpstate.BUGREPORT_MODE_WIFI;
+}
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index d979309..b0b8f49 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -480,14 +480,19 @@
return;
}
- String applicationPackageName = context.getPackageName();
- String devOptInApplicationName = coreSettings.getString(
- Settings.Global.GUP_DEV_OPT_IN_APPS);
- boolean devOptIn = applicationPackageName.equals(devOptInApplicationName);
- boolean whitelisted = onWhitelist(context, driverPackageName, ai.packageName);
- if (!devOptIn && !whitelisted) {
+ if (getGlobalSettingsString(coreSettings, Settings.Global.GUP_DEV_OPT_OUT_APPS)
+ .contains(ai.packageName)) {
if (DEBUG) {
- Log.w(TAG, applicationPackageName + " is not on the whitelist.");
+ Log.w(TAG, ai.packageName + " opts out from GUP.");
+ }
+ return;
+ }
+
+ if (!getGlobalSettingsString(coreSettings, Settings.Global.GUP_DEV_OPT_IN_APPS)
+ .contains(ai.packageName)
+ && !onWhitelist(context, driverPackageName, ai.packageName)) {
+ if (DEBUG) {
+ Log.w(TAG, ai.packageName + " is not on the whitelist.");
}
return;
}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index cc6bb12..b9cdcc0 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -899,6 +899,33 @@
}
/**
+ * Flatten an {@link ArrayMap} with string keys containing a particular object
+ * type into the parcel at the current dataPosition() and growing dataCapacity()
+ * if needed. The type of the objects in the array must be one that implements
+ * Parcelable. Only the raw data of the objects is written and not their type,
+ * so you must use the corresponding {@link #createTypedArrayMap(Parcelable.Creator)}
+ *
+ * @param val The map of objects to be written.
+ * @param parcelableFlags The parcelable flags to use.
+ *
+ * @see #createTypedArrayMap(Parcelable.Creator)
+ * @see Parcelable
+ */
+ public <T extends Parcelable> void writeTypedArrayMap(@Nullable ArrayMap<String, T> val,
+ int parcelableFlags) {
+ if (val == null) {
+ writeInt(-1);
+ return;
+ }
+ final int count = val.size();
+ writeInt(count);
+ for (int i = 0; i < count; i++) {
+ writeString(val.keyAt(i));
+ writeTypedObject(val.valueAt(i), parcelableFlags);
+ }
+ }
+
+ /**
* Write an array set to the parcel.
*
* @param val The array set to write.
@@ -1001,7 +1028,7 @@
* values are written using {@link #writeValue} and must follow the
* specification there.
*/
- public final void writeSparseArray(@Nullable SparseArray<Object> val) {
+ public final <T> void writeSparseArray(@Nullable SparseArray<T> val) {
if (val == null) {
writeInt(-1);
return;
@@ -1400,6 +1427,34 @@
}
/**
+ * Flatten a {@link SparseArray} containing a particular object type into the parcel
+ * at the current dataPosition() and growing dataCapacity() if needed. The
+ * type of the objects in the array must be one that implements Parcelable.
+ * Unlike the generic {@link #writeSparseArray(SparseArray)} method, however, only
+ * the raw data of the objects is written and not their type, so you must use the
+ * corresponding {@link #createTypedSparseArray(Parcelable.Creator)}.
+ *
+ * @param val The list of objects to be written.
+ * @param parcelableFlags The parcelable flags to use.
+ *
+ * @see #createTypedSparseArray(Parcelable.Creator)
+ * @see Parcelable
+ */
+ public final <T extends Parcelable> void writeTypedSparseArray(@Nullable SparseArray<T> val,
+ int parcelableFlags) {
+ if (val == null) {
+ writeInt(-1);
+ return;
+ }
+ final int count = val.size();
+ writeInt(count);
+ for (int i = 0; i < count; i++) {
+ writeInt(val.keyAt(i));
+ writeTypedObject(val.valueAt(i), parcelableFlags);
+ }
+ }
+
+ /**
* @hide
*/
public <T extends Parcelable> void writeTypedList(@Nullable List<T> val, int parcelableFlags) {
@@ -2369,7 +2424,7 @@
* Parcelables.
*/
@Nullable
- public final SparseArray readSparseArray(@Nullable ClassLoader loader) {
+ public final <T> SparseArray<T> readSparseArray(@Nullable ClassLoader loader) {
int N = readInt();
if (N < 0) {
return null;
@@ -2466,6 +2521,62 @@
}
/**
+ * Read into a new {@link SparseArray} items containing a particular object type
+ * that were written with {@link #writeTypedSparseArray(SparseArray, int)} at the
+ * current dataPosition(). The list <em>must</em> have previously been written
+ * via {@link #writeTypedSparseArray(SparseArray, int)} with the same object type.
+ *
+ * @param creator The creator to use when for instantiation.
+ *
+ * @return A newly created {@link SparseArray} containing objects with the same data
+ * as those that were previously written.
+ *
+ * @see #writeTypedSparseArray(SparseArray, int)
+ */
+ public final @Nullable <T extends Parcelable> SparseArray<T> createTypedSparseArray(
+ @NonNull Parcelable.Creator<T> creator) {
+ final int count = readInt();
+ if (count < 0) {
+ return null;
+ }
+ final SparseArray<T> array = new SparseArray<>(count);
+ for (int i = 0; i < count; i++) {
+ final int index = readInt();
+ final T value = readTypedObject(creator);
+ array.append(index, value);
+ }
+ return array;
+ }
+
+ /**
+ * Read into a new {@link ArrayMap} with string keys items containing a particular
+ * object type that were written with {@link #writeTypedArrayMap(ArrayMap, int)} at the
+ * current dataPosition(). The list <em>must</em> have previously been written
+ * via {@link #writeTypedArrayMap(ArrayMap, int)} with the same object type.
+ *
+ * @param creator The creator to use when for instantiation.
+ *
+ * @return A newly created {@link ArrayMap} containing objects with the same data
+ * as those that were previously written.
+ *
+ * @see #writeTypedArrayMap(ArrayMap, int)
+ */
+ public final @Nullable <T extends Parcelable> ArrayMap<String, T> createTypedArrayMap(
+ @NonNull Parcelable.Creator<T> creator) {
+ final int count = readInt();
+ if (count < 0) {
+ return null;
+ }
+ final ArrayMap<String, T> map = new ArrayMap<>(count);
+ for (int i = 0; i < count; i++) {
+ final String key = readString();
+ final T value = readTypedObject(creator);
+ map.append(key, value);
+ }
+ return map;
+ }
+
+ /**
* Read and return a new ArrayList containing String objects from
* the parcel that was written with {@link #writeStringList} at the
* current dataPosition(). Returns null if the
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index c3e0489..0942d97 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -1326,7 +1326,7 @@
* @return Battery saver state data.
*
* @hide
- * @see com.android.server.power.BatterySaverPolicy
+ * @see com.android.server.power.batterysaver.BatterySaverPolicy
* @see PowerSaveState
*/
public PowerSaveState getPowerSaveState(@ServiceType int serviceType) {
diff --git a/core/java/android/os/PowerSaveState.java b/core/java/android/os/PowerSaveState.java
index de1128df..4918ad1 100644
--- a/core/java/android/os/PowerSaveState.java
+++ b/core/java/android/os/PowerSaveState.java
@@ -27,7 +27,7 @@
/**
* Whether we should enable battery saver for this service.
*
- * @see com.android.server.power.BatterySaverPolicy
+ * @see com.android.server.power.batterysaver.BatterySaverPolicy
*/
public final boolean batterySaverEnabled;
/**
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 938b23c..ee56e3d 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -205,6 +205,24 @@
public static final int LAST_APPLICATION_UID = 19999;
/**
+ * First uid used for fully isolated sandboxed processes spawned from an app zygote
+ * @hide
+ */
+ public static final int FIRST_APP_ZYGOTE_ISOLATED_UID = 90000;
+
+ /**
+ * Number of UIDs we allocate per application zygote
+ * @hide
+ */
+ public static final int NUM_UIDS_PER_APP_ZYGOTE = 100;
+
+ /**
+ * Last uid used for fully isolated sandboxed processes spawned from an app zygote
+ * @hide
+ */
+ public static final int LAST_APP_ZYGOTE_ISOLATED_UID = 98999;
+
+ /**
* First uid used for fully isolated sandboxed processes (with no permissions of their own)
* @hide
*/
@@ -650,7 +668,8 @@
/** {@hide} */
public static final boolean isIsolated(int uid) {
uid = UserHandle.getAppId(uid);
- return uid >= FIRST_ISOLATED_UID && uid <= LAST_ISOLATED_UID;
+ return (uid >= FIRST_ISOLATED_UID && uid <= LAST_ISOLATED_UID)
+ || (uid >= FIRST_APP_ZYGOTE_ISOLATED_UID && uid <= LAST_APP_ZYGOTE_ISOLATED_UID);
}
/**
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 6ea155f..68d6d85 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -16,6 +16,8 @@
package android.os;
+import android.annotation.NonNull;
+
import com.android.internal.os.Zygote;
import dalvik.annotation.optimization.FastNative;
@@ -95,6 +97,8 @@
public static final long TRACE_TAG_AIDL = 1L << 24;
/** @hide */
public static final long TRACE_TAG_NNAPI = 1L << 25;
+ /** @hide */
+ public static final long TRACE_TAG_RRO = 1L << 26;
private static final long TRACE_TAG_NOT_READY = 1L << 63;
private static final int MAX_SECTION_NAME_LEN = 127;
@@ -311,7 +315,7 @@
* @param sectionName The name of the code section to appear in the trace. This may be at
* most 127 Unicode code units long.
*/
- public static void beginSection(String sectionName) {
+ public static void beginSection(@NonNull String sectionName) {
if (isTagEnabled(TRACE_TAG_APP)) {
if (sectionName.length() > MAX_SECTION_NAME_LEN) {
throw new IllegalArgumentException("sectionName is too long");
@@ -343,7 +347,7 @@
* @param methodName The method name to appear in the trace.
* @param cookie Unique identifier for distinguishing simultaneous events
*/
- public static void beginAsyncSection(String methodName, int cookie) {
+ public static void beginAsyncSection(@NonNull String methodName, int cookie) {
asyncTraceBegin(TRACE_TAG_APP, methodName, cookie);
}
@@ -355,7 +359,7 @@
* @param methodName The method name to appear in the trace.
* @param cookie Unique identifier for distinguishing simultaneous events
*/
- public static void endAsyncSection(String methodName, int cookie) {
+ public static void endAsyncSection(@NonNull String methodName, int cookie) {
asyncTraceEnd(TRACE_TAG_APP, methodName, cookie);
}
@@ -365,7 +369,7 @@
* @param counterName The counter name to appear in the trace.
* @param counterValue The counter value.
*/
- public static void setCounter(String counterName, long counterValue) {
+ public static void setCounter(@NonNull String counterName, long counterValue) {
if (isTagEnabled(TRACE_TAG_APP)) {
nativeTraceCounter(TRACE_TAG_APP, counterName, counterValue);
}
diff --git a/core/java/android/os/UpdateEngine.java b/core/java/android/os/UpdateEngine.java
index 8f2826c..1df3dad 100644
--- a/core/java/android/os/UpdateEngine.java
+++ b/core/java/android/os/UpdateEngine.java
@@ -66,6 +66,7 @@
public static final int PAYLOAD_HASH_MISMATCH_ERROR = 10;
public static final int PAYLOAD_SIZE_MISMATCH_ERROR = 11;
public static final int DOWNLOAD_PAYLOAD_VERIFICATION_ERROR = 12;
+ public static final int PAYLOAD_TIMESTAMP_ERROR = 51;
public static final int UPDATED_BUT_NOT_ACTIVE = 52;
}
diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java
index f8feb7b..ad8a4d5 100644
--- a/core/java/android/os/UserHandle.java
+++ b/core/java/android/os/UserHandle.java
@@ -138,8 +138,7 @@
*/
public static boolean isIsolated(int uid) {
if (uid > 0) {
- final int appId = getAppId(uid);
- return appId >= Process.FIRST_ISOLATED_UID && appId <= Process.LAST_ISOLATED_UID;
+ return Process.isIsolated(uid);
} else {
return false;
}
@@ -294,9 +293,14 @@
sb.append('u');
sb.append(getUserId(uid));
final int appId = getAppId(uid);
- if (appId >= Process.FIRST_ISOLATED_UID && appId <= Process.LAST_ISOLATED_UID) {
- sb.append('i');
- sb.append(appId - Process.FIRST_ISOLATED_UID);
+ if (isIsolated(appId)) {
+ if (appId > Process.FIRST_ISOLATED_UID) {
+ sb.append('i');
+ sb.append(appId - Process.FIRST_ISOLATED_UID);
+ } else {
+ sb.append("ai");
+ sb.append(appId - Process.FIRST_APP_ZYGOTE_ISOLATED_UID);
+ }
} else if (appId >= Process.FIRST_APPLICATION_UID) {
sb.append('a');
sb.append(appId - Process.FIRST_APPLICATION_UID);
@@ -330,9 +334,14 @@
pw.print('u');
pw.print(getUserId(uid));
final int appId = getAppId(uid);
- if (appId >= Process.FIRST_ISOLATED_UID && appId <= Process.LAST_ISOLATED_UID) {
- pw.print('i');
- pw.print(appId - Process.FIRST_ISOLATED_UID);
+ if (isIsolated(appId)) {
+ if (appId > Process.FIRST_ISOLATED_UID) {
+ pw.print('i');
+ pw.print(appId - Process.FIRST_ISOLATED_UID);
+ } else {
+ pw.print("ai");
+ pw.print(appId - Process.FIRST_APP_ZYGOTE_ISOLATED_UID);
+ }
} else if (appId >= Process.FIRST_APPLICATION_UID) {
pw.print('a');
pw.print(appId - Process.FIRST_APPLICATION_UID);
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index b5aeba0..226d1d0 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -74,10 +74,13 @@
private final String mPackageName;
// The default vibration intensity level for haptic feedback.
@VibrationIntensity
- private final int mDefaultHapticFeedbackIntensity;
+ private int mDefaultHapticFeedbackIntensity;
// The default vibration intensity level for notifications.
@VibrationIntensity
- private final int mDefaultNotificationVibrationIntensity;
+ private int mDefaultNotificationVibrationIntensity;
+ // The default vibration intensity level for ringtones.
+ @VibrationIntensity
+ private int mDefaultRingVibrationIntensity;
/**
* @hide to prevent subclassing from outside of the framework
@@ -85,10 +88,7 @@
public Vibrator() {
mPackageName = ActivityThread.currentPackageName();
final Context ctx = ActivityThread.currentActivityThread().getSystemContext();
- mDefaultHapticFeedbackIntensity = loadDefaultIntensity(ctx,
- com.android.internal.R.integer.config_defaultHapticFeedbackIntensity);
- mDefaultNotificationVibrationIntensity = loadDefaultIntensity(ctx,
- com.android.internal.R.integer.config_defaultNotificationVibrationIntensity);
+ loadVibrationIntensities(ctx);
}
/**
@@ -96,10 +96,16 @@
*/
protected Vibrator(Context context) {
mPackageName = context.getOpPackageName();
+ loadVibrationIntensities(context);
+ }
+
+ private void loadVibrationIntensities(Context context) {
mDefaultHapticFeedbackIntensity = loadDefaultIntensity(context,
com.android.internal.R.integer.config_defaultHapticFeedbackIntensity);
mDefaultNotificationVibrationIntensity = loadDefaultIntensity(context,
com.android.internal.R.integer.config_defaultNotificationVibrationIntensity);
+ mDefaultRingVibrationIntensity = loadDefaultIntensity(context,
+ com.android.internal.R.integer.config_defaultRingVibrationIntensity);
}
private int loadDefaultIntensity(Context ctx, int resId) {
@@ -115,13 +121,20 @@
}
/**
- * Get the default vibration intensity for notifications and ringtones.
+ * Get the default vibration intensity for notifications.
* @hide
*/
public int getDefaultNotificationVibrationIntensity() {
return mDefaultNotificationVibrationIntensity;
}
+ /** Get the default vibration intensity for ringtones.
+ * @hide
+ */
+ public int getDefaultRingVibrationIntensity() {
+ return mDefaultRingVibrationIntensity;
+ }
+
/**
* Check whether the hardware has a vibrator.
*
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index f136cd6..251c5ee 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.pm.ApplicationInfo;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.util.Log;
@@ -34,6 +35,7 @@
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
@@ -673,6 +675,36 @@
}
/**
+ * Instructs the zygote to pre-load the application code for the given Application.
+ * Only the app zygote supports this function.
+ * TODO preloadPackageForAbi() can probably be removed and the callers an use this instead.
+ */
+ public boolean preloadApp(ApplicationInfo appInfo, String abi) throws ZygoteStartFailedEx,
+ IOException {
+ synchronized (mLock) {
+ ZygoteState state = openZygoteSocketIfNeeded(abi);
+ state.writer.write("2");
+ state.writer.newLine();
+
+ state.writer.write("--preload-app");
+ state.writer.newLine();
+
+ // Zygote args needs to be strings, so in order to pass ApplicationInfo,
+ // write it to a Parcel, and base64 the raw Parcel bytes to the other side.
+ Parcel parcel = Parcel.obtain();
+ appInfo.writeToParcel(parcel, 0 /* flags */);
+ String encodedParcelData = Base64.getEncoder().encodeToString(parcel.marshall());
+ parcel.recycle();
+ state.writer.write(encodedParcelData);
+ state.writer.newLine();
+
+ state.writer.flush();
+
+ return (state.inputStream.readInt() == 0);
+ }
+ }
+
+ /**
* Instructs the zygote to pre-load the classes and native libraries at the given paths
* for the specified abi. Not all zygotes support this function.
*/
@@ -763,6 +795,20 @@
* secondary zygotes that inherit data from the zygote that this object
* communicates with. This returns a new ZygoteProcess representing a connection
* to the newly created zygote. Throws an exception if the zygote cannot be started.
+ *
+ * @param processClass The class to use as the child zygote's main entry
+ * point.
+ * @param niceName A more readable name to use for the process.
+ * @param uid The user-id under which the child zygote will run.
+ * @param gid The group-id under which the child zygote will run.
+ * @param gids Additional group-ids associated with the child zygote process.
+ * @param runtimeFlags Additional flags.
+ * @param seInfo null-ok SELinux information for the child zygote process.
+ * @param abi non-null the ABI of the child zygote
+ * @param acceptedAbiList ABIs this child zygote will accept connections for; this
+ * may be different from <code>abi</code> in case the children
+ * spawned from this Zygote only communicate using ABI-safe methods.
+ * @param instructionSet null-ok the instruction set to use.
*/
public ChildZygoteProcess startChildZygote(final String processClass,
final String niceName,
@@ -770,12 +816,14 @@
int runtimeFlags,
String seInfo,
String abi,
+ String acceptedAbiList,
String instructionSet) {
// Create an unguessable address in the global abstract namespace.
final LocalSocketAddress serverAddress = new LocalSocketAddress(
processClass + "/" + UUID.randomUUID().toString());
- final String[] extraArgs = {Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG + serverAddress.getName()};
+ final String[] extraArgs = {Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG + serverAddress.getName(),
+ Zygote.CHILD_ZYGOTE_ABI_LIST_ARG + acceptedAbiList};
Process.ProcessStartResult result;
try {
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 9594a71..735f4f2 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -1540,6 +1540,7 @@
}
/** {@hide} */
+ @SystemApi
@TestApi
public static boolean hasIsolatedStorage() {
// Prefer to use snapshot for current boot when available
diff --git a/core/java/android/permission/IPermissionController.aidl b/core/java/android/permission/IPermissionController.aidl
index 38951d5..0e18b44 100644
--- a/core/java/android/permission/IPermissionController.aidl
+++ b/core/java/android/permission/IPermissionController.aidl
@@ -17,6 +17,7 @@
package android.permission;
import android.os.RemoteCallback;
+import android.os.Bundle;
/**
* Interface for system apps to communication with the permission controller.
@@ -24,6 +25,8 @@
* @hide
*/
oneway interface IPermissionController {
+ void revokeRuntimePermissions(in Bundle request, boolean doDryRun, int reason,
+ String callerPackageName, in RemoteCallback callback);
void getAppPermissions(String packageName, in RemoteCallback callback);
void revokeRuntimePermission(String packageName, String permissionName);
void countPermissionApps(in List<String> permissionNames, boolean countOnlyGranted,
diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java
index 66e8666..e21a660 100644
--- a/core/java/android/permission/PermissionControllerManager.java
+++ b/core/java/android/permission/PermissionControllerManager.java
@@ -22,46 +22,97 @@
import static com.android.internal.util.Preconditions.checkNotNull;
import android.Manifest;
+import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.os.Binder;
+import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.util.ArrayMap;
import android.util.Log;
import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
import com.android.internal.infra.AbstractRemoteService;
+import com.android.internal.util.Preconditions;
+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.Map;
+import java.util.concurrent.Executor;
/**
- * Interface for communicating with the permission controller from system apps. All UI operations
- * regarding permissions and any changes to the permission state should flow through this
- * interface.
+ * Interface for communicating with the permission controller.
*
* @hide
*/
+@TestApi
+@SystemApi
@SystemService(Context.PERMISSION_CONTROLLER_SERVICE)
public final class PermissionControllerManager {
private static final String TAG = PermissionControllerManager.class.getSimpleName();
/**
* The key for retrieving the result from the returned bundle.
+ *
+ * @hide
*/
public static final String KEY_RESULT =
"android.permission.PermissionControllerManager.key.result";
+ /** @hide */
+ @IntDef(prefix = { "REASON_" }, value = {
+ REASON_MALWARE,
+ REASON_INSTALLER_POLICY_VIOLATION,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Reason {}
+
+ /** The permissions are revoked because the apps holding the permissions are malware */
+ public static final int REASON_MALWARE = 1;
+
+ /**
+ * The permissions are revoked because the apps holding the permissions violate a policy of the
+ * app that installed it.
+ *
+ * <p>If this reason is used only permissions of apps that are installed by the caller of the
+ * API can be revoked.
+ */
+ public static final int REASON_INSTALLER_POLICY_VIOLATION = 2;
+
+ /**
+ * Callback for delivering the result of {@link #revokeRuntimePermissions}.
+ */
+ public abstract static class OnRevokeRuntimePermissionsCallback {
+ /**
+ * The result for {@link #revokeRuntimePermissions}.
+ *
+ * @param revoked The actually revoked permissions as
+ * {@code Map<packageName, List<permission>>}
+ */
+ public abstract void onRevokeRuntimePermissions(@NonNull Map<String, List<String>> revoked);
+ }
+
/**
* Callback for delivering the result of {@link #getAppPermissions}.
+ *
+ * @hide
*/
public interface OnGetAppPermissionResultCallback {
/**
@@ -75,6 +126,8 @@
/**
* Callback for delivering the result of {@link #countPermissionApps}.
+ *
+ * @hide
*/
public interface OnCountPermissionAppsResultCallback {
/**
@@ -86,23 +139,61 @@
void onCountPermissionApps(int numApps);
}
+ private final @NonNull Context mContext;
private final RemoteService mRemoteService;
+ /** @hide */
public PermissionControllerManager(@NonNull Context context) {
Intent intent = new Intent(SERVICE_INTERFACE);
intent.setPackage(context.getPackageManager().getPermissionControllerPackageName());
ResolveInfo serviceInfo = context.getPackageManager().resolveService(intent, 0);
+ mContext = context;
mRemoteService = new RemoteService(context,
serviceInfo.getComponentInfo().getComponentName());
}
/**
+ * Revoke a set of runtime permissions for various apps.
+ *
+ * @param request The permissions to revoke as {@code Map<packageName, List<permission>>}
+ * @param doDryRun Compute the permissions that would be revoked, but not actually revoke them
+ * @param reason Why the permission should be revoked
+ * @param executor Executor on which to invoke the callback
+ * @param callback Callback to receive the result
+ */
+ @RequiresPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS)
+ public void revokeRuntimePermissions(@NonNull Map<String, List<String>> request,
+ boolean doDryRun, @Reason int reason, @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnRevokeRuntimePermissionsCallback callback) {
+ // Check input to fail immediately instead of inside the async request
+ checkNotNull(executor);
+ checkNotNull(callback);
+ checkNotNull(request);
+ for (Map.Entry<String, List<String>> appRequest : request.entrySet()) {
+ checkNotNull(appRequest.getKey());
+ checkCollectionElementsNotNull(appRequest.getValue(), "permissions");
+ }
+
+ // 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");
+ }
+
+ mRemoteService.scheduleRequest(new PendingRevokeRuntimePermissionRequest(mRemoteService,
+ request, doDryRun, reason, mContext.getPackageName(), executor, callback));
+ }
+
+ /**
* Gets the runtime permissions for an app.
*
* @param packageName The package for which to query.
* @param callback Callback to receive the result.
* @param handler Handler on which to invoke the callback.
+ *
+ * @hide
*/
@RequiresPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS)
public void getAppPermissions(@NonNull String packageName,
@@ -119,6 +210,8 @@
*
* @param packageName The package for which to revoke
* @param permissionName The permission to revoke
+ *
+ * @hide
*/
@RequiresPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS)
public void revokeRuntimePermission(@NonNull String packageName,
@@ -138,6 +231,8 @@
* @param countSystem Also count system apps
* @param callback Callback to receive the result
* @param handler Handler on which to invoke the callback
+ *
+ * @hide
*/
@RequiresPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS)
public void countPermissionApps(@NonNull List<String> permissionNames,
@@ -206,6 +301,84 @@
}
/**
+ * Request for {@link #revokeRuntimePermissions}
+ */
+ private static final class PendingRevokeRuntimePermissionRequest extends
+ AbstractRemoteService.PendingRequest<RemoteService, IPermissionController> {
+ private final @NonNull Map<String, List<String>> mRequest;
+ private final boolean mDoDryRun;
+ private final int mReason;
+ private final @NonNull String mCallingPackage;
+ private final @NonNull OnRevokeRuntimePermissionsCallback mCallback;
+
+ private final @NonNull RemoteCallback mRemoteCallback;
+
+ private PendingRevokeRuntimePermissionRequest(@NonNull RemoteService service,
+ @NonNull Map<String, List<String>> request, boolean doDryRun,
+ @Reason int reason, @NonNull String callingPackage,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnRevokeRuntimePermissionsCallback callback) {
+ super(service);
+
+ mRequest = request;
+ mDoDryRun = doDryRun;
+ mReason = reason;
+ mCallingPackage = callingPackage;
+ mCallback = callback;
+
+ mRemoteCallback = new RemoteCallback(result -> executor.execute(() -> {
+ long token = Binder.clearCallingIdentity();
+ try {
+ Map<String, List<String>> revoked = new ArrayMap<>();
+ try {
+ Bundle bundleizedRevoked = result.getBundle(KEY_RESULT);
+
+ for (String packageName : bundleizedRevoked.keySet()) {
+ Preconditions.checkNotNull(packageName);
+
+ ArrayList<String> permissions =
+ bundleizedRevoked.getStringArrayList(packageName);
+ Preconditions.checkCollectionElementsNotNull(permissions,
+ "permissions");
+
+ revoked.put(packageName, permissions);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Could not read result when revoking runtime permissions", e);
+ }
+
+ callback.onRevokeRuntimePermissions(revoked);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+
+ finish();
+ }
+ }), null);
+ }
+
+ @Override
+ protected void onTimeout(RemoteService remoteService) {
+ mCallback.onRevokeRuntimePermissions(Collections.emptyMap());
+ }
+
+ @Override
+ public void run() {
+ Bundle bundledizedRequest = new Bundle();
+ for (Map.Entry<String, List<String>> appRequest : mRequest.entrySet()) {
+ bundledizedRequest.putStringArrayList(appRequest.getKey(),
+ new ArrayList<>(appRequest.getValue()));
+ }
+
+ try {
+ getService().getServiceInterface().revokeRuntimePermissions(bundledizedRequest,
+ mDoDryRun, mReason, mCallingPackage, mRemoteCallback);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error revoking runtime permission", e);
+ }
+ }
+ }
+
+ /**
* Request for {@link #getAppPermissions}
*/
private static final class PendingGetAppPermissionRequest extends
diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java
index 5dad071..f621737 100644
--- a/core/java/android/permission/PermissionControllerService.java
+++ b/core/java/android/permission/PermissionControllerService.java
@@ -16,6 +16,7 @@
package android.permission;
+import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull;
import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
@@ -26,12 +27,19 @@
import android.app.Service;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteCallback;
+import android.util.ArrayMap;
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
/**
* This service is meant to be implemented by the app controlling permissions.
@@ -60,6 +68,20 @@
}
/**
+ * Revoke a set of runtime permissions for various apps.
+ *
+ * @param requests The permissions to revoke as {@code Map<packageName, List<permission>>}
+ * @param doDryRun Compute the permissions that would be revoked, but not actually revoke them
+ * @param reason Why the permission should be revoked
+ * @param callerPackageName The package name of the calling app
+ *
+ * @return the actually removed permissions as {@code Map<packageName, List<permission>>}
+ */
+ public abstract @NonNull Map<String, List<String>> onRevokeRuntimePermissions(
+ @NonNull Map<String, List<String>> requests, boolean doDryRun,
+ @PermissionControllerManager.Reason int reason, @NonNull String callerPackageName);
+
+ /**
* Gets the runtime permissions for an app.
*
* @param packageName The package for which to query.
@@ -94,6 +116,41 @@
public final IBinder onBind(Intent intent) {
return new IPermissionController.Stub() {
@Override
+ public void revokeRuntimePermissions(
+ Bundle bundleizedRequest, boolean doDryRun, int reason,
+ String callerPackageName, RemoteCallback callback) {
+ checkNotNull(bundleizedRequest, "bundleizedRequest");
+ checkNotNull(callerPackageName);
+ checkNotNull(callback);
+
+ Map<String, List<String>> request = new ArrayMap<>();
+ for (String packageName : bundleizedRequest.keySet()) {
+ Preconditions.checkNotNull(packageName);
+
+ ArrayList<String> permissions =
+ bundleizedRequest.getStringArrayList(packageName);
+ Preconditions.checkCollectionElementsNotNull(permissions, "permissions");
+
+ request.put(packageName, permissions);
+ }
+
+ enforceCallingPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, null);
+
+ // Verify callerPackageName
+ try {
+ PackageInfo pkgInfo = getPackageManager().getPackageInfo(callerPackageName, 0);
+ checkArgument(getCallingUid() == pkgInfo.applicationInfo.uid);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+
+ mHandler.sendMessage(obtainMessage(
+ PermissionControllerService::revokeRuntimePermissions,
+ PermissionControllerService.this, request, doDryRun, reason,
+ callerPackageName, callback));
+ }
+
+ @Override
public void getAppPermissions(String packageName, RemoteCallback callback) {
checkNotNull(packageName, "packageName");
checkNotNull(callback, "callback");
@@ -133,6 +190,27 @@
};
}
+ private void revokeRuntimePermissions(@NonNull Map<String, List<String>> requests,
+ boolean doDryRun, @PermissionControllerManager.Reason int reason,
+ @NonNull String callerPackageName, @NonNull RemoteCallback callback) {
+ Map<String, List<String>> revoked = onRevokeRuntimePermissions(requests,
+ doDryRun, reason, callerPackageName);
+
+ checkNotNull(revoked);
+ Bundle bundledizedRevoked = new Bundle();
+ for (Map.Entry<String, List<String>> appRevocation : revoked.entrySet()) {
+ checkNotNull(appRevocation.getKey());
+ checkCollectionElementsNotNull(appRevocation.getValue(), "permissions");
+
+ bundledizedRevoked.putStringArrayList(appRevocation.getKey(),
+ new ArrayList<>(appRevocation.getValue()));
+ }
+
+ Bundle result = new Bundle();
+ result.putBundle(PermissionControllerManager.KEY_RESULT, bundledizedRevoked);
+ callback.sendResult(result);
+ }
+
private void getAppPermissions(@NonNull String packageName, @NonNull RemoteCallback callback) {
List<RuntimePermissionPresentationInfo> permissions = onGetAppPermissions(packageName);
if (permissions != null && !permissions.isEmpty()) {
diff --git a/core/java/android/permission/TEST_MAPPING b/core/java/android/permission/TEST_MAPPING
new file mode 100644
index 0000000..ba9f36a
--- /dev/null
+++ b/core/java/android/permission/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsPermissionTestCases",
+ "options": [
+ {
+ "include-filter": "android.permission.cts.PermissionControllerTest"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java
index e5fd29c..b348da4 100644
--- a/core/java/android/provider/Downloads.java
+++ b/core/java/android/provider/Downloads.java
@@ -409,6 +409,15 @@
public static final String COLUMN_MEDIAPROVIDER_URI = "mediaprovider_uri";
/**
+ * Similar to {@link #COLUMN_MEDIAPROVIDER_URI}, except this cannot be updated/queried
+ * by apps and will be the source of truth when updating/deleting download entries in
+ * MediaProvider database.
+ *
+ * <P>Type: TEXT</P>
+ */
+ public static final String COLUMN_MEDIASTORE_URI = "mediastore_uri";
+
+ /**
* The column that is used to remember whether the media scanner was invoked.
* It can take the values: null or 0(not scanned), 1(scanned), 2 (not scannable).
* <P>Type: TEXT</P>
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index c4e2b12..cdbc979 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -17,6 +17,7 @@
package android.provider;
import android.annotation.BytesLong;
+import android.annotation.DurationMillisLong;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -55,6 +56,7 @@
import android.os.storage.VolumeInfo;
import android.service.media.CameraPrewarmService;
import android.text.TextUtils;
+import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
@@ -131,6 +133,8 @@
/** {@hide} */
public static final String PARAM_INCLUDE_PENDING = "includePending";
/** {@hide} */
+ public static final String PARAM_INCLUDE_TRASHED = "includeTrashed";
+ /** {@hide} */
public static final String PARAM_PROGRESS = "progress";
/** {@hide} */
public static final String PARAM_REQUIRE_ORIGINAL = "requireOriginal";
@@ -485,12 +489,29 @@
* By default no pending items are returned.
*
* @see MediaColumns#IS_PENDING
+ * @see MediaStore#setIncludePending(Uri)
+ * @see MediaStore#createPending(Context, PendingParams)
*/
public static @NonNull Uri setIncludePending(@NonNull Uri uri) {
return uri.buildUpon().appendQueryParameter(PARAM_INCLUDE_PENDING, "1").build();
}
/**
+ * Update the given {@link Uri} to also include any trashed media items from
+ * calls such as
+ * {@link ContentResolver#query(Uri, String[], Bundle, CancellationSignal)}.
+ * By default no trashed items are returned.
+ *
+ * @see MediaColumns#IS_TRASHED
+ * @see MediaStore#setIncludeTrashed(Uri)
+ * @see MediaStore#trash(Context, Uri)
+ * @see MediaStore#untrash(Context, Uri)
+ */
+ public static @NonNull Uri setIncludeTrashed(@NonNull Uri uri) {
+ return uri.buildUpon().appendQueryParameter(PARAM_INCLUDE_TRASHED, "1").build();
+ }
+
+ /**
* Update the given {@link Uri} to indicate that the caller requires the
* original file contents when calling
* {@link ContentResolver#openFileDescriptor(Uri, String)}.
@@ -516,6 +537,9 @@
*
* @return token which can be passed to {@link #openPending(Context, Uri)}
* to work with this pending item.
+ * @see MediaColumns#IS_PENDING
+ * @see MediaStore#setIncludePending(Uri)
+ * @see MediaStore#createPending(Context, PendingParams)
*/
public static @NonNull Uri createPending(@NonNull Context context,
@NonNull PendingParams params) {
@@ -572,6 +596,8 @@
this.insertValues.put(MediaColumns.DATE_ADDED, now);
this.insertValues.put(MediaColumns.DATE_MODIFIED, now);
this.insertValues.put(MediaColumns.IS_PENDING, 1);
+ this.insertValues.put(MediaColumns.DATE_EXPIRES,
+ (System.currentTimeMillis() + DateUtils.DAY_IN_MILLIS) / 1000);
}
/**
@@ -696,6 +722,7 @@
public @NonNull Uri publish() {
final ContentValues values = new ContentValues();
values.put(MediaColumns.IS_PENDING, 0);
+ values.putNull(MediaColumns.DATE_EXPIRES);
mContext.getContentResolver().update(mUri, values, null, null);
return mUri;
}
@@ -717,6 +744,67 @@
}
/**
+ * Mark the given item as being "trashed", meaning it should be deleted at
+ * some point in the future. This is a more gentle operation than simply
+ * calling {@link ContentResolver#delete(Uri, String, String[])}, which
+ * would take effect immediately.
+ * <p>
+ * This method preserves trashed items for at least 48 hours before erasing
+ * them, giving the user a chance to untrash the item.
+ *
+ * @see MediaColumns#IS_TRASHED
+ * @see MediaStore#setIncludeTrashed(Uri)
+ * @see MediaStore#trash(Context, Uri)
+ * @see MediaStore#untrash(Context, Uri)
+ */
+ public static void trash(@NonNull Context context, @NonNull Uri uri) {
+ trash(context, uri, 48 * DateUtils.HOUR_IN_MILLIS);
+ }
+
+ /**
+ * Mark the given item as being "trashed", meaning it should be deleted at
+ * some point in the future. This is a more gentle operation than simply
+ * calling {@link ContentResolver#delete(Uri, String, String[])}, which
+ * would take effect immediately.
+ * <p>
+ * This method preserves trashed items for at least the given timeout before
+ * erasing them, giving the user a chance to untrash the item.
+ *
+ * @see MediaColumns#IS_TRASHED
+ * @see MediaStore#setIncludeTrashed(Uri)
+ * @see MediaStore#trash(Context, Uri)
+ * @see MediaStore#untrash(Context, Uri)
+ */
+ public static void trash(@NonNull Context context, @NonNull Uri uri,
+ @DurationMillisLong long timeoutMillis) {
+ if (timeoutMillis < 0) {
+ throw new IllegalArgumentException();
+ }
+
+ final ContentValues values = new ContentValues();
+ values.put(MediaColumns.IS_TRASHED, 1);
+ values.put(MediaColumns.DATE_EXPIRES,
+ (System.currentTimeMillis() + timeoutMillis) / 1000);
+ context.getContentResolver().update(uri, values, null, null);
+ }
+
+ /**
+ * Mark the given item as being "untrashed", meaning it should no longer be
+ * deleted as previously requested through {@link #trash(Context, Uri)}.
+ *
+ * @see MediaColumns#IS_TRASHED
+ * @see MediaStore#setIncludeTrashed(Uri)
+ * @see MediaStore#trash(Context, Uri)
+ * @see MediaStore#untrash(Context, Uri)
+ */
+ public static void untrash(@NonNull Context context, @NonNull Uri uri) {
+ final ContentValues values = new ContentValues();
+ values.put(MediaColumns.IS_TRASHED, 0);
+ values.putNull(MediaColumns.DATE_EXPIRES);
+ context.getContentResolver().update(uri, values, null, null);
+ }
+
+ /**
* Common fields for most MediaProvider tables
*/
public interface MediaColumns extends BaseColumns {
@@ -821,12 +909,34 @@
* <p>
* Type: BOOLEAN
*
+ * @see MediaColumns#IS_PENDING
+ * @see MediaStore#setIncludePending(Uri)
* @see MediaStore#createPending(Context, PendingParams)
- * @see MediaStore#PARAM_INCLUDE_PENDING
*/
public static final String IS_PENDING = "is_pending";
/**
+ * Flag indicating if a media item is trashed.
+ * <p>
+ * Type: BOOLEAN
+ *
+ * @see MediaColumns#IS_TRASHED
+ * @see MediaStore#setIncludeTrashed(Uri)
+ * @see MediaStore#trash(Context, Uri)
+ * @see MediaStore#untrash(Context, Uri)
+ */
+ public static final String IS_TRASHED = "is_trashed";
+
+ /**
+ * The time the file should be considered expired. Units are seconds
+ * since 1970. Typically only meaningful in the context of
+ * {@link #IS_PENDING} or {@link #IS_TRASHED}.
+ * <p>
+ * Type: INTEGER
+ */
+ public static final String DATE_EXPIRES = "date_expires";
+
+ /**
* The width of the image/video in pixels.
*/
public static final String WIDTH = "width";
@@ -915,6 +1025,11 @@
return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("dir").build();
}
+ /** @hide */
+ public static final Uri getContentUriForPath(String path) {
+ return getContentUri(getVolumeNameForPath(path));
+ }
+
/**
* Fields for master table for all media files.
* Table also contains MediaColumns._ID, DATA, SIZE and DATE_MODIFIED.
@@ -1021,6 +1136,13 @@
* Type: TEXT
*/
String REFERER_URI = "referer_uri";
+
+ /**
+ * The description of the download.
+ * <p>
+ * Type: Text
+ */
+ String DESCRIPTION = "description";
}
/**
@@ -1251,18 +1373,32 @@
public static final String MINI_THUMB_MAGIC = "mini_thumb_magic";
/**
- * The bucket id of the image. This is a read-only property that
- * is automatically computed from the DATA column.
- * <P>Type: TEXT</P>
+ * The primary bucket ID of this media item. This can be useful to
+ * present the user a first-level clustering of related media items.
+ * This is a read-only column that is automatically computed.
+ * <p>
+ * Type: INTEGER
*/
public static final String BUCKET_ID = "bucket_id";
/**
- * The bucket display name of the image. This is a read-only property that
- * is automatically computed from the DATA column.
- * <P>Type: TEXT</P>
+ * The primary bucket display name of this media item. This can be
+ * useful to present the user a first-level clustering of related
+ * media items. This is a read-only column that is automatically
+ * computed.
+ * <p>
+ * Type: TEXT
*/
public static final String BUCKET_DISPLAY_NAME = "bucket_display_name";
+
+ /**
+ * The secondary bucket ID of this media item. This can be useful to
+ * present the user a second-level clustering of related media
+ * items. This is a read-only column that is automatically computed.
+ * <p>
+ * Type: INTEGER
+ */
+ public static final String SECONDARY_BUCKET_ID = "secondary_bucket_id";
}
public static final class Media implements ImageColumns {
@@ -2500,20 +2636,34 @@
public static final String MINI_THUMB_MAGIC = "mini_thumb_magic";
/**
- * The bucket id of the video. This is a read-only property that
- * is automatically computed from the DATA column.
- * <P>Type: TEXT</P>
+ * The primary bucket ID of this media item. This can be useful to
+ * present the user a first-level clustering of related media items.
+ * This is a read-only column that is automatically computed.
+ * <p>
+ * Type: INTEGER
*/
public static final String BUCKET_ID = "bucket_id";
/**
- * The bucket display name of the video. This is a read-only property that
- * is automatically computed from the DATA column.
- * <P>Type: TEXT</P>
+ * The primary bucket display name of this media item. This can be
+ * useful to present the user a first-level clustering of related
+ * media items. This is a read-only column that is automatically
+ * computed.
+ * <p>
+ * Type: TEXT
*/
public static final String BUCKET_DISPLAY_NAME = "bucket_display_name";
/**
+ * The secondary bucket ID of this media item. This can be useful to
+ * present the user a second-level clustering of related media
+ * items. This is a read-only column that is automatically computed.
+ * <p>
+ * Type: INTEGER
+ */
+ public static final String SECONDARY_BUCKET_ID = "secondary_bucket_id";
+
+ /**
* The bookmark for the video. Time in ms. Represents the location in the video that the
* video should start playing at the next time it is opened. If the value is null or
* out of the range 0..DURATION-1 then the video should start playing from the
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index bb1784a..4649776 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -86,7 +86,7 @@
import android.util.ArraySet;
import android.util.Log;
import android.util.MemoryIntArray;
-import android.view.textservice.TextServicesManager;
+import android.view.inputmethod.InputMethodSystemProperty;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.ColorDisplayController;
@@ -3377,6 +3377,22 @@
*/
public static final String NOTIFICATION_VIBRATION_INTENSITY =
"notification_vibration_intensity";
+ /**
+ * The intensity of ringtone vibrations, if configurable.
+ *
+ * Not all devices are capable of changing their vibration intensity; on these devices
+ * there will likely be no difference between the various vibration intensities except for
+ * intensity 0 (off) and the rest.
+ *
+ * <b>Values:</b><br/>
+ * 0 - Vibration is disabled<br/>
+ * 1 - Weak vibrations<br/>
+ * 2 - Medium vibrations<br/>
+ * 3 - Strong vibrations
+ * @hide
+ */
+ public static final String RING_VIBRATION_INTENSITY =
+ "ring_vibration_intensity";
/**
* The intensity of haptic feedback vibrations, if configurable.
@@ -4246,6 +4262,7 @@
ACCELEROMETER_ROTATION,
SHOW_BATTERY_PERCENT,
NOTIFICATION_VIBRATION_INTENSITY,
+ RING_VIBRATION_INTENSITY,
HAPTIC_FEEDBACK_INTENSITY,
DISPLAY_COLOR_MODE,
ALARM_ALERT,
@@ -4397,6 +4414,7 @@
VALIDATORS.put(MUTE_STREAMS_AFFECTED, MUTE_STREAMS_AFFECTED_VALIDATOR);
VALIDATORS.put(VIBRATE_ON, VIBRATE_ON_VALIDATOR);
VALIDATORS.put(NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
+ VALIDATORS.put(RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
VALIDATORS.put(HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
VALIDATORS.put(RINGTONE, RINGTONE_VALIDATOR);
VALIDATORS.put(NOTIFICATION_SOUND, NOTIFICATION_SOUND_VALIDATOR);
@@ -5030,10 +5048,6 @@
public static boolean putStringForUser(@NonNull ContentResolver resolver,
@NonNull String name, @Nullable String value, @Nullable String tag,
boolean makeDefault, @UserIdInt int userHandle) {
- if (LOCATION_MODE.equals(name)) {
- // Map LOCATION_MODE to underlying location provider storage API
- return setLocationModeForUser(resolver, Integer.parseInt(value), userHandle);
- }
if (MOVED_TO_GLOBAL.contains(name)) {
Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.Secure"
+ " to android.provider.Settings.Global");
@@ -5186,10 +5200,6 @@
/** @hide */
@UnsupportedAppUsage
public static int getIntForUser(ContentResolver cr, String name, int def, int userHandle) {
- if (LOCATION_MODE.equals(name)) {
- // Map from to underlying location provider storage API to location mode
- return getLocationModeForUser(cr, userHandle);
- }
String v = getStringForUser(cr, name, userHandle);
try {
return v != null ? Integer.parseInt(v) : def;
@@ -5224,10 +5234,6 @@
/** @hide */
public static int getIntForUser(ContentResolver cr, String name, int userHandle)
throws SettingNotFoundException {
- if (LOCATION_MODE.equals(name)) {
- // Map from to underlying location provider storage API to location mode
- return getLocationModeForUser(cr, userHandle);
- }
String v = getStringForUser(cr, name, userHandle);
try {
return Integer.parseInt(v);
@@ -5810,9 +5816,8 @@
* this value being present in settings.db or on ContentObserver notifications on the
* corresponding Uri.
*
- * @deprecated use {@link #LOCATION_MODE} and
- * {@link LocationManager#MODE_CHANGED_ACTION} (or
- * {@link LocationManager#PROVIDERS_CHANGED_ACTION})
+ * @deprecated Providers should not be controlled individually. See {@link #LOCATION_MODE}
+ * documentation for information on reading/writing location information.
*/
@Deprecated
public static final String LOCATION_PROVIDERS_ALLOWED = "location_providers_allowed";
@@ -5830,9 +5835,7 @@
* notifications for the corresponding Uri. Use {@link LocationManager#MODE_CHANGED_ACTION}
* to receive changes in this value.
*
- * @deprecated To check location status, use {@link LocationManager#isLocationEnabled()}. To
- * get the status of a location provider, use
- * {@link LocationManager#isProviderEnabled(String)}.
+ * @deprecated To check location mode, use {@link LocationManager#isLocationEnabled()}.
*/
@Deprecated
public static final String LOCATION_MODE = "location_mode";
@@ -5861,9 +5864,7 @@
/**
* Location access disabled.
*
- * @deprecated To check location status, use {@link LocationManager#isLocationEnabled()}. To
- * get the status of a location provider, use
- * {@link LocationManager#isProviderEnabled(String)}.
+ * @deprecated See {@link #LOCATION_MODE}.
*/
@Deprecated
public static final int LOCATION_MODE_OFF = 0;
@@ -5883,9 +5884,7 @@
* with {@link android.location.Criteria#POWER_HIGH} may be downgraded to
* {@link android.location.Criteria#POWER_MEDIUM}.
*
- * @deprecated To check location status, use {@link LocationManager#isLocationEnabled()}. To
- * get the status of a location provider, use
- * {@link LocationManager#isProviderEnabled(String)}.
+ * @deprecated See {@link #LOCATION_MODE}.
*/
@Deprecated
public static final int LOCATION_MODE_BATTERY_SAVING = 2;
@@ -5893,9 +5892,7 @@
/**
* Best-effort location computation allowed.
*
- * @deprecated To check location status, use {@link LocationManager#isLocationEnabled()}. To
- * get the status of a location provider, use
- * {@link LocationManager#isProviderEnabled(String)}.
+ * @deprecated See {@link #LOCATION_MODE}.
*/
@Deprecated
public static final int LOCATION_MODE_HIGH_ACCURACY = 3;
@@ -6019,6 +6016,15 @@
"lock_screen_allow_remote_input";
/**
+ * Indicates which clock face to show on lock screen and AOD.
+ * @hide
+ */
+ public static final String LOCK_SCREEN_CUSTOM_CLOCK_FACE = "lock_screen_custom_clock_face";
+
+ private static final Validator LOCK_SCREEN_CUSTOM_CLOCK_FACE_VALIDATOR =
+ ANY_STRING_VALIDATOR;
+
+ /**
* Set by the system to track if the user needs to see the call to action for
* the lockscreen notification policy.
* @hide
@@ -6207,7 +6213,7 @@
public static final String CHARGING_SOUNDS_ENABLED = "charging_sounds_enabled";
/**
- * Whether to vibrate for wireless charging events.
+ * Whether to vibrate for charging events.
* @hide
*/
public static final String CHARGING_VIBRATION_ENABLED = "charging_vibration_enabled";
@@ -7835,6 +7841,19 @@
BOOLEAN_VALIDATOR;
/**
+ * Whether or not face unlock always requires user confirmation, meaning {@link
+ * android.hardware.biometrics.BiometricPrompt.Builder#setRequireConfirmation(boolean)}
+ * is always 'true'. This overrides the behavior that apps choose in the
+ * setRequireConfirmation API.
+ * @hide
+ */
+ public static final String FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION =
+ "face_unlock_always_require_confirmation";
+
+ private static final Validator FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION_VALIDATOR =
+ BOOLEAN_VALIDATOR;
+
+ /**
* Whether the assist gesture should be enabled.
*
* @hide
@@ -8431,6 +8450,7 @@
AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN,
FACE_UNLOCK_KEYGUARD_ENABLED,
FACE_UNLOCK_APP_ENABLED,
+ FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
ASSIST_GESTURE_ENABLED,
ASSIST_GESTURE_SILENCE_ALERTS_ENABLED,
ASSIST_GESTURE_WAKE_ENABLED,
@@ -8448,6 +8468,7 @@
HUSH_GESTURE_USED,
IN_CALL_NOTIFICATION_ENABLED,
LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
+ LOCK_SCREEN_CUSTOM_CLOCK_FACE,
LOCK_SCREEN_SHOW_NOTIFICATIONS,
ZEN_DURATION,
SHOW_ZEN_UPGRADE_NOTIFICATION,
@@ -8584,6 +8605,8 @@
AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN_VALIDATOR);
VALIDATORS.put(FACE_UNLOCK_KEYGUARD_ENABLED, FACE_UNLOCK_KEYGUARD_ENABLED_VALIDATOR);
VALIDATORS.put(FACE_UNLOCK_APP_ENABLED, FACE_UNLOCK_APP_ENABLED_VALIDATOR);
+ VALIDATORS.put(FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
+ FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION_VALIDATOR);
VALIDATORS.put(ASSIST_GESTURE_ENABLED, ASSIST_GESTURE_ENABLED_VALIDATOR);
VALIDATORS.put(ASSIST_GESTURE_SILENCE_ALERTS_ENABLED,
ASSIST_GESTURE_SILENCE_ALERTS_ENABLED_VALIDATOR);
@@ -8624,6 +8647,7 @@
VALIDATORS.put(ASSIST_GESTURE_SETUP_COMPLETE, BOOLEAN_VALIDATOR);
VALIDATORS.put(NOTIFICATION_NEW_INTERRUPTION_MODEL, BOOLEAN_VALIDATOR);
VALIDATORS.put(TRUST_AGENTS_EXTEND_UNLOCK, TRUST_AGENTS_EXTEND_UNLOCK_VALIDATOR);
+ VALIDATORS.put(LOCK_SCREEN_CUSTOM_CLOCK_FACE, LOCK_SCREEN_CUSTOM_CLOCK_FACE_VALIDATOR);
VALIDATORS.put(LOCK_SCREEN_WHEN_TRUST_LOST, LOCK_SCREEN_WHEN_TRUST_LOST_VALIDATOR);
}
@@ -8652,14 +8676,14 @@
CLONE_TO_MANAGED_PROFILE.add(ACCESSIBILITY_ENABLED);
CLONE_TO_MANAGED_PROFILE.add(ALLOW_MOCK_LOCATION);
CLONE_TO_MANAGED_PROFILE.add(ALLOWED_GEOLOCATION_ORIGINS);
- CLONE_TO_MANAGED_PROFILE.add(DEFAULT_INPUT_METHOD);
CLONE_TO_MANAGED_PROFILE.add(ENABLED_ACCESSIBILITY_SERVICES);
- CLONE_TO_MANAGED_PROFILE.add(ENABLED_INPUT_METHODS);
CLONE_TO_MANAGED_PROFILE.add(LOCATION_CHANGER);
CLONE_TO_MANAGED_PROFILE.add(LOCATION_MODE);
CLONE_TO_MANAGED_PROFILE.add(LOCATION_PROVIDERS_ALLOWED);
CLONE_TO_MANAGED_PROFILE.add(SELECTED_INPUT_METHOD_SUBTYPE);
- if (TextServicesManager.DISABLE_PER_PROFILE_SPELL_CHECKER) {
+ if (!InputMethodSystemProperty.PER_PROFILE_IME_ENABLED) {
+ CLONE_TO_MANAGED_PROFILE.add(DEFAULT_INPUT_METHOD);
+ CLONE_TO_MANAGED_PROFILE.add(ENABLED_INPUT_METHODS);
CLONE_TO_MANAGED_PROFILE.add(SELECTED_SPELL_CHECKER);
CLONE_TO_MANAGED_PROFILE.add(SELECTED_SPELL_CHECKER_SUBTYPE);
}
@@ -8778,84 +8802,6 @@
userId);
}
}
-
- /**
- * Thread-safe method for setting the location mode to one of
- * {@link #LOCATION_MODE_HIGH_ACCURACY}, {@link #LOCATION_MODE_SENSORS_ONLY},
- * {@link #LOCATION_MODE_BATTERY_SAVING}, or {@link #LOCATION_MODE_OFF}.
- * Necessary because the mode is a composite of the underlying location provider
- * settings.
- *
- * @param cr the content resolver to use
- * @param mode such as {@link #LOCATION_MODE_HIGH_ACCURACY}
- * @param userId the userId for which to change mode
- * @return true if the value was set, false on database errors
- *
- * @throws IllegalArgumentException if mode is not one of the supported values
- *
- * @deprecated To enable/disable location, use
- * {@link LocationManager#setLocationEnabledForUser(boolean, int)}.
- * To enable/disable a specific location provider, use
- * {@link LocationManager#setProviderEnabledForUser(String, boolean, int)}.
- */
- @Deprecated
- private static boolean setLocationModeForUser(
- ContentResolver cr, int mode, int userId) {
- synchronized (mLocationSettingsLock) {
- boolean gps = false;
- boolean network = false;
- switch (mode) {
- case LOCATION_MODE_OFF:
- break;
- case LOCATION_MODE_SENSORS_ONLY:
- gps = true;
- break;
- case LOCATION_MODE_BATTERY_SAVING:
- network = true;
- break;
- case LOCATION_MODE_HIGH_ACCURACY:
- gps = true;
- network = true;
- break;
- default:
- throw new IllegalArgumentException("Invalid location mode: " + mode);
- }
-
- boolean nlpSuccess = Settings.Secure.setLocationProviderEnabledForUser(
- cr, LocationManager.NETWORK_PROVIDER, network, userId);
- boolean gpsSuccess = Settings.Secure.setLocationProviderEnabledForUser(
- cr, LocationManager.GPS_PROVIDER, gps, userId);
- return gpsSuccess && nlpSuccess;
- }
- }
-
- /**
- * Thread-safe method for reading the location mode, returns one of
- * {@link #LOCATION_MODE_HIGH_ACCURACY}, {@link #LOCATION_MODE_SENSORS_ONLY},
- * {@link #LOCATION_MODE_BATTERY_SAVING}, or {@link #LOCATION_MODE_OFF}. Necessary
- * because the mode is a composite of the underlying location provider settings.
- *
- * @param cr the content resolver to use
- * @param userId the userId for which to read the mode
- * @return the location mode
- */
- private static final int getLocationModeForUser(ContentResolver cr, int userId) {
- synchronized (mLocationSettingsLock) {
- boolean gpsEnabled = Settings.Secure.isLocationProviderEnabledForUser(
- cr, LocationManager.GPS_PROVIDER, userId);
- boolean networkEnabled = Settings.Secure.isLocationProviderEnabledForUser(
- cr, LocationManager.NETWORK_PROVIDER, userId);
- if (gpsEnabled && networkEnabled) {
- return LOCATION_MODE_HIGH_ACCURACY;
- } else if (gpsEnabled) {
- return LOCATION_MODE_SENSORS_ONLY;
- } else if (networkEnabled) {
- return LOCATION_MODE_BATTERY_SAVING;
- } else {
- return LOCATION_MODE_OFF;
- }
- }
- }
}
/**
@@ -8882,6 +8828,14 @@
public static final String ADD_USERS_WHEN_LOCKED = "add_users_when_locked";
/**
+ * Whether applying ramping ringer on incoming phone call ringtone.
+ * <p>1 = apply ramping ringer
+ * <p>0 = do not apply ramping ringer
+ * @hide
+ */
+ public static final String APPLY_RAMPING_RINGER = "apply_ramping_ringer";
+
+ /**
* Setting whether the global gesture for enabling accessibility is enabled.
* If this gesture is enabled the user will be able to perfrom it to enable
* the accessibility state without visiting the settings app.
@@ -9415,7 +9369,8 @@
"hdmi_control_auto_wakeup_enabled";
/**
- * Whether TV will also turn off other CEC devices when it goes to standby mode.
+ * Whether TV or Audio System will also turn off other CEC devices when it goes to standby
+ * mode.
* (0 = false, 1 = true)
*
* @hide
@@ -9424,6 +9379,15 @@
"hdmi_control_auto_device_off_enabled";
/**
+ * Whether Audio System will also turn off TV when it goes to standby mode.
+ * (0 = false, 1 = true)
+ *
+ * @hide
+ */
+ public static final String HDMI_CONTROL_AUTO_TV_OFF_ENABLED =
+ "hdmi_control_auto_tv_off_enabled";
+
+ /**
* If <b>true</b>, enables out-of-the-box execution for priv apps.
* Default: false
* Values: 0 = false, 1 = true
@@ -11254,14 +11218,14 @@
* quick_doze_enabled (boolean)
* </pre>
* @hide
- * @see com.android.server.power.BatterySaverPolicy
+ * @see com.android.server.power.batterysaver.BatterySaverPolicy
*/
public static final String BATTERY_SAVER_CONSTANTS = "battery_saver_constants";
/**
* Battery Saver device specific settings
* This is encoded as a key=value list, separated by commas.
- * See {@link com.android.server.power.BatterySaverPolicy} for the details.
+ * See {@link com.android.server.power.batterysaver.BatterySaverPolicy} for the details.
*
* @hide
*/
@@ -11982,12 +11946,18 @@
"angle_gl_driver_selection_values";
/**
- * Apps that are selected to use Game Update Package.
+ * List of Apps selected to use Game Update Packages.
* @hide
*/
public static final String GUP_DEV_OPT_IN_APPS = "gup_dev_opt_in_apps";
/**
+ * List of Apps selected not to use Game Update Packages.
+ * @hide
+ */
+ public static final String GUP_DEV_OPT_OUT_APPS = "gup_dev_opt_out_apps";
+
+ /**
* Apps on the black list that are forbidden to useGame Update Package.
* @hide
*/
@@ -13811,6 +13781,7 @@
* enabled (boolean)
* requires_targeting_p (boolean)
* max_squeeze_remeasure_attempts (int)
+ * edit_choices_before_sending (boolean)
* </pre>
* @see com.android.systemui.statusbar.policy.SmartReplyConstants
* @hide
@@ -13995,6 +13966,53 @@
*/
public static final String NATIVE_FLAGS_HEALTH_CHECK_ENABLED =
"native_flags_health_check_enabled";
+
+ /**
+ * Parameter for {@link #APPOP_HISTORY_PARAMETERS} that controls the mode
+ * in which the historical registry operates.
+ *
+ * @hide
+ */
+ public static final String APPOP_HISTORY_MODE = "mode";
+
+ /**
+ * Parameter for {@link #APPOP_HISTORY_PARAMETERS} that controls how long
+ * is the interval between snapshots in the base case i.e. the most recent
+ * part of the history.
+ *
+ * @hide
+ */
+ public static final String APPOP_HISTORY_BASE_INTERVAL_MILLIS = "baseIntervalMillis";
+
+ /**
+ * Parameter for {@link #APPOP_HISTORY_PARAMETERS} that controls the base
+ * for the logarithmic step when building app op history.
+ *
+ * @hide
+ */
+ public static final String APPOP_HISTORY_INTERVAL_MULTIPLIER = "intervalMultiplier";
+
+ /**
+ * Appop history parameters. These parameters are represented by
+ * a comma-delimited key-value list.
+ *
+ * The following strings are supported as keys:
+ * <pre>
+ * mode (int)
+ * baseIntervalMillis (long)
+ * intervalMultiplier (int)
+ * </pre>
+ *
+ * Ex: "enabled=true,baseIntervalMillis=1000,intervalMultiplier=10"
+ *
+ * @see #APPOP_HISTORY_MODE
+ * @see #APPOP_HISTORY_BASE_INTERVAL_MILLIS
+ * @see #APPOP_HISTORY_INTERVAL_MULTIPLIER
+ *
+ * @hide
+ */
+ public static final String APPOP_HISTORY_PARAMETERS =
+ "appop_history_parameters";
}
/**
diff --git a/core/java/android/service/appprediction/AppPredictionService.java b/core/java/android/service/appprediction/AppPredictionService.java
new file mode 100644
index 0000000..b77405a
--- /dev/null
+++ b/core/java/android/service/appprediction/AppPredictionService.java
@@ -0,0 +1,322 @@
+/*
+ * 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.
+ */
+package android.service.appprediction;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.annotation.CallSuper;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionSessionId;
+import android.app.prediction.AppTarget;
+import android.app.prediction.AppTargetEvent;
+import android.app.prediction.AppTargetId;
+import android.app.prediction.IPredictionCallback;
+import android.content.Intent;
+import android.content.pm.ParceledListSlice;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.service.appprediction.IPredictionService.Stub;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * TODO(b/111701043): Add java docs
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class AppPredictionService extends Service {
+
+ private static final String TAG = "AppPredictionService";
+
+ /**
+ * The {@link Intent} that must be declared as handled by the service.
+ * TODO(b/111701043): Add any docs about permissions the service must hold
+ *
+ * @hide
+ */
+ public static final String SERVICE_INTERFACE =
+ "android.service.appprediction.AppPredictionService";
+
+ private final ArrayMap<AppPredictionSessionId, ArrayList<CallbackWrapper>> mSessionCallbacks =
+ new ArrayMap<>();
+ private Handler mHandler;
+
+ private final IPredictionService mInterface = new Stub() {
+
+ @Override
+ public void onCreatePredictionSession(AppPredictionContext context,
+ AppPredictionSessionId sessionId) {
+ mHandler.sendMessage(
+ obtainMessage(AppPredictionService::doCreatePredictionSession,
+ AppPredictionService.this, context, sessionId));
+ }
+
+ @Override
+ public void notifyAppTargetEvent(AppPredictionSessionId sessionId, AppTargetEvent event) {
+ mHandler.sendMessage(
+ obtainMessage(AppPredictionService::onAppTargetEvent,
+ AppPredictionService.this, sessionId, event));
+ }
+
+ @Override
+ public void notifyLocationShown(AppPredictionSessionId sessionId, String launchLocation,
+ ParceledListSlice targetIds) {
+ mHandler.sendMessage(
+ obtainMessage(AppPredictionService::onLocationShown, AppPredictionService.this,
+ sessionId, launchLocation, targetIds.getList()));
+ }
+
+ @Override
+ public void sortAppTargets(AppPredictionSessionId sessionId, ParceledListSlice targets,
+ IPredictionCallback callback) {
+ mHandler.sendMessage(
+ obtainMessage(AppPredictionService::onSortAppTargets,
+ AppPredictionService.this, sessionId, targets.getList(), null,
+ new CallbackWrapper(callback)));
+ }
+
+ @Override
+ public void registerPredictionUpdates(AppPredictionSessionId sessionId,
+ IPredictionCallback callback) {
+ mHandler.sendMessage(
+ obtainMessage(AppPredictionService::doRegisterPredictionUpdates,
+ AppPredictionService.this, sessionId, callback));
+ }
+
+ @Override
+ public void unregisterPredictionUpdates(AppPredictionSessionId sessionId,
+ IPredictionCallback callback) {
+ mHandler.sendMessage(
+ obtainMessage(AppPredictionService::doUnregisterPredictionUpdates,
+ AppPredictionService.this, sessionId, callback));
+ }
+
+ @Override
+ public void requestPredictionUpdate(AppPredictionSessionId sessionId) {
+ mHandler.sendMessage(
+ obtainMessage(AppPredictionService::doRequestPredictionUpdate,
+ AppPredictionService.this, sessionId));
+ }
+
+ @Override
+ public void onDestroyPredictionSession(AppPredictionSessionId sessionId) {
+ mHandler.sendMessage(
+ obtainMessage(AppPredictionService::doDestroyPredictionSession,
+ AppPredictionService.this, sessionId));
+ }
+ };
+
+ @CallSuper
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mHandler = new Handler(Looper.getMainLooper(), null, true);
+ }
+
+ @Override
+ public final IBinder onBind(Intent intent) {
+ return mInterface.asBinder();
+ }
+
+ /**
+ * Called by a client app to indicate a target launch
+ */
+ @MainThread
+ public abstract void onAppTargetEvent(@NonNull AppPredictionSessionId sessionId,
+ @NonNull AppTargetEvent event);
+
+ /**
+ * Called by a client app to indication a particular location has been shown to the user.
+ */
+ @MainThread
+ public abstract void onLocationShown(@NonNull AppPredictionSessionId sessionId,
+ @NonNull String launchLocation, @NonNull List<AppTargetId> targetIds);
+
+ private void doCreatePredictionSession(@NonNull AppPredictionContext context,
+ @NonNull AppPredictionSessionId sessionId) {
+ mSessionCallbacks.put(sessionId, new ArrayList<>());
+ onCreatePredictionSession(context, sessionId);
+ }
+
+ /**
+ * Creates a new interaction session.
+ *
+ * @param context interaction context
+ * @param sessionId the session's Id
+ */
+ public void onCreatePredictionSession(@NonNull AppPredictionContext context,
+ @NonNull AppPredictionSessionId sessionId) {}
+
+ /**
+ * Called by the client app to request sorting of targets based on prediction rank.
+ * TODO(b/111701043): Implement CancellationSignal so caller can cancel a long running request
+ */
+ @MainThread
+ public abstract void onSortAppTargets(@NonNull AppPredictionSessionId sessionId,
+ @NonNull List<AppTarget> targets, @NonNull CancellationSignal cancellationSignal,
+ @NonNull Consumer<List<AppTarget>> callback);
+
+ private void doRegisterPredictionUpdates(@NonNull AppPredictionSessionId sessionId,
+ @NonNull IPredictionCallback callback) {
+ final ArrayList<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId);
+ if (callbacks == null) {
+ Slog.e(TAG, "Failed to register for updates for unknown session: " + sessionId);
+ return;
+ }
+
+ final CallbackWrapper wrapper = findCallbackWrapper(callbacks, callback);
+ if (wrapper == null) {
+ callbacks.add(new CallbackWrapper(callback));
+ if (callbacks.size() == 1) {
+ onStartPredictionUpdates();
+ }
+ }
+ }
+
+ /**
+ * Called when any continuous prediction callback is registered.
+ */
+ @MainThread
+ public void onStartPredictionUpdates() {}
+
+ private void doUnregisterPredictionUpdates(@NonNull AppPredictionSessionId sessionId,
+ @NonNull IPredictionCallback callback) {
+ final ArrayList<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId);
+ if (callbacks == null) {
+ Slog.e(TAG, "Failed to unregister for updates for unknown session: " + sessionId);
+ return;
+ }
+
+ final CallbackWrapper wrapper = findCallbackWrapper(callbacks, callback);
+ if (wrapper != null) {
+ callbacks.remove(wrapper);
+ if (callbacks.isEmpty()) {
+ onStopPredictionUpdates();
+ }
+ }
+ }
+
+ /**
+ * Called when there are no longer any continuous prediction callbacks registered.
+ */
+ @MainThread
+ public void onStopPredictionUpdates() {}
+
+ private void doRequestPredictionUpdate(@NonNull AppPredictionSessionId sessionId) {
+ final ArrayList<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId);
+ if (callbacks != null && !callbacks.isEmpty()) {
+ onRequestPredictionUpdate(sessionId);
+ }
+ }
+
+ /**
+ * Called by the client app to request target predictions. This method is only called if there
+ * are one or more prediction callbacks registered.
+ * TODO(b/111701043): Add java docs
+ *
+ * @see #updatePredictions(AppPredictionSessionId, List)
+ */
+ @MainThread
+ public abstract void onRequestPredictionUpdate(@NonNull AppPredictionSessionId sessionId);
+
+ private void doDestroyPredictionSession(@NonNull AppPredictionSessionId sessionId) {
+ mSessionCallbacks.remove(sessionId);
+ onDestroyPredictionSession(sessionId);
+ }
+
+ /**
+ * Destroys the interaction session.
+ *
+ * @param sessionId the id of the session to destroy
+ */
+ @MainThread
+ public void onDestroyPredictionSession(@NonNull AppPredictionSessionId sessionId) {}
+
+ /**
+ * Used by the prediction factory to send back results the client app. The can be called
+ * in response to {@link #onRequestPredictionUpdate(AppPredictionSessionId)} or proactively as
+ * a result of changes in predictions.
+ */
+ public final void updatePredictions(@NonNull AppPredictionSessionId sessionId,
+ @NonNull List<AppTarget> targets) {
+ List<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId);
+ if (callbacks != null) {
+ for (CallbackWrapper callback : callbacks) {
+ callback.accept(targets);
+ }
+ }
+ }
+
+ /**
+ * Finds the callback wrapper for the given callback.
+ */
+ private CallbackWrapper findCallbackWrapper(ArrayList<CallbackWrapper> callbacks,
+ IPredictionCallback callback) {
+ for (int i = callbacks.size() - 1; i >= 0; i--) {
+ if (callbacks.get(i).isCallback(callback)) {
+ return callbacks.get(i);
+ }
+ }
+ return null;
+ }
+
+ private static final class CallbackWrapper implements Consumer<List<AppTarget>>,
+ IBinder.DeathRecipient {
+
+ private IPredictionCallback mCallback;
+
+ CallbackWrapper(IPredictionCallback callback) {
+ mCallback = callback;
+ try {
+ mCallback.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to link to death: " + e);
+ }
+ }
+
+ public boolean isCallback(@NonNull IPredictionCallback callback) {
+ return mCallback.equals(callback);
+ }
+
+ @Override
+ public void accept(List<AppTarget> ts) {
+ try {
+ if (mCallback != null) {
+ mCallback.onResult(new ParceledListSlice(ts));
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending result:" + e);
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ mCallback = null;
+ }
+ }
+}
diff --git a/core/java/android/service/appprediction/IPredictionService.aidl b/core/java/android/service/appprediction/IPredictionService.aidl
new file mode 100644
index 0000000..3a6d166
--- /dev/null
+++ b/core/java/android/service/appprediction/IPredictionService.aidl
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+package android.service.appprediction;
+
+import android.app.prediction.AppTarget;
+import android.app.prediction.AppTargetEvent;
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionSessionId;
+import android.app.prediction.IPredictionCallback;
+import android.content.pm.ParceledListSlice;
+
+/**
+ * Interface from the system to a prediction service.
+ *
+ * @hide
+ */
+oneway interface IPredictionService {
+
+ void onCreatePredictionSession(in AppPredictionContext context,
+ in AppPredictionSessionId sessionId);
+
+ void notifyAppTargetEvent(in AppPredictionSessionId sessionId, in AppTargetEvent event);
+
+ void notifyLocationShown(in AppPredictionSessionId sessionId, in String launchLocation,
+ in ParceledListSlice targetIds);
+
+ void sortAppTargets(in AppPredictionSessionId sessionId, in ParceledListSlice targets,
+ in IPredictionCallback callback);
+
+ void registerPredictionUpdates(in AppPredictionSessionId sessionId,
+ in IPredictionCallback callback);
+
+ void unregisterPredictionUpdates(in AppPredictionSessionId sessionId,
+ in IPredictionCallback callback);
+
+ void requestPredictionUpdate(in AppPredictionSessionId sessionId);
+
+ void onDestroyPredictionSession(in AppPredictionSessionId sessionId);
+}
diff --git a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
index 7683b8a..a9f4034 100644
--- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
+++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
@@ -214,7 +214,7 @@
}
@Override
- protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ protected final void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mAutofillProxies != null) {
final int size = mAutofillProxies.size();
pw.print("Number proxies: "); pw.println(size);
@@ -225,6 +225,15 @@
proxy.dump(" ", pw);
}
}
+ dump(pw, args);
+ }
+
+ /**
+ * Implementation specific {@code dump}.
+ */
+ protected void dump(@NonNull PrintWriter pw,
+ @SuppressWarnings("unused") @NonNull String[] args) {
+ pw.print(getClass().getName()); pw.println(": nothing to dump");
}
/** @hide */
diff --git a/core/java/android/service/contentcapture/ContentCaptureEventsRequest.java b/core/java/android/service/contentcapture/ContentCaptureEventsRequest.java
index 784e719..db242a2 100644
--- a/core/java/android/service/contentcapture/ContentCaptureEventsRequest.java
+++ b/core/java/android/service/contentcapture/ContentCaptureEventsRequest.java
@@ -34,7 +34,7 @@
@SystemApi
@Deprecated
public final class ContentCaptureEventsRequest implements Parcelable {
-// TODO(b/121033016): remove .java and .aidl once service implementation doesn't use it anymore
+// TODO(b/121051220): remove .java and .aidl once service implementation doesn't use it anymore
private final ContentCaptureEvent mEvent;
diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java
index c9d46dd..e5e028d 100644
--- a/core/java/android/service/contentcapture/ContentCaptureService.java
+++ b/core/java/android/service/contentcapture/ContentCaptureService.java
@@ -42,6 +42,7 @@
import android.view.contentcapture.ContentCaptureSessionId;
import android.view.contentcapture.IContentCaptureDirectManager;
import android.view.contentcapture.MainContentCaptureSession;
+import android.view.contentcapture.UserDataRemovalRequest;
import com.android.internal.os.IResultReceiver;
@@ -123,12 +124,12 @@
};
/**
- * List of sessions per UID.
+ * UIDs associated with each session.
*
* <p>This map is populated when an session is started, which is called by the system server
* and can be trusted. Then subsequent calls made by the app are verified against this map.
*/
- private final ArrayMap<String, Integer> mSessionsByUid = new ArrayMap<>();
+ private final ArrayMap<String, Integer> mSessionUids = new ArrayMap<>();
@CallSuper
@Override
@@ -165,7 +166,7 @@
*/
public final void setContentCaptureWhitelist(@Nullable List<String> packages,
@Nullable List<ComponentName> activities) {
- //TODO(b/111276913): implement
+ //TODO(b/122595322): implement
}
/**
@@ -176,7 +177,7 @@
*/
public final void setActivityContentCaptureEnabled(@NonNull ComponentName activity,
boolean enabled) {
- //TODO(b/111276913): implement
+ //TODO(b/122595322): implement
}
/**
@@ -187,7 +188,7 @@
*/
public final void setPackageContentCaptureEnabled(@NonNull String packageName,
boolean enabled) {
- //TODO(b/111276913): implement
+ //TODO(b/122595322): implement
}
/**
@@ -196,7 +197,7 @@
*/
@NonNull
public final Set<ComponentName> getContentCaptureDisabledActivities() {
- //TODO(b/111276913): implement
+ //TODO(b/122595322): implement
return null;
}
@@ -206,7 +207,7 @@
*/
@NonNull
public final Set<String> getContentCaptureDisabledPackages() {
- //TODO(b/111276913): implement
+ //TODO(b/122595322): implement
return null;
}
@@ -255,6 +256,16 @@
if (VERBOSE) Log.v(TAG, "onContentCaptureEventsRequest(id=" + sessionId + ")");
onContentCaptureEventsRequest(sessionId, new ContentCaptureEventsRequest(event));
}
+
+ /**
+ * Notifies the service that the app requested to remove data associated with the user.
+ *
+ * @param request the user data requested to be removed
+ */
+ public void onUserDataRemovalRequest(@NonNull UserDataRemovalRequest request) {
+ if (VERBOSE) Log.v(TAG, "onUserDataRemovalRequest()");
+ }
+
/**
* Notifies the service of {@link SnapshotData snapshot data} associated with a session.
*
@@ -285,13 +296,13 @@
@Override
@CallSuper
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- final int size = mSessionsByUid.size();
+ final int size = mSessionUids.size();
pw.print("Number sessions: "); pw.println(size);
if (size > 0) {
final String prefix = " ";
for (int i = 0; i < size; i++) {
- pw.print(prefix); pw.print(mSessionsByUid.keyAt(i));
- pw.print(": uid="); pw.println(mSessionsByUid.valueAt(i));
+ pw.print(prefix); pw.print(mSessionUids.keyAt(i));
+ pw.print(": uid="); pw.println(mSessionUids.valueAt(i));
}
}
}
@@ -309,8 +320,16 @@
private void handleOnCreateSession(@NonNull ContentCaptureContext context,
@NonNull String sessionId, int uid, IResultReceiver clientReceiver) {
- mSessionsByUid.put(sessionId, uid);
+ mSessionUids.put(sessionId, uid);
onCreateContentCaptureSession(context, new ContentCaptureSessionId(sessionId));
+
+ final int flags = context.getFlags();
+ if ((flags & ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE) != 0) {
+ setClientState(clientReceiver, ContentCaptureSession.STATE_DISABLED_BY_FLAG_SECURE,
+ mClientInterface.asBinder());
+ return;
+ }
+
setClientState(clientReceiver, ContentCaptureSession.STATE_ACTIVE,
mClientInterface.asBinder());
}
@@ -336,11 +355,11 @@
case ContentCaptureEvent.TYPE_SESSION_STARTED:
final ContentCaptureContext clientContext = event.getClientContext();
clientContext.setParentSessionId(event.getParentSessionId());
- mSessionsByUid.put(sessionIdString, uid);
+ mSessionUids.put(sessionIdString, uid);
onCreateContentCaptureSession(clientContext, sessionId);
break;
case ContentCaptureEvent.TYPE_SESSION_FINISHED:
- mSessionsByUid.remove(sessionIdString);
+ mSessionUids.remove(sessionIdString);
onDestroyContentCaptureSession(sessionId);
break;
default:
@@ -355,7 +374,7 @@
}
private void handleFinishSession(@NonNull String sessionId) {
- mSessionsByUid.remove(sessionId);
+ mSessionUids.remove(sessionId);
onDestroyContentCaptureSession(new ContentCaptureSessionId(sessionId));
}
@@ -372,10 +391,13 @@
default:
sessionId = event.getSessionId();
}
- final Integer rightUid = mSessionsByUid.get(sessionId);
+ final Integer rightUid = mSessionUids.get(sessionId);
if (rightUid == null) {
- if (VERBOSE) Log.v(TAG, "No session for " + sessionId);
- // Just ignore, as the session could have finished
+ if (DEBUG) {
+ Log.d(TAG, "handleIsRightCallerFor(" + event + "): no session for " + sessionId
+ + ": " + mSessionUids);
+ }
+ // Just ignore, as the session could have been finished already
return false;
}
if (rightUid != uid) {
@@ -389,15 +411,16 @@
}
/**
- * Sends the state of the {@link ContentCaptureManager} in the cleint app.
+ * Sends the state of the {@link ContentCaptureManager} in the client app.
*
* @param clientReceiver receiver in the client app.
+ * @param sessionState state of the session
* @param binder handle to the {@code IContentCaptureDirectManager} object that resides in the
* service.
* @hide
*/
public static void setClientState(@NonNull IResultReceiver clientReceiver,
- int sessionStatus, @Nullable IBinder binder) {
+ int sessionState, @Nullable IBinder binder) {
try {
final Bundle extras;
if (binder != null) {
@@ -406,7 +429,7 @@
} else {
extras = null;
}
- clientReceiver.send(sessionStatus, extras);
+ clientReceiver.send(sessionState, extras);
} catch (RemoteException e) {
Slog.w(TAG, "Error async reporting result to client: " + e);
}
diff --git a/core/java/android/service/contentsuggestions/ContentSuggestionsService.java b/core/java/android/service/contentsuggestions/ContentSuggestionsService.java
new file mode 100644
index 0000000..0da8039
--- /dev/null
+++ b/core/java/android/service/contentsuggestions/ContentSuggestionsService.java
@@ -0,0 +1,154 @@
+/*
+ * 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.
+ */
+
+package android.service.contentsuggestions;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.app.contentsuggestions.ClassificationsRequest;
+import android.app.contentsuggestions.ContentSuggestionsManager;
+import android.app.contentsuggestions.IClassificationsCallback;
+import android.app.contentsuggestions.ISelectionsCallback;
+import android.app.contentsuggestions.SelectionsRequest;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.GraphicBuffer;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Slog;
+
+/**
+ * @hide
+ */
+@SystemApi
+public abstract class ContentSuggestionsService extends Service {
+
+ private static final String TAG = ContentSuggestionsService.class.getSimpleName();
+
+ private Handler mHandler;
+
+ /**
+ * The action for the intent used to define the content suggestions service.
+ */
+ public static final String SERVICE_INTERFACE =
+ "android.service.contentsuggestions.ContentSuggestionsService";
+
+ private final IContentSuggestionsService mInterface = new IContentSuggestionsService.Stub() {
+ @Override
+ public void provideContextImage(int taskId, GraphicBuffer contextImage,
+ Bundle imageContextRequestExtras) {
+ mHandler.sendMessage(
+ obtainMessage(ContentSuggestionsService::processContextImage,
+ ContentSuggestionsService.this, taskId,
+ Bitmap.createHardwareBitmap(contextImage),
+ imageContextRequestExtras));
+ }
+
+ @Override
+ public void suggestContentSelections(SelectionsRequest request,
+ ISelectionsCallback callback) {
+ mHandler.sendMessage(obtainMessage(ContentSuggestionsService::suggestContentSelections,
+ ContentSuggestionsService.this, request, wrapSelectionsCallback(callback)));
+
+ }
+
+ @Override
+ public void classifyContentSelections(ClassificationsRequest request,
+ IClassificationsCallback callback) {
+ mHandler.sendMessage(obtainMessage(ContentSuggestionsService::classifyContentSelections,
+ ContentSuggestionsService.this, request, wrapClassificationCallback(callback)));
+ }
+
+ @Override
+ public void notifyInteraction(String requestId, Bundle interaction) {
+ mHandler.sendMessage(
+ obtainMessage(ContentSuggestionsService::notifyInteraction,
+ ContentSuggestionsService.this, requestId, interaction));
+ }
+ };
+
+ @CallSuper
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mHandler = new Handler(Looper.getMainLooper(), null, true);
+ }
+
+ /** @hide */
+ @Override
+ public final IBinder onBind(Intent intent) {
+ if (SERVICE_INTERFACE.equals(intent.getAction())) {
+ return mInterface.asBinder();
+ }
+ Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent);
+ return null;
+ }
+
+ /**
+ * Called by the system to provide the snapshot for the task associated with the given
+ * {@param taskId}.
+ */
+ public abstract void processContextImage(
+ int taskId, @Nullable Bitmap contextImage, @NonNull Bundle extras);
+
+ /**
+ * Called by a client app to make a request for content selections.
+ */
+ public abstract void suggestContentSelections(@NonNull SelectionsRequest request,
+ @NonNull ContentSuggestionsManager.SelectionsCallback callback);
+
+ /**
+ * Called by a client app to classify the provided content selections.
+ */
+ public abstract void classifyContentSelections(@NonNull ClassificationsRequest request,
+ @NonNull ContentSuggestionsManager.ClassificationsCallback callback);
+
+ /**
+ * Called by a client app to report an interaction.
+ */
+ public abstract void notifyInteraction(@NonNull String requestId, @NonNull Bundle interaction);
+
+ private ContentSuggestionsManager.SelectionsCallback wrapSelectionsCallback(
+ ISelectionsCallback callback) {
+ return (statusCode, selections) -> {
+ try {
+ callback.onContentSelectionsAvailable(statusCode, selections);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending result: " + e);
+ }
+ };
+ }
+
+ private ContentSuggestionsManager.ClassificationsCallback wrapClassificationCallback(
+ IClassificationsCallback callback) {
+ return ((statusCode, classifications) -> {
+ try {
+ callback.onContentClassificationsAvailable(statusCode, classifications);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending result: " + e);
+ }
+ });
+ }
+}
diff --git a/core/java/android/service/contentsuggestions/IContentSuggestionsService.aidl b/core/java/android/service/contentsuggestions/IContentSuggestionsService.aidl
new file mode 100644
index 0000000..1926478
--- /dev/null
+++ b/core/java/android/service/contentsuggestions/IContentSuggestionsService.aidl
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+package android.service.contentsuggestions;
+
+import android.app.contentsuggestions.IClassificationsCallback;
+import android.app.contentsuggestions.ISelectionsCallback;
+import android.app.contentsuggestions.ClassificationsRequest;
+import android.app.contentsuggestions.SelectionsRequest;
+import android.graphics.GraphicBuffer;
+import android.os.Bundle;
+
+/**
+ * Interface from the system to an implementation of a content suggestions service.
+ *
+ * @hide
+ */
+oneway interface IContentSuggestionsService {
+ void provideContextImage(
+ int taskId,
+ in GraphicBuffer contextImage,
+ in Bundle imageContextRequestExtras);
+ void suggestContentSelections(
+ in SelectionsRequest request,
+ in ISelectionsCallback callback);
+ void classifyContentSelections(
+ in ClassificationsRequest request,
+ in IClassificationsCallback callback);
+ void notifyInteraction(in String requestId, in Bundle interaction);
+}
diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl
index be3f3b3..b84e6c9 100644
--- a/core/java/android/service/dreams/IDreamManager.aidl
+++ b/core/java/android/service/dreams/IDreamManager.aidl
@@ -33,4 +33,5 @@
void finishSelf(in IBinder token, boolean immediate);
void startDozing(in IBinder token, int screenState, int screenBrightness);
void stopDozing(in IBinder token);
+ void forceAmbientDisplayEnabled(boolean enabled);
}
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index dd0242a..fd2b0fae 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -185,7 +185,7 @@
* Implement this to know when a direct reply is sent from a notification.
* @param key the notification key
*/
- public void onNotificationDirectReply(@NonNull String key) {}
+ public void onNotificationDirectReplied(@NonNull String key) {}
/**
* Implement this to know when a suggested reply is sent.
@@ -407,7 +407,7 @@
SomeArgs args = (SomeArgs) msg.obj;
String key = (String) args.arg1;
args.recycle();
- onNotificationDirectReply(key);
+ onNotificationDirectReplied(key);
break;
}
case MSG_ON_SUGGESTED_REPLY_SENT: {
diff --git a/core/java/android/service/wallpaper/IWallpaperConnection.aidl b/core/java/android/service/wallpaper/IWallpaperConnection.aidl
index a976d0e..f334d9d 100644
--- a/core/java/android/service/wallpaper/IWallpaperConnection.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperConnection.aidl
@@ -27,5 +27,5 @@
void attachEngine(IWallpaperEngine engine, int displayId);
void engineShown(IWallpaperEngine engine);
ParcelFileDescriptor setWallpaper(String name);
- void onWallpaperColorsChanged(in WallpaperColors colors);
+ void onWallpaperColorsChanged(in WallpaperColors colors, int displayId);
}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index a011d67..0d7223d 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -56,6 +56,7 @@
import android.view.InputEventReceiver;
import android.view.InsetsState;
import android.view.MotionEvent;
+import android.view.SurfaceControl;
import android.view.SurfaceHolder;
import android.view.View;
import android.view.ViewGroup;
@@ -217,6 +218,8 @@
private Context mDisplayContext;
private int mDisplayState;
+ SurfaceControl mSurfaceControl = new SurfaceControl();
+
final BaseSurfaceHolder mSurfaceHolder = new BaseSurfaceHolder() {
{
mRequestedFormat = PixelFormat.RGBX_8888;
@@ -642,7 +645,7 @@
try {
final WallpaperColors newColors = onComputeColors();
if (mConnection != null) {
- mConnection.onWallpaperColorsChanged(newColors);
+ mConnection.onWallpaperColorsChanged(newColors, mDisplay.getDisplayId());
} else {
Log.w(TAG, "Can't notify system because wallpaper connection "
+ "was not established.");
@@ -843,8 +846,12 @@
mWindow, mWindow.mSeq, mLayout, mWidth, mHeight,
View.VISIBLE, 0, -1, mWinFrame, mOverscanInsets, mContentInsets,
mVisibleInsets, mStableInsets, mOutsets, mBackdropFrame,
- mDisplayCutout, mMergedConfiguration, mSurfaceHolder.mSurface,
+ mDisplayCutout, mMergedConfiguration, mSurfaceControl,
mInsetsState);
+ if (mSurfaceControl.isValid()) {
+ mSurfaceHolder.mSurface.copyFrom(mSurfaceControl);
+ mSurfaceControl.release();
+ }
if (DEBUG) Log.v(TAG, "New surface: " + mSurfaceHolder.mSurface
+ ", frame=" + mWinFrame);
@@ -963,7 +970,7 @@
mFinalSystemInsets.set(mDispatchedOverscanInsets);
mFinalStableInsets.set(mDispatchedStableInsets);
WindowInsets insets = new WindowInsets(mFinalSystemInsets,
- null, mFinalStableInsets,
+ mFinalStableInsets,
getResources().getConfiguration().isScreenRound(), false,
mDispatchedDisplayCutout);
if (DEBUG) {
@@ -1466,7 +1473,7 @@
break;
}
try {
- mConnection.onWallpaperColorsChanged(mEngine.onComputeColors());
+ mConnection.onWallpaperColorsChanged(mEngine.onComputeColors(), mDisplayId);
} catch (RemoteException e) {
// Connection went away, nothing to do in here.
}
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 3d0c662..24d746e 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -376,9 +376,13 @@
* Set paragraph justification mode. The default value is
* {@link Layout#JUSTIFICATION_MODE_NONE}. If the last line is too short for justification,
* the last line will be displayed with the alignment set by {@link #setAlignment}.
+ * When Justification mode is JUSTIFICATION_MODE_INTER_WORD, wordSpacing on the given
+ * {@link Paint} will be ignored. This behavior also affects Spans which change the
+ * wordSpacing.
*
* @param justificationMode justification mode for the paragraph.
* @return this builder, useful for chaining.
+ * @see Paint#setWordSpacing(float)
*/
@NonNull
public Builder setJustificationMode(@JustificationMode int justificationMode) {
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 6eb433a..949328f 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -75,8 +75,9 @@
private int mEllipsisEnd;
// Additional width of whitespace for justification. This value is per whitespace, thus
- // the line width will increase by mAddedWidth x (number of stretchable whitespaces).
- private float mAddedWidth;
+ // the line width will increase by mAddedWidthForJustify x (number of stretchable whitespaces).
+ private float mAddedWidthForJustify;
+ private boolean mIsJustifying;
private final TextPaint mWorkPaint = new TextPaint();
private final TextPaint mActivePaint = new TextPaint();
@@ -229,7 +230,8 @@
}
}
mTabs = tabStops;
- mAddedWidth = 0;
+ mAddedWidthForJustify = 0;
+ mIsJustifying = false;
mEllipsisStart = ellipsisStart != ellipsisEnd ? ellipsisStart : 0;
mEllipsisEnd = ellipsisStart != ellipsisEnd ? ellipsisEnd : 0;
@@ -255,7 +257,8 @@
return;
}
final float width = Math.abs(measure(end, false, null));
- mAddedWidth = (justifyWidth - width) / spaces;
+ mAddedWidthForJustify = (justifyWidth - width) / spaces;
+ mIsJustifying = true;
}
/**
@@ -713,7 +716,9 @@
TextPaint wp = mWorkPaint;
wp.set(mPaint);
- wp.setWordSpacing(mAddedWidth);
+ if (mIsJustifying) {
+ wp.setWordSpacing(mAddedWidthForJustify);
+ }
int spanStart = runStart;
int spanLimit;
@@ -849,7 +854,9 @@
FontMetricsInt fmi, boolean needWidth, int offset,
@Nullable ArrayList<DecorationInfo> decorations) {
- wp.setWordSpacing(mAddedWidth);
+ if (mIsJustifying) {
+ wp.setWordSpacing(mAddedWidthForJustify);
+ }
// Get metrics first (even for empty strings or "0" width runs)
if (fmi != null) {
expandMetricsFromPaint(fmi, wp);
diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java
index 433483f7..7a58681 100644
--- a/core/java/android/text/style/SuggestionSpan.java
+++ b/core/java/android/text/style/SuggestionSpan.java
@@ -16,6 +16,7 @@
package android.text.style;
+import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
@@ -370,6 +371,7 @@
/**
* @return The color of the underline for that span, or 0 if there is no underline
*/
+ @ColorInt
public int getUnderlineColor() {
// The order here should match what is used in updateDrawState
final boolean misspelled = (mFlags & FLAG_MISSPELLED) != 0;
diff --git a/core/java/android/util/DebugUtils.java b/core/java/android/util/DebugUtils.java
index af73a16..265b0d3 100644
--- a/core/java/android/util/DebugUtils.java
+++ b/core/java/android/util/DebugUtils.java
@@ -18,6 +18,7 @@
import android.annotation.UnsupportedAppUsage;
import android.os.Build;
+
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
@@ -250,7 +251,7 @@
if (value == 0 && flagsWasZero) {
return constNameWithoutPrefix(prefix, field);
}
- if ((flags & value) != 0) {
+ if ((flags & value) == value) {
flags &= ~value;
res.append(constNameWithoutPrefix(prefix, field)).append('|');
}
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 8b97e0e..0edcb3d 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -22,7 +22,9 @@
import android.text.TextUtils;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
+import java.util.Set;
/**
* Util class to get feature flag information.
@@ -37,8 +39,11 @@
public static final String HEARING_AID_SETTINGS = "settings_bluetooth_hearing_aid";
public static final String SAFETY_HUB = "settings_safety_hub";
public static final String SCREENRECORD_LONG_PRESS = "settings_screenrecord_long_press";
+ public static final String AOD_IMAGEWALLPAPER_ENABLED = "settings_aod_imagewallpaper_enabled";
private static final Map<String, String> DEFAULT_FLAGS;
+ private static final Set<String> OBSERVABLE_FLAGS;
+
static {
DEFAULT_FLAGS = new HashMap<>();
DEFAULT_FLAGS.put("settings_audio_switcher", "true");
@@ -54,6 +59,10 @@
DEFAULT_FLAGS.put(HEARING_AID_SETTINGS, "false");
DEFAULT_FLAGS.put(SAFETY_HUB, "false");
DEFAULT_FLAGS.put(SCREENRECORD_LONG_PRESS, "false");
+ DEFAULT_FLAGS.put(AOD_IMAGEWALLPAPER_ENABLED, "false");
+
+ OBSERVABLE_FLAGS = new HashSet<>();
+ OBSERVABLE_FLAGS.add(AOD_IMAGEWALLPAPER_ENABLED);
}
/**
@@ -90,6 +99,16 @@
*/
public static void setEnabled(Context context, String feature, boolean enabled) {
SystemProperties.set(FFLAG_OVERRIDE_PREFIX + feature, enabled ? "true" : "false");
+
+ // Also update Settings.Global if needed so that we can observe it via observer.
+ if (OBSERVABLE_FLAGS.contains(feature)) {
+ setObservableFlag(context, feature, enabled);
+ }
+ }
+
+ private static void setObservableFlag(Context context, String feature, boolean enabled) {
+ Settings.Global.putString(
+ context.getContentResolver(), feature, enabled ? "true" : "false");
}
/**
diff --git a/core/java/android/util/KeyValueListParser.java b/core/java/android/util/KeyValueListParser.java
index 7eef63e..d051ed8 100644
--- a/core/java/android/util/KeyValueListParser.java
+++ b/core/java/android/util/KeyValueListParser.java
@@ -16,7 +16,9 @@
package android.util;
import android.text.TextUtils;
+import android.util.proto.ProtoOutputStream;
+import java.io.PrintWriter;
import java.time.Duration;
import java.time.format.DateTimeParseException;
@@ -212,4 +214,163 @@
}
return def;
}
+
+ /** Represents an integer config value. */
+ public static class IntValue {
+ private final String mKey;
+ private final int mDefaultValue;
+ private int mValue;
+
+ /** Constructor, initialize with a config key and a default value. */
+ public IntValue(String key, int defaultValue) {
+ mKey = key;
+ mDefaultValue = defaultValue;
+ mValue = mDefaultValue;
+ }
+
+ /** Read a value from {@link KeyValueListParser} */
+ public void parse(KeyValueListParser parser) {
+ mValue = parser.getInt(mKey, mDefaultValue);
+ }
+
+ /** Return the config key. */
+ public String getKey() {
+ return mKey;
+ }
+
+ /** Return the default value. */
+ public int getDefaultValue() {
+ return mDefaultValue;
+ }
+
+ /** Return the actual config value. */
+ public int getValue() {
+ return mValue;
+ }
+
+ /** Overwrites with a value. */
+ public void setValue(int value) {
+ mValue = value;
+ }
+
+ /** Used for dumpsys */
+ public void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix);
+ pw.print(mKey);
+ pw.print("=");
+ pw.print(mValue);
+ pw.println();
+ }
+
+ /** Used for proto dumpsys */
+ public void dumpProto(ProtoOutputStream proto, long tag) {
+ proto.write(tag, mValue);
+ }
+ }
+
+ /** Represents an long config value. */
+ public static class LongValue {
+ private final String mKey;
+ private final long mDefaultValue;
+ private long mValue;
+
+ /** Constructor, initialize with a config key and a default value. */
+ public LongValue(String key, long defaultValue) {
+ mKey = key;
+ mDefaultValue = defaultValue;
+ mValue = mDefaultValue;
+ }
+
+ /** Read a value from {@link KeyValueListParser} */
+ public void parse(KeyValueListParser parser) {
+ mValue = parser.getLong(mKey, mDefaultValue);
+ }
+
+ /** Return the config key. */
+ public String getKey() {
+ return mKey;
+ }
+
+ /** Return the default value. */
+ public long getDefaultValue() {
+ return mDefaultValue;
+ }
+
+ /** Return the actual config value. */
+ public long getValue() {
+ return mValue;
+ }
+
+ /** Overwrites with a value. */
+ public void setValue(long value) {
+ mValue = value;
+ }
+
+ /** Used for dumpsys */
+ public void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix);
+ pw.print(mKey);
+ pw.print("=");
+ pw.print(mValue);
+ pw.println();
+ }
+
+ /** Used for proto dumpsys */
+ public void dumpProto(ProtoOutputStream proto, long tag) {
+ proto.write(tag, mValue);
+ }
+ }
+
+ /** Represents an string config value. */
+ public static class StringValue {
+ private final String mKey;
+ private final String mDefaultValue;
+ private String mValue;
+
+ /** Constructor, initialize with a config key and a default value. */
+ public StringValue(String key, String defaultValue) {
+ mKey = key;
+ mDefaultValue = defaultValue;
+ mValue = mDefaultValue;
+ }
+
+ /** Read a value from {@link KeyValueListParser} */
+ public void parse(KeyValueListParser parser) {
+ mValue = parser.getString(mKey, mDefaultValue);
+ }
+
+ /** Return the config key. */
+ public String getKey() {
+ return mKey;
+ }
+
+ /** Return the default value. */
+ public String getDefaultValue() {
+ return mDefaultValue;
+ }
+
+ /** Return the actual config value. */
+ public String getValue() {
+ return mValue;
+ }
+
+ /** Overwrites with a value. */
+ public void setValue(String value) {
+ mValue = value;
+ }
+
+ /** Used for dumpsys */
+ public void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix);
+ pw.print(mKey);
+ pw.print("=");
+ pw.print(mValue);
+ pw.println();
+ }
+
+ /** Used for proto dumpsys */
+ public void dumpProto(ProtoOutputStream proto, long tag) {
+ proto.write(tag, mValue);
+ }
+ }
}
diff --git a/core/java/android/view/AccessibilityIterators.java b/core/java/android/view/AccessibilityIterators.java
index 9f7560c..54cfc00 100644
--- a/core/java/android/view/AccessibilityIterators.java
+++ b/core/java/android/view/AccessibilityIterators.java
@@ -147,6 +147,9 @@
@Override
public void onConfigurationChanged(Configuration globalConfig) {
final Locale locale = globalConfig.getLocales().get(0);
+ if (locale == null) {
+ return;
+ }
if (!mLocale.equals(locale)) {
mLocale = locale;
onLocaleChanged(locale);
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index d1115c7..658f06a 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -99,7 +99,7 @@
out Rect outContentInsets, out Rect outVisibleInsets, out Rect outStableInsets,
out Rect outOutsets, out Rect outBackdropFrame,
out DisplayCutout.ParcelableWrapper displayCutout,
- out MergedConfiguration outMergedConfiguration, out Surface outSurface,
+ out MergedConfiguration outMergedConfiguration, out SurfaceControl outSurfaceControl,
out InsetsState insetsState);
/*
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 3f8f882..0931914 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -16,6 +16,8 @@
package android.view;
+import static android.view.WindowInsets.Type.indexOf;
+
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.graphics.Insets;
@@ -114,8 +116,8 @@
public WindowInsets calculateInsets(Rect frame, boolean isScreenRound,
boolean alwaysConsumeNavBar, DisplayCutout cutout,
@Nullable @InsetSide SparseIntArray typeSideMap) {
- Insets systemInsets = Insets.NONE;
- Insets maxInsets = Insets.NONE;
+ Insets[] typeInsetsMap = new Insets[Type.SIZE];
+ Insets[] typeMaxInsetsMap = new Insets[Type.SIZE];
final Rect relativeFrame = new Rect(frame);
final Rect relativeFrameMax = new Rect(frame);
for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
@@ -123,32 +125,38 @@
if (source == null) {
continue;
}
- systemInsets = processSource(source, systemInsets, relativeFrame,
- false /* ignoreVisibility */, typeSideMap);
+ processSource(source, relativeFrame, false /* ignoreVisibility */, typeInsetsMap,
+ typeSideMap);
// IME won't be reported in max insets as the size depends on the EditorInfo of the IME
// target.
if (source.getType() != TYPE_IME) {
- maxInsets = processSource(source, maxInsets, relativeFrameMax,
- true /* ignoreVisibility */, null /* typeSideMap */);
+ processSource(source, relativeFrameMax, true /* ignoreVisibility */,
+ typeMaxInsetsMap, null /* typeSideMap */);
}
}
- return new WindowInsets(new Rect(systemInsets), null, new Rect(maxInsets), isScreenRound,
+ return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, isScreenRound,
alwaysConsumeNavBar, cutout);
}
- private Insets processSource(InsetsSource source, Insets insets, Rect relativeFrame,
- boolean ignoreVisibility, @Nullable @InsetSide SparseIntArray typeSideMap) {
- Insets currentInsets = source.calculateInsets(relativeFrame, ignoreVisibility);
- insets = Insets.add(currentInsets, insets);
- relativeFrame.inset(insets);
- if (typeSideMap != null && !Insets.NONE.equals(currentInsets)) {
- @InsetSide int insetSide = getInsetSide(currentInsets);
+ private void processSource(InsetsSource source, Rect relativeFrame, boolean ignoreVisibility,
+ Insets[] typeInsetsMap, @Nullable @InsetSide SparseIntArray typeSideMap) {
+ Insets insets = source.calculateInsets(relativeFrame, ignoreVisibility);
+
+ int index = indexOf(toPublicType(source.getType()));
+ Insets existing = typeInsetsMap[index];
+ if (existing == null) {
+ typeInsetsMap[index] = insets;
+ } else {
+ typeInsetsMap[index] = Insets.max(existing, insets);
+ }
+
+ if (typeSideMap != null && !Insets.NONE.equals(insets)) {
+ @InsetSide int insetSide = getInsetSide(insets);
if (insetSide != INSET_SIDE_UNKNWON) {
- typeSideMap.put(source.getType(), getInsetSide(currentInsets));
+ typeSideMap.put(source.getType(), getInsetSide(insets));
}
}
- return insets;
}
/**
@@ -229,6 +237,21 @@
return result;
}
+ static @InsetType int toPublicType(@InternalInsetType int type) {
+ switch (type) {
+ case TYPE_TOP_BAR:
+ return Type.TOP_BAR;
+ case TYPE_SIDE_BAR_1:
+ case TYPE_SIDE_BAR_2:
+ case TYPE_SIDE_BAR_3:
+ return Type.SIDE_BARS;
+ case TYPE_IME:
+ return Type.IME;
+ default:
+ throw new IllegalArgumentException("Unknown type: " + type);
+ }
+ }
+
public static boolean getDefaultVisibly(@InsetType int type) {
switch (type) {
case TYPE_TOP_BAR:
@@ -250,7 +273,7 @@
}
}
- static String typeToString(int type) {
+ public static String typeToString(int type) {
switch (type) {
case TYPE_TOP_BAR:
return "TYPE_TOP_BAR";
diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java
index be81c06..ffd4156 100644
--- a/core/java/android/view/LayoutInflater.java
+++ b/core/java/android/view/LayoutInflater.java
@@ -784,24 +784,8 @@
ta.recycle();
}
- if (name.equals(TAG_1995)) {
- // Let's party like it's 1995!
- return new BlinkLayout(context, attrs);
- }
-
try {
- View view;
- if (mFactory2 != null) {
- view = mFactory2.onCreateView(parent, name, context, attrs);
- } else if (mFactory != null) {
- view = mFactory.onCreateView(name, context, attrs);
- } else {
- view = null;
- }
-
- if (view == null && mPrivateFactory != null) {
- view = mPrivateFactory.onCreateView(parent, name, context, attrs);
- }
+ View view = tryCreateView(parent, name, context, attrs);
if (view == null) {
final Object lastContext = mConstructorArgs[0];
@@ -836,6 +820,48 @@
}
/**
+ * Tries to create a view from a tag name using the supplied attribute set.
+ *
+ * This method gives the factory provided by {@link LayoutInflater#setFactory} and
+ * {@link LayoutInflater#setFactory2} a chance to create a view. However, it does not apply all
+ * of the general view creation logic, and thus may return {@code null} for some tags. This
+ * method is used by {@link LayoutInflater#inflate} in creating {@code View} objects.
+ *
+ * @hide for use by precompiled layouts.
+ *
+ * @param parent the parent view, used to inflate layout params
+ * @param name the name of the XML tag used to define the view
+ * @param context the inflation context for the view, typically the
+ * {@code parent} or base layout inflater context
+ * @param attrs the attribute set for the XML tag used to define the view
+ */
+ @UnsupportedAppUsage(trackingBug = 122360734)
+ @Nullable
+ public final View tryCreateView(@Nullable View parent, @NonNull String name,
+ @NonNull Context context,
+ @NonNull AttributeSet attrs) {
+ if (name.equals(TAG_1995)) {
+ // Let's party like it's 1995!
+ return new BlinkLayout(context, attrs);
+ }
+
+ View view;
+ if (mFactory2 != null) {
+ view = mFactory2.onCreateView(parent, name, context, attrs);
+ } else if (mFactory != null) {
+ view = mFactory.onCreateView(name, context, attrs);
+ } else {
+ view = null;
+ }
+
+ if (view == null && mPrivateFactory != null) {
+ view = mPrivateFactory.onCreateView(parent, name, context, attrs);
+ }
+
+ return view;
+ }
+
+ /**
* Recursive method used to inflate internal (non-root) children. This
* method calls through to {@link #rInflate} using the parent context as
* the inflation context.
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index a86abe5..9d3552f 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -18,6 +18,9 @@
import static android.view.Display.DEFAULT_DISPLAY;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.graphics.Matrix;
@@ -30,6 +33,7 @@
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
+import java.lang.annotation.Retention;
import java.util.Objects;
/**
@@ -462,7 +466,7 @@
/**
* This flag indicates that the event has been generated by a gesture generator. It
- * provides a hint to the GestureDector to not apply any touch slop.
+ * provides a hint to the GestureDetector to not apply any touch slop.
*
* @hide
*/
@@ -1391,6 +1395,42 @@
};
/**
+ * Classification constant: None.
+ *
+ * No additional information is available about the current motion event stream.
+ *
+ * @see #getClassification
+ */
+ public static final int CLASSIFICATION_NONE = 0;
+
+ /**
+ * Classification constant: Ambiguous gesture.
+ *
+ * The user's intent with respect to the current event stream is not yet determined.
+ * Gestural actions, such as scrolling, should be inhibited until the classification resolves
+ * to another value or the event stream ends.
+ *
+ * @see #getClassification
+ */
+ public static final int CLASSIFICATION_AMBIGUOUS_GESTURE = 1;
+
+ /**
+ * Classification constant: Deep press.
+ *
+ * The current event stream represents the user intentionally pressing harder on the screen.
+ * This classification type should be used to accelerate the long press behaviour.
+ *
+ * @see #getClassification
+ */
+ public static final int CLASSIFICATION_DEEP_PRESS = 2;
+
+ /** @hide */
+ @Retention(SOURCE)
+ @IntDef(prefix = { "CLASSIFICATION" }, value = {
+ CLASSIFICATION_NONE, CLASSIFICATION_AMBIGUOUS_GESTURE, CLASSIFICATION_DEEP_PRESS})
+ public @interface Classification {};
+
+ /**
* Tool type constant: Unknown tool type.
* This constant is used when the tool type is not known or is not relevant,
* such as for a trackball or other non-pointing device.
@@ -1478,7 +1518,7 @@
private static native long nativeInitialize(long nativePtr,
int deviceId, int source, int displayId, int action, int flags, int edgeFlags,
- int metaState, int buttonState,
+ int metaState, int buttonState, @Classification int classification,
float xOffset, float yOffset, float xPrecision, float yPrecision,
long downTimeNanos, long eventTimeNanos,
int pointerCount, PointerProperties[] pointerIds, PointerCoords[] pointerCoords);
@@ -1548,6 +1588,8 @@
@CriticalNative
private static native void nativeSetButtonState(long nativePtr, int buttonState);
@CriticalNative
+ private static native int nativeGetClassification(long nativePtr);
+ @CriticalNative
private static native int nativeGetActionButton(long nativePtr);
@CriticalNative
private static native void nativeSetActionButton(long nativePtr, int actionButton);
@@ -1648,7 +1690,7 @@
MotionEvent ev = obtain();
ev.mNativePtr = nativeInitialize(ev.mNativePtr,
deviceId, source, displayId, action, flags, edgeFlags, metaState, buttonState,
- 0, 0, xPrecision, yPrecision,
+ CLASSIFICATION_NONE, 0, 0, xPrecision, yPrecision,
downTime * NS_PER_MS, eventTime * NS_PER_MS,
pointerCount, pointerProperties, pointerCoords);
if (ev.mNativePtr == 0) {
@@ -1833,7 +1875,7 @@
ev.mNativePtr = nativeInitialize(ev.mNativePtr,
deviceId, source, displayId,
- action, 0, edgeFlags, metaState, 0,
+ action, 0, edgeFlags, metaState, 0 /*buttonState*/, CLASSIFICATION_NONE,
0, 0, xPrecision, yPrecision,
downTime * NS_PER_MS, eventTime * NS_PER_MS,
1, pp, pc);
@@ -2539,6 +2581,18 @@
}
/**
+ * Returns the classification for the current gesture.
+ * The classification may change as more events become available for the same gesture.
+ *
+ * @see #CLASSIFICATION_NONE
+ * @see #CLASSIFICATION_AMBIGUOUS_GESTURE
+ * @see #CLASSIFICATION_DEEP_PRESS
+ */
+ public @Classification int getClassification() {
+ return nativeGetClassification(mNativePtr);
+ }
+
+ /**
* Gets which button has been modified during a press or release action.
*
* For actions other than {@link #ACTION_BUTTON_PRESS} and {@link #ACTION_BUTTON_RELEASE}
@@ -3172,7 +3226,7 @@
/**
* Adds all of the movement samples of the specified event to this one if
* it is compatible. To be compatible, the event must have the same device id,
- * source, display id, action, flags, pointer count, pointer properties.
+ * source, display id, action, flags, classification, pointer count, pointer properties.
*
* Only applies to {@link #ACTION_MOVE} or {@link #ACTION_HOVER_MOVE} events.
*
@@ -3194,7 +3248,9 @@
if (nativeGetDeviceId(mNativePtr) != nativeGetDeviceId(event.mNativePtr)
|| nativeGetSource(mNativePtr) != nativeGetSource(event.mNativePtr)
|| nativeGetDisplayId(mNativePtr) != nativeGetDisplayId(event.mNativePtr)
- || nativeGetFlags(mNativePtr) != nativeGetFlags(event.mNativePtr)) {
+ || nativeGetFlags(mNativePtr) != nativeGetFlags(event.mNativePtr)
+ || nativeGetClassification(mNativePtr)
+ != nativeGetClassification(event.mNativePtr)) {
return false;
}
@@ -3282,7 +3338,7 @@
nativeGetDisplayId(mNativePtr),
nativeGetAction(mNativePtr), nativeGetFlags(mNativePtr),
nativeGetEdgeFlags(mNativePtr), nativeGetMetaState(mNativePtr),
- nativeGetButtonState(mNativePtr),
+ nativeGetButtonState(mNativePtr), nativeGetClassification(mNativePtr),
nativeGetXOffset(mNativePtr), nativeGetYOffset(mNativePtr),
nativeGetXPrecision(mNativePtr), nativeGetYPrecision(mNativePtr),
nativeGetDownTimeNanos(mNativePtr),
@@ -3376,7 +3432,7 @@
nativeGetDisplayId(mNativePtr),
newAction, nativeGetFlags(mNativePtr),
nativeGetEdgeFlags(mNativePtr), nativeGetMetaState(mNativePtr),
- nativeGetButtonState(mNativePtr),
+ nativeGetButtonState(mNativePtr), nativeGetClassification(mNativePtr),
nativeGetXOffset(mNativePtr), nativeGetYOffset(mNativePtr),
nativeGetXPrecision(mNativePtr), nativeGetYPrecision(mNativePtr),
nativeGetDownTimeNanos(mNativePtr), eventTimeNanos,
@@ -3409,6 +3465,8 @@
}
appendUnless("0", msg, ", buttonState=", MotionEvent.buttonStateToString(getButtonState()));
+ appendUnless(classificationToString(CLASSIFICATION_NONE), msg, ", classification=",
+ classificationToString(getClassification()));
appendUnless("0", msg, ", metaState=", KeyEvent.metaStateToString(getMetaState()));
appendUnless("0", msg, ", flags=0x", Integer.toHexString(getFlags()));
appendUnless("0", msg, ", edgeFlags=0x", Integer.toHexString(getEdgeFlags()));
@@ -3547,6 +3605,26 @@
}
/**
+ * Returns a string that represents the symbolic name of the specified classification.
+ *
+ * @param classification The classification type.
+ * @return The symbolic name of this classification.
+ * @hide
+ */
+ public static String classificationToString(@Classification int classification) {
+ switch (classification) {
+ case CLASSIFICATION_NONE:
+ return "NONE";
+ case CLASSIFICATION_AMBIGUOUS_GESTURE:
+ return "AMBIGUOUS_GESTURE";
+ case CLASSIFICATION_DEEP_PRESS:
+ return "DEEP_PRESS";
+
+ }
+ return "NONE";
+ }
+
+ /**
* Returns a string that represents the symbolic name of the specified tool type
* such as "TOOL_TYPE_FINGER" or an equivalent numeric constant such as "42" if unknown.
*
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 1a708e8..acad5d7 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -67,6 +67,7 @@
int w, int h, int format, int flags, long parentObject, int windowType, int ownerUid)
throws OutOfResourcesException;
private static native long nativeReadFromParcel(Parcel in);
+ private static native long nativeCopyFromSurfaceControl(long nativeObject);
private static native void nativeWriteToParcel(long nativeObject, Parcel out);
private static native void nativeRelease(long nativeObject);
private static native void nativeDestroy(long nativeObject);
@@ -157,7 +158,6 @@
private static native void nativeSeverChildren(long transactionObj, long nativeObject);
private static native void nativeSetOverrideScalingMode(long transactionObj, long nativeObject,
int scalingMode);
- private static native void nativeDestroy(long transactionObj, long nativeObject);
private static native IBinder nativeGetHandle(long nativeObject);
private static native boolean nativeGetTransformToDisplayInverse(long nativeObject);
@@ -169,7 +169,7 @@
IBinder toToken);
private final CloseGuard mCloseGuard = CloseGuard.get();
- private final String mName;
+ private String mName;
long mNativeObject; // package visibility only for Surface.java access
// TODO: Move this to native.
@@ -359,6 +359,20 @@
*/
public static final int WINDOW_TYPE_DONT_SCREENSHOT = 441731;
+ private void assignNativeObject(long nativeObject) {
+ if (mNativeObject != 0) {
+ release();
+ }
+ mNativeObject = nativeObject;
+ }
+
+ public void copyFrom(SurfaceControl other) {
+ mName = other.mName;
+ mWidth = other.mWidth;
+ mHeight = other.mHeight;
+ assignNativeObject(nativeCopyFromSurfaceControl(other.mNativeObject));
+ }
+
/**
* Builder class for {@link SurfaceControl} objects.
*/
@@ -660,14 +674,28 @@
}
private SurfaceControl(Parcel in) {
+ readFromParcel(in);
+ mCloseGuard.open("release");
+ }
+
+ public SurfaceControl() {
+ mCloseGuard.open("release");
+ }
+
+ public void readFromParcel(Parcel in) {
+ if (in == null) {
+ throw new IllegalArgumentException("source must not be null");
+ }
+
mName = in.readString();
mWidth = in.readInt();
mHeight = in.readInt();
- mNativeObject = nativeReadFromParcel(in);
- if (mNativeObject == 0) {
- throw new IllegalArgumentException("Couldn't read SurfaceControl from parcel=" + in);
+
+ long object = 0;
+ if (in.readInt() != 0) {
+ object = nativeReadFromParcel(in);
}
- mCloseGuard.open("release");
+ assignNativeObject(object);
}
@Override
@@ -680,7 +708,16 @@
dest.writeString(mName);
dest.writeInt(mWidth);
dest.writeInt(mHeight);
+ if (mNativeObject == 0) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(1);
+ }
nativeWriteToParcel(mNativeObject, dest);
+
+ if ((flags & Parcelable.PARCELABLE_WRITE_RETURN_VALUE) != 0) {
+ release();
+ }
}
/**
@@ -763,6 +800,10 @@
"mNativeObject is null. Have you called release() already?");
}
+ public boolean isValid() {
+ return mNativeObject != 0;
+ }
+
/*
* set surface parameters.
* needs to be inside open/closeTransaction block
@@ -1729,30 +1770,6 @@
return this;
}
- /**
- * Same as {@link #destroy()} except this is invoked in a transaction instead of
- * immediately.
- */
- public Transaction destroy(SurfaceControl sc) {
- sc.checkNotReleased();
-
- /**
- * Perhaps it's safer to transfer the close guard to the Transaction
- * but then we have a whole wonky scenario regarding merging, multiple
- * close-guards per transaction etc...the whole scenario is kind of wonky
- * and it seems really we'd like to just be able to call release here
- * but the WindowManager has some code that looks like
- * --- destroyInTransaction(a)
- * --- reparentChildrenInTransaction(a)
- * so we need to ensure the SC remains valid until the transaction
- * is applied.
- */
- sc.mCloseGuard.close();
-
- nativeDestroy(mNativeObject, sc.mNativeObject);
- return this;
- }
-
public Transaction setDisplaySurface(IBinder displayToken, Surface surface) {
if (displayToken == null) {
throw new IllegalArgumentException("displayToken must not be null");
diff --git a/core/java/android/view/SurfaceSession.java b/core/java/android/view/SurfaceSession.java
index a4fa12a..361ac93 100644
--- a/core/java/android/view/SurfaceSession.java
+++ b/core/java/android/view/SurfaceSession.java
@@ -30,7 +30,6 @@
private long mNativeClient; // SurfaceComposerClient*
private static native long nativeCreate();
- private static native long nativeCreateScoped(long surfacePtr);
private static native void nativeDestroy(long ptr);
private static native void nativeKill(long ptr);
@@ -40,15 +39,6 @@
mNativeClient = nativeCreate();
}
- public SurfaceSession(Surface root) {
- synchronized (root.mLock) {
- if (root.mNativeObject == 0) {
- throw new IllegalStateException("Surface is not initialized or has been released");
- }
- mNativeClient = nativeCreateScoped(root.mNativeObject);
- }
- }
-
/* no user serviceable parts here ... */
@Override
protected void finalize() throws Throwable {
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index a0af83d..61fb00d 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -547,7 +547,7 @@
if (creating) {
viewRoot.createBoundsSurface(mSubLayer);
- mSurfaceSession = new SurfaceSession(viewRoot.mBoundsSurface);
+ mSurfaceSession = new SurfaceSession();
mDeferredDestroySurfaceControl = mSurfaceControl;
updateOpaqueFlag();
@@ -559,6 +559,7 @@
new SurfaceControl.Builder(mSurfaceSession)
.setBufferSize(mSurfaceWidth, mSurfaceHeight)
.setFormat(mFormat)
+ .setParent(viewRoot.getSurfaceControl())
.setFlags(mSurfaceFlags));
} else if (mSurfaceControl == null) {
return;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 6b07efc..cd0e579 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -939,6 +939,26 @@
*/
private static boolean sAcceptZeroSizeDragShadow;
+ /**
+ * Prior to Q, {@link #dispatchApplyWindowInsets} had some issues:
+ * <ul>
+ * <li>The modified insets changed by {@link #onApplyWindowInsets} were passed to the
+ * entire view hierarchy in prefix order, including siblings as well as siblings of parents
+ * further down the hierarchy. This violates the basic concepts of the view hierarchy, and
+ * thus, the hierarchical dispatching mechanism was hard to use for apps.</li>
+ *
+ * <li>Dispatch was stopped after the insets were fully consumed. This is somewhat confusing
+ * for developers, but more importantly, by adding more granular information to
+ * {@link WindowInsets} it becomes really cumbersome to define what consumed actually means
+ * </li>
+ * </ul>
+ *
+ * In order to make window inset dispatching work properly, we dispatch window insets
+ * in the view hierarchy in a proper hierarchical manner and don't stop dispatching if the
+ * insets are consumed if this flag is set to {@code false}.
+ */
+ static boolean sBrokenInsetsDispatch;
+
/** @hide */
@IntDef({NOT_FOCUSABLE, FOCUSABLE, FOCUSABLE_AUTO})
@Retention(RetentionPolicy.SOURCE)
@@ -5108,6 +5128,9 @@
sAcceptZeroSizeDragShadow = targetSdkVersion < Build.VERSION_CODES.P;
+ sBrokenInsetsDispatch = !ViewRootImpl.USE_NEW_INSETS
+ || targetSdkVersion < Build.VERSION_CODES.Q;
+
sCompatibilityDone = true;
}
}
@@ -7660,10 +7683,13 @@
/**
* Convenience method for sending a {@link AccessibilityEvent#TYPE_ANNOUNCEMENT}
- * {@link AccessibilityEvent} to make an announcement which is related to some
- * sort of a context change for which none of the events representing UI transitions
- * is a good fit. For example, announcing a new page in a book. If accessibility
- * is not enabled this method does nothing.
+ * {@link AccessibilityEvent} to suggest that an accessibility service announce the
+ * specified text to its users.
+ * <p>
+ * Note: The event generated with this API carries no semantic meaning, and is appropriate only
+ * in exceptional situations. Apps can generally achieve correct behavior for accessibility by
+ * accurately supplying the semantics of their UI.
+ * They should not need to specify what exactly is announced to users.
*
* @param text The announcement text.
*/
@@ -8173,6 +8199,19 @@
* <p>The populated structure is then passed to the service through
* {@link ContentCaptureSession#notifyViewAppeared(ViewStructure)}.
*
+ * <p><b>Note: </b>views that manage a virtual structure under this view must populate just
+ * the node representing this view and return right away, then asynchronously report (not
+ * necessarily in the UI thread) when the children nodes appear, disappear or have their text
+ * changed by calling
+ * {@link ContentCaptureSession#notifyViewAppeared(ViewStructure)},
+ * {@link ContentCaptureSession#notifyViewDisappeared(AutofillId)}, and
+ * {@link ContentCaptureSession#notifyViewTextChanged(AutofillId, CharSequence, int)}
+ * respectively. The structure for the a child must be created using
+ * {@link ContentCaptureSession#newVirtualViewStructure(AutofillId, int)}, and the
+ * {@code autofillId} for a child can be obtained either through
+ * {@code childStructure.getAutofillId()} or
+ * {@link ContentCaptureSession#newAutofillId(AutofillId, int)}.
+ *
* <p><b>Note: </b>the following methods of the {@code structure} will be ignored:
* <ul>
* <li>{@link ViewStructure#setChildCount(int)}
@@ -8209,10 +8248,6 @@
} else {
structure.setId(id, null, null, null);
}
- if (viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
- //TODO(b/111276913): STOPSHIP - don't set it if not needed
- structure.setDataIsSensitive(false);
- }
if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
|| viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
@@ -25053,9 +25088,10 @@
}
final ViewRootImpl root = mAttachInfo.mViewRootImpl;
- final SurfaceSession session = new SurfaceSession(root.mSurface);
+ final SurfaceSession session = new SurfaceSession();
final SurfaceControl surfaceControl = new SurfaceControl.Builder(session)
.setName("drag surface")
+ .setParent(root.getSurfaceControl())
.setBufferSize(shadowSize.x, shadowSize.y)
.setFormat(PixelFormat.TRANSLUCENT)
.build();
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 8372032..9d11397 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -7111,6 +7111,14 @@
@Override
public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
insets = super.dispatchApplyWindowInsets(insets);
+ if (View.sBrokenInsetsDispatch) {
+ return brokenDispatchApplyWindowInsets(insets);
+ } else {
+ return newDispatchApplyWindowInsets(insets);
+ }
+ }
+
+ private WindowInsets brokenDispatchApplyWindowInsets(WindowInsets insets) {
if (!insets.isConsumed()) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
@@ -7123,6 +7131,14 @@
return insets;
}
+ private WindowInsets newDispatchApplyWindowInsets(WindowInsets insets) {
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ getChildAt(i).dispatchApplyWindowInsets(insets);
+ }
+ return insets;
+ }
+
/**
* Returns the animation listener to which layout animation events are
* sent.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 3f7a512..8e4dc67 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -432,6 +432,7 @@
// Surface can never be reassigned or cleared (use Surface.clear()).
@UnsupportedAppUsage
public final Surface mSurface = new Surface();
+ private final SurfaceControl mSurfaceControl = new SurfaceControl();
/**
* Child surface of {@code mSurface} with the same bounds as its parent, and crop bounds
@@ -1526,7 +1527,7 @@
*/
public void createBoundsSurface(int zOrderLayer) {
if (mSurfaceSession == null) {
- mSurfaceSession = new SurfaceSession(mSurface);
+ mSurfaceSession = new SurfaceSession();
}
if (mBoundsSurfaceControl != null && mBoundsSurface.isValid()) {
return; // surface control for bounds surface already exists.
@@ -1534,6 +1535,7 @@
mBoundsSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
.setName("Bounds for - " + getTitle().toString())
+ .setParent(mSurfaceControl)
.build();
setBoundsSurfaceCrop();
@@ -1567,6 +1569,8 @@
private void destroySurface() {
mSurface.release();
+ mSurfaceControl.release();
+
mSurfaceSession = null;
if (mBoundsSurfaceControl != null) {
@@ -1855,8 +1859,7 @@
mContext.getResources().getConfiguration().isScreenRound(),
mAttachInfo.mAlwaysConsumeNavBar, displayCutout);
} else {
- mLastWindowInsets = new WindowInsets(contentInsets,
- null /* windowDecorInsets */, stableInsets,
+ mLastWindowInsets = new WindowInsets(contentInsets, stableInsets,
mContext.getResources().getConfiguration().isScreenRound(),
mAttachInfo.mAlwaysConsumeNavBar, displayCutout);
}
@@ -6801,7 +6804,12 @@
insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
mTmpFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,
mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingDisplayCutout,
- mPendingMergedConfiguration, mSurface, mTempInsets);
+ mPendingMergedConfiguration, mSurfaceControl, mTempInsets);
+ if (mSurfaceControl.isValid()) {
+ mSurface.copyFrom(mSurfaceControl);
+ } else {
+ destroySurface();
+ }
mPendingAlwaysConsumeNavBar =
(relayoutResult & WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_NAV_BAR) != 0;
@@ -8483,6 +8491,10 @@
mActivityRelaunched = true;
}
+ public SurfaceControl getSurfaceControl() {
+ return mSurfaceControl;
+ }
+
/**
* Class for managing the accessibility interaction connection
* based on the global accessibility state.
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 572d331..b3da727 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -17,18 +17,33 @@
package android.view;
-import android.annotation.NonNull;
+import static android.view.WindowInsets.Type.FIRST;
+import static android.view.WindowInsets.Type.IME;
+import static android.view.WindowInsets.Type.LAST;
+import static android.view.WindowInsets.Type.SIDE_BARS;
+import static android.view.WindowInsets.Type.SIZE;
+import static android.view.WindowInsets.Type.TOP_BAR;
+import static android.view.WindowInsets.Type.all;
+import static android.view.WindowInsets.Type.compatSystemInsets;
+import static android.view.WindowInsets.Type.indexOf;
+
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
import android.graphics.Insets;
import android.graphics.Rect;
+import android.util.SparseArray;
+import android.view.InsetsState.InternalInsetType;
+import android.view.WindowInsets.Type.InsetType;
+import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethod;
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
import java.util.Objects;
/**
@@ -49,9 +64,9 @@
*/
public final class WindowInsets {
- @NonNull private final Insets mSystemWindowInsets;
- @NonNull private final Insets mWindowDecorInsets;
- @NonNull private final Insets mStableInsets;
+ private final Insets[] mTypeInsetsMap;
+ private final Insets[] mTypeMaxInsetsMap;
+
@Nullable private Rect mTempRect;
private final boolean mIsRound;
@Nullable private final DisplayCutout mDisplayCutout;
@@ -64,7 +79,6 @@
private final boolean mAlwaysConsumeNavBar;
private final boolean mSystemWindowInsetsConsumed;
- private final boolean mWindowDecorInsetsConsumed;
private final boolean mStableInsetsConsumed;
private final boolean mDisplayCutoutConsumed;
@@ -78,7 +92,7 @@
public static final WindowInsets CONSUMED;
static {
- CONSUMED = new WindowInsets((Insets) null, null, null, false, false, null);
+ CONSUMED = new WindowInsets((Rect) null, null, false, false, null);
}
/**
@@ -87,24 +101,38 @@
* A {@code null} inset indicates that the respective inset is consumed.
*
* @hide
+ * @deprecated Use {@link WindowInsets(SparseArray, SparseArray, boolean, boolean, DisplayCutout)}
*/
- public WindowInsets(Rect systemWindowInsets, Rect windowDecorInsets, Rect stableInsets,
+ public WindowInsets(Rect systemWindowInsetsRect, Rect stableInsetsRect,
boolean isRound, boolean alwaysConsumeNavBar, DisplayCutout displayCutout) {
- this(insetsOrNull(systemWindowInsets), insetsOrNull(windowDecorInsets),
- insetsOrNull(stableInsets), isRound, alwaysConsumeNavBar, displayCutout);
+ this(createCompatTypeMap(systemWindowInsetsRect), createCompatTypeMap(stableInsetsRect),
+ isRound, alwaysConsumeNavBar, displayCutout);
}
- private WindowInsets(Insets systemWindowInsets, Insets windowDecorInsets,
- Insets stableInsets, boolean isRound, boolean alwaysConsumeNavBar,
- DisplayCutout displayCutout) {
- mSystemWindowInsetsConsumed = systemWindowInsets == null;
- mSystemWindowInsets = mSystemWindowInsetsConsumed ? Insets.NONE : systemWindowInsets;
+ /**
+ * Construct a new WindowInsets from individual insets.
+ *
+ * {@code typeInsetsMap} and {@code typeMaxInsetsMap} are a map of indexOf(type) -> insets that
+ * contain the information what kind of system bars causes how much insets. The insets in this
+ * map are non-additive; i.e. they have the same origin. In other words: If two system bars
+ * overlap on one side, the insets of the larger bar will also include the insets of the smaller
+ * bar.
+ *
+ * {@code null} type inset map indicates that the respective inset is fully consumed.
+ * @hide
+ */
+ public WindowInsets(@Nullable Insets[] typeInsetsMap,
+ @Nullable Insets[] typeMaxInsetsMap, boolean isRound,
+ boolean alwaysConsumeNavBar, DisplayCutout displayCutout) {
+ mSystemWindowInsetsConsumed = typeInsetsMap == null;
+ mTypeInsetsMap = mSystemWindowInsetsConsumed
+ ? new Insets[SIZE]
+ : typeInsetsMap.clone();
- mWindowDecorInsetsConsumed = windowDecorInsets == null;
- mWindowDecorInsets = mWindowDecorInsetsConsumed ? Insets.NONE : windowDecorInsets;
-
- mStableInsetsConsumed = stableInsets == null;
- mStableInsets = mStableInsetsConsumed ? Insets.NONE : stableInsets;
+ mStableInsetsConsumed = typeMaxInsetsMap == null;
+ mTypeMaxInsetsMap = mStableInsetsConsumed
+ ? new Insets[SIZE]
+ : typeMaxInsetsMap.clone();
mIsRound = isRound;
mAlwaysConsumeNavBar = alwaysConsumeNavBar;
@@ -120,10 +148,7 @@
* @param src Source to copy insets from
*/
public WindowInsets(WindowInsets src) {
- this(src.mSystemWindowInsetsConsumed ? null : src.mSystemWindowInsets,
- src.mWindowDecorInsetsConsumed ? null : src.mWindowDecorInsets,
- src.mStableInsetsConsumed ? null : src.mStableInsets,
- src.mIsRound, src.mAlwaysConsumeNavBar,
+ this(src.mTypeInsetsMap, src.mTypeMaxInsetsMap, src.mIsRound, src.mAlwaysConsumeNavBar,
displayCutoutCopyConstructorArgument(src));
}
@@ -137,10 +162,64 @@
}
}
+ /**
+ * @return The insets that include system bars indicated by {@code typeMask}, taken from
+ * {@code typeInsetMap}.
+ */
+ private static Insets getInsets(Insets[] typeInsetsMap, @InsetType int typeMask) {
+ Insets result = null;
+ for (int i = FIRST; i <= LAST; i = i << 1) {
+ if ((typeMask & i) == 0) {
+ continue;
+ }
+ Insets insets = typeInsetsMap[indexOf(i)];
+ if (insets == null) {
+ continue;
+ }
+ if (result == null) {
+ result = insets;
+ } else {
+ result = Insets.max(result, insets);
+ }
+ }
+ return result == null ? Insets.NONE : result;
+ }
+
+ /**
+ * Sets all entries in {@code typeInsetsMap} that belong to {@code typeMask} to {@code insets},
+ */
+ private static void setInsets(Insets[] typeInsetsMap, @InsetType int typeMask, Insets insets) {
+ for (int i = FIRST; i <= LAST; i = i << 1) {
+ if ((typeMask & i) == 0) {
+ continue;
+ }
+ typeInsetsMap[indexOf(i)] = insets;
+ }
+ }
+
/** @hide */
@UnsupportedAppUsage
public WindowInsets(Rect systemWindowInsets) {
- this(systemWindowInsets, null, null, false, false, null);
+ this(createCompatTypeMap(systemWindowInsets), null, false, false, null);
+ }
+
+ /**
+ * Creates a indexOf(type) -> inset map for which the {@code insets} is just mapped to
+ * {@link InsetType#topBar()} and {@link InsetType#sideBars()}, depending on the location of the
+ * inset.
+ */
+ private static Insets[] createCompatTypeMap(@Nullable Rect insets) {
+ if (insets == null) {
+ return null;
+ }
+ Insets[] typeInsetMap = new Insets[SIZE];
+ assignCompatInsets(typeInsetMap, insets);
+ return typeInsetMap;
+ }
+
+ private static void assignCompatInsets(Insets[] typeInsetMap, Rect insets) {
+ typeInsetMap[indexOf(TOP_BAR)] = Insets.of(0, insets.top, 0, 0);
+ typeInsetMap[indexOf(SIDE_BARS)] = Insets.of(insets.left, 0, insets.right, insets.bottom);
}
/**
@@ -156,8 +235,8 @@
if (mTempRect == null) {
mTempRect = new Rect();
}
- mTempRect.set(mSystemWindowInsets.left, mSystemWindowInsets.top,
- mSystemWindowInsets.right, mSystemWindowInsets.bottom);
+ Insets insets = getSystemWindowInsets();
+ mTempRect.set(insets.left, insets.top, insets.right, insets.bottom);
return mTempRect;
}
@@ -172,7 +251,46 @@
*/
@NonNull
public Insets getSystemWindowInsets() {
- return mSystemWindowInsets;
+ return getInsets(mTypeInsetsMap, compatSystemInsets());
+ }
+
+ /**
+ * Returns the insets of a specific set of windows causing insets, denoted by the
+ * {@code typeMask} bit mask of {@link InsetType}s.
+ *
+ * @param typeMask Bit mask of {@link InsetType}s to query the insets for.
+ * @return The insets.
+ *
+ * @hide pending unhide
+ */
+ public Insets getInsets(@InsetType int typeMask) {
+ return getInsets(mTypeInsetsMap, typeMask);
+ }
+
+ /**
+ * Returns the maximum amount of insets a specific set of windows can cause, denoted by the
+ * {@code typeMask} bit mask of {@link InsetType}s.
+ *
+ * <p>The maximum insets represents the area of a a window that that <b>may</b> be partially
+ * or fully obscured by the system window identified by {@code type}. This value does not
+ * change based on the visibility state of those elements. for example, if the status bar is
+ * normally shown, but temporarily hidden, the maximum inset will still provide the inset
+ * associated with the status bar being shown.</p>
+ *
+ * @param typeMask Bit mask of {@link InsetType}s to query the insets for.
+ * @return The insets.
+ *
+ * @throws IllegalArgumentException If the caller tries to query {@link Type#ime()}. Maximum
+ * insets are not available for this type as the height of the
+ * IME is dynamic depending on the {@link EditorInfo} of the
+ * currently focused view, as well as the UI state of the IME.
+ * @hide pending unhide
+ */
+ public Insets getMaxInsets(@InsetType int typeMask) throws IllegalArgumentException {
+ if ((typeMask & IME) != 0) {
+ throw new IllegalArgumentException("Unable to query the maximum insets for IME");
+ }
+ return getInsets(mTypeMaxInsetsMap, typeMask);
}
/**
@@ -185,7 +303,7 @@
* @return The left system window inset
*/
public int getSystemWindowInsetLeft() {
- return mSystemWindowInsets.left;
+ return getSystemWindowInsets().left;
}
/**
@@ -198,7 +316,7 @@
* @return The top system window inset
*/
public int getSystemWindowInsetTop() {
- return mSystemWindowInsets.top;
+ return getSystemWindowInsets().top;
}
/**
@@ -211,7 +329,7 @@
* @return The right system window inset
*/
public int getSystemWindowInsetRight() {
- return mSystemWindowInsets.right;
+ return getSystemWindowInsets().right;
}
/**
@@ -224,63 +342,7 @@
* @return The bottom system window inset
*/
public int getSystemWindowInsetBottom() {
- return mSystemWindowInsets.bottom;
- }
-
- /**
- * Returns the left window decor inset in pixels.
- *
- * <p>The window decor inset represents the area of the window content area that is
- * partially or fully obscured by decorations within the window provided by the framework.
- * This can include action bars, title bars, toolbars, etc.</p>
- *
- * @return The left window decor inset
- * @hide pending API
- */
- public int getWindowDecorInsetLeft() {
- return mWindowDecorInsets.left;
- }
-
- /**
- * Returns the top window decor inset in pixels.
- *
- * <p>The window decor inset represents the area of the window content area that is
- * partially or fully obscured by decorations within the window provided by the framework.
- * This can include action bars, title bars, toolbars, etc.</p>
- *
- * @return The top window decor inset
- * @hide pending API
- */
- public int getWindowDecorInsetTop() {
- return mWindowDecorInsets.top;
- }
-
- /**
- * Returns the right window decor inset in pixels.
- *
- * <p>The window decor inset represents the area of the window content area that is
- * partially or fully obscured by decorations within the window provided by the framework.
- * This can include action bars, title bars, toolbars, etc.</p>
- *
- * @return The right window decor inset
- * @hide pending API
- */
- public int getWindowDecorInsetRight() {
- return mWindowDecorInsets.right;
- }
-
- /**
- * Returns the bottom window decor inset in pixels.
- *
- * <p>The window decor inset represents the area of the window content area that is
- * partially or fully obscured by decorations within the window provided by the framework.
- * This can include action bars, title bars, toolbars, etc.</p>
- *
- * @return The bottom window decor inset
- * @hide pending API
- */
- public int getWindowDecorInsetBottom() {
- return mWindowDecorInsets.bottom;
+ return getSystemWindowInsets().bottom;
}
/**
@@ -293,23 +355,7 @@
* @return true if any of the system window inset values are nonzero
*/
public boolean hasSystemWindowInsets() {
- return mSystemWindowInsets.left != 0 || mSystemWindowInsets.top != 0 ||
- mSystemWindowInsets.right != 0 || mSystemWindowInsets.bottom != 0;
- }
-
- /**
- * Returns true if this WindowInsets has nonzero window decor insets.
- *
- * <p>The window decor inset represents the area of the window content area that is
- * partially or fully obscured by decorations within the window provided by the framework.
- * This can include action bars, title bars, toolbars, etc.</p>
- *
- * @return true if any of the window decor inset values are nonzero
- * @hide pending API
- */
- public boolean hasWindowDecorInsets() {
- return mWindowDecorInsets.left != 0 || mWindowDecorInsets.top != 0 ||
- mWindowDecorInsets.right != 0 || mWindowDecorInsets.bottom != 0;
+ return !getSystemWindowInsets().equals(Insets.NONE);
}
/**
@@ -318,7 +364,8 @@
* @return true if any inset values are nonzero
*/
public boolean hasInsets() {
- return hasSystemWindowInsets() || hasWindowDecorInsets() || hasStableInsets()
+ return !getInsets(mTypeInsetsMap, all()).equals(Insets.NONE)
+ || !getInsets(mTypeMaxInsetsMap, all()).equals(Insets.NONE)
|| mDisplayCutout != null;
}
@@ -340,9 +387,7 @@
*/
@NonNull
public WindowInsets consumeDisplayCutout() {
- return new WindowInsets(mSystemWindowInsetsConsumed ? null : mSystemWindowInsets,
- mWindowDecorInsetsConsumed ? null : mWindowDecorInsets,
- mStableInsetsConsumed ? null : mStableInsets,
+ return new WindowInsets(mTypeInsetsMap, mTypeMaxInsetsMap,
mIsRound, mAlwaysConsumeNavBar,
null /* displayCutout */);
}
@@ -362,7 +407,7 @@
* @return true if the insets have been fully consumed.
*/
public boolean isConsumed() {
- return mSystemWindowInsetsConsumed && mWindowDecorInsetsConsumed && mStableInsetsConsumed
+ return mSystemWindowInsetsConsumed && mStableInsetsConsumed
&& mDisplayCutoutConsumed;
}
@@ -387,9 +432,7 @@
*/
@NonNull
public WindowInsets consumeSystemWindowInsets() {
- return new WindowInsets(null /* systemWindowInsets */,
- mWindowDecorInsetsConsumed ? null : mWindowDecorInsets,
- mStableInsetsConsumed ? null : mStableInsets,
+ return new WindowInsets(null, mStableInsetsConsumed ? null : mTypeMaxInsetsMap,
mIsRound, mAlwaysConsumeNavBar,
displayCutoutCopyConstructorArgument(this));
}
@@ -449,18 +492,6 @@
}
/**
- * @hide
- */
- @NonNull
- public WindowInsets consumeWindowDecorInsets() {
- return new WindowInsets(mSystemWindowInsetsConsumed ? null : mSystemWindowInsets,
- null /* windowDecorInsets */,
- mStableInsetsConsumed ? null : mStableInsets,
- mIsRound, mAlwaysConsumeNavBar,
- displayCutoutCopyConstructorArgument(this));
- }
-
- /**
* Returns the stable insets in pixels.
*
* <p>The stable inset represents the area of a full-screen window that <b>may</b> be
@@ -473,7 +504,7 @@
*/
@NonNull
public Insets getStableInsets() {
- return mStableInsets;
+ return getInsets(mTypeMaxInsetsMap, compatSystemInsets());
}
/**
@@ -488,7 +519,7 @@
* @return The top stable inset
*/
public int getStableInsetTop() {
- return mStableInsets.top;
+ return getStableInsets().top;
}
/**
@@ -503,7 +534,7 @@
* @return The left stable inset
*/
public int getStableInsetLeft() {
- return mStableInsets.left;
+ return getStableInsets().left;
}
/**
@@ -518,7 +549,7 @@
* @return The right stable inset
*/
public int getStableInsetRight() {
- return mStableInsets.right;
+ return getStableInsets().right;
}
/**
@@ -533,7 +564,7 @@
* @return The bottom stable inset
*/
public int getStableInsetBottom() {
- return mStableInsets.bottom;
+ return getStableInsets().bottom;
}
/**
@@ -548,8 +579,7 @@
* @return true if any of the stable inset values are nonzero
*/
public boolean hasStableInsets() {
- return mStableInsets.top != 0 || mStableInsets.left != 0 || mStableInsets.right != 0
- || mStableInsets.bottom != 0;
+ return !getStableInsets().equals(Insets.NONE);
}
/**
@@ -559,9 +589,7 @@
*/
@NonNull
public WindowInsets consumeStableInsets() {
- return new WindowInsets(mSystemWindowInsetsConsumed ? null : mSystemWindowInsets,
- mWindowDecorInsetsConsumed ? null : mWindowDecorInsets,
- null /* stableInsets */,
+ return new WindowInsets(mSystemWindowInsetsConsumed ? null : mTypeInsetsMap, null,
mIsRound, mAlwaysConsumeNavBar,
displayCutoutCopyConstructorArgument(this));
}
@@ -575,9 +603,8 @@
@Override
public String toString() {
- return "WindowInsets{systemWindowInsets=" + mSystemWindowInsets
- + " windowDecorInsets=" + mWindowDecorInsets
- + " stableInsets=" + mStableInsets
+ return "WindowInsets{systemWindowInsets=" + getSystemWindowInsets()
+ + " stableInsets=" + getStableInsets()
+ (mDisplayCutout != null ? " cutout=" + mDisplayCutout : "")
+ (isRound() ? " round" : "")
+ "}";
@@ -634,16 +661,16 @@
Preconditions.checkArgumentNonnegative(bottom);
return new WindowInsets(
- mSystemWindowInsetsConsumed ? null :
- insetInsets(mSystemWindowInsets, left, top, right, bottom),
- mWindowDecorInsetsConsumed ? null :
- insetInsets(mWindowDecorInsets, left, top, right, bottom),
- mStableInsetsConsumed ? null :
- insetInsets(mStableInsets, left, top, right, bottom),
+ mSystemWindowInsetsConsumed
+ ? null
+ : insetInsets(mTypeInsetsMap, left, top, right, bottom),
+ mStableInsetsConsumed
+ ? null
+ : insetInsets(mTypeMaxInsetsMap, left, top, right, bottom),
mIsRound, mAlwaysConsumeNavBar,
mDisplayCutoutConsumed
- ? null :
- mDisplayCutout == null
+ ? null
+ : mDisplayCutout == null
? DisplayCutout.NO_CUTOUT
: mDisplayCutout.inset(left, top, right, bottom));
}
@@ -653,23 +680,49 @@
if (this == o) return true;
if (o == null || !(o instanceof WindowInsets)) return false;
WindowInsets that = (WindowInsets) o;
+
return mIsRound == that.mIsRound
&& mAlwaysConsumeNavBar == that.mAlwaysConsumeNavBar
&& mSystemWindowInsetsConsumed == that.mSystemWindowInsetsConsumed
- && mWindowDecorInsetsConsumed == that.mWindowDecorInsetsConsumed
&& mStableInsetsConsumed == that.mStableInsetsConsumed
&& mDisplayCutoutConsumed == that.mDisplayCutoutConsumed
- && Objects.equals(mSystemWindowInsets, that.mSystemWindowInsets)
- && Objects.equals(mWindowDecorInsets, that.mWindowDecorInsets)
- && Objects.equals(mStableInsets, that.mStableInsets)
+ && Arrays.equals(mTypeInsetsMap, that.mTypeInsetsMap)
+ && Arrays.equals(mTypeMaxInsetsMap, that.mTypeMaxInsetsMap)
&& Objects.equals(mDisplayCutout, that.mDisplayCutout);
}
@Override
public int hashCode() {
- return Objects.hash(mSystemWindowInsets, mWindowDecorInsets, mStableInsets, mIsRound,
- mDisplayCutout, mAlwaysConsumeNavBar, mSystemWindowInsetsConsumed,
- mWindowDecorInsetsConsumed, mStableInsetsConsumed, mDisplayCutoutConsumed);
+ return Objects.hash(Arrays.hashCode(mTypeInsetsMap), Arrays.hashCode(mTypeMaxInsetsMap),
+ mIsRound, mDisplayCutout, mAlwaysConsumeNavBar, mSystemWindowInsetsConsumed,
+ mStableInsetsConsumed, mDisplayCutoutConsumed);
+ }
+
+
+ /**
+ * Insets every inset in {@code typeInsetsMap} by the specified left, top, right, bottom.
+ *
+ * @return {@code typeInsetsMap} if no inset was modified; a copy of the map with the modified
+ * insets otherwise.
+ */
+ private static Insets[] insetInsets(
+ Insets[] typeInsetsMap, int left, int top, int right, int bottom) {
+ boolean cloned = false;
+ for (int i = 0; i < SIZE; i++) {
+ Insets insets = typeInsetsMap[i];
+ if (insets == null) {
+ continue;
+ }
+ Insets insetInsets = insetInsets(insets, left, top, right, bottom);
+ if (insetInsets != insets) {
+ if (!cloned) {
+ typeInsetsMap = typeInsetsMap.clone();
+ cloned = true;
+ }
+ typeInsetsMap[i] = insetInsets;
+ }
+ }
+ return typeInsetsMap;
}
private static Insets insetInsets(Insets insets, int left, int top, int right, int bottom) {
@@ -683,10 +736,6 @@
return Insets.of(newLeft, newTop, newRight, newBottom);
}
- private static Insets insetsOrNull(Rect insets) {
- return insets != null ? Insets.of(insets) : null;
- }
-
/**
* @return whether system window insets have been consumed.
*/
@@ -699,11 +748,13 @@
*/
public static class Builder {
- private Insets mSystemWindowInsets;
- private Insets mStableInsets;
+ private final Insets[] mTypeInsetsMap;
+ private final Insets[] mTypeMaxInsetsMap;
+ private boolean mSystemInsetsConsumed = true;
+ private boolean mStableInsetsConsumed = true;
+
private DisplayCutout mDisplayCutout;
- private Insets mWindowDecorInsets;
private boolean mIsRound;
private boolean mAlwaysConsumeNavBar;
@@ -711,6 +762,8 @@
* Creates a builder where all insets are initially consumed.
*/
public Builder() {
+ mTypeInsetsMap = new Insets[SIZE];
+ mTypeMaxInsetsMap = new Insets[SIZE];
}
/**
@@ -719,12 +772,11 @@
* @param insets the instance to initialize from.
*/
public Builder(WindowInsets insets) {
- mSystemWindowInsets = insets.mSystemWindowInsetsConsumed ? null
- : insets.mSystemWindowInsets;
- mStableInsets = insets.mStableInsetsConsumed ? null : insets.mStableInsets;
+ mTypeInsetsMap = insets.mTypeInsetsMap.clone();
+ mTypeMaxInsetsMap = insets.mTypeMaxInsetsMap.clone();
+ mSystemInsetsConsumed = insets.mSystemWindowInsetsConsumed;
+ mStableInsetsConsumed = insets.mStableInsetsConsumed;
mDisplayCutout = displayCutoutCopyConstructorArgument(insets);
- mWindowDecorInsets = insets.mWindowDecorInsetsConsumed ? null
- : insets.mWindowDecorInsets;
mIsRound = insets.mIsRound;
mAlwaysConsumeNavBar = insets.mAlwaysConsumeNavBar;
}
@@ -742,7 +794,66 @@
@NonNull
public Builder setSystemWindowInsets(@NonNull Insets systemWindowInsets) {
Preconditions.checkNotNull(systemWindowInsets);
- mSystemWindowInsets = systemWindowInsets;
+ assignCompatInsets(mTypeInsetsMap, systemWindowInsets.toRect());
+ mSystemInsetsConsumed = false;
+ return this;
+ }
+
+ /**
+ * Sets the insets of a specific window type in pixels.
+ *
+ * <p>The insets represents the area of a a window that is partially or fully obscured by
+ * the system windows identified by {@code typeMask}.
+ * </p>
+ *
+ * @see #getInsets(int)
+ *
+ * @param typeMask The bitmask of {@link InsetType} to set the insets for.
+ * @param insets The insets to set.
+ *
+ * @return itself
+ * @hide pending unhide
+ */
+ @NonNull
+ public Builder setInsets(@InsetType int typeMask, @NonNull Insets insets) {
+ Preconditions.checkNotNull(insets);
+ WindowInsets.setInsets(mTypeInsetsMap, typeMask, insets);
+ mSystemInsetsConsumed = false;
+ return this;
+ }
+
+ /**
+ * Sets the maximum amount of insets a specific window type in pixels.
+ *
+ * <p>The maximum insets represents the area of a a window that that <b>may</b> be partially
+ * or fully obscured by the system windows identified by {@code typeMask}. This value does
+ * not change based on the visibility state of those elements. for example, if the status
+ * bar is normally shown, but temporarily hidden, the maximum inset will still provide the
+ * inset associated with the status bar being shown.</p>
+ *
+ * @see #getMaxInsets(int)
+ *
+ * @param typeMask The bitmask of {@link InsetType} to set the insets for.
+ * @param insets The insets to set.
+ *
+ * @return itself
+ *
+ * @throws IllegalArgumentException If {@code typeMask} contains {@link Type#ime()}. Maximum
+ * insets are not available for this type as the height of
+ * the IME is dynamic depending on the {@link EditorInfo}
+ * of the currently focused view, as well as the UI
+ * state of the IME.
+ * @hide pending unhide
+ */
+ @NonNull
+ public Builder setMaxInsets(@InsetType int typeMask, @NonNull Insets insets)
+ throws IllegalArgumentException{
+ if (typeMask == IME) {
+ throw new IllegalArgumentException("Maximum inset not available for IME");
+ }
+ Preconditions.checkNotNull(insets);
+ WindowInsets.setInsets(mTypeMaxInsetsMap, typeMask, insets);
+ mStableInsetsConsumed = false;
return this;
}
@@ -761,7 +872,8 @@
@NonNull
public Builder setStableInsets(@NonNull Insets stableInsets) {
Preconditions.checkNotNull(stableInsets);
- mStableInsets = stableInsets;
+ assignCompatInsets(mTypeInsetsMap, stableInsets.toRect());
+ mStableInsetsConsumed = false;
return this;
}
@@ -780,14 +892,6 @@
/** @hide */
@NonNull
- public Builder setWindowDecorInsets(@NonNull Insets windowDecorInsets) {
- Preconditions.checkNotNull(windowDecorInsets);
- mWindowDecorInsets = windowDecorInsets;
- return this;
- }
-
- /** @hide */
- @NonNull
public Builder setRound(boolean round) {
mIsRound = round;
return this;
@@ -807,8 +911,9 @@
*/
@NonNull
public WindowInsets build() {
- return new WindowInsets(mSystemWindowInsets, mWindowDecorInsets, mStableInsets,
- mIsRound, mAlwaysConsumeNavBar, mDisplayCutout);
+ return new WindowInsets(mSystemInsetsConsumed ? null : mTypeInsetsMap,
+ mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mIsRound,
+ mAlwaysConsumeNavBar, mDisplayCutout);
}
}
@@ -818,10 +923,31 @@
*/
public static final class Type {
- static final int TOP_BAR = 0x1;
+ static final int FIRST = 0x1;
+ static final int TOP_BAR = FIRST;
+
static final int IME = 0x2;
static final int SIDE_BARS = 0x4;
- static final int WINDOW_DECOR = 0x8;
+
+ static final int LAST = 0x8;
+ static final int SIZE = 4;
+ static final int WINDOW_DECOR = LAST;
+
+ static int indexOf(@InsetType int type) {
+ switch (type) {
+ case TOP_BAR:
+ return 0;
+ case IME:
+ return 1;
+ case SIDE_BARS:
+ return 2;
+ case WINDOW_DECOR:
+ return 3;
+ default:
+ throw new IllegalArgumentException("type needs to be >= FIRST and <= LAST,"
+ + " type=" + type);
+ }
+ }
private Type() {
}
@@ -870,6 +996,15 @@
}
/**
+ * @return Inset types representing the list of bars that traditionally were denoted as
+ * system insets.
+ * @hide
+ */
+ static @InsetType int compatSystemInsets() {
+ return TOP_BAR | SIDE_BARS | IME;
+ }
+
+ /**
* @return All inset types combined.
*/
public static @InsetType int all() {
diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java
index 95a346f..c630177 100644
--- a/core/java/android/view/animation/Animation.java
+++ b/core/java/android/view/animation/Animation.java
@@ -794,7 +794,7 @@
* @deprecated All window animations are running with detached wallpaper.
*/
public boolean getDetachWallpaper() {
- return false;
+ return true;
}
/**
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 56f973e..90ccc25 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -62,6 +62,7 @@
import com.android.internal.os.IResultReceiver;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
+import com.android.internal.util.SyncResultReceiver;
import org.xmlpull.v1.XmlPullParserException;
@@ -75,8 +76,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Objects;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
+
//TODO: use java.lang.ref.Cleaner once Android supports Java 9
import sun.misc.Cleaner;
@@ -324,6 +324,11 @@
public static final int FC_SERVICE_TIMEOUT = 5000;
/**
+ * Timeout for calls to system_server.
+ */
+ private static final int SYNC_CALLS_TIMEOUT_MS = 5000;
+
+ /**
* Makes an authentication id from a request id and a dataset id.
*
* @param requestId The request id.
@@ -612,7 +617,8 @@
final AutofillClient client = getClient();
if (client != null) {
- final SyncResultReceiver receiver = new SyncResultReceiver();
+ final SyncResultReceiver receiver = new SyncResultReceiver(
+ SYNC_CALLS_TIMEOUT_MS);
try {
mService.restoreSession(mSessionId, client.autofillClientGetActivityToken(),
mServiceClient.asBinder(), receiver);
@@ -732,9 +738,9 @@
*/
@Nullable public FillEventHistory getFillEventHistory() {
try {
- final SyncResultReceiver receiver = new SyncResultReceiver();
+ final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
mService.getFillEventHistory(receiver);
- return receiver.getObjectResult(SyncResultReceiver.TYPE_PARCELABLE);
+ return receiver.getParcelableResult();
} catch (RemoteException e) {
e.rethrowFromSystemServer();
return null;
@@ -1287,7 +1293,7 @@
public boolean hasEnabledAutofillServices() {
if (mService == null) return false;
- final SyncResultReceiver receiver = new SyncResultReceiver();
+ final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
try {
mService.isServiceEnabled(mContext.getUserId(), mContext.getPackageName(), receiver);
return receiver.getIntResult() == 1;
@@ -1304,10 +1310,10 @@
public ComponentName getAutofillServiceComponentName() {
if (mService == null) return null;
- final SyncResultReceiver receiver = new SyncResultReceiver();
+ final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
try {
mService.getAutofillServiceComponentName(receiver);
- return receiver.getObjectResult(SyncResultReceiver.TYPE_PARCELABLE);
+ return receiver.getParcelableResult();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1330,9 +1336,9 @@
*/
@Nullable public String getUserDataId() {
try {
- final SyncResultReceiver receiver = new SyncResultReceiver();
+ final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
mService.getUserDataId(receiver);
- return receiver.getObjectResult(SyncResultReceiver.TYPE_STRING);
+ return receiver.getStringResult();
} catch (RemoteException e) {
e.rethrowFromSystemServer();
return null;
@@ -1352,9 +1358,9 @@
*/
@Nullable public UserData getUserData() {
try {
- final SyncResultReceiver receiver = new SyncResultReceiver();
+ final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
mService.getUserData(receiver);
- return receiver.getObjectResult(SyncResultReceiver.TYPE_PARCELABLE);
+ return receiver.getParcelableResult();
} catch (RemoteException e) {
e.rethrowFromSystemServer();
return null;
@@ -1390,7 +1396,7 @@
* the user.
*/
public boolean isFieldClassificationEnabled() {
- final SyncResultReceiver receiver = new SyncResultReceiver();
+ final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
try {
mService.isFieldClassificationEnabled(receiver);
return receiver.getIntResult() == 1;
@@ -1413,10 +1419,10 @@
*/
@Nullable
public String getDefaultFieldClassificationAlgorithm() {
- final SyncResultReceiver receiver = new SyncResultReceiver();
+ final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
try {
mService.getDefaultFieldClassificationAlgorithm(receiver);
- return receiver.getObjectResult(SyncResultReceiver.TYPE_STRING);
+ return receiver.getStringResult();
} catch (RemoteException e) {
e.rethrowFromSystemServer();
return null;
@@ -1433,11 +1439,10 @@
*/
@NonNull
public List<String> getAvailableFieldClassificationAlgorithms() {
- final SyncResultReceiver receiver = new SyncResultReceiver();
+ final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
try {
mService.getAvailableFieldClassificationAlgorithms(receiver);
- final String[] algorithms = receiver
- .getObjectResult(SyncResultReceiver.TYPE_STRING_ARRAY);
+ final String[] algorithms = receiver.getStringArrayResult();
return algorithms != null ? Arrays.asList(algorithms) : Collections.emptyList();
} catch (RemoteException e) {
e.rethrowFromSystemServer();
@@ -1458,7 +1463,7 @@
public boolean isAutofillSupported() {
if (mService == null) return false;
- final SyncResultReceiver receiver = new SyncResultReceiver();
+ final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
try {
mService.isServiceSupported(mContext.getUserId(), receiver);
return receiver.getIntResult() == 1;
@@ -1582,7 +1587,7 @@
final AutofillClient client = getClient();
if (client == null) return; // NOTE: getClient() already logged it..
- final SyncResultReceiver receiver = new SyncResultReceiver();
+ final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
mService.startSession(client.autofillClientGetActivityToken(),
mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
mCallback != null, flags, client.autofillClientGetComponentName(),
@@ -1665,7 +1670,7 @@
mServiceClient = new AutofillManagerClient(this);
try {
final int userId = mContext.getUserId();
- final SyncResultReceiver receiver = new SyncResultReceiver();
+ final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
mService.addClient(mServiceClient, userId, receiver);
final int flags = receiver.getIntResult();
mEnabled = (flags & FLAG_ADD_CLIENT_ENABLED) != 0;
@@ -2986,104 +2991,4 @@
}
}
}
-
- /**
- * @hide
- */
- public static final class SyncResultReceiver extends IResultReceiver.Stub {
-
- private static final String EXTRA = "EXTRA";
-
- /**
- * How long to block waiting for {@link IResultReceiver} callbacks when calling server.
- */
- private static final long BINDER_TIMEOUT_MS = 5000;
-
- private static final int TYPE_STRING = 0;
- private static final int TYPE_STRING_ARRAY = 1;
- private static final int TYPE_PARCELABLE = 2;
-
- private final CountDownLatch mLatch = new CountDownLatch(1);
- private int mResult;
- private Bundle mBundle;
-
- private void waitResult() {
- try {
- if (!mLatch.await(BINDER_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
- throw new IllegalStateException("Not called in " + BINDER_TIMEOUT_MS + "ms");
- }
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- }
-
- /**
- * Gets the result from an operation that returns an {@code int}.
- */
- int getIntResult() {
- waitResult();
- return mResult;
- }
-
- /**
- * Gets the result from an operation that returns an {@code Object}.
- *
- * @param type type of expected object.
- */
- @Nullable
- @SuppressWarnings("unchecked")
- <T> T getObjectResult(int type) {
- waitResult();
- if (mBundle == null) {
- return null;
- }
- switch (type) {
- case TYPE_STRING:
- return (T) mBundle.getString(EXTRA);
- case TYPE_STRING_ARRAY:
- return (T) mBundle.getStringArray(EXTRA);
- case TYPE_PARCELABLE:
- return (T) mBundle.getParcelable(EXTRA);
- default:
- throw new IllegalArgumentException("unsupported type: " + type);
- }
- }
-
- @Override
- public void send(int resultCode, Bundle resultData) {
- mResult = resultCode;
- mBundle = resultData;
- mLatch.countDown();
- }
-
- /**
- * Creates a bundle for a {@code String} value.
- */
- @NonNull
- public static Bundle bundleFor(@Nullable String value) {
- final Bundle bundle = new Bundle();
- bundle.putString(EXTRA, value);
- return bundle;
- }
-
- /**
- * Creates a bundle for a {@code String[]} value.
- */
- @NonNull
- public static Bundle bundleFor(@Nullable String[] value) {
- final Bundle bundle = new Bundle();
- bundle.putStringArray(EXTRA, value);
- return bundle;
- }
-
- /**
- * Creates a bundle for a {@code Parcelable} value.
- */
- @NonNull
- public static Bundle bundleFor(@Nullable Parcelable value) {
- final Bundle bundle = new Bundle();
- bundle.putParcelable(EXTRA, value);
- return bundle;
- }
- }
}
diff --git a/core/java/android/view/contentcapture/ChildContentCaptureSession.java b/core/java/android/view/contentcapture/ChildContentCaptureSession.java
index 5166831..04e725e 100644
--- a/core/java/android/view/contentcapture/ChildContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ChildContentCaptureSession.java
@@ -33,7 +33,7 @@
final class ChildContentCaptureSession extends ContentCaptureSession {
@NonNull
- private final MainContentCaptureSession mParent;
+ private final ContentCaptureSession mParent;
/**
* {@link ContentCaptureContext} set by client, or {@code null} when it's the
@@ -46,16 +46,25 @@
private final ContentCaptureContext mClientContext;
/** @hide */
- protected ChildContentCaptureSession(@NonNull MainContentCaptureSession parent,
+ protected ChildContentCaptureSession(@NonNull ContentCaptureSession parent,
@NonNull ContentCaptureContext clientContext) {
mParent = parent;
mClientContext = Preconditions.checkNotNull(clientContext);
}
@Override
- ContentCaptureSession newChild(@NonNull ContentCaptureContext context) {
- // TODO(b/121033016): implement it
- throw new UnsupportedOperationException("grand-children not implemented yet");
+ MainContentCaptureSession getMainCaptureSession() {
+ if (mParent instanceof MainContentCaptureSession) {
+ return (MainContentCaptureSession) mParent;
+ }
+ return mParent.getMainCaptureSession();
+ }
+
+ @Override
+ ContentCaptureSession newChild(@NonNull ContentCaptureContext clientContext) {
+ final ContentCaptureSession child = new ChildContentCaptureSession(this, clientContext);
+ getMainCaptureSession().notifyChildSessionStarted(mId, child.mId, clientContext);
+ return child;
}
@Override
@@ -65,27 +74,27 @@
@Override
void onDestroy() {
- mParent.notifyChildSessionFinished(mParent.mId, mId);
+ getMainCaptureSession().notifyChildSessionFinished(mParent.mId, mId);
}
@Override
void internalNotifyViewAppeared(@NonNull ViewStructureImpl node) {
- mParent.notifyViewAppeared(mId, node);
+ getMainCaptureSession().notifyViewAppeared(mId, node);
}
@Override
void internalNotifyViewDisappeared(@NonNull AutofillId id) {
- mParent.notifyViewDisappeared(mId, id);
+ getMainCaptureSession().notifyViewDisappeared(mId, id);
}
@Override
void internalNotifyViewTextChanged(@NonNull AutofillId id, @Nullable CharSequence text,
int flags) {
- mParent.notifyViewTextChanged(mId, id, text, flags);
+ getMainCaptureSession().notifyViewTextChanged(mId, id, text, flags);
}
@Override
boolean isContentCaptureEnabled() {
- return mParent.isContentCaptureEnabled();
+ return getMainCaptureSession().isContentCaptureEnabled();
}
@Override
diff --git a/core/java/android/view/contentcapture/ContentCaptureEvent.java b/core/java/android/view/contentcapture/ContentCaptureEvent.java
index 9e3da92..0d06430 100644
--- a/core/java/android/view/contentcapture/ContentCaptureEvent.java
+++ b/core/java/android/view/contentcapture/ContentCaptureEvent.java
@@ -251,6 +251,10 @@
public String toString() {
final StringBuilder string = new StringBuilder("ContentCaptureEvent[type=")
.append(getTypeAsString(mType));
+ string.append(", session=").append(mSessionId);
+ if (mType == TYPE_SESSION_STARTED && mParentSessionId != null) {
+ string.append(", parent=").append(mParentSessionId);
+ }
if (mFlags > 0) {
string.append(", flags=").append(mFlags);
}
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index 9830790..ff45efd 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -15,6 +15,8 @@
*/
package android.view.contentcapture;
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemService;
@@ -24,9 +26,12 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.RemoteException;
import android.util.Log;
+import com.android.internal.os.IResultReceiver;
import com.android.internal.util.Preconditions;
+import com.android.internal.util.SyncResultReceiver;
import java.io.PrintWriter;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -48,6 +53,11 @@
private static final String BG_THREAD_NAME = "intel_svc_streamer_thread";
+ /**
+ * Timeout for calls to system_server.
+ */
+ private static final int SYNC_CALLS_TIMEOUT_MS = 5000;
+
// TODO(b/121044306): define a way to dynamically set them(for example, using settings?)
static final boolean VERBOSE = false;
static final boolean DEBUG = true; // STOPSHIP if not set to false
@@ -116,14 +126,12 @@
/** @hide */
public void onActivityStarted(@NonNull IBinder applicationToken,
- @NonNull ComponentName activityComponent) {
- // TODO(b/121033016): must start all sessions
- getMainContentCaptureSession().start(applicationToken, activityComponent);
+ @NonNull ComponentName activityComponent, int flags) {
+ getMainContentCaptureSession().start(applicationToken, activityComponent, flags);
}
/** @hide */
public void onActivityStopped() {
- // TODO(b/121033016): must finish all sessions
getMainContentCaptureSession().destroy();
}
@@ -135,7 +143,6 @@
* @hide
*/
public void flush() {
- // TODO(b/121033016): must flush all sessions
getMainContentCaptureSession().flush();
}
@@ -145,8 +152,21 @@
*/
@Nullable
public ComponentName getServiceComponentName() {
- //TODO(b/121047489): implement
- return null;
+ if (!isContentCaptureEnabled()) {
+ return null;
+ }
+ // Wait for system server to return the component name.
+ final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
+ mHandler.sendMessage(obtainMessage(
+ ContentCaptureManager::handleReceiverServiceComponentName,
+ this, mContext.getUserId(), resultReceiver));
+
+ try {
+ return resultReceiver.getParcelableResult();
+ } catch (RemoteException e) {
+ // Unable to retrieve component name in a reasonable amount of time.
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -194,4 +214,14 @@
pw.print(prefix); pw.println("No sessions");
}
}
+
+
+ /** Retrieves the component name of the target content capture service through system_server. */
+ private void handleReceiverServiceComponentName(int userId, IResultReceiver resultReceiver) {
+ try {
+ mService.getReceiverServiceComponentName(userId, resultReceiver);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to retrieve service component name: " + e);
+ }
+ }
}
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index 46e6882..6890beaf 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -27,6 +27,7 @@
import android.view.autofill.AutofillId;
import android.view.contentcapture.ViewNode.ViewStructureImpl;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import dalvik.system.CloseGuard;
@@ -34,7 +35,6 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.UUID;
-import java.util.concurrent.atomic.AtomicBoolean;
/**
* Session used to notify a system-provided Content Capture service about events associated with
@@ -46,8 +46,7 @@
/**
* Used on {@link #notifyViewTextChanged(AutofillId, CharSequence, int)} to indicate that the
- *
- * thext change was caused by user input (for example, through IME).
+ * text change was caused by user input (for example, through IME).
*/
public static final int FLAG_USER_INPUT = 0x1;
@@ -73,11 +72,11 @@
public static final int STATE_ACTIVE = 2;
/**
- * Session is disabled.
+ * Session is disabled because there is no service for this user.
*
* @hide
*/
- public static final int STATE_DISABLED = 3;
+ public static final int STATE_DISABLED_NO_SERVICE = 3;
/**
* Session is disabled because its id already existed on server.
@@ -86,15 +85,25 @@
*/
public static final int STATE_DISABLED_DUPLICATED_ID = 4;
+ /**
+ * Session is disabled by FLAG_SECURE
+ *
+ * @hide
+ */
+ public static final int STATE_DISABLED_BY_FLAG_SECURE = 5;
+
private static final int INITIAL_CHILDREN_CAPACITY = 5;
private final CloseGuard mCloseGuard = CloseGuard.get();
+ private final Object mLock = new Object();
+
/**
* Guard use to ignore events after it's destroyed.
*/
@NonNull
- private final AtomicBoolean mDestroyed = new AtomicBoolean();
+ @GuardedBy("mLock")
+ private boolean mDestroyed;
/** @hide */
@Nullable
@@ -108,11 +117,8 @@
/**
* List of children session.
*/
- // TODO(b/121033016): need to synchonize access, either by changing on handler or UI thread
- // (for now there's no handler on this class, so we need to wait for the next refactoring),
- // most likely the former (as we have no guarantee that createContentCaptureSession()
- // it will be called in the UiThread; for example, WebView most likely won't call on it)
@Nullable
+ @GuardedBy("mLock")
private ArrayList<ContentCaptureSession> mChildren;
/** @hide */
@@ -120,6 +126,10 @@
mCloseGuard.open("destroy");
}
+ /** @hide */
+ @NonNull
+ abstract MainContentCaptureSession getMainCaptureSession();
+
/**
* Gets the id used to identify this session.
*/
@@ -143,10 +153,12 @@
Log.d(TAG, "createContentCaptureSession(" + context + ": parent=" + mId + ", child="
+ child.mId);
}
- if (mChildren == null) {
- mChildren = new ArrayList<>(INITIAL_CHILDREN_CAPACITY);
+ synchronized (mLock) {
+ if (mChildren == null) {
+ mChildren = new ArrayList<>(INITIAL_CHILDREN_CAPACITY);
+ }
+ mChildren.add(child);
}
- mChildren.add(child);
return child;
}
@@ -163,29 +175,31 @@
* <p>Once destroyed, any new notification will be dropped.
*/
public final void destroy() {
- if (!mDestroyed.compareAndSet(false, true)) {
- Log.e(TAG, "destroy(): already destroyed");
- return;
- }
+ synchronized (mLock) {
+ if (mDestroyed) {
+ Log.e(TAG, "destroy(" + mId + "): already destroyed");
+ return;
+ }
+ mDestroyed = true;
- mCloseGuard.close();
+ mCloseGuard.close();
- //TODO(b/111276913): check state (for example, how to handle if it's waiting for remote
- // id) and send it to the cache of batched commands
- if (VERBOSE) {
- Log.v(TAG, "destroy(): state=" + getStateAsString(mState) + ", mId=" + mId);
- }
-
- // Finish children first
- if (mChildren != null) {
- final int numberChildren = mChildren.size();
- if (VERBOSE) Log.v(TAG, "Destroying " + numberChildren + " children first");
- for (int i = 0; i < numberChildren; i++) {
- final ContentCaptureSession child = mChildren.get(i);
- try {
- child.destroy();
- } catch (Exception e) {
- Log.w(TAG, "exception destroying child session #" + i + ": " + e);
+ // TODO(b/111276913): check state (for example, how to handle if it's waiting for remote
+ // id) and send it to the cache of batched commands
+ if (VERBOSE) {
+ Log.v(TAG, "destroy(): state=" + getStateAsString(mState) + ", mId=" + mId);
+ }
+ // Finish children first
+ if (mChildren != null) {
+ final int numberChildren = mChildren.size();
+ if (VERBOSE) Log.v(TAG, "Destroying " + numberChildren + " children first");
+ for (int i = 0; i < numberChildren; i++) {
+ final ContentCaptureSession child = mChildren.get(i);
+ try {
+ child.destroy();
+ } catch (Exception e) {
+ Log.w(TAG, "exception destroying child session #" + i + ": " + e);
+ }
}
}
}
@@ -287,6 +301,26 @@
}
/**
+ * Creates a new {@link AutofillId} for a virtual child, so it can be used to uniquely identify
+ * the children in the session.
+ *
+ * @param parentId id of the virtual view parent (it can be obtained by calling
+ * {@link ViewStructure#getAutofillId()} on the parent).
+ * @param virtualChildId id of the virtual child, relative to the parent.
+ *
+ * @return if for the virtual child
+ *
+ * @throws IllegalArgumentException if the {@code parentId} is a virtual child id.
+ */
+ public @NonNull AutofillId newAutofillId(@NonNull AutofillId parentId, int virtualChildId) {
+ Preconditions.checkNotNull(parentId);
+ Preconditions.checkArgument(!parentId.isVirtual(), "virtual ids cannot have children");
+ // TODO(b/121197119): we need to add the session id to the AutofillId to make them unique
+ // per session
+ return new AutofillId(parentId, virtualChildId);
+ }
+
+ /**
* Creates a {@link ViewStructure} for a "virtual" view, so it can be passed to
* {@link #notifyViewAppeared(ViewStructure)} by the view managing the virtual view hierarchy.
*
@@ -295,33 +329,35 @@
* @param virtualId id of the virtual child, relative to the parent.
*
* @return a new {@link ViewStructure} that can be used for Content Capture purposes.
- *
- * @hide
*/
@NonNull
public final ViewStructure newVirtualViewStructure(@NonNull AutofillId parentId,
int virtualId) {
+ // TODO(b/121197119): use the constructor that takes a session id / assert on unit test.
return new ViewNode.ViewStructureImpl(parentId, virtualId);
}
boolean isContentCaptureEnabled() {
- return !mDestroyed.get();
+ synchronized (mLock) {
+ return !mDestroyed;
+ }
}
@CallSuper
void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
pw.print(prefix); pw.print("id: "); pw.println(mId);
- pw.print(prefix); pw.print("destroyed: "); pw.println(mDestroyed.get());
- if (mChildren != null && !mChildren.isEmpty()) {
- final String prefix2 = prefix + " ";
- final int numberChildren = mChildren.size();
- pw.print(prefix); pw.print("number children: "); pw.println(numberChildren);
- for (int i = 0; i < numberChildren; i++) {
- final ContentCaptureSession child = mChildren.get(i);
- pw.print(prefix); pw.print(i); pw.println(": "); child.dump(prefix2, pw);
+ synchronized (mLock) {
+ pw.print(prefix); pw.print("destroyed: "); pw.println(mDestroyed);
+ if (mChildren != null && !mChildren.isEmpty()) {
+ final String prefix2 = prefix + " ";
+ final int numberChildren = mChildren.size();
+ pw.print(prefix); pw.print("number children: "); pw.println(numberChildren);
+ for (int i = 0; i < numberChildren; i++) {
+ final ContentCaptureSession child = mChildren.get(i);
+ pw.print(prefix); pw.print(i); pw.println(": "); child.dump(prefix2, pw);
+ }
}
}
-
}
@Override
@@ -341,10 +377,12 @@
return "WAITING_FOR_SERVER";
case STATE_ACTIVE:
return "ACTIVE";
- case STATE_DISABLED:
- return "DISABLED";
+ case STATE_DISABLED_NO_SERVICE:
+ return "DISABLED_NO_SERVICE";
case STATE_DISABLED_DUPLICATED_ID:
return "DISABLED_DUPLICATED_ID";
+ case STATE_DISABLED_BY_FLAG_SECURE:
+ return "DISABLED_FLAG_SECURE";
default:
return "INVALID:" + state;
}
diff --git a/core/java/android/view/contentcapture/IContentCaptureManager.aidl b/core/java/android/view/contentcapture/IContentCaptureManager.aidl
index 01776f8..be9c00f 100644
--- a/core/java/android/view/contentcapture/IContentCaptureManager.aidl
+++ b/core/java/android/view/contentcapture/IContentCaptureManager.aidl
@@ -32,7 +32,28 @@
* @hide
*/
oneway interface IContentCaptureManager {
+ /**
+ * Starts a new session for the provided {@code userId} running as part of the
+ * app's activity identified by {@code activityToken}/{@code componentName}.
+ *
+ * @param sessionId Unique session id as provided by the app.
+ * @param flags Meta flags that enable or disable content capture (see
+ * {@link IContentCaptureContext#flags}).
+ */
void startSession(int userId, IBinder activityToken, in ComponentName componentName,
String sessionId, int flags, in IResultReceiver result);
+
+ /**
+ * Marks the end of a session for the provided {@code userId} identified by
+ * the corresponding {@code startSession}'s {@code sessionId}.
+ */
void finishSession(int userId, String sessionId);
+
+ /**
+ * Returns the content capture service's component name (if enabled and
+ * connected).
+ * @param Receiver of the content capture service's @{code ComponentName}
+ * provided {@code Bundle} with key "{@code EXTRA}".
+ */
+ void getReceiverServiceComponentName(int userId, in IResultReceiver result);
}
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index baf4a35..a29aaf0 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -141,6 +141,11 @@
}
@Override
+ MainContentCaptureSession getMainCaptureSession() {
+ return this;
+ }
+
+ @Override
ContentCaptureSession newChild(@NonNull ContentCaptureContext clientContext) {
final ContentCaptureSession child = new ChildContentCaptureSession(this, clientContext);
notifyChildSessionStarted(mId, child.mId, clientContext);
@@ -152,7 +157,8 @@
*
* @hide
*/
- void start(@NonNull IBinder applicationToken, @NonNull ComponentName activityComponent) {
+ void start(@NonNull IBinder applicationToken, @NonNull ComponentName activityComponent,
+ int flags) {
if (!isContentCaptureEnabled()) return;
if (VERBOSE) {
@@ -161,7 +167,7 @@
}
mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleStartSession, this,
- applicationToken, activityComponent));
+ applicationToken, activityComponent, flags));
}
@Override
@@ -171,11 +177,13 @@
@Override
void onDestroy() {
+ mHandler.removeMessages(MSG_FLUSH);
mHandler.sendMessage(
obtainMessage(MainContentCaptureSession::handleDestroySession, this));
}
- private void handleStartSession(@NonNull IBinder token, @NonNull ComponentName componentName) {
+ private void handleStartSession(@NonNull IBinder token, @NonNull ComponentName componentName,
+ int flags) {
if (mState != STATE_UNKNOWN) {
// TODO(b/111276913): revisit this scenario
Log.w(TAG, "ignoring handleStartSession(" + token + ") while on state "
@@ -190,9 +198,10 @@
Log.v(TAG, "handleStartSession(): token=" + token + ", act="
+ getActivityDebugName() + ", id=" + mId);
}
- final int flags = 0; // TODO(b/111276913): get proper flags
try {
+ if (mSystemServerInterface == null) return;
+
mSystemServerInterface.startSession(mContext.getUserId(), mApplicationToken,
componentName, mId, flags, new IResultReceiver.Stub() {
@Override
@@ -237,7 +246,11 @@
Log.w(TAG, "Failed to link to death on " + binder + ": " + e);
}
}
- if (resultCode == STATE_DISABLED || resultCode == STATE_DISABLED_DUPLICATED_ID) {
+
+ // TODO(b/111276913): change the resultCode to use flags so there's just one flag for
+ // disabled stuff
+ if (resultCode == STATE_DISABLED_NO_SERVICE || resultCode == STATE_DISABLED_DUPLICATED_ID
+ || resultCode == STATE_DISABLED_BY_FLAG_SECURE) {
mDisabled.set(true);
handleResetSession(/* resetState= */ false);
} else {
@@ -246,7 +259,7 @@
if (VERBOSE) {
Log.v(TAG, "handleSessionStarted() result: code=" + resultCode + ", id=" + mId
+ ", state=" + getStateAsString(mState) + ", disabled=" + mDisabled.get()
- + ", binder=" + binder);
+ + ", binder=" + binder + ", events=" + (mEvents == null ? 0 : mEvents.size()));
}
}
@@ -285,14 +298,14 @@
return;
}
- if (mState != STATE_ACTIVE) {
+ if (mState != STATE_ACTIVE && numberEvents >= MAX_BUFFER_SIZE) {
// Callback from startSession hasn't been called yet - typically happens on system
// apps that are started before the system service
// TODO(b/111276913): try to ignore session while system is not ready / boot
// not complete instead. Similarly, the manager service should return right away
// when the user does not have a service set
- if (VERBOSE) {
- Log.v(TAG, "Closing session for " + getActivityDebugName()
+ if (DEBUG) {
+ Log.d(TAG, "Closing session for " + getActivityDebugName()
+ " after " + numberEvents + " delayed events and state "
+ getStateAsString(mState));
}
@@ -331,7 +344,9 @@
if (mEvents == null) return;
if (mDirectServiceInterface == null) {
- if (DEBUG) Log.d(TAG, "handleForceFlush(): hold your horses, client not ready yet!");
+ if (VERBOSE) {
+ Log.v(TAG, "handleForceFlush(): hold your horses, client not ready: " + mEvents);
+ }
if (!mHandler.hasMessages(MSG_FLUSH)) {
handleScheduleFlush(/* checkExisting= */ false);
}
@@ -375,6 +390,8 @@
}
try {
+ if (mSystemServerInterface == null) return;
+
mSystemServerInterface.finishSession(mContext.getUserId(), mId);
} catch (RemoteException e) {
Log.e(TAG, "Error destroying system-service session " + mId + " for "
@@ -386,14 +403,14 @@
handleResetSession(/* resetState= */ true);
}
- // TODO(b/121033016): once we support multiple sessions, we might need to move some of these
+ // TODO(b/122454205): once we support multiple sessions, we might need to move some of these
// clearings out.
private void handleResetSession(boolean resetState) {
if (resetState) {
mState = STATE_UNKNOWN;
}
- // TODO(b/121033016): must reset children (which currently is owned by superclass)
+ // TODO(b/122454205): must reset children (which currently is owned by superclass)
mApplicationToken = null;
mComponentName = null;
mEvents = null;
@@ -426,7 +443,7 @@
&& !mDisabled.get();
}
- // TODO(b/121033016): refactor "notifyXXXX" methods below to a common "Buffer" object that is
+ // TODO(b/122454205): refactor "notifyXXXX" methods below to a common "Buffer" object that is
// shared between ActivityContentCaptureSession and ChildContentCaptureSession objects. Such
// change should also get get rid of the "internalNotifyXXXX" methods above
void notifyChildSessionStarted(@NonNull String parentSessionId,
@@ -435,14 +452,14 @@
new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED)
.setParentSessionId(parentSessionId)
.setClientContext(clientContext),
- /* forceFlush= */ false));
+ /* forceFlush= */ true));
}
void notifyChildSessionFinished(@NonNull String parentSessionId,
@NonNull String childSessionId) {
mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED)
- .setParentSessionId(parentSessionId), /* forceFlush= */ false));
+ .setParentSessionId(parentSessionId), /* forceFlush= */ true));
}
void notifyViewAppeared(@NonNull String sessionId, @NonNull ViewStructureImpl node) {
diff --git a/core/java/android/view/contentcapture/ViewNode.java b/core/java/android/view/contentcapture/ViewNode.java
index 86b89adb..b7a486a 100644
--- a/core/java/android/view/contentcapture/ViewNode.java
+++ b/core/java/android/view/contentcapture/ViewNode.java
@@ -24,6 +24,7 @@
import android.os.Bundle;
import android.os.LocaleList;
import android.os.Parcel;
+import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.ViewParent;
@@ -32,30 +33,188 @@
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
-//TODO(b/111276913): add javadocs / implement Parcelable / implement
-//TODO(b/111276913): for now it's extending ViewNode directly as it needs most of its properties,
+//TODO(b/122484602): add javadocs / implement Parcelable / implement
+//TODO(b/122484602): for now it's extending ViewNode directly as it needs most of its properties,
// but it might be better to create a common, abstract android.view.ViewNode class that both extend
// instead
/** @hide */
@SystemApi
public final class ViewNode extends AssistStructure.ViewNode {
- private static final String TAG = "ViewNode";
+ private static final String TAG = ViewNode.class.getSimpleName();
+ private static final boolean VERBOSE = false;
+
+ private static final long FLAGS_HAS_TEXT = 1L << 0;
+ private static final long FLAGS_HAS_COMPLEX_TEXT = 1L << 1;
+ private static final long FLAGS_VISIBILITY_MASK = View.VISIBLE | View.INVISIBLE | View.GONE;
+ private static final long FLAGS_HAS_CLASSNAME = 1L << 4;
+ private static final long FLAGS_HAS_AUTOFILL_ID = 1L << 5;
+ private static final long FLAGS_HAS_AUTOFILL_PARENT_ID = 1L << 6;
+ private static final long FLAGS_HAS_ID = 1L << 7;
+ private static final long FLAGS_HAS_LARGE_COORDS = 1L << 8;
+ private static final long FLAGS_HAS_SCROLL = 1L << 9;
+ private static final long FLAGS_ASSIST_BLOCKED = 1L << 10;
+ private static final long FLAGS_DISABLED = 1L << 11;
+ private static final long FLAGS_CLICKABLE = 1L << 12;
+ private static final long FLAGS_LONG_CLICKABLE = 1L << 13;
+ private static final long FLAGS_CONTEXT_CLICKABLE = 1L << 14;
+ private static final long FLAGS_FOCUSABLE = 1L << 15;
+ private static final long FLAGS_FOCUSED = 1L << 16;
+ private static final long FLAGS_ACCESSIBILITY_FOCUSED = 1L << 17;
+ private static final long FLAGS_CHECKABLE = 1L << 18;
+ private static final long FLAGS_CHECKED = 1L << 19;
+ private static final long FLAGS_SELECTED = 1L << 20;
+ private static final long FLAGS_ACTIVATED = 1L << 21;
+ private static final long FLAGS_OPAQUE = 1L << 22;
+ private static final long FLAGS_HAS_MATRIX = 1L << 23;
+ private static final long FLAGS_HAS_ELEVATION = 1L << 24;
+ private static final long FLAGS_HAS_ALPHA = 1L << 25;
+ private static final long FLAGS_HAS_CONTENT_DESCRIPTION = 1L << 26;
+ private static final long FLAGS_HAS_EXTRAS = 1L << 27;
+ private static final long FLAGS_HAS_LOCALE_LIST = 1L << 28;
+ private static final long FLAGS_HAS_INPUT_TYPE = 1L << 29;
+ private static final long FLAGS_HAS_MIN_TEXT_EMS = 1L << 30;
+ private static final long FLAGS_HAS_MAX_TEXT_EMS = 1L << 31;
+ private static final long FLAGS_HAS_MAX_TEXT_LENGTH = 1L << 32;
+ private static final long FLAGS_HAS_TEXT_ID_ENTRY = 1L << 33;
+ private static final long FLAGS_HAS_AUTOFILL_TYPE = 1L << 34;
+ private static final long FLAGS_HAS_AUTOFILL_VALUE = 1L << 35;
+ private static final long FLAGS_HAS_AUTOFILL_HINTS = 1L << 36;
+ private static final long FLAGS_HAS_AUTOFILL_OPTIONS = 1L << 37;
+
+ /** Flags used to optimize what's written to the parcel */
+ private long mFlags;
private AutofillId mParentAutofillId;
- // TODO(b/111276913): temporarily setting some fields here while they're not accessible from the
- // superclass
private AutofillId mAutofillId;
- private CharSequence mText;
+ private ViewNodeText mText;
private String mClassName;
+ private int mId = View.NO_ID;
+ private String mIdPackage;
+ private String mIdType;
+ private String mIdEntry;
+ private int mX;
+ private int mY;
+ private int mScrollX;
+ private int mScrollY;
+ private int mWidth;
+ private int mHeight;
+ private Matrix mMatrix;
+ private float mElevation;
+ private float mAlpha = 1.0f;
+ private CharSequence mContentDescription;
+ private Bundle mExtras;
+ private LocaleList mLocaleList;
+ private int mInputType;
+ private int mMinEms = -1;
+ private int mMaxEms = -1;
+ private int mMaxLength = -1;
+ private String mTextIdEntry;
+ private @View.AutofillType int mAutofillType = View.AUTOFILL_TYPE_NONE;
+ private String[] mAutofillHints;
+ private AutofillValue mAutofillValue;
+ private CharSequence[] mAutofillOptions;
/** @hide */
public ViewNode() {
}
+ private ViewNode(long nodeFlags, @NonNull Parcel parcel) {
+ mFlags = nodeFlags;
+
+ if ((nodeFlags & FLAGS_HAS_AUTOFILL_ID) != 0) {
+ mAutofillId = parcel.readParcelable(null);
+ }
+ if ((nodeFlags & FLAGS_HAS_AUTOFILL_PARENT_ID) != 0) {
+ mParentAutofillId = parcel.readParcelable(null);
+ }
+ if ((nodeFlags & FLAGS_HAS_TEXT) != 0) {
+ mText = new ViewNodeText(parcel, (nodeFlags & FLAGS_HAS_COMPLEX_TEXT) == 0);
+ }
+ if ((nodeFlags & FLAGS_HAS_CLASSNAME) != 0) {
+ mClassName = parcel.readString();
+ }
+ if ((nodeFlags & FLAGS_HAS_ID) != 0) {
+ mId = parcel.readInt();
+ if (mId != View.NO_ID) {
+ mIdEntry = parcel.readString();
+ if (mIdEntry != null) {
+ mIdType = parcel.readString();
+ mIdPackage = parcel.readString();
+ }
+ }
+ }
+ if ((nodeFlags & FLAGS_HAS_LARGE_COORDS) != 0) {
+ mX = parcel.readInt();
+ mY = parcel.readInt();
+ mWidth = parcel.readInt();
+ mHeight = parcel.readInt();
+ } else {
+ int val = parcel.readInt();
+ mX = val & 0x7fff;
+ mY = (val >> 16) & 0x7fff;
+ val = parcel.readInt();
+ mWidth = val & 0x7fff;
+ mHeight = (val >> 16) & 0x7fff;
+ }
+ if ((nodeFlags & FLAGS_HAS_SCROLL) != 0) {
+ mScrollX = parcel.readInt();
+ mScrollY = parcel.readInt();
+ }
+ if ((nodeFlags & FLAGS_HAS_MATRIX) != 0) {
+ mMatrix = new Matrix();
+ final float[] tmpMatrix = new float[9];
+ parcel.readFloatArray(tmpMatrix);
+ mMatrix.setValues(tmpMatrix);
+ }
+ if ((nodeFlags & FLAGS_HAS_ELEVATION) != 0) {
+ mElevation = parcel.readFloat();
+ }
+ if ((nodeFlags & FLAGS_HAS_ALPHA) != 0) {
+ mAlpha = parcel.readFloat();
+ }
+ if ((nodeFlags & FLAGS_HAS_CONTENT_DESCRIPTION) != 0) {
+ mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+ }
+ if ((nodeFlags & FLAGS_HAS_EXTRAS) != 0) {
+ mExtras = parcel.readBundle();
+ }
+ if ((nodeFlags & FLAGS_HAS_LOCALE_LIST) != 0) {
+ mLocaleList = parcel.readParcelable(null);
+ }
+ if ((nodeFlags & FLAGS_HAS_INPUT_TYPE) != 0) {
+ mInputType = parcel.readInt();
+ }
+ if ((nodeFlags & FLAGS_HAS_MIN_TEXT_EMS) != 0) {
+ mMinEms = parcel.readInt();
+ }
+ if ((nodeFlags & FLAGS_HAS_MAX_TEXT_EMS) != 0) {
+ mMaxEms = parcel.readInt();
+ }
+ if ((nodeFlags & FLAGS_HAS_MAX_TEXT_LENGTH) != 0) {
+ mMaxLength = parcel.readInt();
+ }
+ if ((nodeFlags & FLAGS_HAS_TEXT_ID_ENTRY) != 0) {
+ mTextIdEntry = parcel.readString();
+ }
+ if ((nodeFlags & FLAGS_HAS_AUTOFILL_TYPE) != 0) {
+ mAutofillType = parcel.readInt();
+ }
+ if ((nodeFlags & FLAGS_HAS_AUTOFILL_HINTS) != 0) {
+ mAutofillHints = parcel.readStringArray();
+ }
+ if ((nodeFlags & FLAGS_HAS_AUTOFILL_VALUE) != 0) {
+ mAutofillValue = parcel.readParcelable(null);
+ }
+ if ((nodeFlags & FLAGS_HAS_AUTOFILL_OPTIONS) != 0) {
+ mAutofillOptions = parcel.readCharSequenceArray();
+ }
+ }
+
/**
* Returns the {@link AutofillId} of this view's parent, if the parent is also part of the
* screen observation tree.
@@ -65,53 +224,446 @@
return mParentAutofillId;
}
- // TODO(b/111276913): temporarily overwriting some methods
@Override
public AutofillId getAutofillId() {
return mAutofillId;
}
+
@Override
public CharSequence getText() {
- return mText;
+ return mText != null ? mText.mText : null;
}
+
@Override
public String getClassName() {
return mClassName;
}
+ @Override
+ public int getId() {
+ return mId;
+ }
+
+ @Override
+ public String getIdPackage() {
+ return mIdPackage;
+ }
+
+ @Override
+ public String getIdType() {
+ return mIdType;
+ }
+
+ @Override
+ public String getIdEntry() {
+ return mIdEntry;
+ }
+
+ @Override
+ public int getLeft() {
+ return mX;
+ }
+
+ @Override
+ public int getTop() {
+ return mY;
+ }
+
+ @Override
+ public int getScrollX() {
+ return mScrollX;
+ }
+
+ @Override
+ public int getScrollY() {
+ return mScrollY;
+ }
+
+ @Override
+ public int getWidth() {
+ return mWidth;
+ }
+
+ @Override
+ public int getHeight() {
+ return mHeight;
+ }
+
+ @Override
+ public boolean isAssistBlocked() {
+ return (mFlags & FLAGS_ASSIST_BLOCKED) != 0;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return (mFlags & FLAGS_DISABLED) == 0;
+ }
+
+ @Override
+ public boolean isClickable() {
+ return (mFlags & FLAGS_CLICKABLE) != 0;
+ }
+
+ @Override
+ public boolean isLongClickable() {
+ return (mFlags & FLAGS_LONG_CLICKABLE) != 0;
+ }
+
+ @Override
+ public boolean isContextClickable() {
+ return (mFlags & FLAGS_CONTEXT_CLICKABLE) != 0;
+ }
+
+ @Override
+ public boolean isFocusable() {
+ return (mFlags & FLAGS_FOCUSABLE) != 0;
+ }
+
+ @Override
+ public boolean isFocused() {
+ return (mFlags & FLAGS_FOCUSED) != 0;
+ }
+
+ @Override
+ public boolean isAccessibilityFocused() {
+ return (mFlags & FLAGS_ACCESSIBILITY_FOCUSED) != 0;
+ }
+
+ @Override
+ public boolean isCheckable() {
+ return (mFlags & FLAGS_CHECKABLE) != 0;
+ }
+
+ @Override
+ public boolean isChecked() {
+ return (mFlags & FLAGS_CHECKED) != 0;
+ }
+
+ @Override
+ public boolean isSelected() {
+ return (mFlags & FLAGS_SELECTED) != 0;
+ }
+
+ @Override
+ public boolean isActivated() {
+ return (mFlags & FLAGS_ACTIVATED) != 0;
+ }
+
+ @Override
+ public boolean isOpaque() {
+ return (mFlags & FLAGS_OPAQUE) != 0;
+ }
+
+ @Override
+ public Matrix getTransformation() {
+ return mMatrix;
+ }
+
+ @Override
+ public float getElevation() {
+ return mElevation;
+ }
+
+ @Override
+ public float getAlpha() {
+ return mAlpha;
+ }
+
+ @Override
+ public CharSequence getContentDescription() {
+ return mContentDescription;
+ }
+
+ @Override
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public String getHint() {
+ return mText != null ? mText.mHint : null;
+ }
+
+ @Override
+ public int getTextSelectionStart() {
+ return mText != null ? mText.mTextSelectionStart : -1;
+ }
+
+ @Override
+ public int getTextSelectionEnd() {
+ return mText != null ? mText.mTextSelectionEnd : -1;
+ }
+
+ @Override
+ public int getTextColor() {
+ return mText != null ? mText.mTextColor : TEXT_COLOR_UNDEFINED;
+ }
+
+ @Override
+ public int getTextBackgroundColor() {
+ return mText != null ? mText.mTextBackgroundColor : TEXT_COLOR_UNDEFINED;
+ }
+
+ @Override
+ public float getTextSize() {
+ return mText != null ? mText.mTextSize : 0;
+ }
+
+ @Override
+ public int getTextStyle() {
+ return mText != null ? mText.mTextStyle : 0;
+ }
+
+ @Override
+ public int[] getTextLineCharOffsets() {
+ return mText != null ? mText.mLineCharOffsets : null;
+ }
+
+ @Override
+ public int[] getTextLineBaselines() {
+ return mText != null ? mText.mLineBaselines : null;
+ }
+
+ @Override
+ public int getVisibility() {
+ return (int) (mFlags & FLAGS_VISIBILITY_MASK);
+ }
+
+ @Override
+ public int getInputType() {
+ return mInputType;
+ }
+
+ @Override
+ public int getMinTextEms() {
+ return mMinEms;
+ }
+
+ @Override
+ public int getMaxTextEms() {
+ return mMaxEms;
+ }
+
+ @Override
+ public int getMaxTextLength() {
+ return mMaxLength;
+ }
+
+ @Override
+ public String getTextIdEntry() {
+ return mTextIdEntry;
+ }
+
+ @Override
+ public @View.AutofillType int getAutofillType() {
+ return mAutofillType;
+ }
+
+ @Override
+ @Nullable public String[] getAutofillHints() {
+ return mAutofillHints;
+ }
+
+ @Override
+ @Nullable public AutofillValue getAutofillValue() {
+ return mAutofillValue;
+ }
+
+ @Override
+ @Nullable public CharSequence[] getAutofillOptions() {
+ return mAutofillOptions;
+ }
+
+ @Override
+ public LocaleList getLocaleList() {
+ return mLocaleList;
+ }
+
+ private void writeSelfToParcel(@NonNull Parcel parcel, int parcelFlags) {
+ long nodeFlags = mFlags;
+
+ if (mAutofillId != null) {
+ nodeFlags |= FLAGS_HAS_AUTOFILL_ID;
+ }
+
+ if (mParentAutofillId != null) {
+ nodeFlags |= FLAGS_HAS_AUTOFILL_PARENT_ID;
+ }
+
+ if (mText != null) {
+ nodeFlags |= FLAGS_HAS_TEXT;
+ if (!mText.isSimple()) {
+ nodeFlags |= FLAGS_HAS_COMPLEX_TEXT;
+ }
+ }
+ if (mClassName != null) {
+ nodeFlags |= FLAGS_HAS_CLASSNAME;
+ }
+ if (mId != View.NO_ID) {
+ nodeFlags |= FLAGS_HAS_ID;
+ }
+ if ((mX & ~0x7fff) != 0 || (mY & ~0x7fff) != 0
+ || (mWidth & ~0x7fff) != 0 | (mHeight & ~0x7fff) != 0) {
+ nodeFlags |= FLAGS_HAS_LARGE_COORDS;
+ }
+ if (mScrollX != 0 || mScrollY != 0) {
+ nodeFlags |= FLAGS_HAS_SCROLL;
+ }
+ if (mMatrix != null) {
+ nodeFlags |= FLAGS_HAS_MATRIX;
+ }
+ if (mElevation != 0) {
+ nodeFlags |= FLAGS_HAS_ELEVATION;
+ }
+ if (mAlpha != 1.0f) {
+ nodeFlags |= FLAGS_HAS_ALPHA;
+ }
+ if (mContentDescription != null) {
+ nodeFlags |= FLAGS_HAS_CONTENT_DESCRIPTION;
+ }
+ if (mExtras != null) {
+ nodeFlags |= FLAGS_HAS_EXTRAS;
+ }
+ if (mLocaleList != null) {
+ nodeFlags |= FLAGS_HAS_LOCALE_LIST;
+ }
+ if (mInputType != 0) {
+ nodeFlags |= FLAGS_HAS_INPUT_TYPE;
+ }
+ if (mMinEms > -1) {
+ nodeFlags |= FLAGS_HAS_MIN_TEXT_EMS;
+ }
+ if (mMaxEms > -1) {
+ nodeFlags |= FLAGS_HAS_MAX_TEXT_EMS;
+ }
+ if (mMaxLength > -1) {
+ nodeFlags |= FLAGS_HAS_MAX_TEXT_LENGTH;
+ }
+ if (mTextIdEntry != null) {
+ nodeFlags |= FLAGS_HAS_TEXT_ID_ENTRY;
+ }
+ if (mAutofillValue != null) {
+ nodeFlags |= FLAGS_HAS_AUTOFILL_VALUE;
+ }
+ if (mAutofillType != View.AUTOFILL_TYPE_NONE) {
+ nodeFlags |= FLAGS_HAS_AUTOFILL_TYPE;
+ }
+ if (mAutofillHints != null) {
+ nodeFlags |= FLAGS_HAS_AUTOFILL_HINTS;
+ }
+ if (mAutofillOptions != null) {
+ nodeFlags |= FLAGS_HAS_AUTOFILL_OPTIONS;
+ }
+ parcel.writeLong(nodeFlags);
+
+ if ((nodeFlags & FLAGS_HAS_AUTOFILL_ID) != 0) {
+ parcel.writeParcelable(mAutofillId, parcelFlags);
+ }
+ if ((nodeFlags & FLAGS_HAS_AUTOFILL_PARENT_ID) != 0) {
+ parcel.writeParcelable(mParentAutofillId, parcelFlags);
+ }
+ if ((nodeFlags & FLAGS_HAS_TEXT) != 0) {
+ mText.writeToParcel(parcel, (nodeFlags & FLAGS_HAS_COMPLEX_TEXT) == 0);
+ }
+ if ((nodeFlags & FLAGS_HAS_CLASSNAME) != 0) {
+ parcel.writeString(mClassName);
+ }
+ if ((nodeFlags & FLAGS_HAS_ID) != 0) {
+ parcel.writeInt(mId);
+ if (mId != View.NO_ID) {
+ parcel.writeString(mIdEntry);
+ if (mIdEntry != null) {
+ parcel.writeString(mIdType);
+ parcel.writeString(mIdPackage);
+ }
+ }
+ }
+ if ((nodeFlags & FLAGS_HAS_LARGE_COORDS) != 0) {
+ parcel.writeInt(mX);
+ parcel.writeInt(mY);
+ parcel.writeInt(mWidth);
+ parcel.writeInt(mHeight);
+ } else {
+ parcel.writeInt((mY << 16) | mX);
+ parcel.writeInt((mHeight << 16) | mWidth);
+ }
+ if ((nodeFlags & FLAGS_HAS_SCROLL) != 0) {
+ parcel.writeInt(mScrollX);
+ parcel.writeInt(mScrollY);
+ }
+ if ((nodeFlags & FLAGS_HAS_MATRIX) != 0) {
+ //TODO(b/122484602): use a singleton tmpMatrix (if logic is not moved to superclass)
+ final float[] tmpMatrix = new float[9];
+ mMatrix.getValues(tmpMatrix);
+ parcel.writeFloatArray(tmpMatrix);
+ }
+ if ((nodeFlags & FLAGS_HAS_ELEVATION) != 0) {
+ parcel.writeFloat(mElevation);
+ }
+ if ((nodeFlags & FLAGS_HAS_ALPHA) != 0) {
+ parcel.writeFloat(mAlpha);
+ }
+ if ((nodeFlags & FLAGS_HAS_CONTENT_DESCRIPTION) != 0) {
+ TextUtils.writeToParcel(mContentDescription, parcel, 0);
+ }
+ if ((nodeFlags & FLAGS_HAS_EXTRAS) != 0) {
+ parcel.writeBundle(mExtras);
+ }
+ if ((nodeFlags & FLAGS_HAS_LOCALE_LIST) != 0) {
+ parcel.writeParcelable(mLocaleList, 0);
+ }
+ if ((nodeFlags & FLAGS_HAS_INPUT_TYPE) != 0) {
+ parcel.writeInt(mInputType);
+ }
+ if ((nodeFlags & FLAGS_HAS_MIN_TEXT_EMS) != 0) {
+ parcel.writeInt(mMinEms);
+ }
+ if ((nodeFlags & FLAGS_HAS_MAX_TEXT_EMS) != 0) {
+ parcel.writeInt(mMaxEms);
+ }
+ if ((nodeFlags & FLAGS_HAS_MAX_TEXT_LENGTH) != 0) {
+ parcel.writeInt(mMaxLength);
+ }
+ if ((nodeFlags & FLAGS_HAS_TEXT_ID_ENTRY) != 0) {
+ parcel.writeString(mTextIdEntry);
+ }
+ if ((nodeFlags & FLAGS_HAS_AUTOFILL_TYPE) != 0) {
+ parcel.writeInt(mAutofillType);
+ }
+ if ((nodeFlags & FLAGS_HAS_AUTOFILL_HINTS) != 0) {
+ parcel.writeStringArray(mAutofillHints);
+ }
+ if ((nodeFlags & FLAGS_HAS_AUTOFILL_VALUE) != 0) {
+ parcel.writeParcelable(mAutofillValue, 0);
+ }
+ if ((nodeFlags & FLAGS_HAS_AUTOFILL_OPTIONS) != 0) {
+ parcel.writeCharSequenceArray(mAutofillOptions);
+ }
+ }
+
/** @hide */
public static void writeToParcel(@NonNull Parcel parcel, @Nullable ViewNode node, int flags) {
if (node == null) {
- parcel.writeParcelable(null, flags);
- return;
+ parcel.writeLong(0);
+ } else {
+ node.writeSelfToParcel(parcel, flags);
}
- parcel.writeParcelable(node.mAutofillId, flags);
- parcel.writeParcelable(node.mParentAutofillId, flags);
- parcel.writeCharSequence(node.mText);
- parcel.writeString(node.mClassName);
}
/** @hide */
public static @Nullable ViewNode readFromParcel(@NonNull Parcel parcel) {
- final AutofillId id = parcel.readParcelable(null);
- if (id == null) return null;
-
- final ViewNode node = new ViewNode();
-
- node.mAutofillId = id;
- node.mParentAutofillId = parcel.readParcelable(null);
- node.mText = parcel.readCharSequence();
- node.mClassName = parcel.readString();
-
- return node;
+ final long nodeFlags = parcel.readLong();
+ return nodeFlags == 0 ? new ViewNode() : new ViewNode(nodeFlags, parcel);
}
/** @hide */
- static final class ViewStructureImpl extends ViewStructure {
+ @VisibleForTesting // Must be public to be accessed from FrameworkCoreTests' apk.
+ public static final class ViewStructureImpl extends ViewStructure {
final ViewNode mNode = new ViewNode();
- ViewStructureImpl(@NonNull View view) {
+ @VisibleForTesting // Must be public to be accessed from FrameworkCoreTests' apk.
+ public ViewStructureImpl(@NonNull View view) {
mNode.mAutofillId = Preconditions.checkNotNull(view).getAutofillId();
final ViewParent parent = view.getParent();
if (parent instanceof View) {
@@ -119,179 +671,212 @@
}
}
- ViewStructureImpl(@NonNull AutofillId parentId, int virtualId) {
+ @VisibleForTesting // Must be public to be accessed from FrameworkCoreTests' apk.
+ public ViewStructureImpl(@NonNull AutofillId parentId, int virtualId) {
mNode.mParentAutofillId = Preconditions.checkNotNull(parentId);
mNode.mAutofillId = new AutofillId(parentId, virtualId);
}
+ @VisibleForTesting // Must be public to be accessed from FrameworkCoreTests' apk.
+ public ViewNode getNode() {
+ return mNode;
+ }
+
@Override
public void setId(int id, String packageName, String typeName, String entryName) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mId = id;
+ mNode.mIdPackage = packageName;
+ mNode.mIdType = typeName;
+ mNode.mIdEntry = entryName;
}
@Override
public void setDimens(int left, int top, int scrollX, int scrollY, int width, int height) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mX = left;
+ mNode.mY = top;
+ mNode.mScrollX = scrollX;
+ mNode.mScrollY = scrollY;
+ mNode.mWidth = width;
+ mNode.mHeight = height;
}
@Override
public void setTransformation(Matrix matrix) {
- // TODO(b/111276913): implement or move to superclass
+ if (matrix == null) {
+ mNode.mMatrix = null;
+ } else {
+ mNode.mMatrix = new Matrix(matrix);
+ }
}
@Override
public void setElevation(float elevation) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mElevation = elevation;
}
@Override
public void setAlpha(float alpha) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mAlpha = alpha;
}
@Override
public void setVisibility(int visibility) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mFlags = (mNode.mFlags & ~FLAGS_VISIBILITY_MASK)
+ | (visibility & FLAGS_VISIBILITY_MASK);
}
@Override
public void setAssistBlocked(boolean state) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mFlags = (mNode.mFlags & ~FLAGS_ASSIST_BLOCKED)
+ | (state ? FLAGS_ASSIST_BLOCKED : 0);
}
@Override
public void setEnabled(boolean state) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mFlags = (mNode.mFlags & ~FLAGS_DISABLED) | (state ? 0 : FLAGS_DISABLED);
}
@Override
public void setClickable(boolean state) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mFlags = (mNode.mFlags & ~FLAGS_CLICKABLE) | (state ? FLAGS_CLICKABLE : 0);
}
@Override
public void setLongClickable(boolean state) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mFlags = (mNode.mFlags & ~FLAGS_LONG_CLICKABLE)
+ | (state ? FLAGS_LONG_CLICKABLE : 0);
}
@Override
public void setContextClickable(boolean state) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mFlags = (mNode.mFlags & ~FLAGS_CONTEXT_CLICKABLE)
+ | (state ? FLAGS_CONTEXT_CLICKABLE : 0);
}
@Override
public void setFocusable(boolean state) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mFlags = (mNode.mFlags & ~FLAGS_FOCUSABLE) | (state ? FLAGS_FOCUSABLE : 0);
}
@Override
public void setFocused(boolean state) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mFlags = (mNode.mFlags & ~FLAGS_FOCUSED) | (state ? FLAGS_FOCUSED : 0);
}
@Override
public void setAccessibilityFocused(boolean state) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mFlags = (mNode.mFlags & ~FLAGS_ACCESSIBILITY_FOCUSED)
+ | (state ? FLAGS_ACCESSIBILITY_FOCUSED : 0);
}
@Override
public void setCheckable(boolean state) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mFlags = (mNode.mFlags & ~FLAGS_CHECKABLE) | (state ? FLAGS_CHECKABLE : 0);
}
@Override
public void setChecked(boolean state) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mFlags = (mNode.mFlags & ~FLAGS_CHECKED) | (state ? FLAGS_CHECKED : 0);
}
@Override
public void setSelected(boolean state) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mFlags = (mNode.mFlags & ~FLAGS_SELECTED) | (state ? FLAGS_SELECTED : 0);
}
@Override
public void setActivated(boolean state) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mFlags = (mNode.mFlags & ~FLAGS_ACTIVATED) | (state ? FLAGS_ACTIVATED : 0);
}
@Override
public void setOpaque(boolean opaque) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mFlags = (mNode.mFlags & ~FLAGS_OPAQUE) | (opaque ? FLAGS_OPAQUE : 0);
}
@Override
public void setClassName(String className) {
- // TODO(b/111276913): temporarily setting directly; should be done on superclass instead
mNode.mClassName = className;
}
@Override
public void setContentDescription(CharSequence contentDescription) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mContentDescription = contentDescription;
}
@Override
public void setText(CharSequence text) {
- // TODO(b/111276913): temporarily setting directly; should be done on superclass instead
- mNode.mText = text;
+ final ViewNodeText t = getNodeText();
+ t.mText = TextUtils.trimNoCopySpans(text);
+ t.mTextSelectionStart = t.mTextSelectionEnd = -1;
}
@Override
public void setText(CharSequence text, int selectionStart, int selectionEnd) {
- // TODO(b/111276913): temporarily setting directly; should be done on superclass instead
- mNode.mText = text;
- // TODO(b/111276913): implement or move to superclass
+ final ViewNodeText t = getNodeText();
+ t.mText = TextUtils.trimNoCopySpans(text);
+ t.mTextSelectionStart = selectionStart;
+ t.mTextSelectionEnd = selectionEnd;
}
@Override
public void setTextStyle(float size, int fgColor, int bgColor, int style) {
- // TODO(b/111276913): implement or move to superclass
+ final ViewNodeText t = getNodeText();
+ t.mTextColor = fgColor;
+ t.mTextBackgroundColor = bgColor;
+ t.mTextSize = size;
+ t.mTextStyle = style;
}
@Override
public void setTextLines(int[] charOffsets, int[] baselines) {
- // TODO(b/111276913): implement or move to superclass
+ final ViewNodeText t = getNodeText();
+ t.mLineCharOffsets = charOffsets;
+ t.mLineBaselines = baselines;
+ }
+
+ @Override
+ public void setTextIdEntry(String entryName) {
+ mNode.mTextIdEntry = Preconditions.checkNotNull(entryName);
}
@Override
public void setHint(CharSequence hint) {
- // TODO(b/111276913): implement or move to superclass
+ getNodeText().mHint = hint != null ? hint.toString() : null;
}
@Override
public CharSequence getText() {
- // TODO(b/111276913): temporarily getting directly; should be done on superclass instead
- return mNode.mText;
+ return mNode.getText();
}
@Override
public int getTextSelectionStart() {
- // TODO(b/111276913): implement or move to superclass
- return 0;
+ return mNode.getTextSelectionStart();
}
@Override
public int getTextSelectionEnd() {
- // TODO(b/111276913): implement or move to superclass
- return 0;
+ return mNode.getTextSelectionEnd();
}
@Override
public CharSequence getHint() {
- // TODO(b/111276913): implement or move to superclass
- return null;
+ return mNode.getHint();
}
@Override
public Bundle getExtras() {
- // TODO(b/111276913): implement or move to superclass
- return null;
+ if (mNode.mExtras != null) {
+ return mNode.mExtras;
+ }
+ mNode.mExtras = new Bundle();
+ return mNode.mExtras;
}
@Override
public boolean hasExtras() {
- // TODO(b/111276913): implement or move to superclass
- return false;
+ return mNode.mExtras != null;
}
@Override
@@ -325,50 +910,64 @@
@Override
public AutofillId getAutofillId() {
- // TODO(b/111276913): temporarily getting directly; should be done on superclass instead
return mNode.mAutofillId;
}
@Override
public void setAutofillId(AutofillId id) {
- // TODO(b/111276913): temporarily setting directly; should be done on superclass instead
- mNode.mAutofillId = id;
+ mNode.mAutofillId = Preconditions.checkNotNull(id);
}
+
@Override
public void setAutofillId(AutofillId parentId, int virtualId) {
- // TODO(b/111276913): temporarily setting directly; should be done on superclass instead
+ mNode.mParentAutofillId = Preconditions.checkNotNull(parentId);
mNode.mAutofillId = new AutofillId(parentId, virtualId);
}
@Override
- public void setAutofillType(int type) {
- // TODO(b/111276913): implement or move to superclass
+ public void setAutofillType(@View.AutofillType int type) {
+ mNode.mAutofillType = type;
}
@Override
- public void setAutofillHints(String[] hint) {
- // TODO(b/111276913): implement or move to superclass
+ public void setAutofillHints(String[] hints) {
+ mNode.mAutofillHints = hints;
}
@Override
public void setAutofillValue(AutofillValue value) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mAutofillValue = value;
}
@Override
public void setAutofillOptions(CharSequence[] options) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mAutofillOptions = options;
}
@Override
public void setInputType(int inputType) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mInputType = inputType;
+ }
+
+ @Override
+ public void setMinTextEms(int minEms) {
+ mNode.mMinEms = minEms;
+ }
+
+ @Override
+ public void setMaxTextEms(int maxEms) {
+ mNode.mMaxEms = maxEms;
+ }
+
+ @Override
+ public void setMaxTextLength(int maxLength) {
+ mNode.mMaxLength = maxLength;
}
@Override
public void setDataIsSensitive(boolean sensitive) {
- // TODO(b/111276913): implement or move to superclass
+ Log.w(TAG, "setDataIsSensitive() is not supported");
}
@Override
@@ -378,7 +977,7 @@
@Override
public Rect getTempRect() {
- // TODO(b/111276913): implement or move to superclass
+ Log.w(TAG, "getTempRect() is not supported");
return null;
}
@@ -389,7 +988,7 @@
@Override
public void setLocaleList(LocaleList localeList) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mLocaleList = localeList;
}
@Override
@@ -402,5 +1001,66 @@
public void setHtmlInfo(HtmlInfo htmlInfo) {
Log.w(TAG, "setHtmlInfo() is not supported");
}
+
+ private ViewNodeText getNodeText() {
+ if (mNode.mText != null) {
+ return mNode.mText;
+ }
+ mNode.mText = new ViewNodeText();
+ return mNode.mText;
+ }
+ }
+
+ //TODO(b/122484602): copied 'as-is' from AssistStructure, except for writeSensitive
+ static final class ViewNodeText {
+ CharSequence mText;
+ float mTextSize;
+ int mTextStyle;
+ int mTextColor = ViewNode.TEXT_COLOR_UNDEFINED;
+ int mTextBackgroundColor = ViewNode.TEXT_COLOR_UNDEFINED;
+ int mTextSelectionStart;
+ int mTextSelectionEnd;
+ int[] mLineCharOffsets;
+ int[] mLineBaselines;
+ String mHint;
+
+ ViewNodeText() {
+ }
+
+ boolean isSimple() {
+ return mTextBackgroundColor == ViewNode.TEXT_COLOR_UNDEFINED
+ && mTextSelectionStart == 0 && mTextSelectionEnd == 0
+ && mLineCharOffsets == null && mLineBaselines == null && mHint == null;
+ }
+
+ ViewNodeText(Parcel in, boolean simple) {
+ mText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mTextSize = in.readFloat();
+ mTextStyle = in.readInt();
+ mTextColor = in.readInt();
+ if (!simple) {
+ mTextBackgroundColor = in.readInt();
+ mTextSelectionStart = in.readInt();
+ mTextSelectionEnd = in.readInt();
+ mLineCharOffsets = in.createIntArray();
+ mLineBaselines = in.createIntArray();
+ mHint = in.readString();
+ }
+ }
+
+ void writeToParcel(Parcel out, boolean simple) {
+ TextUtils.writeToParcel(mText, out, 0);
+ out.writeFloat(mTextSize);
+ out.writeInt(mTextStyle);
+ out.writeInt(mTextColor);
+ if (!simple) {
+ out.writeInt(mTextBackgroundColor);
+ out.writeInt(mTextSelectionStart);
+ out.writeInt(mTextSelectionEnd);
+ out.writeIntArray(mLineCharOffsets);
+ out.writeIntArray(mLineBaselines);
+ out.writeString(mHint);
+ }
+ }
}
}
diff --git a/core/java/android/view/inputmethod/InputMethodSystemProperty.java b/core/java/android/view/inputmethod/InputMethodSystemProperty.java
index b233b75..57ed7f9 100644
--- a/core/java/android/view/inputmethod/InputMethodSystemProperty.java
+++ b/core/java/android/view/inputmethod/InputMethodSystemProperty.java
@@ -41,6 +41,17 @@
*/
private static final String PROP_DEBUG_MULTI_CLIENT_IME = "persist.debug.multi_client_ime";
+ /**
+ * System property key for debugging purpose. The value must be empty, "1", or "0".
+ *
+ * <p>Values 'y', 'yes', '1', 'true' or 'on' are considered true.</p>
+ *
+ * <p>To set, run "adb root && adb shell setprop persist.debug.per_profile_ime 1".</p>
+ *
+ * <p>This value will be ignored when {@link Build#IS_DEBUGGABLE} returns {@code false}.</p>
+ */
+ private static final String PROP_DEBUG_PER_PROFILE_IME = "persist.debug.per_profile_ime";
+
@Nullable
private static ComponentName getMultiClientImeComponentName() {
if (Build.IS_DEBUGGABLE) {
@@ -59,6 +70,9 @@
/**
* {@link ComponentName} of multi-client IME to be used.
*
+ * <p>TODO: Move this back to MultiClientInputMethodManagerService once
+ * {@link #PER_PROFILE_IME_ENABLED} always becomes {@code true}.</p>
+ *
* @hide
*/
@Nullable
@@ -68,7 +82,19 @@
/**
* {@code true} when multi-client IME is enabled.
*
+ * <p>TODO: Move this back to MultiClientInputMethodManagerService once
+ * {@link #PER_PROFILE_IME_ENABLED} always becomes {@code true}.</p>
+ *
* @hide
*/
public static final boolean MULTI_CLIENT_IME_ENABLED = (sMultiClientImeComponentName != null);
+
+ /**
+ * {@code true} when per-profile IME is enabled.
+ * @hide
+ */
+ public static final boolean PER_PROFILE_IME_ENABLED = MULTI_CLIENT_IME_ENABLED
+ || Build.IS_DEBUGGABLE && SystemProperties.getBoolean(
+ PROP_DEBUG_PER_PROFILE_IME, false);
+
}
diff --git a/core/java/android/view/textclassifier/GenerateLinksLogger.java b/core/java/android/view/textclassifier/GenerateLinksLogger.java
index 067513f..3996b27 100644
--- a/core/java/android/view/textclassifier/GenerateLinksLogger.java
+++ b/core/java/android/view/textclassifier/GenerateLinksLogger.java
@@ -39,7 +39,6 @@
public final class GenerateLinksLogger {
private static final String LOG_TAG = "GenerateLinksLogger";
- private static final boolean DEBUG_LOG_ENABLED = false;
private static final String ZERO = "0";
private final MetricsLogger mMetricsLogger;
@@ -128,8 +127,9 @@
}
private static void debugLog(LogMaker log) {
- if (!DEBUG_LOG_ENABLED) return;
-
+ if (!Log.ENABLE_FULL_LOGGING) {
+ return;
+ }
final String callId = Objects.toString(
log.getTaggedData(MetricsEvent.FIELD_LINKIFY_CALL_ID), "");
final String entityType = Objects.toString(
@@ -143,7 +143,7 @@
final int latencyMs = Integer.parseInt(
Objects.toString(log.getTaggedData(MetricsEvent.FIELD_LINKIFY_LATENCY), ZERO));
- Log.d(LOG_TAG,
+ Log.v(LOG_TAG,
String.format(Locale.US, "%s:%s %d links (%d/%d chars) %dms %s", callId, entityType,
numLinks, linkLength, textLength, latencyMs, log.getPackageName()));
}
diff --git a/core/java/android/view/textclassifier/Log.java b/core/java/android/view/textclassifier/Log.java
index ef19ee5..5c60c09 100644
--- a/core/java/android/view/textclassifier/Log.java
+++ b/core/java/android/view/textclassifier/Log.java
@@ -16,10 +16,12 @@
package android.view.textclassifier;
-import android.util.Slog;
-
/**
* Logging for android.view.textclassifier package.
+ * <p>
+ * To enable full log:
+ * 1. adb shell setprop log.tag.androidtc VERBOSE
+ * 2. adb shell stop && adb shell start
*/
final class Log {
@@ -27,24 +29,32 @@
* true: Enables full logging.
* false: Limits logging to debug level.
*/
- private static final boolean ENABLE_FULL_LOGGING = false;
+ static final boolean ENABLE_FULL_LOGGING =
+ android.util.Log.isLoggable(TextClassifier.DEFAULT_LOG_TAG, android.util.Log.VERBOSE);
- private Log() {}
+ private Log() {
+ }
+
+ public static void v(String tag, String msg) {
+ if (ENABLE_FULL_LOGGING) {
+ android.util.Log.v(tag, msg);
+ }
+ }
public static void d(String tag, String msg) {
- Slog.d(tag, msg);
+ android.util.Log.d(tag, msg);
}
public static void w(String tag, String msg) {
- Slog.w(tag, msg);
+ android.util.Log.w(tag, msg);
}
public static void e(String tag, String msg, Throwable tr) {
if (ENABLE_FULL_LOGGING) {
- Slog.e(tag, msg, tr);
+ android.util.Log.e(tag, msg, tr);
} else {
final String trString = (tr != null) ? tr.getClass().getSimpleName() : "??";
- Slog.d(tag, String.format("%s (%s)", msg, trString));
+ android.util.Log.d(tag, String.format("%s (%s)", msg, trString));
}
}
}
diff --git a/core/java/android/view/textclassifier/SelectionSessionLogger.java b/core/java/android/view/textclassifier/SelectionSessionLogger.java
index cdacdd5..48a568a 100644
--- a/core/java/android/view/textclassifier/SelectionSessionLogger.java
+++ b/core/java/android/view/textclassifier/SelectionSessionLogger.java
@@ -39,7 +39,6 @@
public final class SelectionSessionLogger {
private static final String LOG_TAG = "SelectionSessionLogger";
- private static final boolean DEBUG_LOG_ENABLED = false;
static final String CLASSIFIER_ID = "androidtc";
private static final int START_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_START;
@@ -195,8 +194,9 @@
}
private static void debugLog(LogMaker log) {
- if (!DEBUG_LOG_ENABLED) return;
-
+ if (!Log.ENABLE_FULL_LOGGING) {
+ return;
+ }
final String widgetType = Objects.toString(log.getTaggedData(WIDGET_TYPE), UNKNOWN);
final String widgetVersion = Objects.toString(log.getTaggedData(WIDGET_VERSION), "");
final String widget = widgetVersion.isEmpty()
@@ -221,7 +221,7 @@
final int eventEnd = Integer.parseInt(
Objects.toString(log.getTaggedData(EVENT_END), ZERO));
- Log.d(LOG_TAG,
+ Log.v(LOG_TAG,
String.format(Locale.US, "%2d: %s/%s/%s, range=%d,%d - smart_range=%d,%d (%s/%s)",
index, type, subType, entity, eventStart, eventEnd, smartStart, smartEnd,
widget, model));
diff --git a/core/java/android/view/textclassifier/TextClassificationSession.java b/core/java/android/view/textclassifier/TextClassificationSession.java
index 4c64198..45668c0 100644
--- a/core/java/android/view/textclassifier/TextClassificationSession.java
+++ b/core/java/android/view/textclassifier/TextClassificationSession.java
@@ -27,7 +27,6 @@
@WorkerThread
final class TextClassificationSession implements TextClassifier {
- /* package */ static final boolean DEBUG_LOG_ENABLED = true;
private static final String LOG_TAG = "TextClassificationSession";
private final TextClassifier mDelegate;
@@ -133,9 +132,7 @@
if (event.getEventType() != SelectionEvent.EVENT_SELECTION_STARTED
&& mStartEvent == null) {
- if (DEBUG_LOG_ENABLED) {
- Log.d(LOG_TAG, "Selection session not yet started. Ignoring event");
- }
+ Log.d(LOG_TAG, "Selection session not yet started. Ignoring event");
return false;
}
diff --git a/core/java/android/view/textclassifier/TextClassifierEvent.java b/core/java/android/view/textclassifier/TextClassifierEvent.java
index f2fea02..b84f6f0 100644
--- a/core/java/android/view/textclassifier/TextClassifierEvent.java
+++ b/core/java/android/view/textclassifier/TextClassifierEvent.java
@@ -116,6 +116,8 @@
public static final int TYPE_SELECTION_RESET = 18;
/** User composed a reply. */
public static final int TYPE_MANUAL_REPLY = 19;
+ /** TextClassifier generated some actions */
+ public static final int TYPE_ACTIONS_GENERATED = 20;
@Category private final int mEventCategory;
@Type private final int mEventType;
diff --git a/core/java/android/view/textclassifier/TextClassifierEventTronLogger.java b/core/java/android/view/textclassifier/TextClassifierEventTronLogger.java
new file mode 100644
index 0000000..439e594
--- /dev/null
+++ b/core/java/android/view/textclassifier/TextClassifierEventTronLogger.java
@@ -0,0 +1,151 @@
+/*
+ * 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.
+ */
+package android.view.textclassifier;
+
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_ENTITY_TYPE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_SESSION_ID;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_WIDGET_TYPE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_WIDGET_VERSION;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXTCLASSIFIER_MODEL;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_EVENT_TIME;
+
+import android.metrics.LogMaker;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.Preconditions;
+
+
+/**
+ * Log {@link TextClassifierEvent} by using Tron, only support language detection and
+ * conversation actions.
+ *
+ * @hide
+ */
+public final class TextClassifierEventTronLogger {
+
+ private static final String TAG = "TCEventTronLogger";
+
+ private final MetricsLogger mMetricsLogger;
+
+ public TextClassifierEventTronLogger() {
+ mMetricsLogger = new MetricsLogger();
+ }
+
+ @VisibleForTesting
+ public TextClassifierEventTronLogger(MetricsLogger metricsLogger) {
+ mMetricsLogger = Preconditions.checkNotNull(metricsLogger);
+ }
+
+ /** Emits a text classifier event to the logs. */
+ public void writeEvent(TextClassifierEvent event) {
+ Preconditions.checkNotNull(event);
+ int category = getCategory(event);
+ if (category == -1) {
+ Log.w(TAG, "Unknown category: " + event.getEventCategory());
+ return;
+ }
+ final LogMaker log = new LogMaker(category)
+ .setType(getLogType(event))
+ .addTaggedData(FIELD_SELECTION_SESSION_ID, event.getResultId())
+ .addTaggedData(FIELD_TEXT_CLASSIFIER_EVENT_TIME, event.getEventTime())
+ .addTaggedData(FIELD_TEXTCLASSIFIER_MODEL,
+ SelectionSessionLogger.SignatureParser.getModelName(event.getResultId()))
+ .addTaggedData(FIELD_SELECTION_ENTITY_TYPE, event.getEntityType());
+ TextClassificationContext eventContext = event.getEventContext();
+ if (eventContext != null) {
+ log.addTaggedData(FIELD_SELECTION_WIDGET_TYPE, eventContext.getWidgetType());
+ log.addTaggedData(FIELD_SELECTION_WIDGET_VERSION, eventContext.getWidgetVersion());
+ log.setPackageName(eventContext.getPackageName());
+ }
+ mMetricsLogger.write(log);
+ debugLog(log);
+ }
+
+ private static int getCategory(TextClassifierEvent event) {
+ switch (event.getEventCategory()) {
+ case TextClassifierEvent.CATEGORY_CONVERSATION_ACTIONS:
+ return MetricsEvent.CONVERSATION_ACTIONS;
+ case TextClassifierEvent.CATEGORY_LANGUAGE_DETECTION:
+ return MetricsEvent.LANGUAGE_DETECTION;
+ }
+ return -1;
+ }
+
+ private static int getLogType(TextClassifierEvent event) {
+ switch (event.getEventType()) {
+ case TextClassifierEvent.TYPE_SMART_ACTION:
+ return MetricsEvent.ACTION_TEXT_SELECTION_SMART_SHARE;
+ case TextClassifierEvent.TYPE_ACTIONS_SHOWN:
+ return MetricsEvent.ACTION_TEXT_CLASSIFIER_ACTIONS_SHOWN;
+ case TextClassifierEvent.TYPE_MANUAL_REPLY:
+ return MetricsEvent.ACTION_TEXT_CLASSIFIER_MANUAL_REPLY;
+ default:
+ return MetricsEvent.VIEW_UNKNOWN;
+ }
+ }
+
+ private String toCategoryName(int category) {
+ switch (category) {
+ case MetricsEvent.CONVERSATION_ACTIONS:
+ return "conversation_actions";
+ case MetricsEvent.LANGUAGE_DETECTION:
+ return "language_detection";
+ }
+ return "unknown";
+ }
+
+ private String toEventName(int logType) {
+ switch (logType) {
+ case MetricsEvent.ACTION_TEXT_SELECTION_SMART_SHARE:
+ return "smart_share";
+ case MetricsEvent.ACTION_TEXT_CLASSIFIER_ACTIONS_SHOWN:
+ return "actions_shown";
+ case MetricsEvent.ACTION_TEXT_CLASSIFIER_MANUAL_REPLY:
+ return "manual_reply";
+ case MetricsEvent.ACTION_TEXT_CLASSIFIER_ACTIONS_GENERATED:
+ return "actions_generated";
+ }
+ return "unknown";
+ }
+
+ private void debugLog(LogMaker log) {
+ if (!Log.ENABLE_FULL_LOGGING) {
+ return;
+ }
+ final String id = String.valueOf(log.getTaggedData(FIELD_SELECTION_SESSION_ID));
+ final String categoryName = toCategoryName(log.getCategory());
+ final String eventName = toEventName(log.getType());
+ final String widgetType = String.valueOf(log.getTaggedData(FIELD_SELECTION_WIDGET_TYPE));
+ final String widgetVersion =
+ String.valueOf(log.getTaggedData(FIELD_SELECTION_WIDGET_VERSION));
+ final String model = String.valueOf(log.getTaggedData(FIELD_TEXTCLASSIFIER_MODEL));
+ final String entityType = String.valueOf(log.getTaggedData(FIELD_SELECTION_ENTITY_TYPE));
+
+ StringBuilder builder = new StringBuilder();
+ builder.append("writeEvent: ");
+ builder.append("id=").append(id);
+ builder.append(", category=").append(categoryName);
+ builder.append(", eventName=").append(eventName);
+ builder.append(", widgetType=").append(widgetType);
+ builder.append(", widgetVersion=").append(widgetVersion);
+ builder.append(", model=").append(model);
+ builder.append(", entityType=").append(entityType);
+
+ Log.v(TAG, builder.toString());
+ }
+}
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index fcd06c3..d5b9eb1 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -115,9 +115,9 @@
@GuardedBy("mLock") // Do not access outside this lock.
private ActionsSuggestionsModel mActionsImpl;
- private final Object mLoggerLock = new Object();
- @GuardedBy("mLoggerLock") // Do not access outside this lock.
- private SelectionSessionLogger mSessionLogger;
+ private final SelectionSessionLogger mSessionLogger = new SelectionSessionLogger();
+ private final TextClassifierEventTronLogger mTextClassifierEventTronLogger =
+ new TextClassifierEventTronLogger();
private final TextClassificationConstants mSettings;
@@ -337,12 +337,7 @@
@Override
public void onSelectionEvent(SelectionEvent event) {
Preconditions.checkNotNull(event);
- synchronized (mLoggerLock) {
- if (mSessionLogger == null) {
- mSessionLogger = new SelectionSessionLogger();
- }
- mSessionLogger.writeEvent(event);
- }
+ mSessionLogger.writeEvent(event);
}
@Override
@@ -350,6 +345,7 @@
if (DEBUG) {
Log.d(DEFAULT_LOG_TAG, "onTextClassifierEvent() called with: event = [" + event + "]");
}
+ mTextClassifierEventTronLogger.writeEvent(event);
}
/** @inheritDoc */
diff --git a/core/java/android/view/textservice/TextServicesManager.java b/core/java/android/view/textservice/TextServicesManager.java
index 4c6862c..5dc8b19 100644
--- a/core/java/android/view/textservice/TextServicesManager.java
+++ b/core/java/android/view/textservice/TextServicesManager.java
@@ -67,12 +67,6 @@
private static final String TAG = TextServicesManager.class.getSimpleName();
private static final boolean DBG = false;
- /**
- * A compile time switch to control per-profile spell checker, which is not yet ready.
- * @hide
- */
- public static final boolean DISABLE_PER_PROFILE_SPELL_CHECKER = true;
-
private static TextServicesManager sInstance;
private final ITextServicesManager mService;
diff --git a/core/java/android/webkit/WebViewZygote.java b/core/java/android/webkit/WebViewZygote.java
index 49e11b8..9f7aa6a 100644
--- a/core/java/android/webkit/WebViewZygote.java
+++ b/core/java/android/webkit/WebViewZygote.java
@@ -159,6 +159,7 @@
0, // runtimeFlags
"webview_zygote", // seInfo
sPackage.applicationInfo.primaryCpuAbi, // abi
+ TextUtils.join(",", Build.SUPPORTED_ABIS),
null); // instructionSet
// All the work below is usually done by LoadedApk, but the zygote can't talk to
diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java
index 7d02757..afe46701 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -104,8 +104,9 @@
private final int mDefaultHorizontalSourceToMagnifierOffset;
// The vertical offset between the source and window coords when #show(float, float) is used.
private final int mDefaultVerticalSourceToMagnifierOffset;
- // Whether the magnifier will be clamped inside the main surface and not overlap system insets.
- private final boolean mForcePositionWithinWindowSystemInsetsBounds;
+ // Whether the area where the magnifier can be positioned will be clipped to the main window
+ // and within system insets.
+ private final boolean mClippingEnabled;
// The behavior of the left bound of the rectangle where the content can be copied from.
private @SourceBound int mLeftContentBound;
// The behavior of the top bound of the rectangle where the content can be copied from.
@@ -165,7 +166,7 @@
params.mOverlay = new ColorDrawable(a.getColor(
R.styleable.Magnifier_magnifierColorOverlay, Color.TRANSPARENT));
a.recycle();
- params.mForcePositionWithinWindowSystemInsetsBounds = true;
+ params.mClippingEnabled = true;
params.mLeftContentBound = SOURCE_BOUND_MAX_VISIBLE;
params.mTopContentBound = SOURCE_BOUND_MAX_IN_SURFACE;
params.mRightContentBound = SOURCE_BOUND_MAX_VISIBLE;
@@ -203,8 +204,7 @@
params.mHorizontalDefaultSourceToMagnifierOffset;
mDefaultVerticalSourceToMagnifierOffset =
params.mVerticalDefaultSourceToMagnifierOffset;
- mForcePositionWithinWindowSystemInsetsBounds =
- params.mForcePositionWithinWindowSystemInsetsBounds;
+ mClippingEnabled = params.mClippingEnabled;
mLeftContentBound = params.mLeftContentBound;
mTopContentBound = params.mTopContentBound;
mRightContentBound = params.mRightContentBound;
@@ -271,7 +271,7 @@
if (mWindow == null) {
synchronized (mLock) {
mWindow = new InternalPopupWindow(mView.getContext(), mView.getDisplay(),
- mParentSurface.mSurface, mWindowWidth, mWindowHeight,
+ mParentSurface.mSurfaceControl, mWindowWidth, mWindowHeight,
mWindowElevation, mWindowCornerRadius,
mOverlay != null ? mOverlay : new ColorDrawable(Color.TRANSPARENT),
Handler.getMain() /* draw the magnifier on the UI thread */, mLock,
@@ -447,7 +447,7 @@
}
/**
- * Returns the overlay to be drawn on the top of the magnifier content, or
+ * Returns the overlay to be drawn on the top of the magnifier, or
* {@code null} if no overlay should be drawn.
* @return the overlay
* @see Magnifier.Builder#setOverlay(Drawable)
@@ -459,13 +459,15 @@
/**
* Returns whether the magnifier position will be adjusted such that the magnifier will be
- * fully within the bounds of the main application window, by also avoiding any overlap with
- * system insets (such as the one corresponding to the status bar).
+ * fully within the bounds of the main application window, by also avoiding any overlap
+ * with system insets (such as the one corresponding to the status bar) i.e. whether the
+ * area where the magnifier can be positioned will be clipped to the main application window
+ * and the system insets.
* @return whether the magnifier position will be adjusted
- * @see Magnifier.Builder#setForcePositionWithinWindowSystemInsetsBounds(boolean)
+ * @see Magnifier.Builder#setClippingEnabled(boolean)
*/
- public boolean isForcePositionWithinWindowSystemInsetsBounds() {
- return mForcePositionWithinWindowSystemInsetsBounds;
+ public boolean isClippingEnabled() {
+ return mClippingEnabled;
}
/**
@@ -528,17 +530,20 @@
final int surfaceHeight =
viewRootImpl.getHeight() + surfaceInsets.top + surfaceInsets.bottom;
validMainWindowSurface =
- new SurfaceInfo(mainWindowSurface, surfaceWidth, surfaceHeight, true);
+ new SurfaceInfo(viewRootImpl.getSurfaceControl(), mainWindowSurface,
+ surfaceWidth, surfaceHeight, true);
}
}
// Get the surface backing the magnified view, if it is a SurfaceView.
SurfaceInfo validSurfaceViewSurface = SurfaceInfo.NULL;
if (mView instanceof SurfaceView) {
+ final SurfaceControl sc = ((SurfaceView) mView).getSurfaceControl();
final SurfaceHolder surfaceHolder = ((SurfaceView) mView).getHolder();
final Surface surfaceViewSurface = surfaceHolder.getSurface();
- if (surfaceViewSurface != null && surfaceViewSurface.isValid()) {
+
+ if (sc != null && sc.isValid()) {
final Rect surfaceFrame = surfaceHolder.getSurfaceFrame();
- validSurfaceViewSurface = new SurfaceInfo(surfaceViewSurface,
+ validSurfaceViewSurface = new SurfaceInfo(sc, surfaceViewSurface,
surfaceFrame.right, surfaceFrame.bottom, false);
}
}
@@ -708,7 +713,7 @@
* @return the current window coordinates, after they are clamped inside the parent surface
*/
private Point getCurrentClampedWindowCoordinates() {
- if (!mForcePositionWithinWindowSystemInsetsBounds) {
+ if (!mClippingEnabled) {
// No position adjustment should be done, so return the raw coordinates.
return new Point(mWindowCoords);
}
@@ -733,15 +738,18 @@
* Contains a surface and metadata corresponding to it.
*/
private static class SurfaceInfo {
- public static final SurfaceInfo NULL = new SurfaceInfo(null, 0, 0, false);
+ public static final SurfaceInfo NULL = new SurfaceInfo(null, null, 0, 0, false);
private Surface mSurface;
+ private SurfaceControl mSurfaceControl;
private int mWidth;
private int mHeight;
private boolean mIsMainWindowSurface;
- SurfaceInfo(final Surface surface, final int width, final int height,
+ SurfaceInfo(final SurfaceControl surfaceControl, final Surface surface,
+ final int width, final int height,
final boolean isMainWindowSurface) {
+ mSurfaceControl = surfaceControl;
mSurface = surface;
mWidth = width;
mHeight = height;
@@ -819,7 +827,7 @@
private Bitmap mCurrentContent;
InternalPopupWindow(final Context context, final Display display,
- final Surface parentSurface, final int width, final int height,
+ final SurfaceControl parentSurfaceControl, final int width, final int height,
final float elevation, final float cornerRadius, final Drawable overlay,
final Handler handler, final Object lock, final Callback callback) {
mDisplay = display;
@@ -829,17 +837,18 @@
mContentWidth = width;
mContentHeight = height;
- mOffsetX = (int) (0.1f * width);
- mOffsetY = (int) (0.1f * height);
+ mOffsetX = (int) (1.05f * elevation);
+ mOffsetY = (int) (1.05f * elevation);
// Setup the surface we will use for drawing the content and shadow.
mSurfaceWidth = mContentWidth + 2 * mOffsetX;
mSurfaceHeight = mContentHeight + 2 * mOffsetY;
- mSurfaceSession = new SurfaceSession(parentSurface);
+ mSurfaceSession = new SurfaceSession();
mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
.setFormat(PixelFormat.TRANSLUCENT)
.setBufferSize(mSurfaceWidth, mSurfaceHeight)
.setName("magnifier surface")
.setFlags(SurfaceControl.HIDDEN)
+ .setParent(parentSurfaceControl)
.build();
mSurface = new Surface();
mSurface.copyFrom(mSurfaceControl);
@@ -1129,7 +1138,7 @@
private @Nullable Drawable mOverlay;
private int mHorizontalDefaultSourceToMagnifierOffset;
private int mVerticalDefaultSourceToMagnifierOffset;
- private boolean mForcePositionWithinWindowSystemInsetsBounds;
+ private boolean mClippingEnabled;
private @SourceBound int mLeftContentBound;
private @SourceBound int mTopContentBound;
private @SourceBound int mRightContentBound;
@@ -1157,7 +1166,7 @@
resources.getDimensionPixelSize(R.dimen.default_magnifier_vertical_offset);
mOverlay = new ColorDrawable(resources.getColor(
R.color.default_magnifier_color_overlay, null));
- mForcePositionWithinWindowSystemInsetsBounds = true;
+ mClippingEnabled = true;
mLeftContentBound = SOURCE_BOUND_MAX_VISIBLE;
mTopContentBound = SOURCE_BOUND_MAX_VISIBLE;
mRightContentBound = SOURCE_BOUND_MAX_VISIBLE;
@@ -1220,11 +1229,11 @@
}
/**
- * Sets an overlay that will be drawn on the top of the magnifier content.
- * In general, the overlay should not be opaque, in order to let the expected magnifier
- * content be partially visible. The default overlay is {@code null} (no overlay).
- * As an example, TextView applies a white {@link ColorDrawable} overlay with
- * 5% alpha, aiming to make the magnifier distinguishable when shown in dark
+ * Sets an overlay that will be drawn on the top of the magnifier.
+ * In general, the overlay should not be opaque, in order to let the magnified
+ * content be partially visible in the magnifier. The default overlay is {@code null}
+ * (no overlay). As an example, TextView applies a white {@link ColorDrawable}
+ * overlay with 5% alpha, aiming to make the magnifier distinguishable when shown in dark
* application regions. To disable the overlay, the parameter should be set
* to {@code null}. If not null, the overlay will be automatically redrawn
* when the drawable is invalidated. To achieve this, the magnifier will set a new
@@ -1258,22 +1267,24 @@
* Defines the behavior of the magnifier when it is requested to position outside the
* surface of the main application window. The default value is {@code true}, which means
* that the position will be adjusted such that the magnifier will be fully within the
- * bounds of the main application window, by also avoiding any overlap with system insets
- * (such as the one corresponding to the status bar). If you require a custom behavior, this
- * flag should be set to {@code false}, meaning that the magnifier will be able to cross the
- * main application surface boundaries (and also overlap the system insets). This should be
- * handled with care, when passing coordinates to {@link #show(float, float)}; note that:
+ * bounds of the main application window, while also avoiding any overlap with system insets
+ * (such as the one corresponding to the status bar). If this flag is set to {@code false},
+ * the area where the magnifier can be positioned will no longer be clipped, so the
+ * magnifier will be able to extend outside the main application window boundaries (and also
+ * overlap the system insets). This can be useful if you require a custom behavior, but it
+ * should be handled with care, when passing coordinates to {@link #show(float, float)};
+ * note that:
* <ul>
* <li>in a multiwindow context, if the magnifier crosses the boundary between the two
* windows, it will not be able to show over the window of the other application</li>
* <li>if the magnifier overlaps the status bar, there is no guarantee about which one
* will be displayed on top. This should be handled with care.</li>
* </ul>
- * @param force whether the magnifier position will be adjusted
+ * @param clip whether the magnifier position will be adjusted
*/
@NonNull
- public Builder setForcePositionWithinWindowSystemInsetsBounds(boolean force) {
- mForcePositionWithinWindowSystemInsetsBounds = force;
+ public Builder setClippingEnabled(boolean clip) {
+ mClippingEnabled = clip;
return this;
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 0ea8579..51b8734 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -3520,7 +3520,7 @@
*/
@android.view.RemotableViewMethod
public void setTextSelectHandle(@DrawableRes int textSelectHandle) {
- Preconditions.checkArgumentPositive(textSelectHandle,
+ Preconditions.checkArgument(textSelectHandle != 0,
"The text select handle should be a valid drawable resource id.");
setTextSelectHandle(mContext.getDrawable(textSelectHandle));
}
@@ -3577,7 +3577,7 @@
*/
@android.view.RemotableViewMethod
public void setTextSelectHandleLeft(@DrawableRes int textSelectHandleLeft) {
- Preconditions.checkArgumentPositive(textSelectHandleLeft,
+ Preconditions.checkArgument(textSelectHandleLeft != 0,
"The text select left handle should be a valid drawable resource id.");
setTextSelectHandleLeft(mContext.getDrawable(textSelectHandleLeft));
}
@@ -3634,7 +3634,7 @@
*/
@android.view.RemotableViewMethod
public void setTextSelectHandleRight(@DrawableRes int textSelectHandleRight) {
- Preconditions.checkArgumentPositive(textSelectHandleRight,
+ Preconditions.checkArgument(textSelectHandleRight != 0,
"The text select right handle should be a valid drawable resource id.");
setTextSelectHandleRight(mContext.getDrawable(textSelectHandleRight));
}
@@ -3667,9 +3667,7 @@
* @see #setTextCursorDrawable(int)
* @attr ref android.R.styleable#TextView_textCursorDrawable
*/
- public void setTextCursorDrawable(@NonNull Drawable textCursorDrawable) {
- Preconditions.checkNotNull(textCursorDrawable,
- "The cursor drawable should not be null.");
+ public void setTextCursorDrawable(@Nullable Drawable textCursorDrawable) {
mCursorDrawable = textCursorDrawable;
mCursorDrawableRes = 0;
if (mEditor != null) {
@@ -3687,8 +3685,6 @@
* @attr ref android.R.styleable#TextView_textCursorDrawable
*/
public void setTextCursorDrawable(@DrawableRes int textCursorDrawable) {
- Preconditions.checkArgumentPositive(textCursorDrawable,
- "The cursor drawable should be a valid drawable resource id.");
setTextCursorDrawable(mContext.getDrawable(textCursorDrawable));
}
@@ -8061,6 +8057,26 @@
}
}
break;
+
+ case KeyEvent.KEYCODE_FORWARD_DEL:
+ if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canCut()) {
+ if (onTextContextMenuItem(ID_CUT)) {
+ return KEY_EVENT_HANDLED;
+ }
+ }
+ break;
+
+ case KeyEvent.KEYCODE_INSERT:
+ if (event.hasModifiers(KeyEvent.META_CTRL_ON) && canCopy()) {
+ if (onTextContextMenuItem(ID_COPY)) {
+ return KEY_EVENT_HANDLED;
+ }
+ } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canPaste()) {
+ if (onTextContextMenuItem(ID_PASTE)) {
+ return KEY_EVENT_HANDLED;
+ }
+ }
+ break;
}
if (mEditor != null && mEditor.mKeyListener != null) {
@@ -10831,25 +10847,6 @@
return onTextContextMenuItem(ID_PASTE);
}
break;
- case KeyEvent.KEYCODE_INSERT:
- if (canCopy()) {
- return onTextContextMenuItem(ID_COPY);
- }
- break;
- }
- } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
- // Handle Shift-only shortcuts.
- switch (keyCode) {
- case KeyEvent.KEYCODE_FORWARD_DEL:
- if (canCut()) {
- return onTextContextMenuItem(ID_CUT);
- }
- break;
- case KeyEvent.KEYCODE_INSERT:
- if (canPaste()) {
- return onTextContextMenuItem(ID_PASTE);
- }
- break;
}
} else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
// Handle Ctrl-Shift shortcuts.
diff --git a/core/java/android/widget/TimePickerClockDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java
index 6a3a8f0..dc9a585 100644
--- a/core/java/android/widget/TimePickerClockDelegate.java
+++ b/core/java/android/widget/TimePickerClockDelegate.java
@@ -454,6 +454,7 @@
(RelativeLayout.LayoutParams) mAmPmLayout.getLayoutParams();
if (params.getRule(RelativeLayout.RIGHT_OF) != 0
|| params.getRule(RelativeLayout.LEFT_OF) != 0) {
+ final int margin = (int) (mContext.getResources().getDisplayMetrics().density * 8);
// Horizontal mode, with AM/PM appearing to left/right of hours and minutes.
final boolean isAmPmAtLeft;
if (TextUtils.getLayoutDirectionFromLocale(mLocale) == View.LAYOUT_DIRECTION_LTR) {
@@ -461,10 +462,6 @@
} else {
isAmPmAtLeft = !isAmPmAtStart;
}
- if (mIsAmPmAtLeft == isAmPmAtLeft) {
- // AM/PM is already at the correct location. No change needed.
- return;
- }
if (isAmPmAtLeft) {
params.removeRule(RelativeLayout.RIGHT_OF);
@@ -473,6 +470,14 @@
params.removeRule(RelativeLayout.LEFT_OF);
params.addRule(RelativeLayout.RIGHT_OF, mMinuteView.getId());
}
+
+ if (isAmPmAtStart) {
+ params.setMarginStart(0);
+ params.setMarginEnd(margin);
+ } else {
+ params.setMarginStart(margin);
+ params.setMarginEnd(0);
+ }
mIsAmPmAtLeft = isAmPmAtLeft;
} else if (params.getRule(RelativeLayout.BELOW) != 0
|| params.getRule(RelativeLayout.ABOVE) != 0) {
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index e59bee4..c4ab91f 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -17,8 +17,10 @@
package com.android.internal.app;
import android.app.AppOpsManager;
+import android.app.AppOpsManager;
import android.content.pm.ParceledListSlice;
import android.os.Bundle;
+import android.os.RemoteCallback;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsActiveCallback;
import com.android.internal.app.IAppOpsNotedCallback;
@@ -42,10 +44,15 @@
int checkPackage(int uid, String packageName);
List<AppOpsManager.PackageOps> getPackagesForOps(in int[] ops);
List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName, in int[] ops);
- ParceledListSlice getAllHistoricalPackagesOps(in String[] ops,
- long beginTimeMillis, long endTimeMillis);
- AppOpsManager.HistoricalPackageOps getHistoricalPackagesOps(int uid, String packageName,
- in String[] ops, long beginTimeMillis, long endTimeMillis);
+ void getHistoricalOps(int uid, String packageName, in String[] ops, long beginTimeMillis,
+ long endTimeMillis, in RemoteCallback callback);
+ void getHistoricalOpsFromDiskRaw(int uid, String packageName, in String[] ops,
+ long beginTimeMillis, long endTimeMillis, in RemoteCallback callback);
+ void offsetHistory(long duration);
+ void setHistoryParameters(int mode, long baseSnapshotInterval, int compressionStep);
+ void addHistoricalOps(in AppOpsManager.HistoricalOps ops);
+ void resetHistoryParameters();
+ void clearHistory();
List<AppOpsManager.PackageOps> getUidOps(int uid, in int[] ops);
void setUidMode(int code, int uid, int mode);
void setMode(int code, int uid, String packageName, int mode);
diff --git a/core/java/com/android/internal/app/ResolverComparator.java b/core/java/com/android/internal/app/ResolverComparator.java
index 96d3baf..f61a03b 100644
--- a/core/java/com/android/internal/app/ResolverComparator.java
+++ b/core/java/com/android/internal/app/ResolverComparator.java
@@ -22,44 +22,36 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
+import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
-import android.content.pm.ComponentInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
-import android.content.SharedPreferences;
-import android.content.ServiceConnection;
import android.metrics.LogMaker;
-import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
-import android.os.storage.StorageManager;
import android.os.UserHandle;
-import android.service.resolver.IResolverRankerService;
import android.service.resolver.IResolverRankerResult;
+import android.service.resolver.IResolverRankerService;
import android.service.resolver.ResolverRankerService;
import android.service.resolver.ResolverTarget;
-import android.text.TextUtils;
-import android.util.ArrayMap;
import android.util.Log;
+
import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import java.io.File;
-import java.lang.InterruptedException;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Comparator;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
/**
* Ranks and compares packages based on usage stats.
@@ -90,6 +82,8 @@
private final Collator mCollator;
private final boolean mHttp;
+ // can be null if mHttp == false or current user has no default browser package
+ private final String mDefaultBrowserPackageName;
private final PackageManager mPm;
private final UsageStatsManager mUsm;
private final Map<String, UsageStats> mStats;
@@ -184,6 +178,10 @@
getContentAnnotations(intent);
mAction = intent.getAction();
mRankerServiceName = new ComponentName(mContext, this.getClass());
+
+ mDefaultBrowserPackageName = mHttp
+ ? mPm.getDefaultBrowserPackageNameAsUser(UserHandle.myUserId())
+ : null;
}
// get annotations of content from intent.
@@ -312,7 +310,14 @@
if (mHttp) {
// Special case: we want filters that match URI paths/schemes to be
// ordered before others. This is for the case when opening URIs,
- // to make native apps go above browsers.
+ // to make native apps go above browsers - except for 1 even more special case
+ // which is the default browser, as we want that to go above them all.
+ if (isDefaultBrowser(lhs)) {
+ return -1;
+ }
+ if (isDefaultBrowser(rhs)) {
+ return 1;
+ }
final boolean lhsSpecific = ResolverActivity.isSpecificUriMatch(lhs.match);
final boolean rhsSpecific = ResolverActivity.isSpecificUriMatch(rhs.match);
if (lhsSpecific != rhsSpecific) {
@@ -419,6 +424,20 @@
}
}
+ private boolean isDefaultBrowser(ResolveInfo ri) {
+ // It makes sense to prefer the default browser
+ // only if the targeted user is the current user
+ if (ri.targetUserId != UserHandle.USER_CURRENT) {
+ return false;
+ }
+
+ if (ri.activityInfo.packageName != null
+ && ri.activityInfo.packageName.equals(mDefaultBrowserPackageName)) {
+ return true;
+ }
+ return false;
+ }
+
// records metrics for evaluation.
private void logMetrics(int selectedPos) {
if (mRankerServiceName != null) {
diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java
index 0a7cff6..9bacf9b 100644
--- a/core/java/com/android/internal/net/NetworkStatsFactory.java
+++ b/core/java/com/android/internal/net/NetworkStatsFactory.java
@@ -20,6 +20,7 @@
import static android.net.NetworkStats.TAG_ALL;
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
+
import static com.android.server.NetworkManagementSocketTagger.kernelToTag;
import android.annotation.Nullable;
@@ -33,10 +34,8 @@
import libcore.io.IoUtils;
-import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
-import java.io.FileReader;
import java.io.IOException;
import java.net.ProtocolException;
import java.util.Arrays;
@@ -127,7 +126,7 @@
}
public NetworkStatsFactory() {
- this(new File("/proc/"), new File("/sys/fs/bpf/traffic_uid_stats_map").exists());
+ this(new File("/proc/"), new File("/sys/fs/bpf/map_netd_app_uid_stats_map").exists());
}
@VisibleForTesting
diff --git a/core/java/com/android/internal/os/AppZygoteInit.java b/core/java/com/android/internal/os/AppZygoteInit.java
new file mode 100644
index 0000000..afe6dad
--- /dev/null
+++ b/core/java/com/android/internal/os/AppZygoteInit.java
@@ -0,0 +1,105 @@
+/*
+ * 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.
+ */
+
+package com.android.internal.os;
+
+import android.app.LoadedApk;
+import android.content.pm.ApplicationInfo;
+import android.net.LocalSocket;
+import android.util.Log;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Startup class for an Application zygote process.
+ *
+ * See {@link ZygoteInit} for generic zygote startup documentation.
+ *
+ * @hide
+ */
+class AppZygoteInit {
+ public static final String TAG = "AppZygoteInit";
+
+ private static ZygoteServer sServer;
+
+ private static class AppZygoteServer extends ZygoteServer {
+ @Override
+ protected ZygoteConnection createNewConnection(LocalSocket socket, String abiList)
+ throws IOException {
+ return new AppZygoteConnection(socket, abiList);
+ }
+ }
+
+ private static class AppZygoteConnection extends ZygoteConnection {
+ AppZygoteConnection(LocalSocket socket, String abiList) throws IOException {
+ super(socket, abiList);
+ }
+
+ @Override
+ protected void preload() {
+ // Nothing to preload by default.
+ }
+
+ @Override
+ protected boolean isPreloadComplete() {
+ // App zygotes don't preload any classes or resources or defaults, all of their
+ // preloading is package specific.
+ return true;
+ }
+
+ @Override
+ protected boolean canPreloadApp() {
+ return true;
+ }
+
+ @Override
+ protected void handlePreloadApp(ApplicationInfo appInfo) {
+ Log.i(TAG, "Beginning application preload for " + appInfo.packageName);
+ LoadedApk loadedApk = new LoadedApk(null, appInfo, null, null, false, true, false);
+ ClassLoader loader = loadedApk.getClassLoader();
+ Class<?> cl;
+ Method m;
+ try {
+ cl = Class.forName(appInfo.packageName + ".ZygotePreload", true, loader);
+ m = cl.getMethod("doPreload");
+ m.setAccessible(true);
+ m.invoke(null);
+ } catch (ClassNotFoundException e) {
+ // Don't treat this as an error since an app may not want to do any preloads
+ Log.w(TAG, "No ZygotePreload class found for " + appInfo.packageName);
+ } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
+ Log.e(TAG, "AppZygote application preload failed for "
+ + appInfo.packageName, e);
+ }
+ try {
+ DataOutputStream socketOut = getSocketOutputStream();
+ socketOut.writeInt(loader != null ? 1 : 0);
+ } catch (IOException e) {
+ throw new IllegalStateException("Error writing to command socket", e);
+ }
+
+ Log.i(TAG, "Application preload done");
+ }
+ }
+
+ public static void main(String[] argv) {
+ AppZygoteServer server = new AppZygoteServer();
+ ChildZygoteInit.runZygoteServer(server, argv);
+ }
+}
diff --git a/core/java/com/android/internal/os/AtomicDirectory.java b/core/java/com/android/internal/os/AtomicDirectory.java
new file mode 100644
index 0000000..f24d12e
--- /dev/null
+++ b/core/java/com/android/internal/os/AtomicDirectory.java
@@ -0,0 +1,294 @@
+/*
+ * 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.
+ */
+
+package com.android.internal.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.FileUtils;
+import android.util.ArrayMap;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * Helper class for performing atomic operations on a directory, by creating a
+ * backup directory until a write has successfully completed.
+ * <p>
+ * Atomic directory guarantees directory integrity by ensuring that a directory has
+ * been completely written and sync'd to disk before removing its backup.
+ * As long as the backup directory exists, the original directory is considered
+ * to be invalid (leftover from a previous attempt to write).
+ * <p>
+ * Atomic directory does not confer any file locking semantics. Do not use this
+ * class when the directory may be accessed or modified concurrently
+ * by multiple threads or processes. The caller is responsible for ensuring
+ * appropriate mutual exclusion invariants whenever it accesses the directory.
+ * <p>
+ * To ensure atomicity you must always use this class to interact with the
+ * backing directory when checking existence, making changes, and deleting.
+ */
+public final class AtomicDirectory {
+ private final @NonNull ArrayMap<File, FileOutputStream> mOpenFiles = new ArrayMap<>();
+ private final @NonNull File mBaseDirectory;
+ private final @NonNull File mBackupDirectory;
+
+ private int mBaseDirectoryFd = -1;
+ private int mBackupDirectoryFd = -1;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param baseDirectory The base directory to treat atomically.
+ */
+ public AtomicDirectory(@NonNull File baseDirectory) {
+ Preconditions.checkNotNull(baseDirectory, "baseDirectory cannot be null");
+ mBaseDirectory = baseDirectory;
+ mBackupDirectory = new File(baseDirectory.getPath() + "_bak");
+ }
+
+ /**
+ * Gets the backup directory if present. This could be useful if you are
+ * writing new state to the dir but need to access the last persisted state
+ * at the same time. This means that this call is useful in between
+ * {@link #startWrite()} and {@link #finishWrite()} or {@link #failWrite()}.
+ * You should not modify the content returned by this method.
+ *
+ * @see #startRead()
+ */
+ public @Nullable File getBackupDirectory() {
+ return mBackupDirectory;
+ }
+
+ /**
+ * Starts reading this directory. After calling this method you should
+ * not make any changes to its contents.
+ *
+ * @throws IOException If an error occurs.
+ *
+ * @see #finishRead()
+ * @see #startWrite()
+ */
+ public @NonNull File startRead() throws IOException {
+ restore();
+ return getOrCreateBaseDirectory();
+ }
+
+ /**
+ * Finishes reading this directory.
+ *
+ * @see #startRead()
+ * @see #startWrite()
+ */
+ public void finishRead() {
+ mBaseDirectoryFd = -1;
+ mBackupDirectoryFd = -1;
+ }
+
+ /**
+ * Starts editing this directory. After calling this method you should
+ * add content to the directory only via the APIs on this class. To open a
+ * file for writing in this directory you should use {@link #openWrite(File)}
+ * and to close the file {@link #closeWrite(FileOutputStream)}. Once all
+ * content has been written and all files closed you should commit via a
+ * call to {@link #finishWrite()} or discard via a call to {@link #failWrite()}.
+ *
+ * @throws IOException If an error occurs.
+ *
+ * @see #startRead()
+ * @see #openWrite(File)
+ * @see #finishWrite()
+ * @see #failWrite()
+ */
+ public @NonNull File startWrite() throws IOException {
+ backup();
+ return getOrCreateBaseDirectory();
+ }
+
+ /**
+ * Opens a file in this directory for writing.
+ *
+ * @param file The file to open. Must be a file in the base directory.
+ * @return An input stream for reading.
+ *
+ * @throws IOException If an I/O error occurs.
+ *
+ * @see #closeWrite(FileOutputStream)
+ */
+ public @NonNull FileOutputStream openWrite(@NonNull File file) throws IOException {
+ if (file.isDirectory() || !file.getParentFile().equals(getOrCreateBaseDirectory())) {
+ throw new IllegalArgumentException("Must be a file in " + getOrCreateBaseDirectory());
+ }
+ final FileOutputStream destination = new FileOutputStream(file);
+ if (mOpenFiles.put(file, destination) != null) {
+ throw new IllegalArgumentException("Already open file" + file.getCanonicalPath());
+ }
+ return destination;
+ }
+
+ /**
+ * Closes a previously opened file.
+ *
+ * @param destination The stream to the file returned by {@link #openWrite(File)}.
+ *
+ * @see #openWrite(File)
+ */
+ public void closeWrite(@NonNull FileOutputStream destination) {
+ final int indexOfValue = mOpenFiles.indexOfValue(destination);
+ if (mOpenFiles.removeAt(indexOfValue) == null) {
+ throw new IllegalArgumentException("Unknown file stream " + destination);
+ }
+ FileUtils.sync(destination);
+ try {
+ destination.close();
+ } catch (IOException ignored) {}
+ }
+
+ public void failWrite(@NonNull FileOutputStream destination) {
+ final int indexOfValue = mOpenFiles.indexOfValue(destination);
+ if (indexOfValue >= 0) {
+ mOpenFiles.removeAt(indexOfValue);
+ }
+ }
+
+ /**
+ * Finishes the edit and commits all changes.
+ *
+ * @see #startWrite()
+ *
+ * @throws IllegalStateException is some files are not closed.
+ */
+ public void finishWrite() {
+ throwIfSomeFilesOpen();
+ fsyncDirectoryFd(mBaseDirectoryFd);
+ deleteDirectory(mBackupDirectory);
+ fsyncDirectoryFd(mBackupDirectoryFd);
+ mBaseDirectoryFd = -1;
+ mBackupDirectoryFd = -1;
+ }
+
+ /**
+ * Finishes the edit and discards all changes.
+ *
+ * @see #startWrite()
+ */
+ public void failWrite() {
+ throwIfSomeFilesOpen();
+ try{
+ restore();
+ } catch (IOException ignored) {}
+ mBaseDirectoryFd = -1;
+ mBackupDirectoryFd = -1;
+ }
+
+ /**
+ * @return Whether this directory exists.
+ */
+ public boolean exists() {
+ return mBaseDirectory.exists() || mBackupDirectory.exists();
+ }
+
+ /**
+ * Deletes this directory.
+ */
+ public void delete() {
+ if (mBaseDirectory.exists()) {
+ deleteDirectory(mBaseDirectory);
+ fsyncDirectoryFd(mBaseDirectoryFd);
+ }
+ if (mBackupDirectory.exists()) {
+ deleteDirectory(mBackupDirectory);
+ fsyncDirectoryFd(mBackupDirectoryFd);
+ }
+ }
+
+ private @NonNull File getOrCreateBaseDirectory() throws IOException {
+ if (!mBaseDirectory.exists()) {
+ if (!mBaseDirectory.mkdirs()) {
+ throw new IOException("Couldn't create directory " + mBaseDirectory);
+ }
+ FileUtils.setPermissions(mBaseDirectory.getPath(),
+ FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IXOTH,
+ -1, -1);
+ }
+ if (mBaseDirectoryFd < 0) {
+ mBaseDirectoryFd = getDirectoryFd(mBaseDirectory.getCanonicalPath());
+ }
+ return mBaseDirectory;
+ }
+
+ private void throwIfSomeFilesOpen() {
+ if (!mOpenFiles.isEmpty()) {
+ throw new IllegalStateException("Unclosed files: "
+ + Arrays.toString(mOpenFiles.keySet().toArray()));
+ }
+ }
+
+ private void backup() throws IOException {
+ if (!mBaseDirectory.exists()) {
+ return;
+ }
+ if (mBaseDirectoryFd < 0) {
+ mBaseDirectoryFd = getDirectoryFd(mBaseDirectory.getCanonicalPath());
+ }
+ if (mBackupDirectory.exists()) {
+ deleteDirectory(mBackupDirectory);
+ }
+ if (!mBaseDirectory.renameTo(mBackupDirectory)) {
+ throw new IOException("Couldn't backup " + mBaseDirectory
+ + " to " + mBackupDirectory);
+ }
+ mBackupDirectoryFd = mBaseDirectoryFd;
+ mBaseDirectoryFd = -1;
+ fsyncDirectoryFd(mBackupDirectoryFd);
+ }
+
+ private void restore() throws IOException {
+ if (!mBackupDirectory.exists()) {
+ return;
+ }
+ if (mBackupDirectoryFd == -1) {
+ mBackupDirectoryFd = getDirectoryFd(mBackupDirectory.getCanonicalPath());
+ }
+ if (mBaseDirectory.exists()) {
+ deleteDirectory(mBaseDirectory);
+ }
+ if (!mBackupDirectory.renameTo(mBaseDirectory)) {
+ throw new IOException("Couldn't restore " + mBackupDirectory
+ + " to " + mBaseDirectory);
+ }
+ mBaseDirectoryFd = mBackupDirectoryFd;
+ mBackupDirectoryFd = -1;
+ fsyncDirectoryFd(mBaseDirectoryFd);
+ }
+
+ private static void deleteDirectory(@NonNull File file) {
+ final File[] children = file.listFiles();
+ if (children != null) {
+ for (File child : children) {
+ deleteDirectory(child);
+ }
+ }
+ file.delete();
+ }
+
+ private static native int getDirectoryFd(String path);
+ private static native void fsyncDirectoryFd(int fd);
+}
diff --git a/core/java/com/android/internal/os/ChildZygoteInit.java b/core/java/com/android/internal/os/ChildZygoteInit.java
new file mode 100644
index 0000000..f90cd02
--- /dev/null
+++ b/core/java/com/android/internal/os/ChildZygoteInit.java
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ */
+package com.android.internal.os;
+
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.util.Log;
+
+/**
+ * ChildZygoteInit is shared by both the Application and WebView zygote to initialize
+ * and run a (child) Zygote server.
+ *
+ * @hide
+ */
+public class ChildZygoteInit {
+ private static final String TAG = "ChildZygoteInit";
+
+ static String parseSocketNameFromArgs(String[] argv) {
+ for (String arg : argv) {
+ if (arg.startsWith(Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG)) {
+ return arg.substring(Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG.length());
+ }
+ }
+
+ return null;
+ }
+
+ static String parseAbiListFromArgs(String[] argv) {
+ for (String arg : argv) {
+ if (arg.startsWith(Zygote.CHILD_ZYGOTE_ABI_LIST_ARG)) {
+ return arg.substring(Zygote.CHILD_ZYGOTE_ABI_LIST_ARG.length());
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Starts a ZygoteServer and listens for requests
+ *
+ * @param server An instance of a ZygoteServer to listen on
+ * @param args Passed in arguments for this ZygoteServer
+ */
+ static void runZygoteServer(ZygoteServer server, String[] args) {
+ String socketName = parseSocketNameFromArgs(args);
+ if (socketName == null) {
+ throw new NullPointerException("No socketName specified");
+ }
+
+ String abiList = parseAbiListFromArgs(args);
+ if (abiList == null) {
+ throw new NullPointerException("No abiList specified");
+ }
+
+ try {
+ Os.prctl(OsConstants.PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
+ } catch (ErrnoException ex) {
+ throw new RuntimeException("Failed to set PR_SET_NO_NEW_PRIVS", ex);
+ }
+
+ final Runnable caller;
+ try {
+ server.registerServerSocketAtAbstractName(socketName);
+
+ // Add the abstract socket to the FD whitelist so that the native zygote code
+ // can properly detach it after forking.
+ Zygote.nativeAllowFileAcrossFork("ABSTRACT/" + socketName);
+
+ // The select loop returns early in the child process after a fork and
+ // loops forever in the zygote.
+ caller = server.runSelectLoop(abiList);
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Fatal exception:", e);
+ throw e;
+ } finally {
+ server.closeServerSocket();
+ }
+
+ // We're in the child process and have exited the select loop. Proceed to execute the
+ // command.
+ if (caller != null) {
+ caller.run();
+ }
+ }
+}
diff --git a/core/java/com/android/internal/os/RoSystemProperties.java b/core/java/com/android/internal/os/RoSystemProperties.java
index dc660a4..a319d83 100644
--- a/core/java/com/android/internal/os/RoSystemProperties.java
+++ b/core/java/com/android/internal/os/RoSystemProperties.java
@@ -30,6 +30,14 @@
public static final String CONTROL_PRIVAPP_PERMISSIONS =
SystemProperties.get("ro.control_privapp_permissions");
+ /**
+ * Property to indicate if a CEC audio device should forward volume keys when system audio
+ * mode is off.
+ */
+ public static final boolean CEC_AUDIO_DEVICE_FORWARD_VOLUME_KEYS_SYSTEM_AUDIO_MODE_OFF =
+ SystemProperties.getBoolean(
+ "ro.hdmi.cec_audio_device_forward_volume_keys_system_audio_mode_off", false);
+
// ------ ro.config.* -------- //
public static final boolean CONFIG_AVOID_GFX_ACCEL =
SystemProperties.getBoolean("ro.config.avoid_gfx_accel", false);
diff --git a/core/java/com/android/internal/os/WebViewZygoteInit.java b/core/java/com/android/internal/os/WebViewZygoteInit.java
index 9f2434e..0b329d7 100644
--- a/core/java/com/android/internal/os/WebViewZygoteInit.java
+++ b/core/java/com/android/internal/os/WebViewZygoteInit.java
@@ -18,11 +18,7 @@
import android.app.ApplicationLoaders;
import android.net.LocalSocket;
-import android.net.LocalServerSocket;
import android.os.Build;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.OsConstants;
import android.text.TextUtils;
import android.util.Log;
import android.webkit.WebViewFactory;
@@ -44,8 +40,6 @@
class WebViewZygoteInit {
public static final String TAG = "WebViewZygoteInit";
- private static ZygoteServer sServer;
-
private static class WebViewZygoteServer extends ZygoteServer {
@Override
protected ZygoteConnection createNewConnection(LocalSocket socket, String abiList)
@@ -127,48 +121,7 @@
public static void main(String argv[]) {
Log.i(TAG, "Starting WebViewZygoteInit");
-
- String socketName = null;
- for (String arg : argv) {
- Log.i(TAG, arg);
- if (arg.startsWith(Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG)) {
- socketName = arg.substring(Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG.length());
- }
- }
- if (socketName == null) {
- throw new RuntimeException("No " + Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG + " specified");
- }
-
- try {
- Os.prctl(OsConstants.PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
- } catch (ErrnoException ex) {
- throw new RuntimeException("Failed to set PR_SET_NO_NEW_PRIVS", ex);
- }
-
- sServer = new WebViewZygoteServer();
-
- final Runnable caller;
- try {
- sServer.registerServerSocketAtAbstractName(socketName);
-
- // Add the abstract socket to the FD whitelist so that the native zygote code
- // can properly detach it after forking.
- Zygote.nativeAllowFileAcrossFork("ABSTRACT/" + socketName);
-
- // The select loop returns early in the child process after a fork and
- // loops forever in the zygote.
- caller = sServer.runSelectLoop(TextUtils.join(",", Build.SUPPORTED_ABIS));
- } catch (RuntimeException e) {
- Log.e(TAG, "Fatal exception:", e);
- throw e;
- } finally {
- sServer.closeServerSocket();
- }
-
- // We're in the child process and have exited the select loop. Proceed to execute the
- // command.
- if (caller != null) {
- caller.run();
- }
+ WebViewZygoteServer server = new WebViewZygoteServer();
+ ChildZygoteInit.runZygoteServer(server, argv);
}
}
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 65b9fad..d720c68 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -98,6 +98,12 @@
*/
public static final String CHILD_ZYGOTE_SOCKET_NAME_ARG = "--zygote-socket=";
+ /**
+ * An extraArg passed when a zygote process is forking a child-zygote, specifying the
+ * requested ABI for the child Zygote.
+ */
+ public static final String CHILD_ZYGOTE_ABI_LIST_ARG = "--abi-list=";
+
private Zygote() {}
/** Called for some security initialization before any fork. */
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index f182c4d..5990d72 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -27,9 +27,11 @@
import static com.android.internal.os.ZygoteConnectionConstants.MAX_ZYGOTE_ARGC;
import static com.android.internal.os.ZygoteConnectionConstants.WRAPPED_PID_TIMEOUT_MILLIS;
+import android.content.pm.ApplicationInfo;
import android.net.Credentials;
import android.net.LocalSocket;
import android.os.FactoryTest;
+import android.os.Parcel;
import android.os.Process;
import android.os.SystemProperties;
import android.os.Trace;
@@ -52,6 +54,7 @@
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Base64;
/**
* A connection that can make spawn requests.
@@ -168,6 +171,21 @@
return null;
}
+ if (canPreloadApp() && parsedArgs.mPreloadApp != null) {
+ byte[] rawParcelData = Base64.getDecoder().decode(parsedArgs.mPreloadApp);
+ Parcel appInfoParcel = Parcel.obtain();
+ appInfoParcel.unmarshall(rawParcelData, 0, rawParcelData.length);
+ appInfoParcel.setDataPosition(0);
+ ApplicationInfo appInfo = ApplicationInfo.CREATOR.createFromParcel(appInfoParcel);
+ appInfoParcel.recycle();
+ if (appInfo != null) {
+ handlePreloadApp(appInfo);
+ } else {
+ throw new IllegalArgumentException("Failed to deserialize --preload-app");
+ }
+ return null;
+ }
+
if (parsedArgs.apiBlacklistExemptions != null) {
handleApiBlacklistExemptions(parsedArgs.apiBlacklistExemptions);
return null;
@@ -341,7 +359,15 @@
protected void handlePreloadPackage(String packagePath, String libsPath, String libFileName,
String cacheKey) {
- throw new RuntimeException("Zyogte does not support package preloading");
+ throw new RuntimeException("Zygote does not support package preloading");
+ }
+
+ protected boolean canPreloadApp() {
+ return false;
+ }
+
+ protected void handlePreloadApp(ApplicationInfo aInfo) {
+ throw new RuntimeException("Zygote does not support app preloading");
}
/**
@@ -467,6 +493,12 @@
String preloadPackage;
/**
+ * A Base64 string representing a serialize ApplicationInfo Parcel,
+ when using --preload-app.
+ */
+ String mPreloadApp;
+
+ /**
* The native library path of the package to preload, when using --preload-package.
*/
String preloadPackageLibs;
@@ -666,6 +698,8 @@
instructionSet = arg.substring(arg.indexOf('=') + 1);
} else if (arg.startsWith("--app-data-dir=")) {
appDataDir = arg.substring(arg.indexOf('=') + 1);
+ } else if (arg.equals("--preload-app")) {
+ mPreloadApp = args[++curArg];
} else if (arg.equals("--preload-package")) {
preloadPackage = args[++curArg];
preloadPackageLibs = args[++curArg];
@@ -714,6 +748,11 @@
throw new IllegalArgumentException(
"Unexpected arguments after --preload-package.");
}
+ } else if (mPreloadApp != null) {
+ if (args.length - curArg > 0) {
+ throw new IllegalArgumentException(
+ "Unexpected arguments after --preload-app.");
+ }
} else if (expectRuntimeArgs) {
if (!seenRuntimeArgs) {
throw new IllegalArgumentException("Unexpected argument : " + args[curArg]);
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index e2c23de..cdaf33f 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -232,7 +232,7 @@
// This is the caption view for the window, containing the caption and window control
// buttons. The visibility of this decor depends on the workspace and the window type.
// If the window type does not require such a view, this member might be null.
- DecorCaptionView mDecorCaptionView;
+ private DecorCaptionView mDecorCaptionView;
private boolean mWindowResizeCallbacksAdded = false;
private Drawable.Callback mLastBackgroundDrawableCb = null;
@@ -977,6 +977,7 @@
@Override
public void onWindowSystemUiVisibilityChanged(int visible) {
updateColorViews(null /* insets */, true /* animate */);
+ updateDecorCaptionStatus(getResources().getConfiguration());
}
@Override
@@ -1855,8 +1856,27 @@
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- final boolean displayWindowDecor =
- newConfig.windowConfiguration.hasWindowDecorCaption();
+ updateDecorCaptionStatus(newConfig);
+
+ updateAvailableWidth();
+ initializeElevation();
+ }
+
+ /**
+ * Determines if the workspace is entirely covered by the window.
+ * @return {@code true} when the window is filling the entire screen/workspace.
+ **/
+ private boolean isFillingScreen(Configuration config) {
+ final boolean isFullscreen = config.windowConfiguration.getWindowingMode()
+ == WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+ return isFullscreen && (0 != ((getWindowSystemUiVisibility() | getSystemUiVisibility())
+ & (View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_IMMERSIVE | View.SYSTEM_UI_FLAG_LOW_PROFILE)));
+ }
+
+ private void updateDecorCaptionStatus(Configuration config) {
+ final boolean displayWindowDecor = config.windowConfiguration.hasWindowDecorCaption()
+ && !isFillingScreen(config);
if (mDecorCaptionView == null && displayWindowDecor) {
// Configuration now requires a caption.
final LayoutInflater inflater = mWindow.getLayoutInflater();
@@ -1875,9 +1895,6 @@
mDecorCaptionView.onConfigurationChanged(displayWindowDecor);
enableCaption(displayWindowDecor);
}
-
- updateAvailableWidth();
- initializeElevation();
}
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
diff --git a/core/java/com/android/internal/policy/ScreenDecorationsUtils.java b/core/java/com/android/internal/policy/ScreenDecorationsUtils.java
index 100c6ee..adf7692 100644
--- a/core/java/com/android/internal/policy/ScreenDecorationsUtils.java
+++ b/core/java/com/android/internal/policy/ScreenDecorationsUtils.java
@@ -16,10 +16,10 @@
package com.android.internal.policy;
-import com.android.internal.R;
-
import android.content.res.Resources;
+import com.android.internal.R;
+
/**
* Utility functions for screen decorations used by both window manager and System UI.
*/
@@ -31,15 +31,19 @@
* scaling, this means that we don't have to reload them on config changes.
*/
public static float getWindowCornerRadius(Resources resources) {
+ if (!supportsRoundedCornersOnWindows(resources)) {
+ return 0f;
+ }
+
// Radius that should be used in case top or bottom aren't defined.
float defaultRadius = resources.getDimension(R.dimen.rounded_corner_radius);
float topRadius = resources.getDimension(R.dimen.rounded_corner_radius_top);
- if (topRadius == 0) {
+ if (topRadius == 0f) {
topRadius = defaultRadius;
}
float bottomRadius = resources.getDimension(R.dimen.rounded_corner_radius_bottom);
- if (bottomRadius == 0) {
+ if (bottomRadius == 0f) {
bottomRadius = defaultRadius;
}
@@ -47,4 +51,11 @@
// completely cover the display.
return Math.min(topRadius, bottomRadius);
}
+
+ /**
+ * If live rounded corners are supported on windows.
+ */
+ public static boolean supportsRoundedCornersOnWindows(Resources resources) {
+ return resources.getBoolean(R.bool.config_supportsRoundedCornersOnWindows);
+ }
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 53b56f2..6a28059 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -153,13 +153,11 @@
void showBiometricDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver, int type,
boolean requireConfirmation, int userId);
// Used to hide the dialog when a biometric is authenticated
- void onBiometricAuthenticated();
+ void onBiometricAuthenticated(boolean authenticated);
// Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc
void onBiometricHelp(String message);
// Used to set a message - the dialog will dismiss after a certain amount of time
void onBiometricError(String error);
// Used to hide the biometric dialog when the AuthenticationClient is stopped
void hideBiometricDialog();
- // Used to request the "try again" button for authentications which requireConfirmation=true
- void showBiometricTryAgain();
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 9087dd2..197e873 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -97,13 +97,11 @@
void showBiometricDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver, int type,
boolean requireConfirmation, int userId);
// Used to hide the dialog when a biometric is authenticated
- void onBiometricAuthenticated();
+ void onBiometricAuthenticated(boolean authenticated);
// Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc
void onBiometricHelp(String message);
// Used to set a message - the dialog will dismiss after a certain amount of time
void onBiometricError(String error);
// Used to hide the biometric dialog when the AuthenticationClient is stopped
void hideBiometricDialog();
- // Used to request the "try again" button for authentications which requireConfirmation=true
- void showBiometricTryAgain();
}
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
index 470533f2..605df04 100644
--- a/core/java/com/android/internal/util/CollectionUtils.java
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -330,4 +330,18 @@
public static @NonNull <T> List<T> defeatNullable(@Nullable List<T> val) {
return (val != null) ? val : Collections.emptyList();
}
+
+ /**
+ * @return the first element if not empty/null, null otherwise
+ */
+ public static @Nullable <T> T firstOrNull(@Nullable List<T> cur) {
+ return isEmpty(cur) ? null : cur.get(0);
+ }
+
+ /**
+ * @return list of single given element if it's not null, empty list otherwise
+ */
+ public static @NonNull <T> List<T> singletonOrEmpty(@Nullable T item) {
+ return item == null ? Collections.emptyList() : Collections.singletonList(item);
+ }
}
diff --git a/core/java/com/android/internal/util/SyncResultReceiver.java b/core/java/com/android/internal/util/SyncResultReceiver.java
new file mode 100644
index 0000000..9a346ac
--- /dev/null
+++ b/core/java/com/android/internal/util/SyncResultReceiver.java
@@ -0,0 +1,145 @@
+/*
+ * 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.
+ */
+package com.android.internal.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+import com.android.internal.os.IResultReceiver;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A {@code IResultReceiver} implementation that can be used to make "sync" Binder calls by blocking
+ * until it receives a result
+ *
+ * @hide
+ */
+public final class SyncResultReceiver extends IResultReceiver.Stub {
+
+ private static final String EXTRA = "EXTRA";
+
+ private final CountDownLatch mLatch = new CountDownLatch(1);
+ private final int mTimeoutMs;
+ private int mResult;
+ private Bundle mBundle;
+
+ /**
+ * Default constructor.
+ *
+ * @param timeoutMs how long to block waiting for {@link IResultReceiver} callbacks.
+ */
+ public SyncResultReceiver(int timeoutMs) {
+ mTimeoutMs = timeoutMs;
+ }
+
+ private void waitResult() throws TimeoutException {
+ try {
+ if (!mLatch.await(mTimeoutMs, TimeUnit.MILLISECONDS)) {
+ throw new TimeoutException("Not called in " + mTimeoutMs + "ms");
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new TimeoutException("Interrupted");
+ }
+ }
+
+ /**
+ * Gets the result from an operation that returns an {@code int}.
+ */
+ public int getIntResult() throws TimeoutException {
+ waitResult();
+ return mResult;
+ }
+
+ /**
+ * Gets the result from an operation that returns an {@code String}.
+ */
+ @Nullable
+ public String getStringResult() throws TimeoutException {
+ waitResult();
+ return mBundle == null ? null : mBundle.getString(EXTRA);
+ }
+
+ /**
+ * Gets the result from an operation that returns a {@code String[]}.
+ */
+ @Nullable
+ public String[] getStringArrayResult() throws TimeoutException {
+ waitResult();
+ return mBundle == null ? null : mBundle.getStringArray(EXTRA);
+ }
+
+ /**
+ * Gets the result from an operation that returns a {@code Parcelable}.
+ */
+ @Nullable
+ public <P extends Parcelable> P getParcelableResult() throws TimeoutException {
+ waitResult();
+ return mBundle == null ? null : mBundle.getParcelable(EXTRA);
+ }
+
+ @Override
+ public void send(int resultCode, Bundle resultData) {
+ mResult = resultCode;
+ mBundle = resultData;
+ mLatch.countDown();
+ }
+
+ /**
+ * Creates a bundle for a {@code String} value so it can be retrieved by
+ * {@link #getStringResult()}.
+ */
+ @NonNull
+ public static Bundle bundleFor(@Nullable String value) {
+ final Bundle bundle = new Bundle();
+ bundle.putString(EXTRA, value);
+ return bundle;
+ }
+
+ /**
+ * Creates a bundle for a {@code String[]} value so it can be retrieved by
+ * {@link #getStringArrayResult()}.
+ */
+ @NonNull
+ public static Bundle bundleFor(@Nullable String[] value) {
+ final Bundle bundle = new Bundle();
+ bundle.putStringArray(EXTRA, value);
+ return bundle;
+ }
+
+ /**
+ * Creates a bundle for a {@code Parcelable} value so it can be retrieved by
+ * {@link #getParcelableResult()}.
+ */
+ @NonNull
+ public static Bundle bundleFor(@Nullable Parcelable value) {
+ final Bundle bundle = new Bundle();
+ bundle.putParcelable(EXTRA, value);
+ return bundle;
+ }
+
+ /** @hide */
+ public static final class TimeoutException extends RemoteException {
+ private TimeoutException(String msg) {
+ super(msg);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/DecorCaptionView.java b/core/java/com/android/internal/widget/DecorCaptionView.java
index b419113..21558d3 100644
--- a/core/java/com/android/internal/widget/DecorCaptionView.java
+++ b/core/java/com/android/internal/widget/DecorCaptionView.java
@@ -319,23 +319,12 @@
mOwner.notifyRestrictedCaptionAreaCallback(mMaximize.getLeft(), mMaximize.getTop(),
mClose.getRight(), mClose.getBottom());
}
- /**
- * Determine if the workspace is entirely covered by the window.
- * @return Returns true when the window is filling the entire screen/workspace.
- **/
- private boolean isFillingScreen() {
- return (0 != ((getWindowSystemUiVisibility() | getSystemUiVisibility()) &
- (View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
- View.SYSTEM_UI_FLAG_IMMERSIVE | View.SYSTEM_UI_FLAG_LOW_PROFILE)));
- }
/**
* Updates the visibility of the caption.
**/
private void updateCaptionVisibility() {
- // Don't show the caption if the window has e.g. entered full screen.
- boolean invisible = isFillingScreen() || !mShow;
- mCaption.setVisibility(invisible ? GONE : VISIBLE);
+ mCaption.setVisibility(mShow ? VISIBLE : GONE);
mCaption.setOnTouchListener(this);
}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index dc6a73a..088e13f 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -41,7 +41,7 @@
"com_google_android_gles_jni_EGLImpl.cpp",
"com_google_android_gles_jni_GLImpl.cpp", // TODO: .arm
"android_app_Activity.cpp",
- "android_app_ActivityThread.cpp",
+ "android_app_ActivityThread.cpp",
"android_app_NativeActivity.cpp",
"android_app_admin_SecurityLog.cpp",
"android_opengl_EGL14.cpp",
@@ -202,6 +202,7 @@
"android_animation_PropertyValuesHolder.cpp",
"android_security_Scrypt.cpp",
"com_android_internal_net_NetworkStatsFactory.cpp",
+ "com_android_internal_os_AtomicDirectory.cpp",
"com_android_internal_os_ClassLoaderFactory.cpp",
"com_android_internal_os_FuseAppLoop.cpp",
"com_android_internal_os_Zygote.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 687b1055..1092222 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -218,6 +218,7 @@
extern int register_android_security_Scrypt(JNIEnv *env);
extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env);
extern int register_com_android_internal_net_NetworkStatsFactory(JNIEnv *env);
+extern int register_com_android_internal_os_AtomicDirectory(JNIEnv *env);
extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env);
extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env);
extern int register_com_android_internal_os_Zygote(JNIEnv *env);
@@ -1495,6 +1496,7 @@
REG_JNI(register_android_security_Scrypt),
REG_JNI(register_com_android_internal_content_NativeLibraryHelper),
REG_JNI(register_com_android_internal_net_NetworkStatsFactory),
+ REG_JNI(register_com_android_internal_os_AtomicDirectory),
REG_JNI(register_com_android_internal_os_FuseAppLoop),
};
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index 9ae05f4..4b0ab5b 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -231,7 +231,7 @@
}
if (isMutable && isHardware) {
- doThrowIAE(env, "Bitmaps with Config.HARWARE are always immutable");
+ doThrowIAE(env, "Bitmaps with Config.HARDWARE are always immutable");
return nullObjectReturn("Cannot create mutable hardware bitmap");
}
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 3329e20..f201ceb 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -875,6 +875,11 @@
return IPCThreadState::self()->getCallingUid();
}
+static jboolean android_os_Binder_isHandlingTransaction()
+{
+ return IPCThreadState::self()->isServingCall();
+}
+
static jlong android_os_Binder_clearCallingIdentity()
{
return IPCThreadState::self()->clearCallingIdentity();
@@ -960,6 +965,8 @@
// @CriticalNative
{ "getCallingUid", "()I", (void*)android_os_Binder_getCallingUid },
// @CriticalNative
+ { "isHandlingTransaction", "()Z", (void*)android_os_Binder_isHandlingTransaction },
+ // @CriticalNative
{ "clearCallingIdentity", "()J", (void*)android_os_Binder_clearCallingIdentity },
{ "restoreCallingIdentity", "(J)V", (void*)android_os_Binder_restoreCallingIdentity },
// @CriticalNative
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index ecf8119..50cff5c 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -334,7 +334,7 @@
static jlong android_view_MotionEvent_nativeInitialize(JNIEnv* env, jclass clazz,
jlong nativePtr,
jint deviceId, jint source, jint displayId, jint action, jint flags, jint edgeFlags,
- jint metaState, jint buttonState,
+ jint metaState, jint buttonState, jint classification,
jfloat xOffset, jfloat yOffset, jfloat xPrecision, jfloat yPrecision,
jlong downTimeNanos, jlong eventTimeNanos,
jint pointerCount, jobjectArray pointerPropertiesObjArray,
@@ -373,7 +373,8 @@
}
event->initialize(deviceId, source, displayId, action, 0, flags, edgeFlags, metaState,
- buttonState, xOffset, yOffset, xPrecision, yPrecision,
+ buttonState, static_cast<MotionClassification>(classification),
+ xOffset, yOffset, xPrecision, yPrecision,
downTimeNanos, eventTimeNanos, pointerCount, pointerProperties, rawPointerCoords);
return reinterpret_cast<jlong>(event);
@@ -668,6 +669,11 @@
event->setButtonState(buttonState);
}
+static jint android_view_MotionEvent_nativeGetClassification(jlong nativePtr) {
+ MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+ return static_cast<jint>(event->getClassification());
+}
+
static void android_view_MotionEvent_nativeOffsetLocation(jlong nativePtr, jfloat deltaX,
jfloat deltaY) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
@@ -747,7 +753,7 @@
static const JNINativeMethod gMotionEventMethods[] = {
/* name, signature, funcPtr */
{ "nativeInitialize",
- "(JIIIIIIIIFFFFJJI[Landroid/view/MotionEvent$PointerProperties;"
+ "(JIIIIIIIIIFFFFJJI[Landroid/view/MotionEvent$PointerProperties;"
"[Landroid/view/MotionEvent$PointerCoords;)J",
(void*)android_view_MotionEvent_nativeInitialize },
{ "nativeDispose",
@@ -846,6 +852,9 @@
{ "nativeSetButtonState",
"(JI)V",
(void*)android_view_MotionEvent_nativeSetButtonState },
+ { "nativeGetClassification",
+ "(J)I",
+ (void*)android_view_MotionEvent_nativeGetClassification },
{ "nativeOffsetLocation",
"(JFF)V",
(void*)android_view_MotionEvent_nativeOffsetLocation },
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 6576587..9ce6df1 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -438,8 +438,9 @@
static jboolean nativeSetDisplayedContentSamplingEnabled(JNIEnv* env, jclass clazz,
jobject tokenObj, jboolean enable, jint componentMask, jint maxFrames) {
sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
- return SurfaceComposerClient::setDisplayContentSamplingEnabled(
+ status_t rc = SurfaceComposerClient::setDisplayContentSamplingEnabled(
token, enable, componentMask, maxFrames);
+ return rc == OK;
}
static jobject nativeGetDisplayedContentSample(JNIEnv* env, jclass clazz, jobject tokenObj,
@@ -869,14 +870,6 @@
transaction->setOverrideScalingMode(ctrl, scalingMode);
}
-static void nativeDestroyInTransaction(JNIEnv* env, jclass clazz,
- jlong transactionObj,
- jlong nativeObject) {
- auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
- auto ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
- transaction->destroySurface(ctrl);
-}
-
static jobject nativeGetHandle(JNIEnv* env, jclass clazz, jlong nativeObject) {
auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
return javaObjectForIBinder(env, ctrl->getHandle());
@@ -916,6 +909,17 @@
return reinterpret_cast<jlong>(surface.get());
}
+static jlong nativeCopyFromSurfaceControl(JNIEnv* env, jclass clazz, jlong surfaceControlNativeObj) {
+ sp<SurfaceControl> surface(reinterpret_cast<SurfaceControl *>(surfaceControlNativeObj));
+ if (surface == nullptr) {
+ return 0;
+ }
+
+ sp<SurfaceControl> newSurface = new SurfaceControl(surface);
+ newSurface->incStrong((void *)nativeCreate);
+ return reinterpret_cast<jlong>(newSurface.get());
+}
+
static void nativeWriteToParcel(JNIEnv* env, jclass clazz,
jlong nativeObject, jobject parcelObj) {
Parcel* parcel = parcelForJavaObject(env, parcelObj);
@@ -924,7 +928,9 @@
return;
}
SurfaceControl* const self = reinterpret_cast<SurfaceControl *>(nativeObject);
- self->writeToParcel(parcel);
+ if (self != nullptr) {
+ self->writeToParcel(parcel);
+ }
}
// ----------------------------------------------------------------------------
@@ -934,6 +940,8 @@
(void*)nativeCreate },
{"nativeReadFromParcel", "(Landroid/os/Parcel;)J",
(void*)nativeReadFromParcel },
+ {"nativeCopyFromSurfaceControl", "(J)J" ,
+ (void*)nativeCopyFromSurfaceControl },
{"nativeWriteToParcel", "(JLandroid/os/Parcel;)V",
(void*)nativeWriteToParcel },
{"nativeRelease", "(J)V",
@@ -1032,8 +1040,6 @@
(void*)nativeSeverChildren } ,
{"nativeSetOverrideScalingMode", "(JJI)V",
(void*)nativeSetOverrideScalingMode },
- {"nativeDestroy", "(JJ)V",
- (void*)nativeDestroyInTransaction },
{"nativeGetHandle", "(J)Landroid/os/IBinder;",
(void*)nativeGetHandle },
{"nativeScreenshot", "(Landroid/os/IBinder;Landroid/graphics/Rect;IIZI)Landroid/graphics/GraphicBuffer;",
diff --git a/core/jni/android_view_SurfaceSession.cpp b/core/jni/android_view_SurfaceSession.cpp
index 30c0030..191f748 100644
--- a/core/jni/android_view_SurfaceSession.cpp
+++ b/core/jni/android_view_SurfaceSession.cpp
@@ -46,13 +46,6 @@
return reinterpret_cast<jlong>(client);
}
-static jlong nativeCreateScoped(JNIEnv* env, jclass clazz, jlong surfaceObject) {
- Surface *parent = reinterpret_cast<Surface*>(surfaceObject);
- SurfaceComposerClient* client = new SurfaceComposerClient(parent->getIGraphicBufferProducer());
- client->incStrong((void*)nativeCreate);
- return reinterpret_cast<jlong>(client);
-}
-
static void nativeDestroy(JNIEnv* env, jclass clazz, jlong ptr) {
SurfaceComposerClient* client = reinterpret_cast<SurfaceComposerClient*>(ptr);
client->decStrong((void*)nativeCreate);
@@ -67,8 +60,6 @@
/* name, signature, funcPtr */
{ "nativeCreate", "()J",
(void*)nativeCreate },
- { "nativeCreateScoped", "(J)J",
- (void*)nativeCreateScoped },
{ "nativeDestroy", "(J)V",
(void*)nativeDestroy },
{ "nativeKill", "(J)V",
diff --git a/core/jni/com_android_internal_os_AtomicDirectory.cpp b/core/jni/com_android_internal_os_AtomicDirectory.cpp
new file mode 100644
index 0000000..50b2288
--- /dev/null
+++ b/core/jni/com_android_internal_os_AtomicDirectory.cpp
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+#include <nativehelper/ScopedUtfChars.h>
+#include "jni.h"
+
+#include "core_jni_helpers.h"
+
+namespace android {
+
+static jint com_android_internal_os_AtomicDirectory_getDirectoryFd(JNIEnv* env,
+ jobject /*clazz*/, jstring path) {
+ ScopedUtfChars path8(env, path);
+ if (path8.c_str() == NULL) {
+ ALOGE("Invalid path: %s", path8.c_str());
+ return -1;
+ }
+ int fd;
+ if ((fd = TEMP_FAILURE_RETRY(open(path8.c_str(), O_DIRECTORY | O_RDONLY))) == -1) {
+ ALOGE("Cannot open directory %s, error: %s\n", path8.c_str(), strerror(errno));
+ return -1;
+ }
+ return fd;
+}
+
+static void com_android_internal_os_AtomicDirectory_fsyncDirectoryFd(JNIEnv* env,
+ jobject /*clazz*/, jint fd) {
+ if (TEMP_FAILURE_RETRY(fsync(fd)) == -1) {
+ ALOGE("Cannot fsync directory %d, error: %s\n", fd, strerror(errno));
+ }
+}
+
+/*
+ * JNI registration.
+ */
+static const JNINativeMethod gRegisterMethods[] = {
+ /* name, signature, funcPtr */
+ { "fsyncDirectoryFd",
+ "(I)V",
+ (void*) com_android_internal_os_AtomicDirectory_fsyncDirectoryFd
+ },
+ { "getDirectoryFd",
+ "(Ljava/lang/String;)I",
+ (void*) com_android_internal_os_AtomicDirectory_getDirectoryFd
+ },
+};
+
+int register_com_android_internal_os_AtomicDirectory(JNIEnv* env) {
+ return RegisterMethodsOrDie(env, "com/android/internal/os/AtomicDirectory",
+ gRegisterMethods, NELEM(gRegisterMethods));
+}
+
+}; // namespace android
diff --git a/core/proto/android/app/window_configuration.proto b/core/proto/android/app/window_configuration.proto
index 2d15552..6cc1a40 100644
--- a/core/proto/android/app/window_configuration.proto
+++ b/core/proto/android/app/window_configuration.proto
@@ -29,4 +29,5 @@
optional .android.graphics.RectProto app_bounds = 1;
optional int32 windowing_mode = 2;
optional int32 activity_type = 3;
+ optional .android.graphics.RectProto bounds = 4;
}
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index a914369..cc5aa20 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -436,11 +436,12 @@
// Ordered GPU debug layer list for GLES
// i.e. <layer1>:<layer2>:...:<layerN>
optional SettingProto debug_layers_gles = 7;
- // Apps opt in to load graphics driver from Game Update Package
- // instead of native graphcis driver through developer options.
+ // GUP - List of Apps selected to use Game Update Packages
optional SettingProto gup_dev_opt_in_apps = 8;
- // Apps on the black list that are forbidden to useGame Update Package.
- optional SettingProto gup_black_list = 9;
+ // GUP - List of Apps selected not to use Game Update Packages
+ optional SettingProto gup_dev_opt_out_apps = 9;
+ // GUP - List of Apps that are forbidden to use Game Update Packages
+ optional SettingProto gup_black_list = 10;
}
optional Gpu gpu = 59;
@@ -558,6 +559,8 @@
// ringer mode.
optional SettingProto mode_ringer = 75 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto apply_ramping_ringer = 147 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
message MultiSim {
option (android.msg_privacy).dest = DEST_EXPLICIT;
@@ -1015,7 +1018,9 @@
optional SettingProto zram_enabled = 139 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto app_ops_constants = 148 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 147;
+ // Next tag = 149;
}
diff --git a/core/proto/android/stats/docsui/docsui_enums.proto b/core/proto/android/stats/docsui/docsui_enums.proto
index 6cb606a..655b5e3 100644
--- a/core/proto/android/stats/docsui/docsui_enums.proto
+++ b/core/proto/android/stats/docsui/docsui_enums.proto
@@ -33,13 +33,14 @@
MIME_UNKNOWN = 0;
MIME_NONE = 1;
MIME_ANY = 2;
- MIME_AUDIO = 3;
- MIME_IMAGE = 4;
- MIME_MESSAGE = 5;
- MIME_MULTIPART = 6;
- MIME_TEXT = 7;
- MIME_VIDEO = 8;
- MIME_OTHER = 9;
+ MIME_APPLICATION = 3;
+ MIME_AUDIO = 4;
+ MIME_IMAGE = 5;
+ MIME_MESSAGE = 6;
+ MIME_MULTIPART = 7;
+ MIME_TEXT = 8;
+ MIME_VIDEO = 9;
+ MIME_OTHER = 10;
}
enum Root {
@@ -163,6 +164,8 @@
ACTION_EXTRACT_TO = 29;
ACTION_VIEW_IN_APPLICATION = 30;
ACTION_INSPECTOR = 31;
+ ACTION_SEARCH_CHIP = 32;
+ ACTION_SEARCH_HISTORY = 33;
}
enum InvalidScopedAccess {
@@ -172,3 +175,20 @@
SCOPED_DIR_ACCESS_ERROR = 3;
SCOPED_DIR_ACCESS_DEPRECATED = 4;
}
+
+enum SearchType {
+ TYPE_UNKNOWN = 0;
+ TYPE_CHIP_IMAGES = 1;
+ TYPE_CHIP_AUDIOS = 2;
+ TYPE_CHIP_VIDEOS = 3;
+ TYPE_CHIP_DOCS = 4;
+ TYPE_SEARCH_HISTORY = 5;
+ TYPE_SEARCH_STRING = 6;
+}
+
+enum SearchMode {
+ SEARCH_UNKNOWN = 0;
+ SEARCH_KEYWORD = 1;
+ SEARCH_CHIPS = 2;
+ SEARCH_KEYWORD_N_CHIPS = 3;
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 0778304..8b85a28 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2256,7 +2256,7 @@
<!-- @SystemApi @TestApi @hide Allows an application to create/manage/remove stacks -->
<permission android:name="android.permission.MANAGE_ACTIVITY_STACKS"
- android:protectionLevel="signature|privileged" />
+ android:protectionLevel="signature" />
<!-- @SystemApi @TestApi @hide Allows an application to embed other activities -->
<permission android:name="android.permission.ACTIVITY_EMBEDDING"
@@ -4254,6 +4254,16 @@
<permission android:name="android.permission.MANAGE_CONTENT_CAPTURE"
android:protectionLevel="signature" />
+ <!-- @SystemApi Allows an application to manage the content suggestions service.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.MANAGE_CONTENT_SUGGESTIONS"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows an application to manage the app predictions service.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.MANAGE_APP_PREDICTIONS"
+ android:protectionLevel="signature" />
+
<!-- Allows an app to set the theme overlay in /vendor/overlay
being used.
@hide <p>Not for use by third-party applications.</p> -->
@@ -4334,6 +4344,11 @@
<permission android:name="android.permission.BIND_SMS_APP_SERVICE"
android:protectionLevel="signature" />
+ <!-- @hide Permission that allows configuring appops.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.MANAGE_APPOPS"
+ android:protectionLevel="signature" />
+
<!-- @hide Permission that allows background clipboard access.
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND"
diff --git a/core/res/res/drawable/ic_account_circle.xml b/core/res/res/drawable/ic_account_circle.xml
index f7317db..71691ad 100644
--- a/core/res/res/drawable/ic_account_circle.xml
+++ b/core/res/res/drawable/ic_account_circle.xml
@@ -14,18 +14,14 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="48.0dp"
- android:height="48.0dp"
- android:viewportWidth="20.0"
- android:viewportHeight="20.0">
- <group
- android:translateX="-2"
- android:translateY="-2" >
- <path
- android:pathData="M12,2C6.48,2 2,6.48 2,12c0,5.52 4.48,10 10,10c5.52,0 10,-4.48 10,-10C22,6.48 17.52,2 12,2zM18.36,16.83c-1.43,-1.74 -4.9,-2.33 -6.36,-2.33s-4.93,0.59 -6.36,2.33C4.62,15.49 4,13.82 4,12c0,-4.41 3.59,-8 8,-8c4.41,0 8,3.59 8,8C20,13.82 19.38,15.49 18.36,16.83z"
- android:fillColor="#FFFFFFFF" />
- <path
- android:pathData="M12,6c-1.94,0 -3.5,1.56 -3.5,3.5S10.06,13 12,13c1.94,0 3.5,-1.56 3.5,-3.5S13.94,6 12,6z"
- android:fillColor="#FFFFFFFF" />
- </group>
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M12,2C6.48,2 2,6.48 2,12c0,5.52 4.48,10 10,10c5.52,0 10,-4.48 10,-10C22,6.48 17.52,2 12,2zM18.36,16.83c-1.43,-1.74 -4.9,-2.33 -6.36,-2.33s-4.93,0.59 -6.36,2.33C4.62,15.49 4,13.82 4,12c0,-4.41 3.59,-8 8,-8c4.41,0 8,3.59 8,8C20,13.82 19.38,15.49 18.36,16.83z"/>
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M12,6c-1.94,0 -3.5,1.56 -3.5,3.5S10.06,13 12,13c1.94,0 3.5,-1.56 3.5,-3.5S13.94,6 12,6z"/>
</vector>
diff --git a/core/res/res/drawable/ic_battery.xml b/core/res/res/drawable/ic_battery.xml
new file mode 100644
index 0000000..bd40f4d
--- /dev/null
+++ b/core/res/res/drawable/ic_battery.xml
@@ -0,0 +1,25 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33v15.33C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V5.33C17,4.6 16.4,4 15.67,4z"/>
+</vector>
diff --git a/core/res/res/drawable/ic_corp_badge.xml b/core/res/res/drawable/ic_corp_badge.xml
index 1dad977..5ab5045 100644
--- a/core/res/res/drawable/ic_corp_badge.xml
+++ b/core/res/res/drawable/ic_corp_badge.xml
@@ -15,22 +15,11 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="20dp"
- android:height="20dp"
- android:viewportWidth="48"
- android:viewportHeight="48">
-
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
<path
- android:fillType="evenOdd"
- android:strokeColor="#1A73E8"
- android:strokeWidth="4"
- android:pathData="M 24 2 C 36.1502644963 2 46 11.8497355037 46 24 C 46 36.1502644963 36.1502644963 46 24 46 C 11.8497355037 46 2 36.1502644963 2 24 C 2 11.8497355037 11.8497355037 2 24 2 Z" />
- <group
- android:translateX="10.400000"
- android:translateY="10.400000">
- <path
- android:fillColor="#1A73E8"
- android:strokeWidth="1"
- android:pathData="M24.2971429,5.38947368 L18.9485714,5.38947368 L18.9485714,2.80902256 C18.9485714,1.37687218 17.7585143,0.228571429 16.2742857,0.228571429 L10.9257143,0.228571429 C9.44148571,0.228571429 8.25142857,1.37687218 8.25142857,2.80902256 L8.25142857,5.38947368 L2.90285714,5.38947368 C1.41862857,5.38947368 0.241942857,6.53777444 0.241942857,7.96992481 L0.228571429,22.162406 C0.228571429,23.5945564 1.41862857,24.7428571 2.90285714,24.7428571 L24.2971429,24.7428571 C25.7813714,24.7428571 26.9714286,23.5945564 26.9714286,22.162406 L26.9714286,7.96992481 C26.9714286,6.53777444 25.7813714,5.38947368 24.2971429,5.38947368 Z M13.6,17.0015038 C12.1291429,17.0015038 10.9257143,15.8403008 10.9257143,14.4210526 C10.9257143,13.0018045 12.1291429,11.8406015 13.6,11.8406015 C15.0708571,11.8406015 16.2742857,13.0018045 16.2742857,14.4210526 C16.2742857,15.8403008 15.0708571,17.0015038 13.6,17.0015038 Z M16.2742857,5.38947368 L10.9257143,5.38947368 L10.9257143,2.80902256 L16.2742857,2.80902256 L16.2742857,5.38947368 Z" />
- </group>
+ android:fillColor="@*android:color/accent_device_default_light"
+ android:pathData="M20,6h-4V4c0,-1.11 -0.89,-2 -2,-2h-4C8.89,2 8,2.89 8,4v2H4C2.89,6 2.01,6.89 2.01,8L2,19c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2V8C22,6.89 21.11,6 20,6zM10,4h4v2h-4V4zM20,19H4V8h16V19z"/>
</vector>
\ No newline at end of file
diff --git a/core/res/res/layout/time_picker_header_material.xml b/core/res/res/layout/time_picker_header_material.xml
index adb2b62..30c10b1 100644
--- a/core/res/res/layout/time_picker_header_material.xml
+++ b/core/res/res/layout/time_picker_header_material.xml
@@ -76,16 +76,14 @@
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/minutes"
android:layout_alignBaseline="@+id/minutes"
- android:paddingStart="4dp"
- android:paddingEnd="4dp"
+ android:layout_marginStart="8dp"
+ android:layout_marginEnd="0dp"
android:orientation="vertical"
android:baselineAlignedChildIndex="1">
<RadioButton
android:id="@+id/am_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:paddingLeft="4dp"
- android:paddingRight="4dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:layout_marginBottom="-8dp"
@@ -101,8 +99,6 @@
android:id="@+id/pm_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:paddingLeft="4dp"
- android:paddingRight="4dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:textAppearance="@style/TextAppearance.Material.TimePicker.AmPmLabel"
diff --git a/core/res/res/values-land/dimens_permission_controller.xml b/core/res/res/values-land/dimens_permission_controller.xml
deleted file mode 100644
index 2146241..0000000
--- a/core/res/res/values-land/dimens_permission_controller.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- 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.
- -->
-
-<!-- Landscape dimensions for the permission grant dialog. -->
-<resources>
- <!-- Assuming the dimension of a sailfish, this yields 95% width in splitscreen and 65% in
- landscape -->
- <dimen name="permissionGrantDialogWeight">8.6</dimen>
- <dimen name="permissionGrantDialogWidth">334dp</dimen>
-</resources>
diff --git a/core/res/res/values-night/themes_permission_controller.xml b/core/res/res/values-night/themes_permission_controller.xml
deleted file mode 100644
index a071927..0000000
--- a/core/res/res/values-night/themes_permission_controller.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- 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.
- -->
-
-<!-- themes for the permission grant dialog. -->
-<resources>
- <style name="Theme.DeviceDefault.PermissionGrantApp"
- parent="@style/Theme.DeviceDefault.Panel">
- <item name="windowIsFloating">false</item>
- <item name="windowTranslucentStatus">true</item>
- <item name="backgroundDimEnabled">true</item>
- <item name="windowAnimationStyle">@style/Animation.Material.Dialog</item>
- </style>
-
- <style name="Theme.DeviceDefault.PermissionGrant"
- parent="@style/Theme.DeviceDefault.Dialog">
- <item name="titleTextStyle">@style/PermissionGrantTitleMessage</item>
- </style>
-</resources>
diff --git a/core/res/res/values-port/dimens_permission_controller.xml b/core/res/res/values-port/dimens_permission_controller.xml
deleted file mode 100644
index af28713..0000000
--- a/core/res/res/values-port/dimens_permission_controller.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- 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.
- -->
-
-<!-- portrait dimensions for the permission grant dialog. -->
-<resources>
- <!-- This yields 95% width -->
- <dimen name="permissionGrantDialogWeight">380</dimen>
- <dimen name="permissionGrantDialogWidth">0dp</dimen>
-</resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 183c2e8..fa3a549 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3660,7 +3660,12 @@
the settings for this service. This setting cannot be changed at runtime. -->
<attr name="settingsActivity" />
<!-- Attribute whether the accessibility service wants to be able to retrieve the
- active window content. This setting cannot be changed at runtime. -->
+ active window content. This setting cannot be changed at runtime.
+ <p>
+ Required to allow setting the {@link android.accessibilityservice
+ #AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS} flag.
+ </p>
+ -->
<attr name="canRetrieveWindowContent" format="boolean" />
<!-- Attribute whether the accessibility service wants to be able to request touch
exploration mode in which touched items are spoken aloud and the UI can be
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index e4abf8f..1feb59a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1152,6 +1152,10 @@
Settings.System.NOTIFICATION_VIBRATION_INTENSITY more details on the constant values and
meanings. -->
<integer name="config_defaultNotificationVibrationIntensity">2</integer>
+ <!-- The default intensity level for ring vibrations. See
+ Settings.System.RING_VIBRATION_INTENSITY more details on the constant values and
+ meanings. -->
+ <integer name="config_defaultRingVibrationIntensity">2</integer>
<bool name="config_use_strict_phone_number_comparation">false</bool>
@@ -1169,7 +1173,7 @@
<integer name="config_lowBatteryAutoTriggerDefaultLevel">15</integer>
<!-- The app which will handle routine based automatic battery saver, if empty the UI for
- routine based battery saver will be hidden -->
+ routine based battery saver will be hidden -->
<string name="config_batterySaverScheduleProvider"></string>
<!-- Close low battery warning when battery level reaches the lowBatteryWarningLevel
@@ -1404,6 +1408,13 @@
<integer-array name="config_autoBrightnessLevels">
</integer-array>
+ <!-- Timeout (in milliseconds) after which we remove the effects any user interactions might've
+ had on the brightness mapping. This timeout doesn't start until we transition to a
+ non-interactive display policy so that we don't reset while users are using their devices,
+ but also so that we don't erroneously keep the short-term model if the device is dozing
+ but the display is fully on. -->
+ <integer name="config_autoBrightnessShortTermModelTimeout">300000</integer>
+
<!-- Array of output values for LCD backlight corresponding to the lux values
in the config_autoBrightnessLevels array. This array should have size one greater
than the size of the config_autoBrightnessLevels array.
@@ -3412,6 +3423,23 @@
-->
<string name="config_defaultAugmentedAutofillService" translatable="false"></string>
+ <!-- The package name for the system's app prediction service.
+ This service must be trusted, as it can be activated without explicit consent of the user.
+ Example: "com.android.intelligence/.AppPredictionService"
+ -->
+ <string name="config_defaultAppPredictionService" translatable="false"></string>
+
+ <!-- The package name for the system's content suggestions service.
+ Provides suggestions for text and image selection regions in snapshots of apps and should
+ be able to classify the type of entities in those selections.
+
+ This service must be trusted, as it can be activated without explicit consent of the user.
+ If no service with the specified name exists on the device, content suggestions wil be
+ disabled.
+ Example: "com.android.contentsuggestions/.ContentSuggestionsService"
+ -->
+ <string name="config_defaultContentSuggestionsService" translatable="false"></string>
+
<!-- Whether the device uses the default focus highlight when focus state isn't specified. -->
<bool name="config_useDefaultFocusHighlight">true</bool>
@@ -3641,4 +3669,8 @@
set in AndroidManifest.
{@see android.view.Display#FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS} -->
<string name="config_secondaryHomeComponent" translatable="false">com.android.launcher3/com.android.launcher3.SecondaryDisplayLauncher</string>
+
+ <!-- If device supports corner radius on windows.
+ This should be turned off on low-end devices to improve animation performance. -->
+ <bool name="config_supportsRoundedCornersOnWindows">true</bool>
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index a7bc57a..0878562 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5238,6 +5238,15 @@
<!-- Content description of the overlay icon in the notification. [CHAR LIMIT=NONE] -->
<string name="notification_appops_overlay_active">displaying over other apps on your screen</string>
+ <!-- Dynamic mode battery saver strings -->
+ <!-- The user visible name of the notification channel for the routine mode battery saver fyi notification [CHAR_LIMIT=80]-->
+ <string name="dynamic_mode_notification_channel_name">Routine Mode info notification</string>
+ <!-- Title of notification letting users know why battery saver was turned on automatically [CHAR_LIMIT=NONE]-->
+ <string name="dynamic_mode_notification_title">Battery may run out before usual charge</string>
+ <!-- Summary of notification letting users know why battery saver was turned on automatically [CHAR_LIMIT=NONE]-->
+ <string name="dynamic_mode_notification_summary">Battery Saver activated to extend battery life</string>
+
+
<!-- Strings for car -->
<!-- String displayed when loading a user in the car [CHAR LIMIT=30] -->
<string name="car_loading_profile">Loading</string>
diff --git a/core/res/res/values/styles_permission_controller.xml b/core/res/res/values/styles_permission_controller.xml
deleted file mode 100644
index 5a9d3e6..0000000
--- a/core/res/res/values/styles_permission_controller.xml
+++ /dev/null
@@ -1,98 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- 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.
- -->
-
-<resources>
- <!-- styles for the permission grant dialog. -->
- <style name="PermissionGrantDialog">
- <item name="background">?attr/windowBackground</item>
- <item name="elevation">?attr/windowElevation</item>
- <item name="layout_weight">@dimen/permissionGrantDialogWeight</item>
- <item name="layout_width">@dimen/permissionGrantDialogWidth</item>
- </style>
-
- <style name="PermissionGrantTitleIcon">
- <item name="layout_width">24dp</item>
- <item name="layout_height">24dp</item>
- <item name="layout_marginBottom">12dp</item>
- <item name="tint">?attr/colorAccent</item>
- <item name="scaleType">fitCenter</item>
- </style>
-
- <style name="PermissionGrantTitleMessage"
- parent="@style/TextAppearance.DeviceDefault">
- <item name="gravity">center</item>
- <item name="textSize">20sp</item>
- <item name="textColor">?attr/textColorPrimary</item>
- </style>
-
- <style name="PermissionGrantIndex"
- parent="@style/TextAppearance.DeviceDefault">
- <item name="paddingEnd">12dp</item>
- <item name="singleLine">true</item>
- <item name="textColor">?attr/textColorSecondary</item>
- </style>
-
- <style name="PermissionGrantDescription">
- <item name="layout_marginStart">24dp</item>
- <item name="layout_marginEnd">24dp</item>
- </style>
-
- <style name="PermissionGrantContent">
- <item name="layout_marginStart">24dp</item>
- <item name="layout_marginEnd">24dp</item>
- </style>
-
- <style name="PermissionGrantDetailMessage"
- parent="@style/TextAppearance.DeviceDefault">
- <item name="layout_marginTop">18dp</item>
- <item name="textColor">?attr/textColorPrimary</item>
- <item name="textSize">16sp</item>
- </style>
-
- <!-- styles for the permission review screen. -->
- <style name="PermissionReviewDescription">
- <item name="layout_marginTop">20dp</item>
- <item name="layout_marginStart">24dp</item>
- <item name="layout_marginBottom">16dp</item>
- <item name="layout_marginEnd">24dp</item>
- </style>
-
- <style name="PermissionReviewTitleIcon">
- <item name="layout_marginTop">4dp</item>
- <item name="layout_width">36dp</item>
- <item name="layout_height">36dp</item>
- <item name="scaleType">fitCenter</item>
- </style>
-
- <style name="PermissionReviewTitleMessage"
- parent="@style/TextAppearance.DeviceDefault">
- <item name="paddingStart">22dp</item>
- <item name="textSize">20sp</item>
- <item name="textColor">?attr/textColorPrimary</item>
- </style>
-
- <style name="PermissionReviewSettings">
- <item name="layout_marginStart">8dp</item>
- <item name="layout_marginEnd">8dp</item>
- </style>
-
- <style name="PermissionReviewButtonBar">
- <item name="layout_marginStart">24dp</item>
- <item name="layout_marginEnd">16dp</item>
- <item name="layout_marginBottom">4dp</item>
- </style>
-</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index e8cbf66..010accf 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1964,6 +1964,7 @@
<java-symbol type="integer" name="config_screenBrightnessDark" />
<java-symbol type="integer" name="config_screenBrightnessDim" />
<java-symbol type="integer" name="config_screenBrightnessDoze" />
+ <java-symbol type="integer" name="config_autoBrightnessShortTermModelTimeout" />
<java-symbol type="integer" name="config_shutdownBatteryTemperature" />
<java-symbol type="integer" name="config_undockedHdmiRotation" />
<java-symbol type="integer" name="config_virtualKeyQuietTimeMillis" />
@@ -3274,6 +3275,8 @@
<java-symbol type="string" name="config_defaultWellbeingPackage" />
<java-symbol type="string" name="config_defaultContentCaptureService" />
<java-symbol type="string" name="config_defaultAugmentedAutofillService" />
+ <java-symbol type="string" name="config_defaultAppPredictionService" />
+ <java-symbol type="string" name="config_defaultContentSuggestionsService" />
<java-symbol type="string" name="notification_channel_foreground_service" />
<java-symbol type="string" name="foreground_service_app_in_background" />
@@ -3488,6 +3491,7 @@
<java-symbol type="integer" name="config_defaultHapticFeedbackIntensity" />
<java-symbol type="integer" name="config_defaultNotificationVibrationIntensity" />
+ <java-symbol type="integer" name="config_defaultRingVibrationIntensity" />
<java-symbol type="bool" name="config_maskMainBuiltInDisplayCutout" />
@@ -3514,9 +3518,15 @@
<java-symbol type="dimen" name="rounded_corner_radius" />
<java-symbol type="dimen" name="rounded_corner_radius_top" />
<java-symbol type="dimen" name="rounded_corner_radius_bottom" />
+ <java-symbol type="bool" name="config_supportsRoundedCornersOnWindows" />
<java-symbol type="string" name="config_defaultModuleMetadataProvider" />
<!-- For Secondary Launcher -->
<java-symbol type="string" name="config_secondaryHomeComponent" />
+
+ <java-symbol type="string" name="dynamic_mode_notification_channel_name" />
+ <java-symbol type="string" name="dynamic_mode_notification_title" />
+ <java-symbol type="string" name="dynamic_mode_notification_summary" />
+ <java-symbol type="drawable" name="ic_battery" />
</resources>
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index 0f4ca66..d60313a 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -1454,12 +1454,17 @@
<item name="panelMenuListTheme">@style/Theme.Material.Settings.CompactMenu</item>
<!-- action bar -->
+ <item name="actionBarStyle">@style/Widget.DeviceDefault.Light.ActionBar.Solid</item>
<item name="actionBarTheme">@style/ThemeOverlay.DeviceDefault.ActionBar</item>
<item name="popupTheme">@style/ThemeOverlay.DeviceDefault.Popup.Light</item>
<!-- Color palette -->
+ <item name="colorBackground">@color/background_device_default_light</item>
+ <item name="colorPrimary">@color/primary_device_default_settings_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_settings_light</item>
<item name="colorSecondary">@color/secondary_device_default_settings_light</item>
+ <item name="colorAccent">@color/accent_device_default_light</item>
+ <item name="colorError">@color/error_color_device_default_light</item>
<item name="colorEdgeEffect">@android:color/black</item>
<!-- Add white nav bar with divider that matches material -->
@@ -1467,9 +1472,16 @@
<item name="navigationBarColor">@android:color/white</item>
<item name="windowLightNavigationBar">true</item>
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+
<!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
<item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
+ <!-- Progress bar attributes -->
+ <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
+
<item name="listDivider">@color/list_divider_color_light</item>
</style>
diff --git a/core/res/res/values/themes_permission_controller.xml b/core/res/res/values/themes_permission_controller.xml
deleted file mode 100644
index 205c4eb..0000000
--- a/core/res/res/values/themes_permission_controller.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- 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.
- -->
-
-<resources>
- <!-- themes for the permission grant dialog. -->
- <style name="Theme.DeviceDefault.PermissionGrantApp"
- parent="@style/Theme.DeviceDefault.Light.Panel">
- <item name="windowIsFloating">false</item>
- <item name="windowTranslucentStatus">true</item>
- <item name="backgroundDimEnabled">true</item>
- <item name="windowAnimationStyle">@style/Animation.Material.Dialog</item>
- </style>
-
- <style name="Theme.DeviceDefault.PermissionGrant"
- parent="@style/Theme.DeviceDefault.Light.Dialog">
- <item name="titleTextStyle">@style/PermissionGrantTitleMessage</item>
- </style>
-
- <!-- themes for the permission review dialog. -->
- <style name="Theme.DeviceDefault.PermissionReviewApp"
- parent="@style/Theme.DeviceDefault.Settings">
- <item name="windowActionBar">false</item>
- <item name="windowNoTitle">true</item>
- <item name="titleTextStyle">@style/PermissionReviewTitleMessage</item>
- </style>
-</resources>
diff --git a/core/tests/coretests/Android.mk b/core/tests/coretests/Android.mk
index 041fb7e..74943c7 100644
--- a/core/tests/coretests/Android.mk
+++ b/core/tests/coretests/Android.mk
@@ -43,7 +43,8 @@
platform-test-annotations \
compatibility-device-util \
truth-prebuilt \
- print-test-util-lib
+ print-test-util-lib \
+ testng # TODO: remove once Android migrates to JUnit 4.12, which provide assertThrows
LOCAL_JAVA_LIBRARIES := \
android.test.runner \
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index 1750dac..0f83a29 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -19,28 +19,35 @@
import static android.content.Intent.ACTION_EDIT;
import static android.content.Intent.ACTION_VIEW;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import android.app.Activity;
import android.app.ActivityThread;
import android.app.IApplicationThread;
+import android.app.servertransaction.ActivityConfigurationChangeItem;
import android.app.servertransaction.ActivityRelaunchItem;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.ClientTransactionItem;
import android.app.servertransaction.ResumeActivityItem;
import android.app.servertransaction.StopActivityItem;
import android.content.Intent;
+import android.content.res.Configuration;
import android.os.IBinder;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.MediumTest;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.util.MergedConfiguration;
+import android.view.Display;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.concurrent.CountDownLatch;
+
/**
* Test for verifying {@link android.app.ActivityThread} class.
* Build/Install/Run:
@@ -50,8 +57,12 @@
@MediumTest
public class ActivityThreadTest {
- private final ActivityTestRule mActivityTestRule =
- new ActivityTestRule(TestActivity.class, true /* initialTouchMode */,
+ // The first sequence number to try with. Use a large number to avoid conflicts with the first a
+ // few sequence numbers the framework used to launch the test activity.
+ private static final int BASE_SEQ = 10000;
+
+ private final ActivityTestRule<TestActivity> mActivityTestRule =
+ new ActivityTestRule<>(TestActivity.class, true /* initialTouchMode */,
false /* launchActivity */);
@Test
@@ -129,6 +140,179 @@
});
}
+ @Test
+ public void testHandleActivityConfigurationChanged() {
+ final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ final int numOfConfig = activity.mNumOfConfigChanges;
+ applyConfigurationChange(activity, BASE_SEQ);
+ assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges);
+ });
+ }
+
+ @Test
+ public void testHandleActivityConfigurationChanged_DropStaleConfigurations() {
+ final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ // Set the sequence number to BASE_SEQ.
+ applyConfigurationChange(activity, BASE_SEQ);
+
+ final int orientation = activity.mConfig.orientation;
+ final int numOfConfig = activity.mNumOfConfigChanges;
+
+ // Try to apply an old configuration change.
+ applyConfigurationChange(activity, BASE_SEQ - 1);
+ assertEquals(numOfConfig, activity.mNumOfConfigChanges);
+ assertEquals(orientation, activity.mConfig.orientation);
+ });
+ }
+
+ @Test
+ public void testHandleActivityConfigurationChanged_ApplyNewConfigurations() {
+ final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ // Set the sequence number to BASE_SEQ and record the final sequence number it used.
+ final int seq = applyConfigurationChange(activity, BASE_SEQ);
+
+ final int orientation = activity.mConfig.orientation;
+ final int numOfConfig = activity.mNumOfConfigChanges;
+
+ // Try to apply an new configuration change.
+ applyConfigurationChange(activity, seq + 1);
+ assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges);
+ assertNotEquals(orientation, activity.mConfig.orientation);
+ });
+ }
+
+ @Test
+ public void testHandleActivityConfigurationChanged_PickNewerPendingConfiguration() {
+ final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ // Set the sequence number to BASE_SEQ and record the final sequence number it used.
+ final int seq = applyConfigurationChange(activity, BASE_SEQ);
+
+ final int orientation = activity.mConfig.orientation;
+ final int numOfConfig = activity.mNumOfConfigChanges;
+
+ final ActivityThread activityThread = activity.getActivityThread();
+
+ final Configuration pendingConfig = new Configuration();
+ pendingConfig.orientation = orientation == Configuration.ORIENTATION_LANDSCAPE
+ ? Configuration.ORIENTATION_PORTRAIT
+ : Configuration.ORIENTATION_LANDSCAPE;
+ pendingConfig.seq = seq + 2;
+ activityThread.updatePendingActivityConfiguration(activity.getActivityToken(),
+ pendingConfig);
+
+ final Configuration newConfig = new Configuration();
+ newConfig.orientation = orientation;
+ newConfig.seq = seq + 1;
+
+ activityThread.handleActivityConfigurationChanged(activity.getActivityToken(),
+ newConfig, Display.INVALID_DISPLAY);
+ assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges);
+ assertEquals(pendingConfig.orientation, activity.mConfig.orientation);
+ });
+ }
+
+ @Test
+ public void testHandleActivityConfigurationChanged_OnlyAppliesNewestConfiguration()
+ throws Exception {
+ final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
+
+ final ActivityThread activityThread = activity.getActivityThread();
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ final Configuration config = new Configuration();
+ config.seq = BASE_SEQ;
+ config.orientation = Configuration.ORIENTATION_PORTRAIT;
+
+ activityThread.handleActivityConfigurationChanged(activity.getActivityToken(),
+ config, Display.INVALID_DISPLAY);
+ });
+
+ final int numOfConfig = activity.mNumOfConfigChanges;
+ final IApplicationThread appThread = activityThread.getApplicationThread();
+
+ activity.mConfigLatch = new CountDownLatch(1);
+ activity.mTestLatch = new CountDownLatch(1);
+
+ Configuration config = new Configuration();
+ config.seq = BASE_SEQ + 1;
+ config.smallestScreenWidthDp = 100;
+ appThread.scheduleTransaction(newActivityConfigTransaction(activity, config));
+
+ // Wait until the main thread is performing the configuration change for the configuration
+ // with sequence number BASE_SEQ + 1 before proceeding. This is to mimic the situation where
+ // the activity takes very long time to process configuration changes.
+ activity.mTestLatch.await();
+
+ config = new Configuration();
+ config.seq = BASE_SEQ + 2;
+ config.smallestScreenWidthDp = 200;
+ appThread.scheduleTransaction(newActivityConfigTransaction(activity, config));
+
+ config = new Configuration();
+ config.seq = BASE_SEQ + 3;
+ config.smallestScreenWidthDp = 300;
+ appThread.scheduleTransaction(newActivityConfigTransaction(activity, config));
+
+ config = new Configuration();
+ config.seq = BASE_SEQ + 4;
+ config.smallestScreenWidthDp = 400;
+ appThread.scheduleTransaction(newActivityConfigTransaction(activity, config));
+
+ activity.mConfigLatch.countDown();
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+ activity.mConfigLatch = null;
+ activity.mTestLatch = null;
+
+ // Only two more configuration changes: one with seq BASE_SEQ + 1; another with seq
+ // BASE_SEQ + 4. Configurations scheduled in between should be dropped.
+ assertEquals(numOfConfig + 2, activity.mNumOfConfigChanges);
+ assertEquals(400, activity.mConfig.smallestScreenWidthDp);
+ }
+
+ /**
+ * Calls {@link ActivityThread#handleActivityConfigurationChanged(IBinder, Configuration, int)}
+ * to try to push activity configuration to the activity for the given sequence number.
+ * <p>
+ * It uses orientation to push the configuration and it tries a different orientation if the
+ * first attempt doesn't make through, to rule out the possibility that the previous
+ * configuration already has the same orientation.
+ *
+ * @param activity the test target activity
+ * @param seq the specified sequence number
+ * @return the sequence number this method tried with the last time, so that the caller can use
+ * the next sequence number for next configuration update.
+ */
+ private int applyConfigurationChange(TestActivity activity, int seq) {
+ final ActivityThread activityThread = activity.getActivityThread();
+
+ final int numOfConfig = activity.mNumOfConfigChanges;
+ Configuration config = new Configuration();
+ config.orientation = Configuration.ORIENTATION_PORTRAIT;
+ config.seq = seq;
+ activityThread.handleActivityConfigurationChanged(activity.getActivityToken(), config,
+ Display.INVALID_DISPLAY);
+
+ if (activity.mNumOfConfigChanges > numOfConfig) {
+ return config.seq;
+ }
+
+ config = new Configuration();
+ config.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ config.seq = seq + 1;
+ activityThread.handleActivityConfigurationChanged(activity.getActivityToken(), config,
+ Display.INVALID_DISPLAY);
+
+ return config.seq;
+ }
+
private static ClientTransaction newRelaunchResumeTransaction(Activity activity) {
final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(null,
null, 0, new MergedConfiguration(), false /* preserveWindow */);
@@ -162,6 +346,16 @@
return transaction;
}
+ private static ClientTransaction newActivityConfigTransaction(Activity activity,
+ Configuration config) {
+ final ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem.obtain(config);
+
+ final ClientTransaction transaction = newTransaction(activity);
+ transaction.addCallback(item);
+
+ return transaction;
+ }
+
private static ClientTransaction newTransaction(Activity activity) {
final IApplicationThread appThread = activity.getActivityThread().getApplicationThread();
return ClientTransaction.obtain(appThread, activity.getActivityToken());
@@ -169,5 +363,37 @@
// Test activity
public static class TestActivity extends Activity {
+ int mNumOfConfigChanges;
+ final Configuration mConfig = new Configuration();
+
+ /**
+ * A latch used to notify tests that we're about to wait for configuration latch. This
+ * is used to notify test code that preExecute phase for activity configuration change
+ * transaction has passed.
+ */
+ volatile CountDownLatch mTestLatch;
+ /**
+ * If not {@code null} {@link #onConfigurationChanged(Configuration)} won't return until the
+ * latch reaches 0.
+ */
+ volatile CountDownLatch mConfigLatch;
+
+ @Override
+ public void onConfigurationChanged(Configuration config) {
+ super.onConfigurationChanged(config);
+ mConfig.setTo(config);
+ ++mNumOfConfigChanges;
+
+ if (mConfigLatch != null) {
+ if (mTestLatch != null) {
+ mTestLatch.countDown();
+ }
+ try {
+ mConfigLatch.await();
+ } catch (InterruptedException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ }
}
}
diff --git a/core/tests/coretests/src/android/os/BinderTest.java b/core/tests/coretests/src/android/os/BinderTest.java
index 534c5cd..6c9c3c1 100644
--- a/core/tests/coretests/src/android/os/BinderTest.java
+++ b/core/tests/coretests/src/android/os/BinderTest.java
@@ -43,4 +43,13 @@
Binder.restoreCallingWorkSource(token);
assertEquals(UID, Binder.getCallingWorkSourceUid());
}
+
+ @SmallTest
+ public void testGetCallingUidOrThrow() throws Exception {
+ try {
+ Binder.getCallingUidOrThrow();
+ throw new AssertionError("IllegalStateException expected");
+ } catch (IllegalStateException expected) {
+ }
+ }
}
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index ac57d20..4e405ca 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -27,6 +27,7 @@
import static java.lang.reflect.Modifier.isStatic;
import android.platform.test.annotations.Presubmit;
+import android.provider.Settings.Global;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -114,6 +115,7 @@
Settings.Global.ANOMALY_CONFIG_VERSION,
Settings.Global.APN_DB_UPDATE_CONTENT_URL,
Settings.Global.APN_DB_UPDATE_METADATA_URL,
+ Settings.Global.APPLY_RAMPING_RINGER,
Settings.Global.APP_BINDING_CONSTANTS,
Settings.Global.APP_IDLE_CONSTANTS,
Settings.Global.APP_OPS_CONSTANTS,
@@ -269,6 +271,7 @@
Settings.Global.GNSS_SATELLITE_BLACKLIST,
Settings.Global.GPRS_REGISTER_CHECK_PERIOD_MS,
Settings.Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
+ Settings.Global.HDMI_CONTROL_AUTO_TV_OFF_ENABLED,
Settings.Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED,
Settings.Global.HDMI_CONTROL_ENABLED,
Settings.Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED,
@@ -476,6 +479,7 @@
Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS,
Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES,
Settings.Global.GUP_DEV_OPT_IN_APPS,
+ Settings.Global.GUP_DEV_OPT_OUT_APPS,
Settings.Global.GUP_BLACK_LIST,
Settings.Global.GPU_DEBUG_LAYER_APP,
Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING,
@@ -547,7 +551,12 @@
Settings.Global.BACKUP_AGENT_TIMEOUT_PARAMETERS,
Settings.Global.BACKUP_MULTI_USER_ENABLED,
Settings.Global.ISOLATED_STORAGE_LOCAL,
- Settings.Global.ISOLATED_STORAGE_REMOTE);
+ Settings.Global.ISOLATED_STORAGE_REMOTE,
+ Settings.Global.APPOP_HISTORY_PARAMETERS,
+ Settings.Global.APPOP_HISTORY_MODE,
+ Settings.Global.APPOP_HISTORY_INTERVAL_MULTIPLIER,
+ Settings.Global.APPOP_HISTORY_BASE_INTERVAL_MILLIS);
+
private static final Set<String> BACKUP_BLACKLISTED_SECURE_SETTINGS =
newHashSet(
Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
diff --git a/core/tests/coretests/src/android/text/TextLineTest.java b/core/tests/coretests/src/android/text/TextLineTest.java
index 61f976a..2fe882c 100644
--- a/core/tests/coretests/src/android/text/TextLineTest.java
+++ b/core/tests/coretests/src/android/text/TextLineTest.java
@@ -254,6 +254,18 @@
}
@Test
+ public void testMeasure_wordSpacing() {
+ final TextPaint paint = new TextPaint();
+ paint.setTypeface(TYPEFACE);
+ paint.setTextSize(10.0f); // make 1em = 10px
+ paint.setWordSpacing(10.0f);
+
+ TextLine tl = getTextLine("I I", paint);
+ assertMeasurements(tl, 3, false,
+ new float[]{0.0f, 10.0f, 120.0f, 130.0f});
+ }
+
+ @Test
public void testHandleRun_ellipsizedReplacementSpan_isSkipped() {
final Spannable text = new SpannableStringBuilder("This is a... text");
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index 9807f26..95af525 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -29,12 +29,14 @@
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertNotEquals;
+import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.FlakyTest;
import android.support.test.runner.AndroidJUnit4;
import android.util.SparseIntArray;
+import android.view.WindowInsets.Type;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -56,9 +58,12 @@
SparseIntArray typeSideMap = new SparseIntArray();
WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false,
DisplayCutout.NO_CUTOUT, typeSideMap);
- assertEquals(new Rect(0, 100, 0, 100), insets.getSystemWindowInsets());
+ assertEquals(Insets.of(0, 100, 0, 100), insets.getSystemWindowInsets());
+ assertEquals(Insets.of(0, 100, 0, 100), insets.getInsets(Type.all()));
assertEquals(INSET_SIDE_TOP, typeSideMap.get(TYPE_TOP_BAR));
assertEquals(INSET_SIDE_BOTTOM, typeSideMap.get(TYPE_IME));
+ assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(Type.topBar()));
+ assertEquals(Insets.of(0, 0, 0, 100), insets.getInsets(Type.ime()));
}
@Test
@@ -70,7 +75,11 @@
WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false,
DisplayCutout.NO_CUTOUT, null);
assertEquals(100, insets.getStableInsetBottom());
- assertEquals(new Rect(0, 0, 0, 200), insets.getSystemWindowInsets());
+ assertEquals(Insets.of(0, 0, 0, 100), insets.getMaxInsets(Type.all()));
+ assertEquals(Insets.of(0, 0, 0, 200), insets.getSystemWindowInsets());
+ assertEquals(Insets.of(0, 0, 0, 200), insets.getInsets(Type.all()));
+ assertEquals(Insets.of(0, 0, 0, 100), insets.getInsets(Type.sideBars()));
+ assertEquals(Insets.of(0, 0, 0, 200), insets.getInsets(Type.ime()));
}
@Test
@@ -81,7 +90,9 @@
mState.getSource(TYPE_NAVIGATION_BAR).setVisible(true);
WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false,
DisplayCutout.NO_CUTOUT, null);
- assertEquals(new Rect(0, 100, 20, 0), insets.getSystemWindowInsets());
+ assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets());
+ assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(Type.topBar()));
+ assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(Type.sideBars()));
}
@Test
diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java
index 1c2df2c..1513a1a 100644
--- a/core/tests/coretests/src/android/view/WindowInsetsTest.java
+++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java
@@ -16,12 +16,19 @@
package android.view;
+import static android.view.WindowInsets.Type.ime;
+import static android.view.WindowInsets.Type.indexOf;
+import static android.view.WindowInsets.Type.sideBars;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import android.graphics.Insets;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import android.view.WindowInsets.Builder;
+import android.view.WindowInsets.Type;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -33,13 +40,13 @@
@Test
public void systemWindowInsets_afterConsuming_isConsumed() {
- assertTrue(new WindowInsets(new Rect(1, 2, 3, 4), null, null, false, false, null)
+ assertTrue(new WindowInsets(new Rect(1, 2, 3, 4), null, false, false, null)
.consumeSystemWindowInsets().isConsumed());
}
@Test
public void multiNullConstructor_isConsumed() {
- assertTrue(new WindowInsets(null, null, null, false, false, null).isConsumed());
+ assertTrue(new WindowInsets((Rect) null, null, false, false, null).isConsumed());
}
@Test
@@ -47,4 +54,12 @@
assertTrue(new WindowInsets((Rect) null).isConsumed());
}
+ @Test
+ public void typeMap() {
+ Builder b = new WindowInsets.Builder();
+ b.setInsets(sideBars(), Insets.of(0, 0, 0, 100));
+ b.setInsets(ime(), Insets.of(0, 0, 0, 300));
+ WindowInsets insets = b.build();
+ assertEquals(300, insets.getSystemWindowInsets().bottom);
+ }
}
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
new file mode 100644
index 0000000..59f3a4c
--- /dev/null
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.view.contentcapture;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.view.autofill.AutofillId;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
+
+/**
+ * Unit test for {@link ContentCaptureSessionTest}.
+ *
+ * <p>To run it:
+ * {@code atest FrameworksCoreTests:android.view.contentcapture.ContentCaptureSessionTest}
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class ContentCaptureSessionTest {
+
+ /**
+ * Uses a spy as ContentCaptureSession is abstract but (so far) we're testing its concrete
+ * methods.
+ */
+ @Spy
+ private ContentCaptureSession mMockSession;
+
+ @Test
+ public void testNewAutofillId_invalid() {
+ assertThrows(NullPointerException.class, () -> mMockSession.newAutofillId(null, 42));
+ assertThrows(IllegalArgumentException.class,
+ () -> mMockSession.newAutofillId(new AutofillId(42, 42), 42));
+ }
+
+ @Test
+ public void testNewAutofillId_valid() {
+ final AutofillId parentId = new AutofillId(42);
+ final AutofillId childId = mMockSession.newAutofillId(parentId, 108);
+ assertThat(childId.getViewId()).isEqualTo(42);
+ assertThat(childId.getVirtualChildId()).isEqualTo(108);
+ // TODO(b/121197119): assert session id
+ }
+
+ @Test
+ public void testNotifyXXX_null() {
+ assertThrows(NullPointerException.class, () -> mMockSession.notifyViewAppeared(null));
+ assertThrows(NullPointerException.class, () -> mMockSession.notifyViewDisappeared(null));
+ assertThrows(NullPointerException.class,
+ () -> mMockSession.notifyViewTextChanged(null, "whatever", 0));
+ }
+}
diff --git a/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java b/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java
new file mode 100644
index 0000000..995946b
--- /dev/null
+++ b/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java
@@ -0,0 +1,462 @@
+/*
+ * 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.view.contentcapture;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.Context;
+import android.graphics.Matrix;
+import android.os.Bundle;
+import android.os.LocaleList;
+import android.os.Parcel;
+import android.support.test.InstrumentationRegistry;
+import android.view.View;
+import android.view.ViewStructure.HtmlInfo;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.view.contentcapture.ViewNode.ViewStructureImpl;
+import android.widget.FrameLayout;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.Locale;
+
+/**
+ * Unit test for {@link ViewNode}.
+ *
+ * <p>To run it: {@code atest FrameworksCoreTests:android.view.contentcapture.ViewNodeTest}
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class ViewNodeTest {
+
+ private final Context mContext = InstrumentationRegistry.getTargetContext();
+
+ @Mock
+ private HtmlInfo mHtmlInfoMock;
+
+ @Test
+ public void testAutofillIdMethods_orphanView() {
+ View view = new View(mContext);
+ AutofillId initialId = new AutofillId(42);
+ view.setAutofillId(initialId);
+
+ ViewStructureImpl structure = new ViewStructureImpl(view);
+ ViewNode node = structure.getNode();
+
+ assertThat(node.getAutofillId()).isEqualTo(initialId);
+ assertThat(node.getParentAutofillId()).isNull();
+
+ AutofillId newId = new AutofillId(108);
+ structure.setAutofillId(newId);
+ assertThat(node.getAutofillId()).isEqualTo(newId);
+ assertThat(node.getParentAutofillId()).isNull();
+
+ structure.setAutofillId(new AutofillId(66), 6);
+ assertThat(node.getAutofillId()).isEqualTo(new AutofillId(66, 6));
+ assertThat(node.getParentAutofillId()).isEqualTo(new AutofillId(66));
+ }
+
+ @Test
+ public void testAutofillIdMethods_parentedView() {
+ FrameLayout parent = new FrameLayout(mContext);
+ AutofillId initialParentId = new AutofillId(48);
+ parent.setAutofillId(initialParentId);
+
+ View child = new View(mContext);
+ AutofillId initialChildId = new AutofillId(42);
+ child.setAutofillId(initialChildId);
+
+ parent.addView(child);
+
+ ViewStructureImpl structure = new ViewStructureImpl(child);
+ ViewNode node = structure.getNode();
+
+ assertThat(node.getAutofillId()).isEqualTo(initialChildId);
+ assertThat(node.getParentAutofillId()).isEqualTo(initialParentId);
+
+ AutofillId newChildId = new AutofillId(108);
+ structure.setAutofillId(newChildId);
+ assertThat(node.getAutofillId()).isEqualTo(newChildId);
+ assertThat(node.getParentAutofillId()).isEqualTo(initialParentId);
+
+ AutofillId newParentId = new AutofillId(15162342);
+ parent.setAutofillId(newParentId);
+ assertThat(node.getAutofillId()).isEqualTo(newChildId);
+ assertThat(node.getParentAutofillId()).isEqualTo(initialParentId);
+
+ structure.setAutofillId(new AutofillId(66), 6);
+ assertThat(node.getAutofillId()).isEqualTo(new AutofillId(66, 6));
+ assertThat(node.getParentAutofillId()).isEqualTo(new AutofillId(66));
+ }
+
+ @Test
+ public void testAutofillIdMethods_explicitIdsConstructor() {
+ AutofillId initialParentId = new AutofillId(42);
+ ViewStructureImpl structure = new ViewStructureImpl(initialParentId, 108);
+ ViewNode node = structure.getNode();
+
+ assertThat(node.getAutofillId()).isEqualTo(new AutofillId(initialParentId, 108));
+ assertThat(node.getParentAutofillId()).isEqualTo(initialParentId);
+
+ AutofillId newChildId = new AutofillId(108);
+ structure.setAutofillId(newChildId);
+ assertThat(node.getAutofillId()).isEqualTo(newChildId);
+ assertThat(node.getParentAutofillId()).isEqualTo(initialParentId);
+
+ structure.setAutofillId(new AutofillId(66), 6);
+ assertThat(node.getAutofillId()).isEqualTo(new AutofillId(66, 6));
+ assertThat(node.getParentAutofillId()).isEqualTo(new AutofillId(66));
+ }
+
+ @Test
+ public void testInvalidSetters() {
+ View view = new View(mContext);
+ AutofillId initialId = new AutofillId(42);
+ view.setAutofillId(initialId);
+
+ ViewStructureImpl structure = new ViewStructureImpl(view);
+ ViewNode node = structure.getNode();
+ assertThat(node.getAutofillId()).isEqualTo(initialId); // sanity check
+
+ assertThrows(NullPointerException.class, () -> structure.setAutofillId(null));
+ assertThat(node.getAutofillId()).isEqualTo(initialId); // invariant
+
+ assertThrows(NullPointerException.class, () -> structure.setAutofillId(null, 666));
+ assertThat(node.getAutofillId()).isEqualTo(initialId); // invariant
+
+ assertThrows(NullPointerException.class, () -> structure.setTextIdEntry(null));
+ assertThat(node.getTextIdEntry()).isNull();
+ }
+
+ @Test
+ public void testUnsupportedProperties() {
+ View view = new View(mContext);
+
+ ViewStructureImpl structure = new ViewStructureImpl(view);
+ ViewNode node = structure.getNode();
+
+ structure.setChildCount(1);
+ assertThat(node.getChildCount()).isEqualTo(0);
+
+ structure.addChildCount(1);
+ assertThat(node.getChildCount()).isEqualTo(0);
+
+ assertThat(structure.newChild(0)).isNull();
+ assertThat(node.getChildCount()).isEqualTo(0);
+
+ assertThat(structure.asyncNewChild(0)).isNull();
+ assertThat(node.getChildCount()).isEqualTo(0);
+
+ structure.asyncCommit();
+ assertThat(node.getChildCount()).isEqualTo(0);
+
+ structure.setWebDomain("Y U NO SET?");
+ assertThat(node.getWebDomain()).isNull();
+
+ assertThat(structure.newHtmlInfoBuilder("WHATEVER")).isNull();
+
+ structure.setHtmlInfo(mHtmlInfoMock);
+ assertThat(node.getHtmlInfo()).isNull();
+
+ structure.setDataIsSensitive(true);
+
+ assertThat(structure.getTempRect()).isNull();
+ }
+
+ @Test
+ public void testValidProperties_directly() {
+ ViewStructureImpl structure = newSimpleStructure();
+ assertSimpleStructure(structure);
+ assertSimpleNode(structure.getNode());
+ }
+
+ @Test
+ public void testValidProperties_throughParcel() {
+ ViewStructureImpl structure = newSimpleStructure();
+ final ViewNode node = structure.getNode();
+ assertSimpleNode(node); // sanity check
+
+ final ViewNode clone = cloneThroughParcel(node);
+ assertSimpleNode(clone);
+ }
+
+ @Test
+ public void testComplexText_directly() {
+ ViewStructureImpl structure = newStructureWithComplexText();
+ assertStructureWithComplexText(structure);
+ assertNodeWithComplexText(structure.getNode());
+ }
+
+ @Test
+ public void testComplexText_throughParcel() {
+ ViewStructureImpl structure = newStructureWithComplexText();
+ final ViewNode node = structure.getNode();
+ assertNodeWithComplexText(node); // sanity check
+
+ ViewNode clone = cloneThroughParcel(node);
+ assertNodeWithComplexText(clone);
+ }
+
+ @Test
+ public void testVisibility() {
+ // Visibility is a special case becase it use flag masks, so we want to make sure it works
+ // fine
+ View view = new View(mContext);
+ ViewStructureImpl structure = new ViewStructureImpl(view);
+ ViewNode node = structure.getNode();
+
+ structure.setVisibility(View.VISIBLE);
+ assertThat(node.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(cloneThroughParcel(node).getVisibility()).isEqualTo(View.VISIBLE);
+
+ structure.setVisibility(View.GONE);
+ assertThat(node.getVisibility()).isEqualTo(View.GONE);
+ assertThat(cloneThroughParcel(node).getVisibility()).isEqualTo(View.GONE);
+
+ structure.setVisibility(View.VISIBLE);
+ assertThat(node.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(cloneThroughParcel(node).getVisibility()).isEqualTo(View.VISIBLE);
+
+ structure.setVisibility(View.INVISIBLE);
+ assertThat(node.getVisibility()).isEqualTo(View.INVISIBLE);
+ assertThat(cloneThroughParcel(node).getVisibility()).isEqualTo(View.INVISIBLE);
+
+ structure.setVisibility(View.INVISIBLE | View.GONE);
+ assertThat(node.getVisibility()).isEqualTo(View.INVISIBLE | View.GONE);
+ assertThat(cloneThroughParcel(node).getVisibility()).isEqualTo(View.INVISIBLE | View.GONE);
+
+
+ final int invalidValue = Math.max(Math.max(View.VISIBLE, View.INVISIBLE), View.GONE) * 2;
+ structure.setVisibility(View.VISIBLE);
+ structure.setVisibility(invalidValue); // should be ignored
+ assertThat(node.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(cloneThroughParcel(node).getVisibility()).isEqualTo(View.VISIBLE);
+
+ structure.setVisibility(View.GONE | invalidValue);
+ assertThat(node.getVisibility()).isEqualTo(View.GONE);
+ assertThat(cloneThroughParcel(node).getVisibility()).isEqualTo(View.GONE);
+ }
+
+ /**
+ * Creates a {@link ViewStructureImpl} that can be asserted through
+ * {@link #assertSimpleNode(ViewNode)}.
+ */
+ private ViewStructureImpl newSimpleStructure() {
+ View view = new View(mContext);
+ view.setAutofillId(new AutofillId(42));
+
+ ViewStructureImpl structure = new ViewStructureImpl(view);
+
+ // Basic properties
+ structure.setText("Text is set!");
+ structure.setClassName("Classy!");
+ structure.setContentDescription("Described I am!");
+ structure.setVisibility(View.INVISIBLE);
+
+ // Autofill properties
+ structure.setAutofillType(View.AUTOFILL_TYPE_TEXT);
+ structure.setAutofillHints(new String[] { "Auto", "Man" });
+ structure.setAutofillOptions(new String[] { "Maybe" });
+ structure.setAutofillValue(AutofillValue.forText("Malkovich"));
+
+ // Graphic properties
+ structure.setElevation(6.66f);
+ structure.setAlpha(66.6f);
+ structure.setTransformation(Matrix.IDENTITY_MATRIX);
+
+ // Extra text properties
+ structure.setMinTextEms(6);
+ structure.setMaxTextLength(66);
+ structure.setMaxTextEms(666);
+ structure.setInputType(42);
+ structure.setTextIdEntry("TEXT, Y U NO ENTRY?");
+ structure.setLocaleList(new LocaleList(Locale.US, Locale.ENGLISH));
+
+ // Resource id
+ structure.setId(16, "package.name", "type.name", "entry.name");
+
+ // Dimensions
+ structure.setDimens(4, 8, 15, 16, 23, 42);
+
+ // Boolean properties
+ structure.setAssistBlocked(true);
+ structure.setEnabled(true);
+ structure.setClickable(true);
+ structure.setLongClickable(true);
+ structure.setContextClickable(true);
+ structure.setFocusable(true);
+ structure.setFocused(true);
+ structure.setAccessibilityFocused(true);
+ structure.setChecked(true);
+ structure.setActivated(true);
+ structure.setOpaque(true);
+
+ // Bundle
+ assertThat(structure.hasExtras()).isFalse();
+ final Bundle bundle = structure.getExtras();
+ assertThat(bundle).isNotNull();
+ bundle.putString("Marlon", "Bundle");
+ assertThat(structure.hasExtras()).isTrue();
+ return structure;
+ }
+
+ /**
+ * Asserts the properties of a {@link ViewNode} that was created by
+ * {@link #newSimpleStructure()}.
+ */
+ private void assertSimpleNode(ViewNode node) {
+
+ // Basic properties
+ assertThat(node.getAutofillId()).isEqualTo(new AutofillId(42));
+ assertThat(node.getParentAutofillId()).isNull();
+ assertThat(node.getText()).isEqualTo("Text is set!");
+ assertThat(node.getClassName()).isEqualTo("Classy!");
+ assertThat(node.getContentDescription().toString()).isEqualTo("Described I am!");
+ assertThat(node.getVisibility()).isEqualTo(View.INVISIBLE);
+
+ // Autofill properties
+ assertThat(node.getAutofillType()).isEqualTo(View.AUTOFILL_TYPE_TEXT);
+ assertThat(node.getAutofillHints()).asList().containsExactly("Auto", "Man").inOrder();
+ assertThat(node.getAutofillOptions()).asList().containsExactly("Maybe").inOrder();
+ assertThat(node.getAutofillValue().getTextValue()).isEqualTo("Malkovich");
+
+ // Graphic properties
+ assertThat(node.getElevation()).isWithin(1.0e-10f).of(6.66f);
+ assertThat(node.getAlpha()).isWithin(1.0e-10f).of(66.6f);
+ assertThat(node.getTransformation()).isEqualTo(Matrix.IDENTITY_MATRIX);
+
+ // Extra text properties
+ assertThat(node.getMinTextEms()).isEqualTo(6);
+ assertThat(node.getMaxTextLength()).isEqualTo(66);
+ assertThat(node.getMaxTextEms()).isEqualTo(666);
+ assertThat(node.getInputType()).isEqualTo(42);
+ assertThat(node.getTextIdEntry()).isEqualTo("TEXT, Y U NO ENTRY?");
+ assertThat(node.getLocaleList()).isEqualTo(new LocaleList(Locale.US, Locale.ENGLISH));
+
+ // Resource id
+ assertThat(node.getId()).isEqualTo(16);
+ assertThat(node.getIdPackage()).isEqualTo("package.name");
+ assertThat(node.getIdType()).isEqualTo("type.name");
+ assertThat(node.getIdEntry()).isEqualTo("entry.name");
+
+ // Dimensions
+ assertThat(node.getLeft()).isEqualTo(4);
+ assertThat(node.getTop()).isEqualTo(8);
+ assertThat(node.getScrollX()).isEqualTo(15);
+ assertThat(node.getScrollY()).isEqualTo(16);
+ assertThat(node.getWidth()).isEqualTo(23);
+ assertThat(node.getHeight()).isEqualTo(42);
+
+ // Boolean properties
+ assertThat(node.isAssistBlocked()).isTrue();
+ assertThat(node.isEnabled()).isTrue();
+ assertThat(node.isClickable()).isTrue();
+ assertThat(node.isLongClickable()).isTrue();
+ assertThat(node.isContextClickable()).isTrue();
+ assertThat(node.isFocusable()).isTrue();
+ assertThat(node.isFocused()).isTrue();
+ assertThat(node.isAccessibilityFocused()).isTrue();
+ assertThat(node.isChecked()).isTrue();
+ assertThat(node.isActivated()).isTrue();
+ assertThat(node.isOpaque()).isTrue();
+
+ // Bundle
+ final Bundle bundle = node.getExtras();
+ assertThat(bundle).isNotNull();
+ assertThat(bundle.size()).isEqualTo(1);
+ assertThat(bundle.getString("Marlon")).isEqualTo("Bundle");
+ }
+
+ /**
+ * Asserts the properties of a {@link ViewStructureImpl} that was created by
+ * {@link #newSimpleStructure()}.
+ */
+ private void assertSimpleStructure(ViewStructureImpl structure) {
+ assertThat(structure.getAutofillId()).isEqualTo(new AutofillId(42));
+ assertThat(structure.getText()).isEqualTo("Text is set!");
+
+ // Bundle
+ final Bundle bundle = structure.getExtras();
+ assertThat(bundle.size()).isEqualTo(1);
+ assertThat(bundle.getString("Marlon")).isEqualTo("Bundle");
+ }
+
+ /**
+ * Creates a {@link ViewStructureImpl} with "complex" text properties (such as selection); it
+ * can be asserted through {@link #assertNodeWithComplexText(ViewNode)}.
+ */
+ private ViewStructureImpl newStructureWithComplexText() {
+ View view = new View(mContext);
+ ViewStructureImpl structure = new ViewStructureImpl(view);
+ structure.setText("IGNORE ME!");
+ structure.setText("Now we're talking!", 4, 8);
+ structure.setHint("Soylent Green is SPOILER ALERT");
+ structure.setTextStyle(15.0f, 16, 23, 42);
+ structure.setTextLines(new int[] {4, 8, 15} , new int[] {16, 23, 42});
+ return structure;
+ }
+
+ /**
+ * Asserts the properties of a {@link ViewNode} that was created by
+ * {@link #newStructureWithComplexText()}.
+ */
+ private void assertNodeWithComplexText(ViewNode node) {
+ assertThat(node.getText()).isEqualTo("Now we're talking!");
+ assertThat(node.getTextSelectionStart()).isEqualTo(4);
+ assertThat(node.getTextSelectionEnd()).isEqualTo(8);
+ assertThat(node.getHint()).isEqualTo("Soylent Green is SPOILER ALERT");
+ assertThat(node.getTextSize()).isWithin(1.0e-10f).of(15.0f);
+ assertThat(node.getTextColor()).isEqualTo(16);
+ assertThat(node.getTextBackgroundColor()).isEqualTo(23);
+ assertThat(node.getTextStyle()).isEqualTo(42);
+ assertThat(node.getTextLineCharOffsets()).asList().containsExactly(4, 8, 15).inOrder();
+ assertThat(node.getTextLineBaselines()).asList().containsExactly(16, 23, 42).inOrder();
+ }
+
+ /**
+ * Asserts the properties of a {@link ViewStructureImpl} that was created by
+ * {@link #newStructureWithComplexText()}.
+ */
+ private void assertStructureWithComplexText(ViewStructureImpl structure) {
+ assertThat(structure.getText()).isEqualTo("Now we're talking!");
+ assertThat(structure.getTextSelectionStart()).isEqualTo(4);
+ assertThat(structure.getTextSelectionEnd()).isEqualTo(8);
+ assertThat(structure.getHint()).isEqualTo("Soylent Green is SPOILER ALERT");
+ }
+
+ private ViewNode cloneThroughParcel(ViewNode node) {
+ Parcel parcel = Parcel.obtain();
+
+ try {
+ // Write to parcel
+ parcel.setDataPosition(0); // Sanity / paranoid check
+ ViewNode.writeToParcel(parcel, node, 0);
+
+ // Read from parcel
+ parcel.setDataPosition(0);
+ ViewNode clone = ViewNode.readFromParcel(parcel);
+ assertThat(clone).isNotNull();
+ return clone;
+ } finally {
+ parcel.recycle();
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
index 81ec85e..82eaf88 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
@@ -368,6 +368,7 @@
assertThat(textLanguage, isTextLanguage("ja"));
}
+ /* DISABLED: b/122467291
@Test
public void testSuggestConversationActions_textReplyOnly_maxThree() {
if (isTextClassifierDisabled()) return;
@@ -395,7 +396,7 @@
assertThat(conversationAction,
isConversationAction(ConversationActions.TYPE_TEXT_REPLY));
}
- }
+ }*/
@Test
public void testSuggestConversationActions_textReplyOnly_noMax() {
diff --git a/core/tests/coretests/src/android/view/textclassifier/logging/TextClassifierEventTronLoggerTest.java b/core/tests/coretests/src/android/view/textclassifier/logging/TextClassifierEventTronLoggerTest.java
new file mode 100644
index 0000000..344f79d
--- /dev/null
+++ b/core/tests/coretests/src/android/view/textclassifier/logging/TextClassifierEventTronLoggerTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.
+ */
+package android.view.textclassifier.logging;
+
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_TEXT_SELECTION_SMART_SHARE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.CONVERSATION_ACTIONS;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_ENTITY_TYPE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_WIDGET_TYPE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_EVENT_TIME;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.metrics.LogMaker;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.textclassifier.ConversationActions;
+import android.view.textclassifier.TextClassificationContext;
+import android.view.textclassifier.TextClassifierEvent;
+import android.view.textclassifier.TextClassifierEventTronLogger;
+
+import com.android.internal.logging.MetricsLogger;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextClassifierEventTronLoggerTest {
+ private static final String WIDGET_TYPE = "notification";
+ private static final String PACKAGE_NAME = "pkg";
+ private static final long EVENT_TIME = System.currentTimeMillis();
+
+
+ @Mock
+ private MetricsLogger mMetricsLogger;
+ private TextClassifierEventTronLogger mTextClassifierEventTronLogger;
+
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mTextClassifierEventTronLogger = new TextClassifierEventTronLogger(mMetricsLogger);
+ }
+
+ @Test
+ public void testWriteEvent() {
+ TextClassificationContext textClassificationContext =
+ new TextClassificationContext.Builder(PACKAGE_NAME, WIDGET_TYPE)
+ .build();
+ TextClassifierEvent textClassifierEvent =
+ new TextClassifierEvent.Builder(
+ TextClassifierEvent.CATEGORY_CONVERSATION_ACTIONS,
+ TextClassifierEvent.TYPE_SMART_ACTION)
+ .setEntityType(ConversationActions.TYPE_CALL_PHONE)
+ .setEventTime(EVENT_TIME)
+ .setEventContext(textClassificationContext)
+ .build();
+
+ mTextClassifierEventTronLogger.writeEvent(textClassifierEvent);
+
+ ArgumentCaptor<LogMaker> captor = ArgumentCaptor.forClass(LogMaker.class);
+ Mockito.verify(mMetricsLogger).write(captor.capture());
+ LogMaker logMaker = captor.getValue();
+ assertThat(logMaker.getCategory()).isEqualTo(
+ CONVERSATION_ACTIONS);
+ assertThat(logMaker.getType()).isEqualTo(
+ ACTION_TEXT_SELECTION_SMART_SHARE);
+ assertThat(logMaker.getTaggedData(FIELD_SELECTION_ENTITY_TYPE))
+ .isEqualTo(ConversationActions.TYPE_CALL_PHONE);
+ assertThat(logMaker.getTaggedData(FIELD_TEXT_CLASSIFIER_EVENT_TIME))
+ .isEqualTo(EVENT_TIME);
+ assertThat(logMaker.getPackageName()).isEqualTo(PACKAGE_NAME);
+ assertThat(logMaker.getTaggedData(FIELD_SELECTION_WIDGET_TYPE))
+ .isEqualTo(WIDGET_TYPE);
+ }
+
+ @Test
+ public void testWriteEvent_unsupportedCategory() {
+ TextClassifierEvent textClassifierEvent =
+ new TextClassifierEvent.Builder(
+ TextClassifierEvent.CATEGORY_SELECTION,
+ TextClassifierEvent.TYPE_SMART_ACTION)
+ .build();
+
+ mTextClassifierEventTronLogger.writeEvent(textClassifierEvent);
+
+ Mockito.verify(mMetricsLogger, Mockito.never()).write(Mockito.any(LogMaker.class));
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
index 218566e..d782c0c 100644
--- a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
@@ -167,7 +167,7 @@
}
private WindowInsets insetsWith(Insets content, DisplayCutout cutout) {
- return new WindowInsets(content.toRect(), null, null, false, false, cutout);
+ return new WindowInsets(content.toRect(), null, false, false, cutout);
}
private ViewGroup createViewGroupWithId(int id) {
diff --git a/core/tests/hdmitests/Android.mk b/core/tests/hdmitests/Android.mk
index e0d2c09..2ca31a6 100644
--- a/core/tests/hdmitests/Android.mk
+++ b/core/tests/hdmitests/Android.mk
@@ -20,7 +20,7 @@
# Include all test java files
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test frameworks-base-testutils
+LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules frameworks-base-testutils
LOCAL_JAVA_LIBRARIES := android.test.runner
LOCAL_PACKAGE_NAME := HdmiCecTests
diff --git a/core/tests/hdmitests/AndroidManifest.xml b/core/tests/hdmitests/AndroidManifest.xml
index 1460b41..f8ed118 100644
--- a/core/tests/hdmitests/AndroidManifest.xml
+++ b/core/tests/hdmitests/AndroidManifest.xml
@@ -22,7 +22,7 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="android.hardware.hdmi"
android:label="HDMI CEC Tests"/>
diff --git a/core/tests/hdmitests/AndroidTest.xml b/core/tests/hdmitests/AndroidTest.xml
index 7ef672d..0c8da28 100644
--- a/core/tests/hdmitests/AndroidTest.xml
+++ b/core/tests/hdmitests/AndroidTest.xml
@@ -29,6 +29,6 @@
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.hardware.hdmi" />
<option name="hidden-api-checks" value="false"/>
- <option name="runner" value="android.support.test.runner.AndroidJUnitRunner"/>
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner"/>
</test>
</configuration>
\ No newline at end of file
diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
index 7b76a08..0dd9d09 100644
--- a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
+++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
@@ -18,15 +18,18 @@
import android.os.Handler;
import android.os.test.TestLooper;
-import android.support.test.filters.SmallTest;
import android.util.Log;
-import java.util.List;
+
+import androidx.test.filters.SmallTest;
+
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.util.List;
+
/**
* Tests for {@link HdmiAudioSystemClient}
*/
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/Android.mk
index f2bd353..2dc30ea 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/Android.mk
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/Android.mk
@@ -18,7 +18,7 @@
## The tests with only one dex
include $(CLEAR_VARS)
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex-instrumentation android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex-instrumentation androidx.test.rules
LOCAL_MODULE_TAGS := tests
LOCAL_SRC_FILES := $(call all-java-files-under, src)
@@ -41,7 +41,7 @@
## The tests with a minimal main dex
include $(CLEAR_VARS)
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex-instrumentation android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex-instrumentation androidx.test.rules
LOCAL_MODULE_TAGS := tests
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/InstrumentationTest.java b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/InstrumentationTest.java
index 4e6ec14..7812c58 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/InstrumentationTest.java
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/InstrumentationTest.java
@@ -15,10 +15,13 @@
*/
package com.android.multidexlegacytestapp.test2;
-import android.support.test.runner.AndroidJUnit4;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.multidexlegacytestapp.manymethods.Big001;
import com.android.multidexlegacytestapp.manymethods.Big079;
+
import junit.framework.Assert;
+
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/MultiDexAndroidJUnitRunner.java b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/MultiDexAndroidJUnitRunner.java
index 9e41a92..b3044dc 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/MultiDexAndroidJUnitRunner.java
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/MultiDexAndroidJUnitRunner.java
@@ -1,8 +1,9 @@
package com.android.multidexlegacytestapp.test2;
import android.os.Bundle;
+
import androidx.multidex.MultiDex;
-import android.support.test.runner.AndroidJUnitRunner;
+import androidx.test.runner.AndroidJUnitRunner;
public class MultiDexAndroidJUnitRunner extends AndroidJUnitRunner {
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index b65bc1d..221708b 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -29,7 +29,6 @@
<permission name="android.permission.DUMP"/>
<permission name="android.permission.GET_APP_OPS_STATS"/>
<permission name="android.permission.INTERACT_ACROSS_USERS"/>
- <permission name="android.permission.MANAGE_ACTIVITY_STACKS"/>
<permission name="android.permission.MANAGE_DEBUGGING"/>
<permission name="android.permission.MANAGE_SENSOR_PRIVACY"/>
<permission name="android.permission.MANAGE_USB"/>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 58b57e5..fbe613d 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -242,6 +242,32 @@
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
</privapp-permissions>
+ <privapp-permissions package="com.android.mainline.networkstack">
+ <permission name="android.permission.ACCESS_NETWORK_CONDITIONS"/>
+ <permission name="android.permission.CHANGE_BACKGROUND_DATA_SETTING"/>
+ <permission name="android.permission.CONNECTIVITY_INTERNAL"/>
+ <permission name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/>
+ <permission name="android.permission.CONTROL_VPN"/>
+ <permission name="android.permission.LOCAL_MAC_ADDRESS"/>
+ <permission name="android.permission.MANAGE_IPSEC_TUNNELS"/>
+ <permission name="android.permission.MANAGE_NETWORK_POLICY"/>
+ <permission name="android.permission.MANAGE_SUBSCRIPTION_PLANS"/>
+ <permission name="android.permission.MANAGE_USB"/>
+ <permission name="android.permission.NETWORK_BYPASS_PRIVATE_DNS"/>
+ <permission name="android.permission.NETWORK_SETTINGS"/>
+ <permission name="android.permission.NETWORK_STACK" />
+ <permission name="android.permission.NET_TUNNELING"/>
+ <permission name="android.permission.PACKET_KEEPALIVE_OFFLOAD"/>
+ <permission name="android.permission.PEERS_MAC_ADDRESS"/>
+ <permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/>
+ <permission name="android.permission.READ_PRECISE_PHONE_STATE"/>
+ <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
+ <permission name="android.permission.READ_WIFI_CREDENTIAL"/>
+ <permission name="android.permission.RECEIVE_DATA_ACTIVITY_CHANGE"/>
+ <permission name="android.permission.TETHER_PRIVILEGED"/>
+ <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
+ </privapp-permissions>
+
<privapp-permissions package="com.android.server.telecom">
<permission name="android.permission.BIND_CONNECTION_SERVICE"/>
<permission name="android.permission.BIND_INCALL_SERVICE"/>
@@ -289,7 +315,6 @@
<permission name="android.permission.INTERACT_ACROSS_USERS"/>
<permission name="android.permission.LOCAL_MAC_ADDRESS"/>
<permission name="android.permission.MANAGE_ACCESSIBILITY"/>
- <permission name="android.permission.MANAGE_ACTIVITY_STACKS"/>
<permission name="android.permission.MANAGE_DEVICE_ADMINS"/>
<permission name="android.permission.MANAGE_USB"/>
<permission name="android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS"/>
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index 6a40792..21531ab 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -99,6 +99,46 @@
<font weight="400" style="normal">CarroisGothicSC-Regular.ttf</font>
</family>
+ <family name="arbutus-slab">
+ <font weight="400" style="normal">ArbutusSlab-Regular.ttf</font>
+ </family>
+
+ <family name="arvo">
+ <font weight="400" style="normal">Arvo-Regular.ttf</font>
+ <font weight="400" style="italic">Arvo-Italic.ttf</font>
+ <font weight="700" style="normal">Arvo-Bold.ttf</font>
+ <font weight="700" style="italic">Arvo-BoldItalic.ttf</font>
+ </family>
+ <alias name="arvo-bold" to="arvo" weight="700" />
+
+ <family name="lato">
+ <font weight="400" style="normal">Lato-Regular.ttf</font>
+ <font weight="400" style="italic">Lato-Italic.ttf</font>
+ <font weight="700" style="normal">Lato-Bold.ttf</font>
+ <font weight="700" style="italic">Lato-BoldItalic.ttf</font>
+ </family>
+ <alias name="lato-bold" to="lato" weight="700" />
+
+ <family name="rubik">
+ <font weight="400" style="normal">Rubik-Regular.ttf</font>
+ <font weight="400" style="italic">Rubik-Italic.ttf</font>
+ <font weight="500" style="normal">Rubik-Medium.ttf</font>
+ <font weight="500" style="italic">Rubik-MediumItalic.ttf</font>
+ <font weight="700" style="normal">Rubik-Bold.ttf</font>
+ <font weight="700" style="italic">Rubik-BoldItalic.ttf</font>
+ </family>
+ <alias name="rubik-medium" to="rubik" weight="500" />
+
+ <family name="source-sans-pro">
+ <font weight="400" style="normal">SourceSansPro-Regular.ttf</font>
+ <font weight="400" style="italic">SourceSansPro-Italic.ttf</font>
+ <font weight="600" style="normal">SourceSansPro-SemiBold.ttf</font>
+ <font weight="600" style="italic">SourceSansPro-SemiBoldItalic.ttf</font>
+ <font weight="700" style="normal">SourceSansPro-Bold.ttf</font>
+ <font weight="700" style="italic">SourceSansPro-BoldItalic.ttf</font>
+ </family>
+ <alias name="source-sans-pro-semi-bold" to="source-sans-pro" weight="600" />
+
<!-- fallback fonts -->
<family lang="und-Arab" variant="elegant">
<font weight="400" style="normal">NotoNaskhArabic-Regular.ttf</font>
@@ -312,9 +352,8 @@
<font weight="700" style="normal">NotoSansLaoUI-Bold.ttf</font>
</family>
<family lang="und-Mymr" variant="elegant">
- <font weight="400" style="normal">NotoSansMyanmar-Regular.otf</font>
- <font weight="500" style="normal">NotoSansMyanmar-Medium.otf</font>
- <font weight="700" style="normal">NotoSansMyanmar-Bold.otf</font>
+ <font weight="400" style="normal">NotoSansMyanmar-Regular-ZawDecode.ttf</font>
+ <font weight="700" style="normal">NotoSansMyanmar-Bold-ZawDecode.ttf</font>
<font weight="400" style="normal" fallbackFor="serif">NotoSerifMyanmar-Regular.otf</font>
<font weight="700" style="normal" fallbackFor="serif">NotoSerifMyanmar-Bold.otf</font>
</family>
diff --git a/data/sounds/AllAudio.mk b/data/sounds/AllAudio.mk
index bb8add1..c6c7d3b 100644
--- a/data/sounds/AllAudio.mk
+++ b/data/sounds/AllAudio.mk
@@ -15,227 +15,227 @@
LOCAL_PATH := frameworks/base/data/sounds
PRODUCT_COPY_FILES += \
- $(LOCAL_PATH)/Alarm_Beep_01.ogg:system/media/audio/alarms/Alarm_Beep_01.ogg \
- $(LOCAL_PATH)/Alarm_Beep_02.ogg:system/media/audio/alarms/Alarm_Beep_02.ogg \
- $(LOCAL_PATH)/Alarm_Beep_03.ogg:system/media/audio/alarms/Alarm_Beep_03.ogg \
- $(LOCAL_PATH)/Alarm_Buzzer.ogg:system/media/audio/alarms/Alarm_Buzzer.ogg \
- $(LOCAL_PATH)/Alarm_Classic.ogg:system/media/audio/alarms/Alarm_Classic.ogg \
- $(LOCAL_PATH)/Alarm_Rooster_02.ogg:system/media/audio/alarms/Alarm_Rooster_02.ogg \
- $(LOCAL_PATH)/alarms/ogg/Argon.ogg:system/media/audio/alarms/Argon.ogg \
- $(LOCAL_PATH)/alarms/ogg/Barium.ogg:system/media/audio/alarms/Barium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Carbon.ogg:system/media/audio/alarms/Carbon.ogg \
- $(LOCAL_PATH)/alarms/ogg/Cesium.ogg:system/media/audio/alarms/Cesium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Fermium.ogg:system/media/audio/alarms/Fermium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Hassium.ogg:system/media/audio/alarms/Hassium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Helium.ogg:system/media/audio/alarms/Helium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Krypton.ogg:system/media/audio/alarms/Krypton.ogg \
- $(LOCAL_PATH)/alarms/ogg/Neon.ogg:system/media/audio/alarms/Neon.ogg \
- $(LOCAL_PATH)/alarms/ogg/Neptunium.ogg:system/media/audio/alarms/Neptunium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Nobelium.ogg:system/media/audio/alarms/Nobelium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Osmium.ogg:system/media/audio/alarms/Osmium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Oxygen.ogg:system/media/audio/alarms/Oxygen.ogg \
- $(LOCAL_PATH)/alarms/ogg/Platinum.ogg:system/media/audio/alarms/Platinum.ogg \
- $(LOCAL_PATH)/alarms/ogg/Plutonium.ogg:system/media/audio/alarms/Plutonium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Promethium.ogg:system/media/audio/alarms/Promethium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Scandium.ogg:system/media/audio/alarms/Scandium.ogg \
- $(LOCAL_PATH)/notifications/ogg/Adara.ogg:system/media/audio/notifications/Adara.ogg \
- $(LOCAL_PATH)/notifications/Aldebaran.ogg:system/media/audio/notifications/Aldebaran.ogg \
- $(LOCAL_PATH)/notifications/Altair.ogg:system/media/audio/notifications/Altair.ogg \
- $(LOCAL_PATH)/notifications/ogg/Alya.ogg:system/media/audio/notifications/Alya.ogg \
- $(LOCAL_PATH)/notifications/Antares.ogg:system/media/audio/notifications/Antares.ogg \
- $(LOCAL_PATH)/notifications/ogg/Antimony.ogg:system/media/audio/notifications/Antimony.ogg \
- $(LOCAL_PATH)/notifications/ogg/Arcturus.ogg:system/media/audio/notifications/Arcturus.ogg \
- $(LOCAL_PATH)/notifications/ogg/Argon.ogg:system/media/audio/notifications/Argon.ogg \
- $(LOCAL_PATH)/notifications/Beat_Box_Android.ogg:system/media/audio/notifications/Beat_Box_Android.ogg \
- $(LOCAL_PATH)/notifications/ogg/Bellatrix.ogg:system/media/audio/notifications/Bellatrix.ogg \
- $(LOCAL_PATH)/notifications/ogg/Beryllium.ogg:system/media/audio/notifications/Beryllium.ogg \
- $(LOCAL_PATH)/notifications/Betelgeuse.ogg:system/media/audio/notifications/Betelgeuse.ogg \
- $(LOCAL_PATH)/newwavelabs/CaffeineSnake.ogg:system/media/audio/notifications/CaffeineSnake.ogg \
- $(LOCAL_PATH)/notifications/Canopus.ogg:system/media/audio/notifications/Canopus.ogg \
- $(LOCAL_PATH)/notifications/ogg/Capella.ogg:system/media/audio/notifications/Capella.ogg \
- $(LOCAL_PATH)/notifications/Castor.ogg:system/media/audio/notifications/Castor.ogg \
- $(LOCAL_PATH)/notifications/ogg/CetiAlpha.ogg:system/media/audio/notifications/CetiAlpha.ogg \
- $(LOCAL_PATH)/notifications/ogg/Cobalt.ogg:system/media/audio/notifications/Cobalt.ogg \
- $(LOCAL_PATH)/notifications/Cricket.ogg:system/media/audio/notifications/Cricket.ogg \
- $(LOCAL_PATH)/newwavelabs/DearDeer.ogg:system/media/audio/notifications/DearDeer.ogg \
- $(LOCAL_PATH)/notifications/Deneb.ogg:system/media/audio/notifications/Deneb.ogg \
- $(LOCAL_PATH)/notifications/Doink.ogg:system/media/audio/notifications/Doink.ogg \
- $(LOCAL_PATH)/newwavelabs/DontPanic.ogg:system/media/audio/notifications/DontPanic.ogg \
- $(LOCAL_PATH)/notifications/Drip.ogg:system/media/audio/notifications/Drip.ogg \
- $(LOCAL_PATH)/notifications/Electra.ogg:system/media/audio/notifications/Electra.ogg \
- $(LOCAL_PATH)/F1_MissedCall.ogg:system/media/audio/notifications/F1_MissedCall.ogg \
- $(LOCAL_PATH)/F1_New_MMS.ogg:system/media/audio/notifications/F1_New_MMS.ogg \
- $(LOCAL_PATH)/F1_New_SMS.ogg:system/media/audio/notifications/F1_New_SMS.ogg \
- $(LOCAL_PATH)/notifications/ogg/Fluorine.ogg:system/media/audio/notifications/Fluorine.ogg \
- $(LOCAL_PATH)/notifications/Fomalhaut.ogg:system/media/audio/notifications/Fomalhaut.ogg \
- $(LOCAL_PATH)/notifications/ogg/Gallium.ogg:system/media/audio/notifications/Gallium.ogg \
- $(LOCAL_PATH)/notifications/Heaven.ogg:system/media/audio/notifications/Heaven.ogg \
- $(LOCAL_PATH)/notifications/ogg/Helium.ogg:system/media/audio/notifications/Helium.ogg \
- $(LOCAL_PATH)/newwavelabs/Highwire.ogg:system/media/audio/notifications/Highwire.ogg \
- $(LOCAL_PATH)/notifications/ogg/Hojus.ogg:system/media/audio/notifications/Hojus.ogg \
- $(LOCAL_PATH)/notifications/ogg/Iridium.ogg:system/media/audio/notifications/Iridium.ogg \
- $(LOCAL_PATH)/notifications/ogg/Krypton.ogg:system/media/audio/notifications/Krypton.ogg \
- $(LOCAL_PATH)/newwavelabs/KzurbSonar.ogg:system/media/audio/notifications/KzurbSonar.ogg \
- $(LOCAL_PATH)/notifications/ogg/Lalande.ogg:system/media/audio/notifications/Lalande.ogg \
- $(LOCAL_PATH)/notifications/Merope.ogg:system/media/audio/notifications/Merope.ogg \
- $(LOCAL_PATH)/notifications/ogg/Mira.ogg:system/media/audio/notifications/Mira.ogg \
- $(LOCAL_PATH)/newwavelabs/OnTheHunt.ogg:system/media/audio/notifications/OnTheHunt.ogg \
- $(LOCAL_PATH)/notifications/ogg/Palladium.ogg:system/media/audio/notifications/Palladium.ogg \
- $(LOCAL_PATH)/notifications/Plastic_Pipe.ogg:system/media/audio/notifications/Plastic_Pipe.ogg \
- $(LOCAL_PATH)/notifications/ogg/Polaris.ogg:system/media/audio/notifications/Polaris.ogg \
- $(LOCAL_PATH)/notifications/ogg/Pollux.ogg:system/media/audio/notifications/Pollux.ogg \
- $(LOCAL_PATH)/notifications/ogg/Procyon.ogg:system/media/audio/notifications/Procyon.ogg \
- $(LOCAL_PATH)/notifications/ogg/Proxima.ogg:system/media/audio/notifications/Proxima.ogg \
- $(LOCAL_PATH)/notifications/ogg/Radon.ogg:system/media/audio/notifications/Radon.ogg \
- $(LOCAL_PATH)/notifications/ogg/Rubidium.ogg:system/media/audio/notifications/Rubidium.ogg \
- $(LOCAL_PATH)/notifications/ogg/Selenium.ogg:system/media/audio/notifications/Selenium.ogg \
- $(LOCAL_PATH)/notifications/ogg/Shaula.ogg:system/media/audio/notifications/Shaula.ogg \
- $(LOCAL_PATH)/notifications/Sirrah.ogg:system/media/audio/notifications/Sirrah.ogg \
- $(LOCAL_PATH)/notifications/SpaceSeed.ogg:system/media/audio/notifications/SpaceSeed.ogg \
- $(LOCAL_PATH)/notifications/ogg/Spica.ogg:system/media/audio/notifications/Spica.ogg \
- $(LOCAL_PATH)/notifications/ogg/Strontium.ogg:system/media/audio/notifications/Strontium.ogg \
- $(LOCAL_PATH)/notifications/ogg/Syrma.ogg:system/media/audio/notifications/Syrma.ogg \
- $(LOCAL_PATH)/notifications/TaDa.ogg:system/media/audio/notifications/TaDa.ogg \
- $(LOCAL_PATH)/notifications/ogg/Talitha.ogg:system/media/audio/notifications/Talitha.ogg \
- $(LOCAL_PATH)/notifications/ogg/Tejat.ogg:system/media/audio/notifications/Tejat.ogg \
- $(LOCAL_PATH)/notifications/ogg/Thallium.ogg:system/media/audio/notifications/Thallium.ogg \
- $(LOCAL_PATH)/notifications/Tinkerbell.ogg:system/media/audio/notifications/Tinkerbell.ogg \
- $(LOCAL_PATH)/notifications/ogg/Upsilon.ogg:system/media/audio/notifications/Upsilon.ogg \
- $(LOCAL_PATH)/notifications/ogg/Vega.ogg:system/media/audio/notifications/Vega.ogg \
- $(LOCAL_PATH)/newwavelabs/Voila.ogg:system/media/audio/notifications/Voila.ogg \
- $(LOCAL_PATH)/notifications/ogg/Xenon.ogg:system/media/audio/notifications/Xenon.ogg \
- $(LOCAL_PATH)/notifications/ogg/Zirconium.ogg:system/media/audio/notifications/Zirconium.ogg \
- $(LOCAL_PATH)/notifications/arcturus.ogg:system/media/audio/notifications/arcturus.ogg \
- $(LOCAL_PATH)/notifications/moonbeam.ogg:system/media/audio/notifications/moonbeam.ogg \
- $(LOCAL_PATH)/notifications/pixiedust.ogg:system/media/audio/notifications/pixiedust.ogg \
- $(LOCAL_PATH)/notifications/pizzicato.ogg:system/media/audio/notifications/pizzicato.ogg \
- $(LOCAL_PATH)/notifications/regulus.ogg:system/media/audio/notifications/regulus.ogg \
- $(LOCAL_PATH)/notifications/sirius.ogg:system/media/audio/notifications/sirius.ogg \
- $(LOCAL_PATH)/notifications/tweeters.ogg:system/media/audio/notifications/tweeters.ogg \
- $(LOCAL_PATH)/notifications/vega.ogg:system/media/audio/notifications/vega.ogg \
- $(LOCAL_PATH)/ringtones/ANDROMEDA.ogg:system/media/audio/ringtones/ANDROMEDA.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Andromeda.ogg:system/media/audio/ringtones/Andromeda.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Aquila.ogg:system/media/audio/ringtones/Aquila.ogg \
- $(LOCAL_PATH)/ringtones/ogg/ArgoNavis.ogg:system/media/audio/ringtones/ArgoNavis.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Atria.ogg:system/media/audio/ringtones/Atria.ogg \
- $(LOCAL_PATH)/ringtones/BOOTES.ogg:system/media/audio/ringtones/BOOTES.ogg \
- $(LOCAL_PATH)/newwavelabs/Backroad.ogg:system/media/audio/ringtones/Backroad.ogg \
- $(LOCAL_PATH)/newwavelabs/BeatPlucker.ogg:system/media/audio/ringtones/BeatPlucker.ogg \
- $(LOCAL_PATH)/newwavelabs/BentleyDubs.ogg:system/media/audio/ringtones/BentleyDubs.ogg \
- $(LOCAL_PATH)/newwavelabs/Big_Easy.ogg:system/media/audio/ringtones/Big_Easy.ogg \
- $(LOCAL_PATH)/newwavelabs/BirdLoop.ogg:system/media/audio/ringtones/BirdLoop.ogg \
- $(LOCAL_PATH)/newwavelabs/Bollywood.ogg:system/media/audio/ringtones/Bollywood.ogg \
- $(LOCAL_PATH)/newwavelabs/BussaMove.ogg:system/media/audio/ringtones/BussaMove.ogg \
- $(LOCAL_PATH)/ringtones/CANISMAJOR.ogg:system/media/audio/ringtones/CANISMAJOR.ogg \
- $(LOCAL_PATH)/ringtones/CASSIOPEIA.ogg:system/media/audio/ringtones/CASSIOPEIA.ogg \
- $(LOCAL_PATH)/newwavelabs/Cairo.ogg:system/media/audio/ringtones/Cairo.ogg \
- $(LOCAL_PATH)/newwavelabs/Calypso_Steel.ogg:system/media/audio/ringtones/Calypso_Steel.ogg \
- $(LOCAL_PATH)/ringtones/ogg/CanisMajor.ogg:system/media/audio/ringtones/CanisMajor.ogg \
- $(LOCAL_PATH)/newwavelabs/CaribbeanIce.ogg:system/media/audio/ringtones/CaribbeanIce.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Carina.ogg:system/media/audio/ringtones/Carina.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Centaurus.ogg:system/media/audio/ringtones/Centaurus.ogg \
- $(LOCAL_PATH)/newwavelabs/Champagne_Edition.ogg:system/media/audio/ringtones/Champagne_Edition.ogg \
- $(LOCAL_PATH)/newwavelabs/Club_Cubano.ogg:system/media/audio/ringtones/Club_Cubano.ogg \
- $(LOCAL_PATH)/newwavelabs/CrayonRock.ogg:system/media/audio/ringtones/CrayonRock.ogg \
- $(LOCAL_PATH)/newwavelabs/CrazyDream.ogg:system/media/audio/ringtones/CrazyDream.ogg \
- $(LOCAL_PATH)/newwavelabs/CurveBall.ogg:system/media/audio/ringtones/CurveBall.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Cygnus.ogg:system/media/audio/ringtones/Cygnus.ogg \
- $(LOCAL_PATH)/newwavelabs/DancinFool.ogg:system/media/audio/ringtones/DancinFool.ogg \
- $(LOCAL_PATH)/newwavelabs/Ding.ogg:system/media/audio/ringtones/Ding.ogg \
- $(LOCAL_PATH)/newwavelabs/DonMessWivIt.ogg:system/media/audio/ringtones/DonMessWivIt.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Draco.ogg:system/media/audio/ringtones/Draco.ogg \
- $(LOCAL_PATH)/newwavelabs/DreamTheme.ogg:system/media/audio/ringtones/DreamTheme.ogg \
- $(LOCAL_PATH)/newwavelabs/Eastern_Sky.ogg:system/media/audio/ringtones/Eastern_Sky.ogg \
- $(LOCAL_PATH)/newwavelabs/Enter_the_Nexus.ogg:system/media/audio/ringtones/Enter_the_Nexus.ogg \
- $(LOCAL_PATH)/ringtones/Eridani.ogg:system/media/audio/ringtones/Eridani.ogg \
- $(LOCAL_PATH)/newwavelabs/EtherShake.ogg:system/media/audio/ringtones/EtherShake.ogg \
- $(LOCAL_PATH)/ringtones/FreeFlight.ogg:system/media/audio/ringtones/FreeFlight.ogg \
- $(LOCAL_PATH)/newwavelabs/FriendlyGhost.ogg:system/media/audio/ringtones/FriendlyGhost.ogg \
- $(LOCAL_PATH)/newwavelabs/Funk_Yall.ogg:system/media/audio/ringtones/Funk_Yall.ogg \
- $(LOCAL_PATH)/newwavelabs/GameOverGuitar.ogg:system/media/audio/ringtones/GameOverGuitar.ogg \
- $(LOCAL_PATH)/newwavelabs/Gimme_Mo_Town.ogg:system/media/audio/ringtones/Gimme_Mo_Town.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Girtab.ogg:system/media/audio/ringtones/Girtab.ogg \
- $(LOCAL_PATH)/newwavelabs/Glacial_Groove.ogg:system/media/audio/ringtones/Glacial_Groove.ogg \
- $(LOCAL_PATH)/newwavelabs/Growl.ogg:system/media/audio/ringtones/Growl.ogg \
- $(LOCAL_PATH)/newwavelabs/HalfwayHome.ogg:system/media/audio/ringtones/HalfwayHome.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Hydra.ogg:system/media/audio/ringtones/Hydra.ogg \
- $(LOCAL_PATH)/newwavelabs/InsertCoin.ogg:system/media/audio/ringtones/InsertCoin.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Kuma.ogg:system/media/audio/ringtones/Kuma.ogg \
- $(LOCAL_PATH)/newwavelabs/LoopyLounge.ogg:system/media/audio/ringtones/LoopyLounge.ogg \
- $(LOCAL_PATH)/newwavelabs/LoveFlute.ogg:system/media/audio/ringtones/LoveFlute.ogg \
- $(LOCAL_PATH)/ringtones/Lyra.ogg:system/media/audio/ringtones/Lyra.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Machina.ogg:system/media/audio/ringtones/Machina.ogg \
- $(LOCAL_PATH)/newwavelabs/MidEvilJaunt.ogg:system/media/audio/ringtones/MidEvilJaunt.ogg \
- $(LOCAL_PATH)/newwavelabs/MildlyAlarming.ogg:system/media/audio/ringtones/MildlyAlarming.ogg \
- $(LOCAL_PATH)/newwavelabs/Nairobi.ogg:system/media/audio/ringtones/Nairobi.ogg \
- $(LOCAL_PATH)/newwavelabs/Nassau.ogg:system/media/audio/ringtones/Nassau.ogg \
- $(LOCAL_PATH)/newwavelabs/NewPlayer.ogg:system/media/audio/ringtones/NewPlayer.ogg \
- $(LOCAL_PATH)/newwavelabs/No_Limits.ogg:system/media/audio/ringtones/No_Limits.ogg \
- $(LOCAL_PATH)/newwavelabs/Noises1.ogg:system/media/audio/ringtones/Noises1.ogg \
- $(LOCAL_PATH)/newwavelabs/Noises2.ogg:system/media/audio/ringtones/Noises2.ogg \
- $(LOCAL_PATH)/newwavelabs/Noises3.ogg:system/media/audio/ringtones/Noises3.ogg \
- $(LOCAL_PATH)/newwavelabs/OrganDub.ogg:system/media/audio/ringtones/OrganDub.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Orion.ogg:system/media/audio/ringtones/Orion.ogg \
- $(LOCAL_PATH)/ringtones/PERSEUS.ogg:system/media/audio/ringtones/PERSEUS.ogg \
- $(LOCAL_PATH)/newwavelabs/Paradise_Island.ogg:system/media/audio/ringtones/Paradise_Island.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Pegasus.ogg:system/media/audio/ringtones/Pegasus.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Perseus.ogg:system/media/audio/ringtones/Perseus.ogg \
- $(LOCAL_PATH)/newwavelabs/Playa.ogg:system/media/audio/ringtones/Playa.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Pyxis.ogg:system/media/audio/ringtones/Pyxis.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Rasalas.ogg:system/media/audio/ringtones/Rasalas.ogg \
- $(LOCAL_PATH)/newwavelabs/Revelation.ogg:system/media/audio/ringtones/Revelation.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Rigel.ogg:system/media/audio/ringtones/Rigel.ogg \
- $(LOCAL_PATH)/Ring_Classic_02.ogg:system/media/audio/ringtones/Ring_Classic_02.ogg \
- $(LOCAL_PATH)/Ring_Digital_02.ogg:system/media/audio/ringtones/Ring_Digital_02.ogg \
- $(LOCAL_PATH)/Ring_Synth_02.ogg:system/media/audio/ringtones/Ring_Synth_02.ogg \
- $(LOCAL_PATH)/Ring_Synth_04.ogg:system/media/audio/ringtones/Ring_Synth_04.ogg \
- $(LOCAL_PATH)/newwavelabs/Road_Trip.ogg:system/media/audio/ringtones/Road_Trip.ogg \
- $(LOCAL_PATH)/newwavelabs/RomancingTheTone.ogg:system/media/audio/ringtones/RomancingTheTone.ogg \
- $(LOCAL_PATH)/newwavelabs/Safari.ogg:system/media/audio/ringtones/Safari.ogg \
- $(LOCAL_PATH)/newwavelabs/Savannah.ogg:system/media/audio/ringtones/Savannah.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Scarabaeus.ogg:system/media/audio/ringtones/Scarabaeus.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Sceptrum.ogg:system/media/audio/ringtones/Sceptrum.ogg \
- $(LOCAL_PATH)/newwavelabs/Seville.ogg:system/media/audio/ringtones/Seville.ogg \
- $(LOCAL_PATH)/newwavelabs/Shes_All_That.ogg:system/media/audio/ringtones/Shes_All_That.ogg \
- $(LOCAL_PATH)/newwavelabs/SilkyWay.ogg:system/media/audio/ringtones/SilkyWay.ogg \
- $(LOCAL_PATH)/newwavelabs/SitarVsSitar.ogg:system/media/audio/ringtones/SitarVsSitar.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Solarium.ogg:system/media/audio/ringtones/Solarium.ogg \
- $(LOCAL_PATH)/newwavelabs/SpringyJalopy.ogg:system/media/audio/ringtones/SpringyJalopy.ogg \
- $(LOCAL_PATH)/newwavelabs/Steppin_Out.ogg:system/media/audio/ringtones/Steppin_Out.ogg \
- $(LOCAL_PATH)/newwavelabs/Terminated.ogg:system/media/audio/ringtones/Terminated.ogg \
- $(LOCAL_PATH)/ringtones/Testudo.ogg:system/media/audio/ringtones/Testudo.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Themos.ogg:system/media/audio/ringtones/Themos.ogg \
- $(LOCAL_PATH)/newwavelabs/Third_Eye.ogg:system/media/audio/ringtones/Third_Eye.ogg \
- $(LOCAL_PATH)/newwavelabs/Thunderfoot.ogg:system/media/audio/ringtones/Thunderfoot.ogg \
- $(LOCAL_PATH)/newwavelabs/TwirlAway.ogg:system/media/audio/ringtones/TwirlAway.ogg \
- $(LOCAL_PATH)/ringtones/URSAMINOR.ogg:system/media/audio/ringtones/URSAMINOR.ogg \
- $(LOCAL_PATH)/ringtones/ogg/UrsaMinor.ogg:system/media/audio/ringtones/UrsaMinor.ogg \
- $(LOCAL_PATH)/newwavelabs/VeryAlarmed.ogg:system/media/audio/ringtones/VeryAlarmed.ogg \
- $(LOCAL_PATH)/ringtones/Vespa.ogg:system/media/audio/ringtones/Vespa.ogg \
- $(LOCAL_PATH)/newwavelabs/World.ogg:system/media/audio/ringtones/World.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Zeta.ogg:system/media/audio/ringtones/Zeta.ogg \
- $(LOCAL_PATH)/ringtones/hydra.ogg:system/media/audio/ringtones/hydra.ogg \
- $(LOCAL_PATH)/effects/ogg/Dock.ogg:system/media/audio/ui/Dock.ogg \
- $(LOCAL_PATH)/effects/ogg/Effect_Tick_48k.ogg:system/media/audio/ui/Effect_Tick.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressDelete_120_48k.ogg:system/media/audio/ui/KeypressDelete.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressReturn_120_48k.ogg:system/media/audio/ui/KeypressReturn.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressSpacebar_120_48k.ogg:system/media/audio/ui/KeypressSpacebar.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressStandard_120_48k.ogg:system/media/audio/ui/KeypressStandard.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressInvalid_120_48k.ogg:system/media/audio/ui/KeypressInvalid.ogg \
- $(LOCAL_PATH)/effects/ogg/Lock.ogg:system/media/audio/ui/Lock.ogg \
- $(LOCAL_PATH)/effects/ogg/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
- $(LOCAL_PATH)/effects/ogg/Undock.ogg:system/media/audio/ui/Undock.ogg \
- $(LOCAL_PATH)/effects/ogg/Unlock.ogg:system/media/audio/ui/Unlock.ogg \
- $(LOCAL_PATH)/effects/ogg/Trusted_48k.ogg:system/media/audio/ui/Trusted.ogg \
- $(LOCAL_PATH)/effects/ogg/VideoRecord_48k.ogg:system/media/audio/ui/VideoRecord.ogg \
- $(LOCAL_PATH)/effects/ogg/VideoStop_48k.ogg:system/media/audio/ui/VideoStop.ogg \
- $(LOCAL_PATH)/effects/ogg/WirelessChargingStarted.ogg:system/media/audio/ui/WirelessChargingStarted.ogg \
- $(LOCAL_PATH)/effects/ogg/camera_click_48k.ogg:system/media/audio/ui/camera_click.ogg \
- $(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
- $(LOCAL_PATH)/effects/ogg/ChargingStarted.ogg:system/media/audio/ui/ChargingStarted.ogg \
- $(LOCAL_PATH)/effects/ogg/InCallNotification.ogg:system/media/audio/ui/InCallNotification.ogg \
- $(LOCAL_PATH)/effects/ogg/NFCFailure.ogg:system/media/audio/ui/NFCFailure.ogg \
- $(LOCAL_PATH)/effects/ogg/NFCInitiated.ogg:system/media/audio/ui/NFCInitiated.ogg \
- $(LOCAL_PATH)/effects/ogg/NFCSuccess.ogg:system/media/audio/ui/NFCSuccess.ogg \
- $(LOCAL_PATH)/effects/ogg/NFCTransferComplete.ogg:system/media/audio/ui/NFCTransferComplete.ogg \
- $(LOCAL_PATH)/effects/ogg/NFCTransferInitiated.ogg:system/media/audio/ui/NFCTransferInitiated.ogg \
+ $(LOCAL_PATH)/Alarm_Beep_01.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Alarm_Beep_01.ogg \
+ $(LOCAL_PATH)/Alarm_Beep_02.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Alarm_Beep_02.ogg \
+ $(LOCAL_PATH)/Alarm_Beep_03.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Alarm_Beep_03.ogg \
+ $(LOCAL_PATH)/Alarm_Buzzer.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Alarm_Buzzer.ogg \
+ $(LOCAL_PATH)/Alarm_Classic.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Alarm_Classic.ogg \
+ $(LOCAL_PATH)/Alarm_Rooster_02.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Alarm_Rooster_02.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Argon.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Argon.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Barium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Barium.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Carbon.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Carbon.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Cesium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Cesium.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Fermium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Fermium.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Hassium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Hassium.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Helium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Helium.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Krypton.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Krypton.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Neon.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Neon.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Neptunium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Neptunium.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Nobelium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Nobelium.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Osmium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Osmium.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Oxygen.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Oxygen.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Platinum.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Platinum.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Plutonium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Plutonium.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Promethium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Promethium.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Scandium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Scandium.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Adara.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Adara.ogg \
+ $(LOCAL_PATH)/notifications/Aldebaran.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Aldebaran.ogg \
+ $(LOCAL_PATH)/notifications/Altair.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Altair.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Alya.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Alya.ogg \
+ $(LOCAL_PATH)/notifications/Antares.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Antares.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Antimony.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Antimony.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Arcturus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Arcturus.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Argon.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Argon.ogg \
+ $(LOCAL_PATH)/notifications/Beat_Box_Android.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Beat_Box_Android.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Bellatrix.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Bellatrix.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Beryllium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Beryllium.ogg \
+ $(LOCAL_PATH)/notifications/Betelgeuse.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Betelgeuse.ogg \
+ $(LOCAL_PATH)/newwavelabs/CaffeineSnake.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/CaffeineSnake.ogg \
+ $(LOCAL_PATH)/notifications/Canopus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Canopus.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Capella.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Capella.ogg \
+ $(LOCAL_PATH)/notifications/Castor.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Castor.ogg \
+ $(LOCAL_PATH)/notifications/ogg/CetiAlpha.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/CetiAlpha.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Cobalt.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Cobalt.ogg \
+ $(LOCAL_PATH)/notifications/Cricket.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Cricket.ogg \
+ $(LOCAL_PATH)/newwavelabs/DearDeer.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/DearDeer.ogg \
+ $(LOCAL_PATH)/notifications/Deneb.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Deneb.ogg \
+ $(LOCAL_PATH)/notifications/Doink.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Doink.ogg \
+ $(LOCAL_PATH)/newwavelabs/DontPanic.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/DontPanic.ogg \
+ $(LOCAL_PATH)/notifications/Drip.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Drip.ogg \
+ $(LOCAL_PATH)/notifications/Electra.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Electra.ogg \
+ $(LOCAL_PATH)/F1_MissedCall.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/F1_MissedCall.ogg \
+ $(LOCAL_PATH)/F1_New_MMS.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/F1_New_MMS.ogg \
+ $(LOCAL_PATH)/F1_New_SMS.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/F1_New_SMS.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Fluorine.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Fluorine.ogg \
+ $(LOCAL_PATH)/notifications/Fomalhaut.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Fomalhaut.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Gallium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Gallium.ogg \
+ $(LOCAL_PATH)/notifications/Heaven.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Heaven.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Helium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Helium.ogg \
+ $(LOCAL_PATH)/newwavelabs/Highwire.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Highwire.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Hojus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Hojus.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Iridium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Iridium.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Krypton.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Krypton.ogg \
+ $(LOCAL_PATH)/newwavelabs/KzurbSonar.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/KzurbSonar.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Lalande.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Lalande.ogg \
+ $(LOCAL_PATH)/notifications/Merope.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Merope.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Mira.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Mira.ogg \
+ $(LOCAL_PATH)/newwavelabs/OnTheHunt.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/OnTheHunt.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Palladium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Palladium.ogg \
+ $(LOCAL_PATH)/notifications/Plastic_Pipe.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Plastic_Pipe.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Polaris.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Polaris.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Pollux.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Pollux.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Procyon.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Procyon.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Proxima.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Proxima.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Radon.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Radon.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Rubidium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Rubidium.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Selenium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Selenium.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Shaula.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Shaula.ogg \
+ $(LOCAL_PATH)/notifications/Sirrah.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Sirrah.ogg \
+ $(LOCAL_PATH)/notifications/SpaceSeed.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/SpaceSeed.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Spica.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Spica.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Strontium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Strontium.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Syrma.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Syrma.ogg \
+ $(LOCAL_PATH)/notifications/TaDa.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/TaDa.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Talitha.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Talitha.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Tejat.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Tejat.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Thallium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Thallium.ogg \
+ $(LOCAL_PATH)/notifications/Tinkerbell.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Tinkerbell.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Upsilon.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Upsilon.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Vega.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Vega.ogg \
+ $(LOCAL_PATH)/newwavelabs/Voila.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Voila.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Xenon.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Xenon.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Zirconium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Zirconium.ogg \
+ $(LOCAL_PATH)/notifications/arcturus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/arcturus.ogg \
+ $(LOCAL_PATH)/notifications/moonbeam.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/moonbeam.ogg \
+ $(LOCAL_PATH)/notifications/pixiedust.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/pixiedust.ogg \
+ $(LOCAL_PATH)/notifications/pizzicato.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/pizzicato.ogg \
+ $(LOCAL_PATH)/notifications/regulus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/regulus.ogg \
+ $(LOCAL_PATH)/notifications/sirius.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/sirius.ogg \
+ $(LOCAL_PATH)/notifications/tweeters.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/tweeters.ogg \
+ $(LOCAL_PATH)/notifications/vega.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/vega.ogg \
+ $(LOCAL_PATH)/ringtones/ANDROMEDA.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/ANDROMEDA.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Andromeda.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Andromeda.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Aquila.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Aquila.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/ArgoNavis.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/ArgoNavis.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Atria.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Atria.ogg \
+ $(LOCAL_PATH)/ringtones/BOOTES.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/BOOTES.ogg \
+ $(LOCAL_PATH)/newwavelabs/Backroad.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Backroad.ogg \
+ $(LOCAL_PATH)/newwavelabs/BeatPlucker.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/BeatPlucker.ogg \
+ $(LOCAL_PATH)/newwavelabs/BentleyDubs.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/BentleyDubs.ogg \
+ $(LOCAL_PATH)/newwavelabs/Big_Easy.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Big_Easy.ogg \
+ $(LOCAL_PATH)/newwavelabs/BirdLoop.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/BirdLoop.ogg \
+ $(LOCAL_PATH)/newwavelabs/Bollywood.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Bollywood.ogg \
+ $(LOCAL_PATH)/newwavelabs/BussaMove.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/BussaMove.ogg \
+ $(LOCAL_PATH)/ringtones/CANISMAJOR.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/CANISMAJOR.ogg \
+ $(LOCAL_PATH)/ringtones/CASSIOPEIA.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/CASSIOPEIA.ogg \
+ $(LOCAL_PATH)/newwavelabs/Cairo.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Cairo.ogg \
+ $(LOCAL_PATH)/newwavelabs/Calypso_Steel.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Calypso_Steel.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/CanisMajor.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/CanisMajor.ogg \
+ $(LOCAL_PATH)/newwavelabs/CaribbeanIce.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/CaribbeanIce.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Carina.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Carina.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Centaurus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Centaurus.ogg \
+ $(LOCAL_PATH)/newwavelabs/Champagne_Edition.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Champagne_Edition.ogg \
+ $(LOCAL_PATH)/newwavelabs/Club_Cubano.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Club_Cubano.ogg \
+ $(LOCAL_PATH)/newwavelabs/CrayonRock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/CrayonRock.ogg \
+ $(LOCAL_PATH)/newwavelabs/CrazyDream.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/CrazyDream.ogg \
+ $(LOCAL_PATH)/newwavelabs/CurveBall.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/CurveBall.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Cygnus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Cygnus.ogg \
+ $(LOCAL_PATH)/newwavelabs/DancinFool.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/DancinFool.ogg \
+ $(LOCAL_PATH)/newwavelabs/Ding.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Ding.ogg \
+ $(LOCAL_PATH)/newwavelabs/DonMessWivIt.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/DonMessWivIt.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Draco.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Draco.ogg \
+ $(LOCAL_PATH)/newwavelabs/DreamTheme.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/DreamTheme.ogg \
+ $(LOCAL_PATH)/newwavelabs/Eastern_Sky.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Eastern_Sky.ogg \
+ $(LOCAL_PATH)/newwavelabs/Enter_the_Nexus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Enter_the_Nexus.ogg \
+ $(LOCAL_PATH)/ringtones/Eridani.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Eridani.ogg \
+ $(LOCAL_PATH)/newwavelabs/EtherShake.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/EtherShake.ogg \
+ $(LOCAL_PATH)/ringtones/FreeFlight.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/FreeFlight.ogg \
+ $(LOCAL_PATH)/newwavelabs/FriendlyGhost.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/FriendlyGhost.ogg \
+ $(LOCAL_PATH)/newwavelabs/Funk_Yall.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Funk_Yall.ogg \
+ $(LOCAL_PATH)/newwavelabs/GameOverGuitar.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/GameOverGuitar.ogg \
+ $(LOCAL_PATH)/newwavelabs/Gimme_Mo_Town.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Gimme_Mo_Town.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Girtab.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Girtab.ogg \
+ $(LOCAL_PATH)/newwavelabs/Glacial_Groove.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Glacial_Groove.ogg \
+ $(LOCAL_PATH)/newwavelabs/Growl.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Growl.ogg \
+ $(LOCAL_PATH)/newwavelabs/HalfwayHome.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/HalfwayHome.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Hydra.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Hydra.ogg \
+ $(LOCAL_PATH)/newwavelabs/InsertCoin.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/InsertCoin.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Kuma.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Kuma.ogg \
+ $(LOCAL_PATH)/newwavelabs/LoopyLounge.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/LoopyLounge.ogg \
+ $(LOCAL_PATH)/newwavelabs/LoveFlute.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/LoveFlute.ogg \
+ $(LOCAL_PATH)/ringtones/Lyra.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Lyra.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Machina.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Machina.ogg \
+ $(LOCAL_PATH)/newwavelabs/MidEvilJaunt.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/MidEvilJaunt.ogg \
+ $(LOCAL_PATH)/newwavelabs/MildlyAlarming.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/MildlyAlarming.ogg \
+ $(LOCAL_PATH)/newwavelabs/Nairobi.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Nairobi.ogg \
+ $(LOCAL_PATH)/newwavelabs/Nassau.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Nassau.ogg \
+ $(LOCAL_PATH)/newwavelabs/NewPlayer.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/NewPlayer.ogg \
+ $(LOCAL_PATH)/newwavelabs/No_Limits.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/No_Limits.ogg \
+ $(LOCAL_PATH)/newwavelabs/Noises1.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Noises1.ogg \
+ $(LOCAL_PATH)/newwavelabs/Noises2.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Noises2.ogg \
+ $(LOCAL_PATH)/newwavelabs/Noises3.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Noises3.ogg \
+ $(LOCAL_PATH)/newwavelabs/OrganDub.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/OrganDub.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Orion.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Orion.ogg \
+ $(LOCAL_PATH)/ringtones/PERSEUS.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/PERSEUS.ogg \
+ $(LOCAL_PATH)/newwavelabs/Paradise_Island.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Paradise_Island.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Pegasus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Pegasus.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Perseus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Perseus.ogg \
+ $(LOCAL_PATH)/newwavelabs/Playa.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Playa.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Pyxis.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Pyxis.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Rasalas.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Rasalas.ogg \
+ $(LOCAL_PATH)/newwavelabs/Revelation.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Revelation.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Rigel.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Rigel.ogg \
+ $(LOCAL_PATH)/Ring_Classic_02.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Ring_Classic_02.ogg \
+ $(LOCAL_PATH)/Ring_Digital_02.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Ring_Digital_02.ogg \
+ $(LOCAL_PATH)/Ring_Synth_02.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Ring_Synth_02.ogg \
+ $(LOCAL_PATH)/Ring_Synth_04.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Ring_Synth_04.ogg \
+ $(LOCAL_PATH)/newwavelabs/Road_Trip.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Road_Trip.ogg \
+ $(LOCAL_PATH)/newwavelabs/RomancingTheTone.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/RomancingTheTone.ogg \
+ $(LOCAL_PATH)/newwavelabs/Safari.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Safari.ogg \
+ $(LOCAL_PATH)/newwavelabs/Savannah.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Savannah.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Scarabaeus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Scarabaeus.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Sceptrum.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Sceptrum.ogg \
+ $(LOCAL_PATH)/newwavelabs/Seville.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Seville.ogg \
+ $(LOCAL_PATH)/newwavelabs/Shes_All_That.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Shes_All_That.ogg \
+ $(LOCAL_PATH)/newwavelabs/SilkyWay.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/SilkyWay.ogg \
+ $(LOCAL_PATH)/newwavelabs/SitarVsSitar.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/SitarVsSitar.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Solarium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Solarium.ogg \
+ $(LOCAL_PATH)/newwavelabs/SpringyJalopy.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/SpringyJalopy.ogg \
+ $(LOCAL_PATH)/newwavelabs/Steppin_Out.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Steppin_Out.ogg \
+ $(LOCAL_PATH)/newwavelabs/Terminated.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Terminated.ogg \
+ $(LOCAL_PATH)/ringtones/Testudo.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Testudo.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Themos.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Themos.ogg \
+ $(LOCAL_PATH)/newwavelabs/Third_Eye.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Third_Eye.ogg \
+ $(LOCAL_PATH)/newwavelabs/Thunderfoot.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Thunderfoot.ogg \
+ $(LOCAL_PATH)/newwavelabs/TwirlAway.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/TwirlAway.ogg \
+ $(LOCAL_PATH)/ringtones/URSAMINOR.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/URSAMINOR.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/UrsaMinor.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/UrsaMinor.ogg \
+ $(LOCAL_PATH)/newwavelabs/VeryAlarmed.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/VeryAlarmed.ogg \
+ $(LOCAL_PATH)/ringtones/Vespa.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Vespa.ogg \
+ $(LOCAL_PATH)/newwavelabs/World.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/World.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Zeta.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Zeta.ogg \
+ $(LOCAL_PATH)/ringtones/hydra.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/hydra.ogg \
+ $(LOCAL_PATH)/effects/ogg/Dock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Dock.ogg \
+ $(LOCAL_PATH)/effects/ogg/Effect_Tick_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Effect_Tick.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressDelete_120_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressDelete.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressReturn_120_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressReturn.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressSpacebar_120_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressSpacebar.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressStandard_120_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressStandard.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressInvalid_120_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressInvalid.ogg \
+ $(LOCAL_PATH)/effects/ogg/Lock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Lock.ogg \
+ $(LOCAL_PATH)/effects/ogg/LowBattery.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/LowBattery.ogg \
+ $(LOCAL_PATH)/effects/ogg/Undock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Undock.ogg \
+ $(LOCAL_PATH)/effects/ogg/Unlock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Unlock.ogg \
+ $(LOCAL_PATH)/effects/ogg/Trusted_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Trusted.ogg \
+ $(LOCAL_PATH)/effects/ogg/VideoRecord_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/ogg/VideoStop_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/VideoStop.ogg \
+ $(LOCAL_PATH)/effects/ogg/WirelessChargingStarted.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/WirelessChargingStarted.ogg \
+ $(LOCAL_PATH)/effects/ogg/camera_click_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/camera_click.ogg \
+ $(LOCAL_PATH)/effects/ogg/camera_focus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/camera_focus.ogg \
+ $(LOCAL_PATH)/effects/ogg/ChargingStarted.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/ChargingStarted.ogg \
+ $(LOCAL_PATH)/effects/ogg/InCallNotification.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/InCallNotification.ogg \
+ $(LOCAL_PATH)/effects/ogg/NFCFailure.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/NFCFailure.ogg \
+ $(LOCAL_PATH)/effects/ogg/NFCInitiated.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/NFCInitiated.ogg \
+ $(LOCAL_PATH)/effects/ogg/NFCSuccess.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/NFCSuccess.ogg \
+ $(LOCAL_PATH)/effects/ogg/NFCTransferComplete.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/NFCTransferComplete.ogg \
+ $(LOCAL_PATH)/effects/ogg/NFCTransferInitiated.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/NFCTransferInitiated.ogg \
diff --git a/data/sounds/AudioPackage10.mk b/data/sounds/AudioPackage10.mk
index 72aa7fe..699dbd6 100644
--- a/data/sounds/AudioPackage10.mk
+++ b/data/sounds/AudioPackage10.mk
@@ -8,63 +8,63 @@
LOCAL_PATH:= frameworks/base/data/sounds
PRODUCT_COPY_FILES += \
- $(LOCAL_PATH)/alarms/ogg/Argon.ogg:system/media/audio/alarms/Argon.ogg \
- $(LOCAL_PATH)/alarms/ogg/Carbon.ogg:system/media/audio/alarms/Carbon.ogg \
- $(LOCAL_PATH)/alarms/ogg/Helium.ogg:system/media/audio/alarms/Helium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Krypton.ogg:system/media/audio/alarms/Krypton.ogg \
- $(LOCAL_PATH)/alarms/ogg/Neon.ogg:system/media/audio/alarms/Neon.ogg \
- $(LOCAL_PATH)/alarms/ogg/Oxygen.ogg:system/media/audio/alarms/Oxygen.ogg \
- $(LOCAL_PATH)/alarms/ogg/Osmium.ogg:system/media/audio/alarms/Osmium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Platinum.ogg:system/media/audio/alarms/Platinum.ogg \
- $(LOCAL_PATH)/effects/ogg/Effect_Tick_48k.ogg:system/media/audio/ui/Effect_Tick.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressStandard_48k.ogg:system/media/audio/ui/KeypressStandard.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressSpacebar_48k.ogg:system/media/audio/ui/KeypressSpacebar.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressDelete_48k.ogg:system/media/audio/ui/KeypressDelete.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressInvalid_48k.ogg:system/media/audio/ui/KeypressInvalid.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressReturn_48k.ogg:system/media/audio/ui/KeypressReturn.ogg \
- $(LOCAL_PATH)/effects/material/ogg/VideoRecord_48k.ogg:system/media/audio/ui/VideoRecord.ogg \
- $(LOCAL_PATH)/effects/material/ogg/VideoStop_48k.ogg:system/media/audio/ui/VideoStop.ogg \
- $(LOCAL_PATH)/effects/material/ogg/camera_click_48k.ogg:system/media/audio/ui/camera_click.ogg \
- $(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
- $(LOCAL_PATH)/effects/material/ogg/LowBattery_48k.ogg:system/media/audio/ui/LowBattery.ogg \
- $(LOCAL_PATH)/effects/ogg/Dock.ogg:system/media/audio/ui/Dock.ogg \
- $(LOCAL_PATH)/effects/ogg/Undock.ogg:system/media/audio/ui/Undock.ogg \
- $(LOCAL_PATH)/effects/ogg/Lock_48k.ogg:system/media/audio/ui/Lock.ogg \
- $(LOCAL_PATH)/effects/ogg/Unlock_48k.ogg:system/media/audio/ui/Unlock.ogg \
- $(LOCAL_PATH)/effects/ogg/Trusted_48k.ogg:system/media/audio/ui/Trusted.ogg \
- $(LOCAL_PATH)/effects/ogg/ChargingStarted.ogg:system/media/audio/ui/ChargingStarted.ogg \
- $(LOCAL_PATH)/effects/ogg/InCallNotification.ogg:system/media/audio/ui/InCallNotification.ogg \
- $(LOCAL_PATH)/effects/material/ogg/WirelessChargingStarted_48k.ogg:system/media/audio/ui/WirelessChargingStarted.ogg \
- $(LOCAL_PATH)/notifications/ogg/Adara.ogg:system/media/audio/notifications/Adara.ogg \
- $(LOCAL_PATH)/notifications/ogg/Alya.ogg:system/media/audio/notifications/Alya.ogg \
- $(LOCAL_PATH)/notifications/ogg/Arcturus.ogg:system/media/audio/notifications/Arcturus.ogg \
- $(LOCAL_PATH)/notifications/ogg/Capella.ogg:system/media/audio/notifications/Capella.ogg \
- $(LOCAL_PATH)/notifications/ogg/CetiAlpha.ogg:system/media/audio/notifications/CetiAlpha.ogg \
- $(LOCAL_PATH)/notifications/ogg/Hojus.ogg:system/media/audio/notifications/Hojus.ogg \
- $(LOCAL_PATH)/notifications/ogg/Mira.ogg:system/media/audio/notifications/Mira.ogg \
- $(LOCAL_PATH)/notifications/ogg/Pollux.ogg:system/media/audio/notifications/Pollux.ogg \
- $(LOCAL_PATH)/notifications/ogg/Procyon.ogg:system/media/audio/notifications/Procyon.ogg \
- $(LOCAL_PATH)/notifications/ogg/Shaula.ogg:system/media/audio/notifications/Shaula.ogg \
- $(LOCAL_PATH)/notifications/ogg/Spica.ogg:system/media/audio/notifications/Spica.ogg \
- $(LOCAL_PATH)/notifications/ogg/Syrma.ogg:system/media/audio/notifications/Syrma.ogg \
- $(LOCAL_PATH)/notifications/ogg/Talitha.ogg:system/media/audio/notifications/Talitha.ogg \
- $(LOCAL_PATH)/notifications/ogg/Tejat.ogg:system/media/audio/notifications/Tejat.ogg \
- $(LOCAL_PATH)/notifications/ogg/Vega.ogg:system/media/audio/notifications/Vega.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Andromeda.ogg:system/media/audio/ringtones/Andromeda.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Aquila.ogg:system/media/audio/ringtones/Aquila.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Atria.ogg:system/media/audio/ringtones/Atria.ogg \
- $(LOCAL_PATH)/ringtones/ogg/ArgoNavis.ogg:system/media/audio/ringtones/ArgoNavis.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Centaurus.ogg:system/media/audio/ringtones/Centaurus.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Girtab.ogg:system/media/audio/ringtones/Girtab.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Hydra.ogg:system/media/audio/ringtones/Hydra.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Kuma.ogg:system/media/audio/ringtones/Kuma.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Machina.ogg:system/media/audio/ringtones/Machina.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Orion.ogg:system/media/audio/ringtones/Orion.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Pegasus.ogg:system/media/audio/ringtones/Pegasus.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Pyxis.ogg:system/media/audio/ringtones/Pyxis.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Rasalas.ogg:system/media/audio/ringtones/Rasalas.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Scarabaeus.ogg:system/media/audio/ringtones/Scarabaeus.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Sceptrum.ogg:system/media/audio/ringtones/Sceptrum.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Solarium.ogg:system/media/audio/ringtones/Solarium.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Themos.ogg:system/media/audio/ringtones/Themos.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Zeta.ogg:system/media/audio/ringtones/Zeta.ogg
+ $(LOCAL_PATH)/alarms/ogg/Argon.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Argon.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Carbon.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Carbon.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Helium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Helium.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Krypton.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Krypton.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Neon.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Neon.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Oxygen.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Oxygen.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Osmium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Osmium.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Platinum.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Platinum.ogg \
+ $(LOCAL_PATH)/effects/ogg/Effect_Tick_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Effect_Tick.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressStandard_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressStandard.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressSpacebar_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressSpacebar.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressDelete_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressDelete.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressInvalid_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressInvalid.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressReturn_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressReturn.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/VideoRecord_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/VideoStop_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/VideoStop.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/camera_click_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/camera_click.ogg \
+ $(LOCAL_PATH)/effects/ogg/camera_focus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/camera_focus.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/LowBattery_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/LowBattery.ogg \
+ $(LOCAL_PATH)/effects/ogg/Dock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Dock.ogg \
+ $(LOCAL_PATH)/effects/ogg/Undock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Undock.ogg \
+ $(LOCAL_PATH)/effects/ogg/Lock_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Lock.ogg \
+ $(LOCAL_PATH)/effects/ogg/Unlock_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Unlock.ogg \
+ $(LOCAL_PATH)/effects/ogg/Trusted_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Trusted.ogg \
+ $(LOCAL_PATH)/effects/ogg/ChargingStarted.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/ChargingStarted.ogg \
+ $(LOCAL_PATH)/effects/ogg/InCallNotification.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/InCallNotification.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/WirelessChargingStarted_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/WirelessChargingStarted.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Adara.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Adara.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Alya.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Alya.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Arcturus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Arcturus.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Capella.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Capella.ogg \
+ $(LOCAL_PATH)/notifications/ogg/CetiAlpha.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/CetiAlpha.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Hojus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Hojus.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Mira.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Mira.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Pollux.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Pollux.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Procyon.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Procyon.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Shaula.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Shaula.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Spica.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Spica.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Syrma.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Syrma.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Talitha.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Talitha.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Tejat.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Tejat.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Vega.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Vega.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Andromeda.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Andromeda.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Aquila.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Aquila.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Atria.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Atria.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/ArgoNavis.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/ArgoNavis.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Centaurus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Centaurus.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Girtab.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Girtab.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Hydra.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Hydra.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Kuma.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Kuma.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Machina.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Machina.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Orion.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Orion.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Pegasus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Pegasus.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Pyxis.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Pyxis.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Rasalas.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Rasalas.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Scarabaeus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Scarabaeus.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Sceptrum.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Sceptrum.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Solarium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Solarium.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Themos.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Themos.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Zeta.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Zeta.ogg
diff --git a/data/sounds/AudioPackage11.mk b/data/sounds/AudioPackage11.mk
index 665ce52..99dfd0a 100644
--- a/data/sounds/AudioPackage11.mk
+++ b/data/sounds/AudioPackage11.mk
@@ -8,63 +8,63 @@
LOCAL_PATH:= frameworks/base/data/sounds
PRODUCT_COPY_FILES += \
- $(LOCAL_PATH)/alarms/ogg/Argon.ogg:system/media/audio/alarms/Argon.ogg \
- $(LOCAL_PATH)/alarms/ogg/Carbon.ogg:system/media/audio/alarms/Carbon.ogg \
- $(LOCAL_PATH)/alarms/ogg/Helium.ogg:system/media/audio/alarms/Helium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Krypton.ogg:system/media/audio/alarms/Krypton.ogg \
- $(LOCAL_PATH)/alarms/ogg/Neon.ogg:system/media/audio/alarms/Neon.ogg \
- $(LOCAL_PATH)/alarms/ogg/Oxygen.ogg:system/media/audio/alarms/Oxygen.ogg \
- $(LOCAL_PATH)/alarms/ogg/Osmium.ogg:system/media/audio/alarms/Osmium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Platinum.ogg:system/media/audio/alarms/Platinum.ogg \
- $(LOCAL_PATH)/effects/ogg/Effect_Tick_48k.ogg:system/media/audio/ui/Effect_Tick.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressStandard_48k.ogg:system/media/audio/ui/KeypressStandard.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressSpacebar_48k.ogg:system/media/audio/ui/KeypressSpacebar.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressDelete_48k.ogg:system/media/audio/ui/KeypressDelete.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressInvalid_48k.ogg:system/media/audio/ui/KeypressInvalid.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressReturn_48k.ogg:system/media/audio/ui/KeypressReturn.ogg \
- $(LOCAL_PATH)/effects/material/ogg/VideoRecord_48k.ogg:system/media/audio/ui/VideoRecord.ogg \
- $(LOCAL_PATH)/effects/material/ogg/VideoStop_48k.ogg:system/media/audio/ui/VideoStop.ogg \
- $(LOCAL_PATH)/effects/material/ogg/camera_click_48k.ogg:system/media/audio/ui/camera_click.ogg \
- $(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
- $(LOCAL_PATH)/effects/material/ogg/LowBattery_48k.ogg:system/media/audio/ui/LowBattery.ogg \
- $(LOCAL_PATH)/effects/ogg/Dock.ogg:system/media/audio/ui/Dock.ogg \
- $(LOCAL_PATH)/effects/ogg/Undock.ogg:system/media/audio/ui/Undock.ogg \
- $(LOCAL_PATH)/effects/ogg/Lock_48k.ogg:system/media/audio/ui/Lock.ogg \
- $(LOCAL_PATH)/effects/ogg/Unlock_48k.ogg:system/media/audio/ui/Unlock.ogg \
- $(LOCAL_PATH)/effects/ogg/Trusted_48k.ogg:system/media/audio/ui/Trusted.ogg \
- $(LOCAL_PATH)/effects/ogg/ChargingStarted.ogg:system/media/audio/ui/ChargingStarted.ogg \.
- $(LOCAL_PATH)/effects/ogg/InCallNotification.ogg:system/media/audio/ui/InCallNotification.ogg \
- $(LOCAL_PATH)/effects/material/ogg/WirelessChargingStarted_48k.ogg:system/media/audio/ui/WirelessChargingStarted.ogg \
- $(LOCAL_PATH)/notifications/ogg/Adara.ogg:system/media/audio/notifications/Adara.ogg \
- $(LOCAL_PATH)/notifications/ogg/Alya.ogg:system/media/audio/notifications/Alya.ogg \
- $(LOCAL_PATH)/notifications/ogg/Arcturus.ogg:system/media/audio/notifications/Arcturus.ogg \
- $(LOCAL_PATH)/notifications/ogg/Capella.ogg:system/media/audio/notifications/Capella.ogg \
- $(LOCAL_PATH)/notifications/ogg/CetiAlpha.ogg:system/media/audio/notifications/CetiAlpha.ogg \
- $(LOCAL_PATH)/notifications/ogg/Hojus.ogg:system/media/audio/notifications/Hojus.ogg \
- $(LOCAL_PATH)/notifications/ogg/Mira.ogg:system/media/audio/notifications/Mira.ogg \
- $(LOCAL_PATH)/notifications/ogg/Pollux.ogg:system/media/audio/notifications/Pollux.ogg \
- $(LOCAL_PATH)/notifications/ogg/Procyon.ogg:system/media/audio/notifications/Procyon.ogg \
- $(LOCAL_PATH)/notifications/ogg/Shaula.ogg:system/media/audio/notifications/Shaula.ogg \
- $(LOCAL_PATH)/notifications/ogg/Spica.ogg:system/media/audio/notifications/Spica.ogg \
- $(LOCAL_PATH)/notifications/ogg/Syrma.ogg:system/media/audio/notifications/Syrma.ogg \
- $(LOCAL_PATH)/notifications/ogg/Talitha.ogg:system/media/audio/notifications/Talitha.ogg \
- $(LOCAL_PATH)/notifications/ogg/Tejat_proc48.ogg:system/media/audio/notifications/Tejat.ogg \
- $(LOCAL_PATH)/notifications/ogg/Vega.ogg:system/media/audio/notifications/Vega.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Andromeda.ogg:system/media/audio/ringtones/Andromeda.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Aquila.ogg:system/media/audio/ringtones/Aquila.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Atria.ogg:system/media/audio/ringtones/Atria.ogg \
- $(LOCAL_PATH)/ringtones/ogg/ArgoNavis.ogg:system/media/audio/ringtones/ArgoNavis.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Centaurus.ogg:system/media/audio/ringtones/Centaurus.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Girtab.ogg:system/media/audio/ringtones/Girtab.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Hydra.ogg:system/media/audio/ringtones/Hydra.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Kuma.ogg:system/media/audio/ringtones/Kuma.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Machina.ogg:system/media/audio/ringtones/Machina.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Orion.ogg:system/media/audio/ringtones/Orion.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Pegasus.ogg:system/media/audio/ringtones/Pegasus.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Pyxis.ogg:system/media/audio/ringtones/Pyxis.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Rasalas.ogg:system/media/audio/ringtones/Rasalas.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Scarabaeus.ogg:system/media/audio/ringtones/Scarabaeus.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Sceptrum.ogg:system/media/audio/ringtones/Sceptrum.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Solarium.ogg:system/media/audio/ringtones/Solarium.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Themos.ogg:system/media/audio/ringtones/Themos.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Zeta.ogg:system/media/audio/ringtones/Zeta.ogg
+ $(LOCAL_PATH)/alarms/ogg/Argon.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Argon.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Carbon.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Carbon.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Helium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Helium.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Krypton.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Krypton.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Neon.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Neon.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Oxygen.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Oxygen.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Osmium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Osmium.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Platinum.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Platinum.ogg \
+ $(LOCAL_PATH)/effects/ogg/Effect_Tick_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Effect_Tick.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressStandard_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressStandard.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressSpacebar_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressSpacebar.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressDelete_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressDelete.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressInvalid_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressInvalid.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressReturn_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressReturn.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/VideoRecord_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/VideoStop_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/VideoStop.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/camera_click_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/camera_click.ogg \
+ $(LOCAL_PATH)/effects/ogg/camera_focus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/camera_focus.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/LowBattery_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/LowBattery.ogg \
+ $(LOCAL_PATH)/effects/ogg/Dock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Dock.ogg \
+ $(LOCAL_PATH)/effects/ogg/Undock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Undock.ogg \
+ $(LOCAL_PATH)/effects/ogg/Lock_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Lock.ogg \
+ $(LOCAL_PATH)/effects/ogg/Unlock_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Unlock.ogg \
+ $(LOCAL_PATH)/effects/ogg/Trusted_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Trusted.ogg \
+ $(LOCAL_PATH)/effects/ogg/ChargingStarted.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/ChargingStarted.ogg \.
+ $(LOCAL_PATH)/effects/ogg/InCallNotification.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/InCallNotification.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/WirelessChargingStarted_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/WirelessChargingStarted.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Adara.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Adara.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Alya.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Alya.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Arcturus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Arcturus.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Capella.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Capella.ogg \
+ $(LOCAL_PATH)/notifications/ogg/CetiAlpha.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/CetiAlpha.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Hojus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Hojus.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Mira.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Mira.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Pollux.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Pollux.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Procyon.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Procyon.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Shaula.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Shaula.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Spica.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Spica.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Syrma.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Syrma.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Talitha.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Talitha.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Tejat_proc48.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Tejat.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Vega.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Vega.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Andromeda.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Andromeda.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Aquila.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Aquila.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Atria.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Atria.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/ArgoNavis.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/ArgoNavis.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Centaurus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Centaurus.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Girtab.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Girtab.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Hydra.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Hydra.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Kuma.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Kuma.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Machina.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Machina.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Orion.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Orion.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Pegasus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Pegasus.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Pyxis.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Pyxis.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Rasalas.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Rasalas.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Scarabaeus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Scarabaeus.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Sceptrum.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Sceptrum.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Solarium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Solarium.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Themos.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Themos.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Zeta.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Zeta.ogg
diff --git a/data/sounds/AudioPackage12.mk b/data/sounds/AudioPackage12.mk
index 44a8f9e..6159a89 100644
--- a/data/sounds/AudioPackage12.mk
+++ b/data/sounds/AudioPackage12.mk
@@ -16,15 +16,15 @@
MATERIAL_EFFECT_FILES := camera_click VideoRecord LowBattery WirelessChargingStarted VideoStop
PRODUCT_COPY_FILES += $(foreach fn,$(ALARM_FILES),\
- $(LOCAL_PATH)/alarms/ogg/$(fn).ogg:system/media/audio/alarms/$(fn).ogg)
+ $(LOCAL_PATH)/alarms/ogg/$(fn).ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/$(fn).ogg)
PRODUCT_COPY_FILES += $(foreach fn,$(NOTIFICATION_FILES),\
- $(LOCAL_PATH)/notifications/ogg/$(fn).ogg:system/media/audio/notifications/$(fn).ogg)
+ $(LOCAL_PATH)/notifications/ogg/$(fn).ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/$(fn).ogg)
PRODUCT_COPY_FILES += $(foreach fn,$(RINGTONE_FILES),\
- $(LOCAL_PATH)/ringtones/ogg/$(fn).ogg:system/media/audio/ringtones/$(fn).ogg)
+ $(LOCAL_PATH)/ringtones/ogg/$(fn).ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/$(fn).ogg)
PRODUCT_COPY_FILES += $(foreach fn,$(EFFECT_FILES),\
- $(LOCAL_PATH)/effects/ogg/$(fn).ogg:system/media/audio/ui/$(fn).ogg)
+ $(LOCAL_PATH)/effects/ogg/$(fn).ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/$(fn).ogg)
PRODUCT_COPY_FILES += $(foreach fn,$(MATERIAL_EFFECT_FILES),\
- $(LOCAL_PATH)/effects/material/ogg/$(fn).ogg:system/media/audio/ui/$(fn).ogg)
+ $(LOCAL_PATH)/effects/material/ogg/$(fn).ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/$(fn).ogg)
diff --git a/data/sounds/AudioPackage12_48.mk b/data/sounds/AudioPackage12_48.mk
index 09fab04..2899cd1 100644
--- a/data/sounds/AudioPackage12_48.mk
+++ b/data/sounds/AudioPackage12_48.mk
@@ -17,21 +17,21 @@
# Alarms not yet available in 48 kHz
PRODUCT_COPY_FILES += $(foreach fn,$(ALARM_FILES),\
- $(LOCAL_PATH)/alarms/ogg/$(fn).ogg:system/media/audio/alarms/$(fn).ogg)
+ $(LOCAL_PATH)/alarms/ogg/$(fn).ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/$(fn).ogg)
PRODUCT_COPY_FILES += $(foreach fn,$(NOTIFICATION_FILES),\
- $(LOCAL_PATH)/notifications/ogg/$(fn)_48k.ogg:system/media/audio/notifications/$(fn).ogg)
+ $(LOCAL_PATH)/notifications/ogg/$(fn)_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/$(fn).ogg)
PRODUCT_COPY_FILES += $(foreach fn,$(RINGTONE_FILES),\
- $(LOCAL_PATH)/ringtones/ogg/$(fn)_48k.ogg:system/media/audio/ringtones/$(fn).ogg)
+ $(LOCAL_PATH)/ringtones/ogg/$(fn)_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/$(fn).ogg)
PRODUCT_COPY_FILES += $(foreach fn,$(EFFECT_FILES),\
- $(LOCAL_PATH)/effects/ogg/$(fn)_48k.ogg:system/media/audio/ui/$(fn).ogg)
+ $(LOCAL_PATH)/effects/ogg/$(fn)_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/$(fn).ogg)
PRODUCT_COPY_FILES += $(foreach fn,$(MATERIAL_EFFECT_FILES),\
- $(LOCAL_PATH)/effects/material/ogg/$(fn)_48k.ogg:system/media/audio/ui/$(fn).ogg)
+ $(LOCAL_PATH)/effects/material/ogg/$(fn)_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/$(fn).ogg)
# no gold-plated version yet
PRODUCT_COPY_FILES += \
- $(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
- $(LOCAL_PATH)/effects/ogg/Dock.ogg:system/media/audio/ui/Dock.ogg \
- $(LOCAL_PATH)/effects/ogg/Undock.ogg:system/media/audio/ui/Undock.ogg
+ $(LOCAL_PATH)/effects/ogg/camera_focus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/camera_focus.ogg \
+ $(LOCAL_PATH)/effects/ogg/Dock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Dock.ogg \
+ $(LOCAL_PATH)/effects/ogg/Undock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Undock.ogg
diff --git a/data/sounds/AudioPackage13.mk b/data/sounds/AudioPackage13.mk
index de4ee04..9423c0b 100644
--- a/data/sounds/AudioPackage13.mk
+++ b/data/sounds/AudioPackage13.mk
@@ -17,15 +17,15 @@
MATERIAL_EFFECT_FILES := camera_click VideoRecord WirelessChargingStarted LowBattery VideoStop
PRODUCT_COPY_FILES += $(foreach fn,$(ALARM_FILES),\
- $(LOCAL_PATH)/alarms/material/ogg/$(fn).ogg:system/media/audio/alarms/$(fn).ogg)
+ $(LOCAL_PATH)/alarms/material/ogg/$(fn).ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/$(fn).ogg)
PRODUCT_COPY_FILES += $(foreach fn,$(NOTIFICATION_FILES),\
- $(LOCAL_PATH)/notifications/material/ogg/$(fn).ogg:system/media/audio/notifications/$(fn).ogg)
+ $(LOCAL_PATH)/notifications/material/ogg/$(fn).ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/$(fn).ogg)
PRODUCT_COPY_FILES += $(foreach fn,$(RINGTONE_FILES),\
- $(LOCAL_PATH)/ringtones/material/ogg/$(fn).ogg:system/media/audio/ringtones/$(fn).ogg)
+ $(LOCAL_PATH)/ringtones/material/ogg/$(fn).ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/$(fn).ogg)
PRODUCT_COPY_FILES += $(foreach fn,$(EFFECT_FILES),\
- $(LOCAL_PATH)/effects/ogg/$(fn).ogg:system/media/audio/ui/$(fn).ogg)
+ $(LOCAL_PATH)/effects/ogg/$(fn).ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/$(fn).ogg)
PRODUCT_COPY_FILES += $(foreach fn,$(MATERIAL_EFFECT_FILES),\
- $(LOCAL_PATH)/effects/material/ogg/$(fn).ogg:system/media/audio/ui/$(fn).ogg)
+ $(LOCAL_PATH)/effects/material/ogg/$(fn).ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/$(fn).ogg)
diff --git a/data/sounds/AudioPackage13_48.mk b/data/sounds/AudioPackage13_48.mk
index 889d581..806c4e2 100644
--- a/data/sounds/AudioPackage13_48.mk
+++ b/data/sounds/AudioPackage13_48.mk
@@ -17,21 +17,21 @@
MATERIAL_EFFECT_FILES := camera_click VideoRecord WirelessChargingStarted LowBattery VideoStop
PRODUCT_COPY_FILES += $(foreach fn,$(ALARM_FILES),\
- $(LOCAL_PATH)/alarms/material/ogg/$(fn)_48k.ogg:system/media/audio/alarms/$(fn).ogg)
+ $(LOCAL_PATH)/alarms/material/ogg/$(fn)_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/$(fn).ogg)
PRODUCT_COPY_FILES += $(foreach fn,$(NOTIFICATION_FILES),\
- $(LOCAL_PATH)/notifications/material/ogg/$(fn)_48k.ogg:system/media/audio/notifications/$(fn).ogg)
+ $(LOCAL_PATH)/notifications/material/ogg/$(fn)_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/$(fn).ogg)
PRODUCT_COPY_FILES += $(foreach fn,$(RINGTONE_FILES),\
- $(LOCAL_PATH)/ringtones/material/ogg/$(fn)_48k.ogg:system/media/audio/ringtones/$(fn).ogg)
+ $(LOCAL_PATH)/ringtones/material/ogg/$(fn)_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/$(fn).ogg)
PRODUCT_COPY_FILES += $(foreach fn,$(EFFECT_FILES),\
- $(LOCAL_PATH)/effects/ogg/$(fn)_48k.ogg:system/media/audio/ui/$(fn).ogg)
+ $(LOCAL_PATH)/effects/ogg/$(fn)_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/$(fn).ogg)
PRODUCT_COPY_FILES += $(foreach fn,$(MATERIAL_EFFECT_FILES),\
- $(LOCAL_PATH)/effects/material/ogg/$(fn)_48k.ogg:system/media/audio/ui/$(fn).ogg)
+ $(LOCAL_PATH)/effects/material/ogg/$(fn)_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/$(fn).ogg)
# no gold-plated version yet
PRODUCT_COPY_FILES += \
- $(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
- $(LOCAL_PATH)/effects/ogg/Dock.ogg:system/media/audio/ui/Dock.ogg \
- $(LOCAL_PATH)/effects/ogg/Undock.ogg:system/media/audio/ui/Undock.ogg
+ $(LOCAL_PATH)/effects/ogg/camera_focus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/camera_focus.ogg \
+ $(LOCAL_PATH)/effects/ogg/Dock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Dock.ogg \
+ $(LOCAL_PATH)/effects/ogg/Undock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Undock.ogg
diff --git a/data/sounds/AudioPackage14.mk b/data/sounds/AudioPackage14.mk
index c903a2b..3d161aa 100644
--- a/data/sounds/AudioPackage14.mk
+++ b/data/sounds/AudioPackage14.mk
@@ -18,15 +18,15 @@
MATERIAL_EFFECT_FILES := camera_click VideoRecord WirelessChargingStarted LowBattery VideoStop
PRODUCT_COPY_FILES += $(foreach fn,$(ALARM_FILES),\
- $(LOCAL_PATH)/alarms/material/ogg/$(fn).ogg:system/media/audio/alarms/$(fn).ogg)
+ $(LOCAL_PATH)/alarms/material/ogg/$(fn).ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/$(fn).ogg)
PRODUCT_COPY_FILES += $(foreach fn,$(NOTIFICATION_FILES),\
- $(LOCAL_PATH)/notifications/material/ogg/$(fn).ogg:system/media/audio/notifications/$(fn).ogg)
+ $(LOCAL_PATH)/notifications/material/ogg/$(fn).ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/$(fn).ogg)
PRODUCT_COPY_FILES += $(foreach fn,$(RINGTONE_FILES),\
- $(LOCAL_PATH)/ringtones/material/ogg/$(fn).ogg:system/media/audio/ringtones/$(fn).ogg)
+ $(LOCAL_PATH)/ringtones/material/ogg/$(fn).ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/$(fn).ogg)
PRODUCT_COPY_FILES += $(foreach fn,$(EFFECT_FILES),\
- $(LOCAL_PATH)/effects/ogg/$(fn).ogg:system/media/audio/ui/$(fn).ogg)
+ $(LOCAL_PATH)/effects/ogg/$(fn).ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/$(fn).ogg)
PRODUCT_COPY_FILES += $(foreach fn,$(MATERIAL_EFFECT_FILES),\
- $(LOCAL_PATH)/effects/material/ogg/$(fn).ogg:system/media/audio/ui/$(fn).ogg)
+ $(LOCAL_PATH)/effects/material/ogg/$(fn).ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/$(fn).ogg)
diff --git a/data/sounds/AudioPackage2.mk b/data/sounds/AudioPackage2.mk
index 40319c4..bc4e8fb 100644
--- a/data/sounds/AudioPackage2.mk
+++ b/data/sounds/AudioPackage2.mk
@@ -10,98 +10,98 @@
LOCAL_PATH:= frameworks/base/data/sounds
PRODUCT_COPY_FILES += \
- $(LOCAL_PATH)/F1_MissedCall.ogg:system/media/audio/notifications/F1_MissedCall.ogg \
- $(LOCAL_PATH)/F1_New_MMS.ogg:system/media/audio/notifications/F1_New_MMS.ogg \
- $(LOCAL_PATH)/F1_New_SMS.ogg:system/media/audio/notifications/F1_New_SMS.ogg \
- $(LOCAL_PATH)/Alarm_Buzzer.ogg:system/media/audio/alarms/Alarm_Buzzer.ogg \
- $(LOCAL_PATH)/Alarm_Beep_01.ogg:system/media/audio/alarms/Alarm_Beep_01.ogg \
- $(LOCAL_PATH)/Alarm_Beep_02.ogg:system/media/audio/alarms/Alarm_Beep_02.ogg \
- $(LOCAL_PATH)/Alarm_Classic.ogg:system/media/audio/alarms/Alarm_Classic.ogg \
- $(LOCAL_PATH)/Alarm_Beep_03.ogg:system/media/audio/alarms/Alarm_Beep_03.ogg \
- $(LOCAL_PATH)/Alarm_Rooster_02.ogg:system/media/audio/alarms/Alarm_Rooster_02.ogg \
- $(LOCAL_PATH)/Ring_Classic_02.ogg:system/media/audio/ringtones/Ring_Classic_02.ogg \
- $(LOCAL_PATH)/Ring_Digital_02.ogg:system/media/audio/ringtones/Ring_Digital_02.ogg \
- $(LOCAL_PATH)/Ring_Synth_04.ogg:system/media/audio/ringtones/Ring_Synth_04.ogg \
- $(LOCAL_PATH)/Ring_Synth_02.ogg:system/media/audio/ringtones/Ring_Synth_02.ogg \
- $(LOCAL_PATH)/notifications/Beat_Box_Android.ogg:system/media/audio/notifications/Beat_Box_Android.ogg \
- $(LOCAL_PATH)/notifications/Heaven.ogg:system/media/audio/notifications/Heaven.ogg \
- $(LOCAL_PATH)/notifications/TaDa.ogg:system/media/audio/notifications/TaDa.ogg \
- $(LOCAL_PATH)/notifications/Tinkerbell.ogg:system/media/audio/notifications/Tinkerbell.ogg \
- $(LOCAL_PATH)/effects/Effect_Tick.ogg:system/media/audio/ui/Effect_Tick.ogg \
- $(LOCAL_PATH)/effects/KeypressStandard.ogg:system/media/audio/ui/KeypressStandard.ogg \
- $(LOCAL_PATH)/effects/KeypressSpacebar.ogg:system/media/audio/ui/KeypressSpacebar.ogg \
- $(LOCAL_PATH)/effects/KeypressDelete.ogg:system/media/audio/ui/KeypressDelete.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \
- $(LOCAL_PATH)/effects/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
- $(LOCAL_PATH)/effects/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
- $(LOCAL_PATH)/effects/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
- $(LOCAL_PATH)/effects/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
- $(LOCAL_PATH)/effects/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
- $(LOCAL_PATH)/effects/Dock.ogg:system/media/audio/ui/Dock.ogg \
- $(LOCAL_PATH)/effects/Undock.ogg:system/media/audio/ui/Undock.ogg \
- $(LOCAL_PATH)/effects/Lock.ogg:system/media/audio/ui/Lock.ogg \
- $(LOCAL_PATH)/effects/Unlock.ogg:system/media/audio/ui/Unlock.ogg \
- $(LOCAL_PATH)/effects/ogg/Trusted.ogg:system/media/audio/ui/Trusted.ogg \
- $(LOCAL_PATH)/notifications/moonbeam.ogg:system/media/audio/notifications/moonbeam.ogg \
- $(LOCAL_PATH)/notifications/pixiedust.ogg:system/media/audio/notifications/pixiedust.ogg \
- $(LOCAL_PATH)/notifications/pizzicato.ogg:system/media/audio/notifications/pizzicato.ogg \
- $(LOCAL_PATH)/notifications/tweeters.ogg:system/media/audio/notifications/tweeters.ogg \
- $(LOCAL_PATH)/newwavelabs/BeatPlucker.ogg:system/media/audio/ringtones/BeatPlucker.ogg \
- $(LOCAL_PATH)/newwavelabs/CaffeineSnake.ogg:system/media/audio/notifications/CaffeineSnake.ogg
+ $(LOCAL_PATH)/F1_MissedCall.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/F1_MissedCall.ogg \
+ $(LOCAL_PATH)/F1_New_MMS.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/F1_New_MMS.ogg \
+ $(LOCAL_PATH)/F1_New_SMS.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/F1_New_SMS.ogg \
+ $(LOCAL_PATH)/Alarm_Buzzer.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Alarm_Buzzer.ogg \
+ $(LOCAL_PATH)/Alarm_Beep_01.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Alarm_Beep_01.ogg \
+ $(LOCAL_PATH)/Alarm_Beep_02.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Alarm_Beep_02.ogg \
+ $(LOCAL_PATH)/Alarm_Classic.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Alarm_Classic.ogg \
+ $(LOCAL_PATH)/Alarm_Beep_03.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Alarm_Beep_03.ogg \
+ $(LOCAL_PATH)/Alarm_Rooster_02.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Alarm_Rooster_02.ogg \
+ $(LOCAL_PATH)/Ring_Classic_02.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Ring_Classic_02.ogg \
+ $(LOCAL_PATH)/Ring_Digital_02.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Ring_Digital_02.ogg \
+ $(LOCAL_PATH)/Ring_Synth_04.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Ring_Synth_04.ogg \
+ $(LOCAL_PATH)/Ring_Synth_02.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Ring_Synth_02.ogg \
+ $(LOCAL_PATH)/notifications/Beat_Box_Android.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Beat_Box_Android.ogg \
+ $(LOCAL_PATH)/notifications/Heaven.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Heaven.ogg \
+ $(LOCAL_PATH)/notifications/TaDa.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/TaDa.ogg \
+ $(LOCAL_PATH)/notifications/Tinkerbell.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Tinkerbell.ogg \
+ $(LOCAL_PATH)/effects/Effect_Tick.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Effect_Tick.ogg \
+ $(LOCAL_PATH)/effects/KeypressStandard.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressStandard.ogg \
+ $(LOCAL_PATH)/effects/KeypressSpacebar.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressSpacebar.ogg \
+ $(LOCAL_PATH)/effects/KeypressDelete.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressDelete.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressInvalid.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressInvalid.ogg \
+ $(LOCAL_PATH)/effects/KeypressReturn.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressReturn.ogg \
+ $(LOCAL_PATH)/effects/VideoRecord.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/VideoStop.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/VideoStop.ogg \
+ $(LOCAL_PATH)/effects/camera_click.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/camera_click.ogg \
+ $(LOCAL_PATH)/effects/LowBattery.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/LowBattery.ogg \
+ $(LOCAL_PATH)/effects/Dock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Dock.ogg \
+ $(LOCAL_PATH)/effects/Undock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Undock.ogg \
+ $(LOCAL_PATH)/effects/Lock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Lock.ogg \
+ $(LOCAL_PATH)/effects/Unlock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Unlock.ogg \
+ $(LOCAL_PATH)/effects/ogg/Trusted.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Trusted.ogg \
+ $(LOCAL_PATH)/notifications/moonbeam.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/moonbeam.ogg \
+ $(LOCAL_PATH)/notifications/pixiedust.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/pixiedust.ogg \
+ $(LOCAL_PATH)/notifications/pizzicato.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/pizzicato.ogg \
+ $(LOCAL_PATH)/notifications/tweeters.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/tweeters.ogg \
+ $(LOCAL_PATH)/newwavelabs/BeatPlucker.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/BeatPlucker.ogg \
+ $(LOCAL_PATH)/newwavelabs/CaffeineSnake.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/CaffeineSnake.ogg
ifneq ($(MINIMAL_NEWWAVELABS),true)
PRODUCT_COPY_FILES += \
- $(LOCAL_PATH)/newwavelabs/BentleyDubs.ogg:system/media/audio/ringtones/BentleyDubs.ogg \
- $(LOCAL_PATH)/newwavelabs/BirdLoop.ogg:system/media/audio/ringtones/BirdLoop.ogg \
- $(LOCAL_PATH)/newwavelabs/CaribbeanIce.ogg:system/media/audio/ringtones/CaribbeanIce.ogg \
- $(LOCAL_PATH)/newwavelabs/CurveBall.ogg:system/media/audio/ringtones/CurveBall.ogg \
- $(LOCAL_PATH)/newwavelabs/EtherShake.ogg:system/media/audio/ringtones/EtherShake.ogg \
- $(LOCAL_PATH)/newwavelabs/FriendlyGhost.ogg:system/media/audio/ringtones/FriendlyGhost.ogg \
- $(LOCAL_PATH)/newwavelabs/GameOverGuitar.ogg:system/media/audio/ringtones/GameOverGuitar.ogg \
- $(LOCAL_PATH)/newwavelabs/Growl.ogg:system/media/audio/ringtones/Growl.ogg \
- $(LOCAL_PATH)/newwavelabs/InsertCoin.ogg:system/media/audio/ringtones/InsertCoin.ogg \
- $(LOCAL_PATH)/newwavelabs/LoopyLounge.ogg:system/media/audio/ringtones/LoopyLounge.ogg \
- $(LOCAL_PATH)/newwavelabs/LoveFlute.ogg:system/media/audio/ringtones/LoveFlute.ogg \
- $(LOCAL_PATH)/newwavelabs/MidEvilJaunt.ogg:system/media/audio/ringtones/MidEvilJaunt.ogg \
- $(LOCAL_PATH)/newwavelabs/MildlyAlarming.ogg:system/media/audio/ringtones/MildlyAlarming.ogg \
- $(LOCAL_PATH)/newwavelabs/NewPlayer.ogg:system/media/audio/ringtones/NewPlayer.ogg \
- $(LOCAL_PATH)/newwavelabs/Noises1.ogg:system/media/audio/ringtones/Noises1.ogg \
- $(LOCAL_PATH)/newwavelabs/Noises2.ogg:system/media/audio/ringtones/Noises2.ogg \
- $(LOCAL_PATH)/newwavelabs/Noises3.ogg:system/media/audio/ringtones/Noises3.ogg \
- $(LOCAL_PATH)/newwavelabs/OrganDub.ogg:system/media/audio/ringtones/OrganDub.ogg \
- $(LOCAL_PATH)/newwavelabs/RomancingTheTone.ogg:system/media/audio/ringtones/RomancingTheTone.ogg \
- $(LOCAL_PATH)/newwavelabs/SitarVsSitar.ogg:system/media/audio/ringtones/SitarVsSitar.ogg \
- $(LOCAL_PATH)/newwavelabs/SpringyJalopy.ogg:system/media/audio/ringtones/SpringyJalopy.ogg \
- $(LOCAL_PATH)/newwavelabs/Terminated.ogg:system/media/audio/ringtones/Terminated.ogg \
- $(LOCAL_PATH)/newwavelabs/TwirlAway.ogg:system/media/audio/ringtones/TwirlAway.ogg \
- $(LOCAL_PATH)/newwavelabs/VeryAlarmed.ogg:system/media/audio/ringtones/VeryAlarmed.ogg \
- $(LOCAL_PATH)/newwavelabs/World.ogg:system/media/audio/ringtones/World.ogg \
- $(LOCAL_PATH)/newwavelabs/DearDeer.ogg:system/media/audio/notifications/DearDeer.ogg \
- $(LOCAL_PATH)/newwavelabs/DontPanic.ogg:system/media/audio/notifications/DontPanic.ogg \
- $(LOCAL_PATH)/newwavelabs/Highwire.ogg:system/media/audio/notifications/Highwire.ogg \
- $(LOCAL_PATH)/newwavelabs/KzurbSonar.ogg:system/media/audio/notifications/KzurbSonar.ogg \
- $(LOCAL_PATH)/newwavelabs/OnTheHunt.ogg:system/media/audio/notifications/OnTheHunt.ogg \
- $(LOCAL_PATH)/newwavelabs/Voila.ogg:system/media/audio/notifications/Voila.ogg \
- $(LOCAL_PATH)/newwavelabs/CrazyDream.ogg:system/media/audio/ringtones/CrazyDream.ogg \
- $(LOCAL_PATH)/newwavelabs/DreamTheme.ogg:system/media/audio/ringtones/DreamTheme.ogg \
- $(LOCAL_PATH)/newwavelabs/Big_Easy.ogg:system/media/audio/ringtones/Big_Easy.ogg \
- $(LOCAL_PATH)/newwavelabs/Bollywood.ogg:system/media/audio/ringtones/Bollywood.ogg \
- $(LOCAL_PATH)/newwavelabs/Cairo.ogg:system/media/audio/ringtones/Cairo.ogg \
- $(LOCAL_PATH)/newwavelabs/Calypso_Steel.ogg:system/media/audio/ringtones/Calypso_Steel.ogg \
- $(LOCAL_PATH)/newwavelabs/Champagne_Edition.ogg:system/media/audio/ringtones/Champagne_Edition.ogg \
- $(LOCAL_PATH)/newwavelabs/Club_Cubano.ogg:system/media/audio/ringtones/Club_Cubano.ogg \
- $(LOCAL_PATH)/newwavelabs/Eastern_Sky.ogg:system/media/audio/ringtones/Eastern_Sky.ogg \
- $(LOCAL_PATH)/newwavelabs/Funk_Yall.ogg:system/media/audio/ringtones/Funk_Yall.ogg \
- $(LOCAL_PATH)/newwavelabs/Savannah.ogg:system/media/audio/ringtones/Savannah.ogg \
- $(LOCAL_PATH)/newwavelabs/Gimme_Mo_Town.ogg:system/media/audio/ringtones/Gimme_Mo_Town.ogg \
- $(LOCAL_PATH)/newwavelabs/Glacial_Groove.ogg:system/media/audio/ringtones/Glacial_Groove.ogg \
- $(LOCAL_PATH)/newwavelabs/Seville.ogg:system/media/audio/ringtones/Seville.ogg \
- $(LOCAL_PATH)/newwavelabs/No_Limits.ogg:system/media/audio/ringtones/No_Limits.ogg \
- $(LOCAL_PATH)/newwavelabs/Revelation.ogg:system/media/audio/ringtones/Revelation.ogg \
- $(LOCAL_PATH)/newwavelabs/Paradise_Island.ogg:system/media/audio/ringtones/Paradise_Island.ogg \
- $(LOCAL_PATH)/newwavelabs/Road_Trip.ogg:system/media/audio/ringtones/Road_Trip.ogg \
- $(LOCAL_PATH)/newwavelabs/Shes_All_That.ogg:system/media/audio/ringtones/Shes_All_That.ogg \
- $(LOCAL_PATH)/newwavelabs/Steppin_Out.ogg:system/media/audio/ringtones/Steppin_Out.ogg \
- $(LOCAL_PATH)/newwavelabs/Third_Eye.ogg:system/media/audio/ringtones/Third_Eye.ogg \
- $(LOCAL_PATH)/newwavelabs/Thunderfoot.ogg:system/media/audio/ringtones/Thunderfoot.ogg
+ $(LOCAL_PATH)/newwavelabs/BentleyDubs.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/BentleyDubs.ogg \
+ $(LOCAL_PATH)/newwavelabs/BirdLoop.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/BirdLoop.ogg \
+ $(LOCAL_PATH)/newwavelabs/CaribbeanIce.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/CaribbeanIce.ogg \
+ $(LOCAL_PATH)/newwavelabs/CurveBall.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/CurveBall.ogg \
+ $(LOCAL_PATH)/newwavelabs/EtherShake.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/EtherShake.ogg \
+ $(LOCAL_PATH)/newwavelabs/FriendlyGhost.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/FriendlyGhost.ogg \
+ $(LOCAL_PATH)/newwavelabs/GameOverGuitar.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/GameOverGuitar.ogg \
+ $(LOCAL_PATH)/newwavelabs/Growl.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Growl.ogg \
+ $(LOCAL_PATH)/newwavelabs/InsertCoin.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/InsertCoin.ogg \
+ $(LOCAL_PATH)/newwavelabs/LoopyLounge.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/LoopyLounge.ogg \
+ $(LOCAL_PATH)/newwavelabs/LoveFlute.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/LoveFlute.ogg \
+ $(LOCAL_PATH)/newwavelabs/MidEvilJaunt.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/MidEvilJaunt.ogg \
+ $(LOCAL_PATH)/newwavelabs/MildlyAlarming.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/MildlyAlarming.ogg \
+ $(LOCAL_PATH)/newwavelabs/NewPlayer.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/NewPlayer.ogg \
+ $(LOCAL_PATH)/newwavelabs/Noises1.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Noises1.ogg \
+ $(LOCAL_PATH)/newwavelabs/Noises2.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Noises2.ogg \
+ $(LOCAL_PATH)/newwavelabs/Noises3.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Noises3.ogg \
+ $(LOCAL_PATH)/newwavelabs/OrganDub.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/OrganDub.ogg \
+ $(LOCAL_PATH)/newwavelabs/RomancingTheTone.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/RomancingTheTone.ogg \
+ $(LOCAL_PATH)/newwavelabs/SitarVsSitar.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/SitarVsSitar.ogg \
+ $(LOCAL_PATH)/newwavelabs/SpringyJalopy.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/SpringyJalopy.ogg \
+ $(LOCAL_PATH)/newwavelabs/Terminated.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Terminated.ogg \
+ $(LOCAL_PATH)/newwavelabs/TwirlAway.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/TwirlAway.ogg \
+ $(LOCAL_PATH)/newwavelabs/VeryAlarmed.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/VeryAlarmed.ogg \
+ $(LOCAL_PATH)/newwavelabs/World.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/World.ogg \
+ $(LOCAL_PATH)/newwavelabs/DearDeer.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/DearDeer.ogg \
+ $(LOCAL_PATH)/newwavelabs/DontPanic.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/DontPanic.ogg \
+ $(LOCAL_PATH)/newwavelabs/Highwire.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Highwire.ogg \
+ $(LOCAL_PATH)/newwavelabs/KzurbSonar.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/KzurbSonar.ogg \
+ $(LOCAL_PATH)/newwavelabs/OnTheHunt.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/OnTheHunt.ogg \
+ $(LOCAL_PATH)/newwavelabs/Voila.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Voila.ogg \
+ $(LOCAL_PATH)/newwavelabs/CrazyDream.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/CrazyDream.ogg \
+ $(LOCAL_PATH)/newwavelabs/DreamTheme.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/DreamTheme.ogg \
+ $(LOCAL_PATH)/newwavelabs/Big_Easy.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Big_Easy.ogg \
+ $(LOCAL_PATH)/newwavelabs/Bollywood.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Bollywood.ogg \
+ $(LOCAL_PATH)/newwavelabs/Cairo.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Cairo.ogg \
+ $(LOCAL_PATH)/newwavelabs/Calypso_Steel.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Calypso_Steel.ogg \
+ $(LOCAL_PATH)/newwavelabs/Champagne_Edition.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Champagne_Edition.ogg \
+ $(LOCAL_PATH)/newwavelabs/Club_Cubano.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Club_Cubano.ogg \
+ $(LOCAL_PATH)/newwavelabs/Eastern_Sky.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Eastern_Sky.ogg \
+ $(LOCAL_PATH)/newwavelabs/Funk_Yall.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Funk_Yall.ogg \
+ $(LOCAL_PATH)/newwavelabs/Savannah.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Savannah.ogg \
+ $(LOCAL_PATH)/newwavelabs/Gimme_Mo_Town.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Gimme_Mo_Town.ogg \
+ $(LOCAL_PATH)/newwavelabs/Glacial_Groove.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Glacial_Groove.ogg \
+ $(LOCAL_PATH)/newwavelabs/Seville.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Seville.ogg \
+ $(LOCAL_PATH)/newwavelabs/No_Limits.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/No_Limits.ogg \
+ $(LOCAL_PATH)/newwavelabs/Revelation.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Revelation.ogg \
+ $(LOCAL_PATH)/newwavelabs/Paradise_Island.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Paradise_Island.ogg \
+ $(LOCAL_PATH)/newwavelabs/Road_Trip.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Road_Trip.ogg \
+ $(LOCAL_PATH)/newwavelabs/Shes_All_That.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Shes_All_That.ogg \
+ $(LOCAL_PATH)/newwavelabs/Steppin_Out.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Steppin_Out.ogg \
+ $(LOCAL_PATH)/newwavelabs/Third_Eye.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Third_Eye.ogg \
+ $(LOCAL_PATH)/newwavelabs/Thunderfoot.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Thunderfoot.ogg
endif
diff --git a/data/sounds/AudioPackage3.mk b/data/sounds/AudioPackage3.mk
index a05de72..a98fb74 100644
--- a/data/sounds/AudioPackage3.mk
+++ b/data/sounds/AudioPackage3.mk
@@ -10,94 +10,94 @@
LOCAL_PATH:= frameworks/base/data/sounds
PRODUCT_COPY_FILES += \
- $(LOCAL_PATH)/F1_MissedCall.ogg:system/media/audio/notifications/F1_MissedCall.ogg \
- $(LOCAL_PATH)/F1_New_MMS.ogg:system/media/audio/notifications/F1_New_MMS.ogg \
- $(LOCAL_PATH)/F1_New_SMS.ogg:system/media/audio/notifications/F1_New_SMS.ogg \
- $(LOCAL_PATH)/Alarm_Buzzer.ogg:system/media/audio/alarms/Alarm_Buzzer.ogg \
- $(LOCAL_PATH)/Alarm_Beep_01.ogg:system/media/audio/alarms/Alarm_Beep_01.ogg \
- $(LOCAL_PATH)/Alarm_Beep_02.ogg:system/media/audio/alarms/Alarm_Beep_02.ogg \
- $(LOCAL_PATH)/Alarm_Classic.ogg:system/media/audio/alarms/Alarm_Classic.ogg \
- $(LOCAL_PATH)/Alarm_Beep_03.ogg:system/media/audio/alarms/Alarm_Beep_03.ogg \
- $(LOCAL_PATH)/Alarm_Rooster_02.ogg:system/media/audio/alarms/Alarm_Rooster_02.ogg \
- $(LOCAL_PATH)/Ring_Classic_02.ogg:system/media/audio/ringtones/Ring_Classic_02.ogg \
- $(LOCAL_PATH)/Ring_Digital_02.ogg:system/media/audio/ringtones/Ring_Digital_02.ogg \
- $(LOCAL_PATH)/Ring_Synth_04.ogg:system/media/audio/ringtones/Ring_Synth_04.ogg \
- $(LOCAL_PATH)/Ring_Synth_02.ogg:system/media/audio/ringtones/Ring_Synth_02.ogg \
- $(LOCAL_PATH)/notifications/Beat_Box_Android.ogg:system/media/audio/notifications/Beat_Box_Android.ogg \
- $(LOCAL_PATH)/notifications/Heaven.ogg:system/media/audio/notifications/Heaven.ogg \
- $(LOCAL_PATH)/notifications/TaDa.ogg:system/media/audio/notifications/TaDa.ogg \
- $(LOCAL_PATH)/notifications/Tinkerbell.ogg:system/media/audio/notifications/Tinkerbell.ogg \
- $(LOCAL_PATH)/effects/Effect_Tick.ogg:system/media/audio/ui/Effect_Tick.ogg \
- $(LOCAL_PATH)/effects/KeypressStandard.ogg:system/media/audio/ui/KeypressStandard.ogg \
- $(LOCAL_PATH)/effects/KeypressSpacebar.ogg:system/media/audio/ui/KeypressSpacebar.ogg \
- $(LOCAL_PATH)/effects/KeypressDelete.ogg:system/media/audio/ui/KeypressDelete.ogg \
- $(LOCAL_PATH)/effects/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \
- $(LOCAL_PATH)/effects/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
- $(LOCAL_PATH)/effects/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
- $(LOCAL_PATH)/effects/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
- $(LOCAL_PATH)/effects/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
- $(LOCAL_PATH)/effects/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
- $(LOCAL_PATH)/effects/Dock.ogg:system/media/audio/ui/Dock.ogg \
- $(LOCAL_PATH)/effects/Undock.ogg:system/media/audio/ui/Undock.ogg \
- $(LOCAL_PATH)/effects/Lock.ogg:system/media/audio/ui/Lock.ogg \
- $(LOCAL_PATH)/effects/Unlock.ogg:system/media/audio/ui/Unlock.ogg \
- $(LOCAL_PATH)/effects/ogg/Trusted.ogg:system/media/audio/ui/Trusted.ogg \
- $(LOCAL_PATH)/notifications/moonbeam.ogg:system/media/audio/notifications/moonbeam.ogg \
- $(LOCAL_PATH)/notifications/pixiedust.ogg:system/media/audio/notifications/pixiedust.ogg \
- $(LOCAL_PATH)/notifications/pizzicato.ogg:system/media/audio/notifications/pizzicato.ogg \
- $(LOCAL_PATH)/notifications/tweeters.ogg:system/media/audio/notifications/tweeters.ogg \
- $(LOCAL_PATH)/newwavelabs/BeatPlucker.ogg:system/media/audio/ringtones/BeatPlucker.ogg \
- $(LOCAL_PATH)/newwavelabs/CaffeineSnake.ogg:system/media/audio/notifications/CaffeineSnake.ogg
+ $(LOCAL_PATH)/F1_MissedCall.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/F1_MissedCall.ogg \
+ $(LOCAL_PATH)/F1_New_MMS.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/F1_New_MMS.ogg \
+ $(LOCAL_PATH)/F1_New_SMS.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/F1_New_SMS.ogg \
+ $(LOCAL_PATH)/Alarm_Buzzer.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Alarm_Buzzer.ogg \
+ $(LOCAL_PATH)/Alarm_Beep_01.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Alarm_Beep_01.ogg \
+ $(LOCAL_PATH)/Alarm_Beep_02.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Alarm_Beep_02.ogg \
+ $(LOCAL_PATH)/Alarm_Classic.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Alarm_Classic.ogg \
+ $(LOCAL_PATH)/Alarm_Beep_03.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Alarm_Beep_03.ogg \
+ $(LOCAL_PATH)/Alarm_Rooster_02.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Alarm_Rooster_02.ogg \
+ $(LOCAL_PATH)/Ring_Classic_02.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Ring_Classic_02.ogg \
+ $(LOCAL_PATH)/Ring_Digital_02.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Ring_Digital_02.ogg \
+ $(LOCAL_PATH)/Ring_Synth_04.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Ring_Synth_04.ogg \
+ $(LOCAL_PATH)/Ring_Synth_02.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Ring_Synth_02.ogg \
+ $(LOCAL_PATH)/notifications/Beat_Box_Android.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Beat_Box_Android.ogg \
+ $(LOCAL_PATH)/notifications/Heaven.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Heaven.ogg \
+ $(LOCAL_PATH)/notifications/TaDa.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/TaDa.ogg \
+ $(LOCAL_PATH)/notifications/Tinkerbell.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Tinkerbell.ogg \
+ $(LOCAL_PATH)/effects/Effect_Tick.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Effect_Tick.ogg \
+ $(LOCAL_PATH)/effects/KeypressStandard.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressStandard.ogg \
+ $(LOCAL_PATH)/effects/KeypressSpacebar.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressSpacebar.ogg \
+ $(LOCAL_PATH)/effects/KeypressDelete.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressDelete.ogg \
+ $(LOCAL_PATH)/effects/KeypressInvalid.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressInvalid.ogg \
+ $(LOCAL_PATH)/effects/KeypressReturn.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressReturn.ogg \
+ $(LOCAL_PATH)/effects/VideoRecord.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/VideoStop.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/VideoStop.ogg \
+ $(LOCAL_PATH)/effects/camera_click.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/camera_click.ogg \
+ $(LOCAL_PATH)/effects/LowBattery.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/LowBattery.ogg \
+ $(LOCAL_PATH)/effects/Dock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Dock.ogg \
+ $(LOCAL_PATH)/effects/Undock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Undock.ogg \
+ $(LOCAL_PATH)/effects/Lock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Lock.ogg \
+ $(LOCAL_PATH)/effects/Unlock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Unlock.ogg \
+ $(LOCAL_PATH)/effects/ogg/Trusted.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Trusted.ogg \
+ $(LOCAL_PATH)/notifications/moonbeam.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/moonbeam.ogg \
+ $(LOCAL_PATH)/notifications/pixiedust.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/pixiedust.ogg \
+ $(LOCAL_PATH)/notifications/pizzicato.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/pizzicato.ogg \
+ $(LOCAL_PATH)/notifications/tweeters.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/tweeters.ogg \
+ $(LOCAL_PATH)/newwavelabs/BeatPlucker.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/BeatPlucker.ogg \
+ $(LOCAL_PATH)/newwavelabs/CaffeineSnake.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/CaffeineSnake.ogg
ifneq ($(MINIMAL_NEWWAVELABS),true)
PRODUCT_COPY_FILES += \
- $(LOCAL_PATH)/newwavelabs/BentleyDubs.ogg:system/media/audio/ringtones/BentleyDubs.ogg \
- $(LOCAL_PATH)/newwavelabs/BirdLoop.ogg:system/media/audio/ringtones/BirdLoop.ogg \
- $(LOCAL_PATH)/newwavelabs/CurveBall.ogg:system/media/audio/ringtones/CurveBall.ogg \
- $(LOCAL_PATH)/newwavelabs/EtherShake.ogg:system/media/audio/ringtones/EtherShake.ogg \
- $(LOCAL_PATH)/newwavelabs/Growl.ogg:system/media/audio/ringtones/Growl.ogg \
- $(LOCAL_PATH)/newwavelabs/LoopyLounge.ogg:system/media/audio/ringtones/LoopyLounge.ogg \
- $(LOCAL_PATH)/newwavelabs/LoveFlute.ogg:system/media/audio/ringtones/LoveFlute.ogg \
- $(LOCAL_PATH)/newwavelabs/MidEvilJaunt.ogg:system/media/audio/ringtones/MidEvilJaunt.ogg \
- $(LOCAL_PATH)/newwavelabs/MildlyAlarming.ogg:system/media/audio/ringtones/MildlyAlarming.ogg \
- $(LOCAL_PATH)/newwavelabs/NewPlayer.ogg:system/media/audio/ringtones/NewPlayer.ogg \
- $(LOCAL_PATH)/newwavelabs/Noises1.ogg:system/media/audio/ringtones/Noises1.ogg \
- $(LOCAL_PATH)/newwavelabs/Noises2.ogg:system/media/audio/ringtones/Noises2.ogg \
- $(LOCAL_PATH)/newwavelabs/Noises3.ogg:system/media/audio/ringtones/Noises3.ogg \
- $(LOCAL_PATH)/newwavelabs/OrganDub.ogg:system/media/audio/ringtones/OrganDub.ogg \
- $(LOCAL_PATH)/newwavelabs/Terminated.ogg:system/media/audio/ringtones/Terminated.ogg \
- $(LOCAL_PATH)/newwavelabs/TwirlAway.ogg:system/media/audio/ringtones/TwirlAway.ogg \
- $(LOCAL_PATH)/newwavelabs/World.ogg:system/media/audio/ringtones/World.ogg \
- $(LOCAL_PATH)/newwavelabs/DearDeer.ogg:system/media/audio/notifications/DearDeer.ogg \
- $(LOCAL_PATH)/newwavelabs/DontPanic.ogg:system/media/audio/notifications/DontPanic.ogg \
- $(LOCAL_PATH)/newwavelabs/Highwire.ogg:system/media/audio/notifications/Highwire.ogg \
- $(LOCAL_PATH)/newwavelabs/KzurbSonar.ogg:system/media/audio/notifications/KzurbSonar.ogg \
- $(LOCAL_PATH)/newwavelabs/OnTheHunt.ogg:system/media/audio/notifications/OnTheHunt.ogg \
- $(LOCAL_PATH)/newwavelabs/Voila.ogg:system/media/audio/notifications/Voila.ogg \
- $(LOCAL_PATH)/newwavelabs/Big_Easy.ogg:system/media/audio/ringtones/Big_Easy.ogg \
- $(LOCAL_PATH)/newwavelabs/Bollywood.ogg:system/media/audio/ringtones/Bollywood.ogg \
- $(LOCAL_PATH)/newwavelabs/Cairo.ogg:system/media/audio/ringtones/Cairo.ogg \
- $(LOCAL_PATH)/newwavelabs/Calypso_Steel.ogg:system/media/audio/ringtones/Calypso_Steel.ogg \
- $(LOCAL_PATH)/newwavelabs/Champagne_Edition.ogg:system/media/audio/ringtones/Champagne_Edition.ogg \
- $(LOCAL_PATH)/newwavelabs/Club_Cubano.ogg:system/media/audio/ringtones/Club_Cubano.ogg \
- $(LOCAL_PATH)/newwavelabs/Eastern_Sky.ogg:system/media/audio/ringtones/Eastern_Sky.ogg \
- $(LOCAL_PATH)/newwavelabs/Funk_Yall.ogg:system/media/audio/ringtones/Funk_Yall.ogg \
- $(LOCAL_PATH)/newwavelabs/Savannah.ogg:system/media/audio/ringtones/Savannah.ogg \
- $(LOCAL_PATH)/newwavelabs/Gimme_Mo_Town.ogg:system/media/audio/ringtones/Gimme_Mo_Town.ogg \
- $(LOCAL_PATH)/newwavelabs/Glacial_Groove.ogg:system/media/audio/ringtones/Glacial_Groove.ogg \
- $(LOCAL_PATH)/newwavelabs/Seville.ogg:system/media/audio/ringtones/Seville.ogg \
- $(LOCAL_PATH)/newwavelabs/No_Limits.ogg:system/media/audio/ringtones/No_Limits.ogg \
- $(LOCAL_PATH)/newwavelabs/Paradise_Island.ogg:system/media/audio/ringtones/Paradise_Island.ogg \
- $(LOCAL_PATH)/newwavelabs/Road_Trip.ogg:system/media/audio/ringtones/Road_Trip.ogg \
- $(LOCAL_PATH)/newwavelabs/Shes_All_That.ogg:system/media/audio/ringtones/Shes_All_That.ogg \
- $(LOCAL_PATH)/newwavelabs/Steppin_Out.ogg:system/media/audio/ringtones/Steppin_Out.ogg \
- $(LOCAL_PATH)/newwavelabs/Third_Eye.ogg:system/media/audio/ringtones/Third_Eye.ogg \
- $(LOCAL_PATH)/newwavelabs/Thunderfoot.ogg:system/media/audio/ringtones/Thunderfoot.ogg \
- $(LOCAL_PATH)/newwavelabs/HalfwayHome.ogg:system/media/audio/ringtones/HalfwayHome.ogg \
- $(LOCAL_PATH)/newwavelabs/CrayonRock.ogg:system/media/audio/ringtones/CrayonRock.ogg \
- $(LOCAL_PATH)/newwavelabs/DancinFool.ogg:system/media/audio/ringtones/DancinFool.ogg \
- $(LOCAL_PATH)/newwavelabs/BussaMove.ogg:system/media/audio/ringtones/BussaMove.ogg \
- $(LOCAL_PATH)/newwavelabs/DonMessWivIt.ogg:system/media/audio/ringtones/DonMessWivIt.ogg \
- $(LOCAL_PATH)/newwavelabs/SilkyWay.ogg:system/media/audio/ringtones/SilkyWay.ogg \
- $(LOCAL_PATH)/newwavelabs/Playa.ogg:system/media/audio/ringtones/Playa.ogg
+ $(LOCAL_PATH)/newwavelabs/BentleyDubs.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/BentleyDubs.ogg \
+ $(LOCAL_PATH)/newwavelabs/BirdLoop.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/BirdLoop.ogg \
+ $(LOCAL_PATH)/newwavelabs/CurveBall.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/CurveBall.ogg \
+ $(LOCAL_PATH)/newwavelabs/EtherShake.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/EtherShake.ogg \
+ $(LOCAL_PATH)/newwavelabs/Growl.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Growl.ogg \
+ $(LOCAL_PATH)/newwavelabs/LoopyLounge.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/LoopyLounge.ogg \
+ $(LOCAL_PATH)/newwavelabs/LoveFlute.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/LoveFlute.ogg \
+ $(LOCAL_PATH)/newwavelabs/MidEvilJaunt.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/MidEvilJaunt.ogg \
+ $(LOCAL_PATH)/newwavelabs/MildlyAlarming.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/MildlyAlarming.ogg \
+ $(LOCAL_PATH)/newwavelabs/NewPlayer.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/NewPlayer.ogg \
+ $(LOCAL_PATH)/newwavelabs/Noises1.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Noises1.ogg \
+ $(LOCAL_PATH)/newwavelabs/Noises2.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Noises2.ogg \
+ $(LOCAL_PATH)/newwavelabs/Noises3.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Noises3.ogg \
+ $(LOCAL_PATH)/newwavelabs/OrganDub.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/OrganDub.ogg \
+ $(LOCAL_PATH)/newwavelabs/Terminated.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Terminated.ogg \
+ $(LOCAL_PATH)/newwavelabs/TwirlAway.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/TwirlAway.ogg \
+ $(LOCAL_PATH)/newwavelabs/World.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/World.ogg \
+ $(LOCAL_PATH)/newwavelabs/DearDeer.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/DearDeer.ogg \
+ $(LOCAL_PATH)/newwavelabs/DontPanic.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/DontPanic.ogg \
+ $(LOCAL_PATH)/newwavelabs/Highwire.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Highwire.ogg \
+ $(LOCAL_PATH)/newwavelabs/KzurbSonar.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/KzurbSonar.ogg \
+ $(LOCAL_PATH)/newwavelabs/OnTheHunt.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/OnTheHunt.ogg \
+ $(LOCAL_PATH)/newwavelabs/Voila.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Voila.ogg \
+ $(LOCAL_PATH)/newwavelabs/Big_Easy.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Big_Easy.ogg \
+ $(LOCAL_PATH)/newwavelabs/Bollywood.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Bollywood.ogg \
+ $(LOCAL_PATH)/newwavelabs/Cairo.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Cairo.ogg \
+ $(LOCAL_PATH)/newwavelabs/Calypso_Steel.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Calypso_Steel.ogg \
+ $(LOCAL_PATH)/newwavelabs/Champagne_Edition.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Champagne_Edition.ogg \
+ $(LOCAL_PATH)/newwavelabs/Club_Cubano.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Club_Cubano.ogg \
+ $(LOCAL_PATH)/newwavelabs/Eastern_Sky.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Eastern_Sky.ogg \
+ $(LOCAL_PATH)/newwavelabs/Funk_Yall.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Funk_Yall.ogg \
+ $(LOCAL_PATH)/newwavelabs/Savannah.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Savannah.ogg \
+ $(LOCAL_PATH)/newwavelabs/Gimme_Mo_Town.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Gimme_Mo_Town.ogg \
+ $(LOCAL_PATH)/newwavelabs/Glacial_Groove.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Glacial_Groove.ogg \
+ $(LOCAL_PATH)/newwavelabs/Seville.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Seville.ogg \
+ $(LOCAL_PATH)/newwavelabs/No_Limits.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/No_Limits.ogg \
+ $(LOCAL_PATH)/newwavelabs/Paradise_Island.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Paradise_Island.ogg \
+ $(LOCAL_PATH)/newwavelabs/Road_Trip.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Road_Trip.ogg \
+ $(LOCAL_PATH)/newwavelabs/Shes_All_That.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Shes_All_That.ogg \
+ $(LOCAL_PATH)/newwavelabs/Steppin_Out.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Steppin_Out.ogg \
+ $(LOCAL_PATH)/newwavelabs/Third_Eye.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Third_Eye.ogg \
+ $(LOCAL_PATH)/newwavelabs/Thunderfoot.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Thunderfoot.ogg \
+ $(LOCAL_PATH)/newwavelabs/HalfwayHome.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/HalfwayHome.ogg \
+ $(LOCAL_PATH)/newwavelabs/CrayonRock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/CrayonRock.ogg \
+ $(LOCAL_PATH)/newwavelabs/DancinFool.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/DancinFool.ogg \
+ $(LOCAL_PATH)/newwavelabs/BussaMove.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/BussaMove.ogg \
+ $(LOCAL_PATH)/newwavelabs/DonMessWivIt.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/DonMessWivIt.ogg \
+ $(LOCAL_PATH)/newwavelabs/SilkyWay.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/SilkyWay.ogg \
+ $(LOCAL_PATH)/newwavelabs/Playa.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Playa.ogg
endif
diff --git a/data/sounds/AudioPackage4.mk b/data/sounds/AudioPackage4.mk
index d376a2d..54c3c02 100644
--- a/data/sounds/AudioPackage4.mk
+++ b/data/sounds/AudioPackage4.mk
@@ -10,98 +10,98 @@
LOCAL_PATH:= frameworks/base/data/sounds
PRODUCT_COPY_FILES += \
- $(LOCAL_PATH)/F1_MissedCall.ogg:system/media/audio/notifications/F1_MissedCall.ogg \
- $(LOCAL_PATH)/F1_New_MMS.ogg:system/media/audio/notifications/F1_New_MMS.ogg \
- $(LOCAL_PATH)/F1_New_SMS.ogg:system/media/audio/notifications/F1_New_SMS.ogg \
- $(LOCAL_PATH)/Alarm_Buzzer.ogg:system/media/audio/alarms/Alarm_Buzzer.ogg \
- $(LOCAL_PATH)/Alarm_Beep_01.ogg:system/media/audio/alarms/Alarm_Beep_01.ogg \
- $(LOCAL_PATH)/Alarm_Beep_02.ogg:system/media/audio/alarms/Alarm_Beep_02.ogg \
- $(LOCAL_PATH)/Alarm_Classic.ogg:system/media/audio/alarms/Alarm_Classic.ogg \
- $(LOCAL_PATH)/Alarm_Beep_03.ogg:system/media/audio/alarms/Alarm_Beep_03.ogg \
- $(LOCAL_PATH)/Alarm_Rooster_02.ogg:system/media/audio/alarms/Alarm_Rooster_02.ogg \
- $(LOCAL_PATH)/notifications/Beat_Box_Android.ogg:system/media/audio/notifications/Beat_Box_Android.ogg \
- $(LOCAL_PATH)/notifications/Cricket.ogg:system/media/audio/notifications/Cricket.ogg \
- $(LOCAL_PATH)/notifications/Doink.ogg:system/media/audio/notifications/Doink.ogg \
- $(LOCAL_PATH)/notifications/Drip.ogg:system/media/audio/notifications/Drip.ogg \
- $(LOCAL_PATH)/notifications/Heaven.ogg:system/media/audio/notifications/Heaven.ogg \
- $(LOCAL_PATH)/notifications/SpaceSeed.ogg:system/media/audio/notifications/SpaceSeed.ogg \
- $(LOCAL_PATH)/notifications/TaDa.ogg:system/media/audio/notifications/TaDa.ogg \
- $(LOCAL_PATH)/notifications/Tinkerbell.ogg:system/media/audio/notifications/Tinkerbell.ogg \
- $(LOCAL_PATH)/notifications/moonbeam.ogg:system/media/audio/notifications/moonbeam.ogg \
- $(LOCAL_PATH)/notifications/pixiedust.ogg:system/media/audio/notifications/pixiedust.ogg \
- $(LOCAL_PATH)/notifications/pizzicato.ogg:system/media/audio/notifications/pizzicato.ogg \
- $(LOCAL_PATH)/notifications/Plastic_Pipe.ogg:system/media/audio/notifications/Plastic_Pipe.ogg \
- $(LOCAL_PATH)/notifications/tweeters.ogg:system/media/audio/notifications/tweeters.ogg \
- $(LOCAL_PATH)/effects/Effect_Tick.ogg:system/media/audio/ui/Effect_Tick.ogg \
- $(LOCAL_PATH)/effects/KeypressStandard.ogg:system/media/audio/ui/KeypressStandard.ogg \
- $(LOCAL_PATH)/effects/KeypressSpacebar.ogg:system/media/audio/ui/KeypressSpacebar.ogg \
- $(LOCAL_PATH)/effects/KeypressDelete.ogg:system/media/audio/ui/KeypressDelete.ogg \
- $(LOCAL_PATH)/effects/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \
- $(LOCAL_PATH)/effects/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
- $(LOCAL_PATH)/effects/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
- $(LOCAL_PATH)/effects/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
- $(LOCAL_PATH)/effects/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
- $(LOCAL_PATH)/effects/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
- $(LOCAL_PATH)/effects/Dock.ogg:system/media/audio/ui/Dock.ogg \
- $(LOCAL_PATH)/effects/Undock.ogg:system/media/audio/ui/Undock.ogg \
- $(LOCAL_PATH)/effects/Lock.ogg:system/media/audio/ui/Lock.ogg \
- $(LOCAL_PATH)/effects/Unlock.ogg:system/media/audio/ui/Unlock.ogg \
- $(LOCAL_PATH)/effects/ogg/Trusted.ogg:system/media/audio/ui/Trusted.ogg \
- $(LOCAL_PATH)/Ring_Classic_02.ogg:system/media/audio/ringtones/Ring_Classic_02.ogg \
- $(LOCAL_PATH)/Ring_Digital_02.ogg:system/media/audio/ringtones/Ring_Digital_02.ogg \
- $(LOCAL_PATH)/Ring_Synth_04.ogg:system/media/audio/ringtones/Ring_Synth_04.ogg \
- $(LOCAL_PATH)/Ring_Synth_02.ogg:system/media/audio/ringtones/Ring_Synth_02.ogg \
- $(LOCAL_PATH)/ringtones/FreeFlight.ogg:system/media/audio/ringtones/FreeFlight.ogg \
- $(LOCAL_PATH)/newwavelabs/Backroad.ogg:system/media/audio/ringtones/Backroad.ogg \
- $(LOCAL_PATH)/newwavelabs/CaffeineSnake.ogg:system/media/audio/notifications/CaffeineSnake.ogg
+ $(LOCAL_PATH)/F1_MissedCall.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/F1_MissedCall.ogg \
+ $(LOCAL_PATH)/F1_New_MMS.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/F1_New_MMS.ogg \
+ $(LOCAL_PATH)/F1_New_SMS.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/F1_New_SMS.ogg \
+ $(LOCAL_PATH)/Alarm_Buzzer.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Alarm_Buzzer.ogg \
+ $(LOCAL_PATH)/Alarm_Beep_01.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Alarm_Beep_01.ogg \
+ $(LOCAL_PATH)/Alarm_Beep_02.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Alarm_Beep_02.ogg \
+ $(LOCAL_PATH)/Alarm_Classic.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Alarm_Classic.ogg \
+ $(LOCAL_PATH)/Alarm_Beep_03.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Alarm_Beep_03.ogg \
+ $(LOCAL_PATH)/Alarm_Rooster_02.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Alarm_Rooster_02.ogg \
+ $(LOCAL_PATH)/notifications/Beat_Box_Android.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Beat_Box_Android.ogg \
+ $(LOCAL_PATH)/notifications/Cricket.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Cricket.ogg \
+ $(LOCAL_PATH)/notifications/Doink.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Doink.ogg \
+ $(LOCAL_PATH)/notifications/Drip.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Drip.ogg \
+ $(LOCAL_PATH)/notifications/Heaven.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Heaven.ogg \
+ $(LOCAL_PATH)/notifications/SpaceSeed.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/SpaceSeed.ogg \
+ $(LOCAL_PATH)/notifications/TaDa.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/TaDa.ogg \
+ $(LOCAL_PATH)/notifications/Tinkerbell.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Tinkerbell.ogg \
+ $(LOCAL_PATH)/notifications/moonbeam.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/moonbeam.ogg \
+ $(LOCAL_PATH)/notifications/pixiedust.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/pixiedust.ogg \
+ $(LOCAL_PATH)/notifications/pizzicato.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/pizzicato.ogg \
+ $(LOCAL_PATH)/notifications/Plastic_Pipe.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Plastic_Pipe.ogg \
+ $(LOCAL_PATH)/notifications/tweeters.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/tweeters.ogg \
+ $(LOCAL_PATH)/effects/Effect_Tick.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Effect_Tick.ogg \
+ $(LOCAL_PATH)/effects/KeypressStandard.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressStandard.ogg \
+ $(LOCAL_PATH)/effects/KeypressSpacebar.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressSpacebar.ogg \
+ $(LOCAL_PATH)/effects/KeypressDelete.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressDelete.ogg \
+ $(LOCAL_PATH)/effects/KeypressInvalid.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressInvalid.ogg \
+ $(LOCAL_PATH)/effects/KeypressReturn.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressReturn.ogg \
+ $(LOCAL_PATH)/effects/VideoRecord.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/VideoStop.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/VideoStop.ogg \
+ $(LOCAL_PATH)/effects/camera_click.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/camera_click.ogg \
+ $(LOCAL_PATH)/effects/LowBattery.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/LowBattery.ogg \
+ $(LOCAL_PATH)/effects/Dock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Dock.ogg \
+ $(LOCAL_PATH)/effects/Undock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Undock.ogg \
+ $(LOCAL_PATH)/effects/Lock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Lock.ogg \
+ $(LOCAL_PATH)/effects/Unlock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Unlock.ogg \
+ $(LOCAL_PATH)/effects/ogg/Trusted.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Trusted.ogg \
+ $(LOCAL_PATH)/Ring_Classic_02.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Ring_Classic_02.ogg \
+ $(LOCAL_PATH)/Ring_Digital_02.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Ring_Digital_02.ogg \
+ $(LOCAL_PATH)/Ring_Synth_04.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Ring_Synth_04.ogg \
+ $(LOCAL_PATH)/Ring_Synth_02.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Ring_Synth_02.ogg \
+ $(LOCAL_PATH)/ringtones/FreeFlight.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/FreeFlight.ogg \
+ $(LOCAL_PATH)/newwavelabs/Backroad.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Backroad.ogg \
+ $(LOCAL_PATH)/newwavelabs/CaffeineSnake.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/CaffeineSnake.ogg
ifneq ($(MINIMAL_NEWWAVELABS),true)
PRODUCT_COPY_FILES += \
- $(LOCAL_PATH)/newwavelabs/DearDeer.ogg:system/media/audio/notifications/DearDeer.ogg \
- $(LOCAL_PATH)/newwavelabs/DontPanic.ogg:system/media/audio/notifications/DontPanic.ogg \
- $(LOCAL_PATH)/newwavelabs/Highwire.ogg:system/media/audio/notifications/Highwire.ogg \
- $(LOCAL_PATH)/newwavelabs/KzurbSonar.ogg:system/media/audio/notifications/KzurbSonar.ogg \
- $(LOCAL_PATH)/newwavelabs/OnTheHunt.ogg:system/media/audio/notifications/OnTheHunt.ogg \
- $(LOCAL_PATH)/newwavelabs/Voila.ogg:system/media/audio/notifications/Voila.ogg \
- $(LOCAL_PATH)/newwavelabs/Big_Easy.ogg:system/media/audio/ringtones/Big_Easy.ogg \
- $(LOCAL_PATH)/newwavelabs/BirdLoop.ogg:system/media/audio/ringtones/BirdLoop.ogg \
- $(LOCAL_PATH)/newwavelabs/Bollywood.ogg:system/media/audio/ringtones/Bollywood.ogg \
- $(LOCAL_PATH)/newwavelabs/BussaMove.ogg:system/media/audio/ringtones/BussaMove.ogg \
- $(LOCAL_PATH)/newwavelabs/Cairo.ogg:system/media/audio/ringtones/Cairo.ogg \
- $(LOCAL_PATH)/newwavelabs/Calypso_Steel.ogg:system/media/audio/ringtones/Calypso_Steel.ogg \
- $(LOCAL_PATH)/newwavelabs/Champagne_Edition.ogg:system/media/audio/ringtones/Champagne_Edition.ogg \
- $(LOCAL_PATH)/newwavelabs/Club_Cubano.ogg:system/media/audio/ringtones/Club_Cubano.ogg \
- $(LOCAL_PATH)/newwavelabs/CrayonRock.ogg:system/media/audio/ringtones/CrayonRock.ogg \
- $(LOCAL_PATH)/newwavelabs/CurveBall.ogg:system/media/audio/ringtones/CurveBall.ogg \
- $(LOCAL_PATH)/newwavelabs/DancinFool.ogg:system/media/audio/ringtones/DancinFool.ogg \
- $(LOCAL_PATH)/newwavelabs/Ding.ogg:system/media/audio/ringtones/Ding.ogg \
- $(LOCAL_PATH)/newwavelabs/DonMessWivIt.ogg:system/media/audio/ringtones/DonMessWivIt.ogg \
- $(LOCAL_PATH)/newwavelabs/Eastern_Sky.ogg:system/media/audio/ringtones/Eastern_Sky.ogg \
- $(LOCAL_PATH)/newwavelabs/Enter_the_Nexus.ogg:system/media/audio/ringtones/Enter_the_Nexus.ogg \
- $(LOCAL_PATH)/newwavelabs/EtherShake.ogg:system/media/audio/ringtones/EtherShake.ogg \
- $(LOCAL_PATH)/newwavelabs/Funk_Yall.ogg:system/media/audio/ringtones/Funk_Yall.ogg \
- $(LOCAL_PATH)/newwavelabs/Gimme_Mo_Town.ogg:system/media/audio/ringtones/Gimme_Mo_Town.ogg \
- $(LOCAL_PATH)/newwavelabs/Glacial_Groove.ogg:system/media/audio/ringtones/Glacial_Groove.ogg \
- $(LOCAL_PATH)/newwavelabs/Growl.ogg:system/media/audio/ringtones/Growl.ogg \
- $(LOCAL_PATH)/newwavelabs/HalfwayHome.ogg:system/media/audio/ringtones/HalfwayHome.ogg \
- $(LOCAL_PATH)/newwavelabs/LoopyLounge.ogg:system/media/audio/ringtones/LoopyLounge.ogg \
- $(LOCAL_PATH)/newwavelabs/LoveFlute.ogg:system/media/audio/ringtones/LoveFlute.ogg \
- $(LOCAL_PATH)/newwavelabs/MidEvilJaunt.ogg:system/media/audio/ringtones/MidEvilJaunt.ogg \
- $(LOCAL_PATH)/newwavelabs/MildlyAlarming.ogg:system/media/audio/ringtones/MildlyAlarming.ogg \
- $(LOCAL_PATH)/newwavelabs/Nairobi.ogg:system/media/audio/ringtones/Nairobi.ogg \
- $(LOCAL_PATH)/newwavelabs/Nassau.ogg:system/media/audio/ringtones/Nassau.ogg \
- $(LOCAL_PATH)/newwavelabs/No_Limits.ogg:system/media/audio/ringtones/No_Limits.ogg \
- $(LOCAL_PATH)/newwavelabs/OrganDub.ogg:system/media/audio/ringtones/OrganDub.ogg \
- $(LOCAL_PATH)/newwavelabs/Paradise_Island.ogg:system/media/audio/ringtones/Paradise_Island.ogg \
- $(LOCAL_PATH)/newwavelabs/Playa.ogg:system/media/audio/ringtones/Playa.ogg \
- $(LOCAL_PATH)/newwavelabs/Road_Trip.ogg:system/media/audio/ringtones/Road_Trip.ogg \
- $(LOCAL_PATH)/newwavelabs/Safari.ogg:system/media/audio/ringtones/Safari.ogg \
- $(LOCAL_PATH)/newwavelabs/Seville.ogg:system/media/audio/ringtones/Seville.ogg \
- $(LOCAL_PATH)/newwavelabs/Shes_All_That.ogg:system/media/audio/ringtones/Shes_All_That.ogg \
- $(LOCAL_PATH)/newwavelabs/SilkyWay.ogg:system/media/audio/ringtones/SilkyWay.ogg \
- $(LOCAL_PATH)/newwavelabs/Steppin_Out.ogg:system/media/audio/ringtones/Steppin_Out.ogg \
- $(LOCAL_PATH)/newwavelabs/Terminated.ogg:system/media/audio/ringtones/Terminated.ogg \
- $(LOCAL_PATH)/newwavelabs/Third_Eye.ogg:system/media/audio/ringtones/Third_Eye.ogg \
- $(LOCAL_PATH)/newwavelabs/TwirlAway.ogg:system/media/audio/ringtones/TwirlAway.ogg \
- $(LOCAL_PATH)/newwavelabs/World.ogg:system/media/audio/ringtones/World.ogg
+ $(LOCAL_PATH)/newwavelabs/DearDeer.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/DearDeer.ogg \
+ $(LOCAL_PATH)/newwavelabs/DontPanic.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/DontPanic.ogg \
+ $(LOCAL_PATH)/newwavelabs/Highwire.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Highwire.ogg \
+ $(LOCAL_PATH)/newwavelabs/KzurbSonar.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/KzurbSonar.ogg \
+ $(LOCAL_PATH)/newwavelabs/OnTheHunt.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/OnTheHunt.ogg \
+ $(LOCAL_PATH)/newwavelabs/Voila.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Voila.ogg \
+ $(LOCAL_PATH)/newwavelabs/Big_Easy.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Big_Easy.ogg \
+ $(LOCAL_PATH)/newwavelabs/BirdLoop.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/BirdLoop.ogg \
+ $(LOCAL_PATH)/newwavelabs/Bollywood.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Bollywood.ogg \
+ $(LOCAL_PATH)/newwavelabs/BussaMove.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/BussaMove.ogg \
+ $(LOCAL_PATH)/newwavelabs/Cairo.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Cairo.ogg \
+ $(LOCAL_PATH)/newwavelabs/Calypso_Steel.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Calypso_Steel.ogg \
+ $(LOCAL_PATH)/newwavelabs/Champagne_Edition.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Champagne_Edition.ogg \
+ $(LOCAL_PATH)/newwavelabs/Club_Cubano.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Club_Cubano.ogg \
+ $(LOCAL_PATH)/newwavelabs/CrayonRock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/CrayonRock.ogg \
+ $(LOCAL_PATH)/newwavelabs/CurveBall.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/CurveBall.ogg \
+ $(LOCAL_PATH)/newwavelabs/DancinFool.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/DancinFool.ogg \
+ $(LOCAL_PATH)/newwavelabs/Ding.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Ding.ogg \
+ $(LOCAL_PATH)/newwavelabs/DonMessWivIt.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/DonMessWivIt.ogg \
+ $(LOCAL_PATH)/newwavelabs/Eastern_Sky.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Eastern_Sky.ogg \
+ $(LOCAL_PATH)/newwavelabs/Enter_the_Nexus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Enter_the_Nexus.ogg \
+ $(LOCAL_PATH)/newwavelabs/EtherShake.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/EtherShake.ogg \
+ $(LOCAL_PATH)/newwavelabs/Funk_Yall.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Funk_Yall.ogg \
+ $(LOCAL_PATH)/newwavelabs/Gimme_Mo_Town.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Gimme_Mo_Town.ogg \
+ $(LOCAL_PATH)/newwavelabs/Glacial_Groove.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Glacial_Groove.ogg \
+ $(LOCAL_PATH)/newwavelabs/Growl.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Growl.ogg \
+ $(LOCAL_PATH)/newwavelabs/HalfwayHome.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/HalfwayHome.ogg \
+ $(LOCAL_PATH)/newwavelabs/LoopyLounge.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/LoopyLounge.ogg \
+ $(LOCAL_PATH)/newwavelabs/LoveFlute.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/LoveFlute.ogg \
+ $(LOCAL_PATH)/newwavelabs/MidEvilJaunt.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/MidEvilJaunt.ogg \
+ $(LOCAL_PATH)/newwavelabs/MildlyAlarming.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/MildlyAlarming.ogg \
+ $(LOCAL_PATH)/newwavelabs/Nairobi.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Nairobi.ogg \
+ $(LOCAL_PATH)/newwavelabs/Nassau.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Nassau.ogg \
+ $(LOCAL_PATH)/newwavelabs/No_Limits.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/No_Limits.ogg \
+ $(LOCAL_PATH)/newwavelabs/OrganDub.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/OrganDub.ogg \
+ $(LOCAL_PATH)/newwavelabs/Paradise_Island.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Paradise_Island.ogg \
+ $(LOCAL_PATH)/newwavelabs/Playa.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Playa.ogg \
+ $(LOCAL_PATH)/newwavelabs/Road_Trip.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Road_Trip.ogg \
+ $(LOCAL_PATH)/newwavelabs/Safari.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Safari.ogg \
+ $(LOCAL_PATH)/newwavelabs/Seville.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Seville.ogg \
+ $(LOCAL_PATH)/newwavelabs/Shes_All_That.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Shes_All_That.ogg \
+ $(LOCAL_PATH)/newwavelabs/SilkyWay.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/SilkyWay.ogg \
+ $(LOCAL_PATH)/newwavelabs/Steppin_Out.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Steppin_Out.ogg \
+ $(LOCAL_PATH)/newwavelabs/Terminated.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Terminated.ogg \
+ $(LOCAL_PATH)/newwavelabs/Third_Eye.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Third_Eye.ogg \
+ $(LOCAL_PATH)/newwavelabs/TwirlAway.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/TwirlAway.ogg \
+ $(LOCAL_PATH)/newwavelabs/World.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/World.ogg
endif
diff --git a/data/sounds/AudioPackage5.mk b/data/sounds/AudioPackage5.mk
index 72384c8..8a03a2e1 100644
--- a/data/sounds/AudioPackage5.mk
+++ b/data/sounds/AudioPackage5.mk
@@ -8,69 +8,69 @@
LOCAL_PATH:= frameworks/base/data/sounds
PRODUCT_COPY_FILES += \
- $(LOCAL_PATH)/Alarm_Buzzer.ogg:system/media/audio/alarms/Alarm_Buzzer.ogg \
- $(LOCAL_PATH)/Alarm_Beep_01.ogg:system/media/audio/alarms/Alarm_Beep_01.ogg \
- $(LOCAL_PATH)/Alarm_Beep_02.ogg:system/media/audio/alarms/Alarm_Beep_02.ogg \
- $(LOCAL_PATH)/Alarm_Classic.ogg:system/media/audio/alarms/Alarm_Classic.ogg \
- $(LOCAL_PATH)/Alarm_Beep_03.ogg:system/media/audio/alarms/Alarm_Beep_03.ogg \
- $(LOCAL_PATH)/effects/Effect_Tick.ogg:system/media/audio/ui/Effect_Tick.ogg \
- $(LOCAL_PATH)/effects/KeypressStandard.ogg:system/media/audio/ui/KeypressStandard.ogg \
- $(LOCAL_PATH)/effects/KeypressSpacebar.ogg:system/media/audio/ui/KeypressSpacebar.ogg \
- $(LOCAL_PATH)/effects/KeypressDelete.ogg:system/media/audio/ui/KeypressDelete.ogg \
- $(LOCAL_PATH)/effects/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \
- $(LOCAL_PATH)/effects/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
- $(LOCAL_PATH)/effects/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
- $(LOCAL_PATH)/effects/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
- $(LOCAL_PATH)/effects/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
- $(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
- $(LOCAL_PATH)/effects/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
- $(LOCAL_PATH)/effects/Dock.ogg:system/media/audio/ui/Dock.ogg \
- $(LOCAL_PATH)/effects/Undock.ogg:system/media/audio/ui/Undock.ogg \
- $(LOCAL_PATH)/effects/Lock.ogg:system/media/audio/ui/Lock.ogg \
- $(LOCAL_PATH)/effects/Unlock.ogg:system/media/audio/ui/Unlock.ogg \
- $(LOCAL_PATH)/effects/ogg/Trusted.ogg:system/media/audio/ui/Trusted.ogg \
- $(LOCAL_PATH)/notifications/Aldebaran.ogg:system/media/audio/notifications/Aldebaran.ogg \
- $(LOCAL_PATH)/notifications/Altair.ogg:system/media/audio/notifications/Altair.ogg \
- $(LOCAL_PATH)/notifications/Antares.ogg:system/media/audio/notifications/Antares.ogg \
- $(LOCAL_PATH)/notifications/arcturus.ogg:system/media/audio/notifications/arcturus.ogg \
- $(LOCAL_PATH)/notifications/Betelgeuse.ogg:system/media/audio/notifications/Betelgeuse.ogg \
- $(LOCAL_PATH)/notifications/Canopus.ogg:system/media/audio/notifications/Canopus.ogg \
- $(LOCAL_PATH)/notifications/Capella.ogg:system/media/audio/notifications/Capella.ogg \
- $(LOCAL_PATH)/notifications/Castor.ogg:system/media/audio/notifications/Castor.ogg \
- $(LOCAL_PATH)/notifications/CetiAlpha.ogg:system/media/audio/notifications/CetiAlpha.ogg \
- $(LOCAL_PATH)/notifications/Deneb.ogg:system/media/audio/notifications/Deneb.ogg \
- $(LOCAL_PATH)/notifications/Electra.ogg:system/media/audio/notifications/Electra.ogg \
- $(LOCAL_PATH)/notifications/Fomalhaut.ogg:system/media/audio/notifications/Fomalhaut.ogg \
- $(LOCAL_PATH)/notifications/Merope.ogg:system/media/audio/notifications/Merope.ogg \
- $(LOCAL_PATH)/notifications/Polaris.ogg:system/media/audio/notifications/Polaris.ogg \
- $(LOCAL_PATH)/notifications/Pollux.ogg:system/media/audio/notifications/Pollux.ogg \
- $(LOCAL_PATH)/notifications/Procyon.ogg:system/media/audio/notifications/Procyon.ogg \
- $(LOCAL_PATH)/notifications/regulus.ogg:system/media/audio/notifications/regulus.ogg \
- $(LOCAL_PATH)/notifications/sirius.ogg:system/media/audio/notifications/sirius.ogg \
- $(LOCAL_PATH)/notifications/Sirrah.ogg:system/media/audio/notifications/Sirrah.ogg \
- $(LOCAL_PATH)/notifications/vega.ogg:system/media/audio/notifications/vega.ogg \
- $(LOCAL_PATH)/ringtones/ANDROMEDA.ogg:system/media/audio/ringtones/ANDROMEDA.ogg \
- $(LOCAL_PATH)/ringtones/Aquila.ogg:system/media/audio/ringtones/Aquila.ogg \
- $(LOCAL_PATH)/ringtones/ArgoNavis.ogg:system/media/audio/ringtones/ArgoNavis.ogg \
- $(LOCAL_PATH)/ringtones/BOOTES.ogg:system/media/audio/ringtones/BOOTES.ogg \
- $(LOCAL_PATH)/ringtones/CANISMAJOR.ogg:system/media/audio/ringtones/CANISMAJOR.ogg \
- $(LOCAL_PATH)/ringtones/Carina.ogg:system/media/audio/ringtones/Carina.ogg \
- $(LOCAL_PATH)/ringtones/CASSIOPEIA.ogg:system/media/audio/ringtones/CASSIOPEIA.ogg \
- $(LOCAL_PATH)/ringtones/Centaurus.ogg:system/media/audio/ringtones/Centaurus.ogg \
- $(LOCAL_PATH)/ringtones/Cygnus.ogg:system/media/audio/ringtones/Cygnus.ogg \
- $(LOCAL_PATH)/ringtones/Draco.ogg:system/media/audio/ringtones/Draco.ogg \
- $(LOCAL_PATH)/ringtones/Eridani.ogg:system/media/audio/ringtones/Eridani.ogg \
- $(LOCAL_PATH)/ringtones/hydra.ogg:system/media/audio/ringtones/hydra.ogg \
- $(LOCAL_PATH)/ringtones/Lyra.ogg:system/media/audio/ringtones/Lyra.ogg \
- $(LOCAL_PATH)/ringtones/Machina.ogg:system/media/audio/ringtones/Machina.ogg \
- $(LOCAL_PATH)/ringtones/Orion.ogg:system/media/audio/ringtones/Orion.ogg \
- $(LOCAL_PATH)/ringtones/Pegasus.ogg:system/media/audio/ringtones/Pegasus.ogg \
- $(LOCAL_PATH)/ringtones/PERSEUS.ogg:system/media/audio/ringtones/PERSEUS.ogg \
- $(LOCAL_PATH)/ringtones/Pyxis.ogg:system/media/audio/ringtones/Pyxis.ogg \
- $(LOCAL_PATH)/ringtones/Rigel.ogg:system/media/audio/ringtones/Rigel.ogg \
- $(LOCAL_PATH)/ringtones/Scarabaeus.ogg:system/media/audio/ringtones/Scarabaeus.ogg \
- $(LOCAL_PATH)/ringtones/Sceptrum.ogg:system/media/audio/ringtones/Sceptrum.ogg \
- $(LOCAL_PATH)/ringtones/Solarium.ogg:system/media/audio/ringtones/Solarium.ogg \
- $(LOCAL_PATH)/ringtones/Testudo.ogg:system/media/audio/ringtones/Testudo.ogg \
- $(LOCAL_PATH)/ringtones/URSAMINOR.ogg:system/media/audio/ringtones/URSAMINOR.ogg \
- $(LOCAL_PATH)/ringtones/Vespa.ogg:system/media/audio/ringtones/Vespa.ogg
+ $(LOCAL_PATH)/Alarm_Buzzer.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Alarm_Buzzer.ogg \
+ $(LOCAL_PATH)/Alarm_Beep_01.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Alarm_Beep_01.ogg \
+ $(LOCAL_PATH)/Alarm_Beep_02.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Alarm_Beep_02.ogg \
+ $(LOCAL_PATH)/Alarm_Classic.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Alarm_Classic.ogg \
+ $(LOCAL_PATH)/Alarm_Beep_03.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Alarm_Beep_03.ogg \
+ $(LOCAL_PATH)/effects/Effect_Tick.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Effect_Tick.ogg \
+ $(LOCAL_PATH)/effects/KeypressStandard.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressStandard.ogg \
+ $(LOCAL_PATH)/effects/KeypressSpacebar.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressSpacebar.ogg \
+ $(LOCAL_PATH)/effects/KeypressDelete.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressDelete.ogg \
+ $(LOCAL_PATH)/effects/KeypressInvalid.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressInvalid.ogg \
+ $(LOCAL_PATH)/effects/KeypressReturn.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressReturn.ogg \
+ $(LOCAL_PATH)/effects/VideoRecord.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/VideoStop.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/VideoStop.ogg \
+ $(LOCAL_PATH)/effects/camera_click.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/camera_click.ogg \
+ $(LOCAL_PATH)/effects/ogg/camera_focus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/camera_focus.ogg \
+ $(LOCAL_PATH)/effects/LowBattery.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/LowBattery.ogg \
+ $(LOCAL_PATH)/effects/Dock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Dock.ogg \
+ $(LOCAL_PATH)/effects/Undock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Undock.ogg \
+ $(LOCAL_PATH)/effects/Lock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Lock.ogg \
+ $(LOCAL_PATH)/effects/Unlock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Unlock.ogg \
+ $(LOCAL_PATH)/effects/ogg/Trusted.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Trusted.ogg \
+ $(LOCAL_PATH)/notifications/Aldebaran.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Aldebaran.ogg \
+ $(LOCAL_PATH)/notifications/Altair.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Altair.ogg \
+ $(LOCAL_PATH)/notifications/Antares.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Antares.ogg \
+ $(LOCAL_PATH)/notifications/arcturus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/arcturus.ogg \
+ $(LOCAL_PATH)/notifications/Betelgeuse.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Betelgeuse.ogg \
+ $(LOCAL_PATH)/notifications/Canopus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Canopus.ogg \
+ $(LOCAL_PATH)/notifications/Capella.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Capella.ogg \
+ $(LOCAL_PATH)/notifications/Castor.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Castor.ogg \
+ $(LOCAL_PATH)/notifications/CetiAlpha.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/CetiAlpha.ogg \
+ $(LOCAL_PATH)/notifications/Deneb.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Deneb.ogg \
+ $(LOCAL_PATH)/notifications/Electra.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Electra.ogg \
+ $(LOCAL_PATH)/notifications/Fomalhaut.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Fomalhaut.ogg \
+ $(LOCAL_PATH)/notifications/Merope.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Merope.ogg \
+ $(LOCAL_PATH)/notifications/Polaris.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Polaris.ogg \
+ $(LOCAL_PATH)/notifications/Pollux.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Pollux.ogg \
+ $(LOCAL_PATH)/notifications/Procyon.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Procyon.ogg \
+ $(LOCAL_PATH)/notifications/regulus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/regulus.ogg \
+ $(LOCAL_PATH)/notifications/sirius.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/sirius.ogg \
+ $(LOCAL_PATH)/notifications/Sirrah.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Sirrah.ogg \
+ $(LOCAL_PATH)/notifications/vega.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/vega.ogg \
+ $(LOCAL_PATH)/ringtones/ANDROMEDA.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/ANDROMEDA.ogg \
+ $(LOCAL_PATH)/ringtones/Aquila.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Aquila.ogg \
+ $(LOCAL_PATH)/ringtones/ArgoNavis.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/ArgoNavis.ogg \
+ $(LOCAL_PATH)/ringtones/BOOTES.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/BOOTES.ogg \
+ $(LOCAL_PATH)/ringtones/CANISMAJOR.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/CANISMAJOR.ogg \
+ $(LOCAL_PATH)/ringtones/Carina.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Carina.ogg \
+ $(LOCAL_PATH)/ringtones/CASSIOPEIA.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/CASSIOPEIA.ogg \
+ $(LOCAL_PATH)/ringtones/Centaurus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Centaurus.ogg \
+ $(LOCAL_PATH)/ringtones/Cygnus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Cygnus.ogg \
+ $(LOCAL_PATH)/ringtones/Draco.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Draco.ogg \
+ $(LOCAL_PATH)/ringtones/Eridani.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Eridani.ogg \
+ $(LOCAL_PATH)/ringtones/hydra.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/hydra.ogg \
+ $(LOCAL_PATH)/ringtones/Lyra.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Lyra.ogg \
+ $(LOCAL_PATH)/ringtones/Machina.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Machina.ogg \
+ $(LOCAL_PATH)/ringtones/Orion.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Orion.ogg \
+ $(LOCAL_PATH)/ringtones/Pegasus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Pegasus.ogg \
+ $(LOCAL_PATH)/ringtones/PERSEUS.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/PERSEUS.ogg \
+ $(LOCAL_PATH)/ringtones/Pyxis.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Pyxis.ogg \
+ $(LOCAL_PATH)/ringtones/Rigel.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Rigel.ogg \
+ $(LOCAL_PATH)/ringtones/Scarabaeus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Scarabaeus.ogg \
+ $(LOCAL_PATH)/ringtones/Sceptrum.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Sceptrum.ogg \
+ $(LOCAL_PATH)/ringtones/Solarium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Solarium.ogg \
+ $(LOCAL_PATH)/ringtones/Testudo.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Testudo.ogg \
+ $(LOCAL_PATH)/ringtones/URSAMINOR.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/URSAMINOR.ogg \
+ $(LOCAL_PATH)/ringtones/Vespa.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Vespa.ogg
diff --git a/data/sounds/AudioPackage6.mk b/data/sounds/AudioPackage6.mk
index 5413704..a778261 100644
--- a/data/sounds/AudioPackage6.mk
+++ b/data/sounds/AudioPackage6.mk
@@ -8,41 +8,41 @@
LOCAL_PATH:= frameworks/base/data/sounds
PRODUCT_COPY_FILES += \
- $(LOCAL_PATH)/alarms/ogg/Barium.ogg:system/media/audio/alarms/Barium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Cesium.ogg:system/media/audio/alarms/Cesium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Plutonium.ogg:system/media/audio/alarms/Plutonium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Scandium.ogg:system/media/audio/alarms/Scandium.ogg \
- $(LOCAL_PATH)/effects/ogg/Effect_Tick.ogg:system/media/audio/ui/Effect_Tick.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressStandard.ogg:system/media/audio/ui/KeypressStandard.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressSpacebar.ogg:system/media/audio/ui/KeypressSpacebar.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressDelete.ogg:system/media/audio/ui/KeypressDelete.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
- $(LOCAL_PATH)/effects/material/ogg/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
- $(LOCAL_PATH)/effects/material/ogg/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
- $(LOCAL_PATH)/effects/material/ogg/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
- $(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
- $(LOCAL_PATH)/effects/material/ogg/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
- $(LOCAL_PATH)/effects/ogg/Dock.ogg:system/media/audio/ui/Dock.ogg \
- $(LOCAL_PATH)/effects/ogg/Undock.ogg:system/media/audio/ui/Undock.ogg \
- $(LOCAL_PATH)/effects/ogg/Lock.ogg:system/media/audio/ui/Lock.ogg \
- $(LOCAL_PATH)/effects/ogg/Unlock.ogg:system/media/audio/ui/Unlock.ogg \
- $(LOCAL_PATH)/effects/ogg/Trusted.ogg:system/media/audio/ui/Trusted.ogg \
- $(LOCAL_PATH)/notifications/ogg/Antimony.ogg:system/media/audio/notifications/Antimony.ogg \
- $(LOCAL_PATH)/notifications/ogg/Argon.ogg:system/media/audio/notifications/Argon.ogg \
- $(LOCAL_PATH)/notifications/ogg/Beryllium.ogg:system/media/audio/notifications/Beryllium.ogg \
- $(LOCAL_PATH)/notifications/ogg/Cobalt.ogg:system/media/audio/notifications/Cobalt.ogg \
- $(LOCAL_PATH)/notifications/ogg/Fluorine.ogg:system/media/audio/notifications/Fluorine.ogg \
- $(LOCAL_PATH)/notifications/ogg/Gallium.ogg:system/media/audio/notifications/Gallium.ogg \
- $(LOCAL_PATH)/notifications/ogg/Helium.ogg:system/media/audio/notifications/Helium.ogg \
- $(LOCAL_PATH)/notifications/ogg/Iridium.ogg:system/media/audio/notifications/Iridium.ogg \
- $(LOCAL_PATH)/notifications/ogg/Krypton.ogg:system/media/audio/notifications/Krypton.ogg \
- $(LOCAL_PATH)/notifications/ogg/Palladium.ogg:system/media/audio/notifications/Palladium.ogg \
- $(LOCAL_PATH)/notifications/ogg/Radon.ogg:system/media/audio/notifications/Radon.ogg \
- $(LOCAL_PATH)/notifications/ogg/Rubidium.ogg:system/media/audio/notifications/Rubidium.ogg \
- $(LOCAL_PATH)/notifications/ogg/Selenium.ogg:system/media/audio/notifications/Selenium.ogg \
- $(LOCAL_PATH)/notifications/ogg/Strontium.ogg:system/media/audio/notifications/Strontium.ogg \
- $(LOCAL_PATH)/notifications/ogg/Thallium.ogg:system/media/audio/notifications/Thallium.ogg \
- $(LOCAL_PATH)/notifications/ogg/Xenon.ogg:system/media/audio/notifications/Xenon.ogg \
- $(LOCAL_PATH)/notifications/ogg/Zirconium.ogg:system/media/audio/notifications/Zirconium.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Sceptrum.ogg:system/media/audio/ringtones/Sceptrum.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Barium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Barium.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Cesium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Cesium.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Plutonium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Plutonium.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Scandium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Scandium.ogg \
+ $(LOCAL_PATH)/effects/ogg/Effect_Tick.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Effect_Tick.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressStandard.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressStandard.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressSpacebar.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressSpacebar.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressDelete.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressDelete.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressInvalid.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressInvalid.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressReturn.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressReturn.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/VideoRecord.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/VideoStop.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/VideoStop.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/camera_click.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/camera_click.ogg \
+ $(LOCAL_PATH)/effects/ogg/camera_focus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/camera_focus.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/LowBattery.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/LowBattery.ogg \
+ $(LOCAL_PATH)/effects/ogg/Dock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Dock.ogg \
+ $(LOCAL_PATH)/effects/ogg/Undock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Undock.ogg \
+ $(LOCAL_PATH)/effects/ogg/Lock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Lock.ogg \
+ $(LOCAL_PATH)/effects/ogg/Unlock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Unlock.ogg \
+ $(LOCAL_PATH)/effects/ogg/Trusted.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Trusted.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Antimony.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Antimony.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Argon.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Argon.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Beryllium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Beryllium.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Cobalt.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Cobalt.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Fluorine.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Fluorine.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Gallium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Gallium.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Helium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Helium.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Iridium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Iridium.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Krypton.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Krypton.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Palladium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Palladium.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Radon.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Radon.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Rubidium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Rubidium.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Selenium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Selenium.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Strontium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Strontium.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Thallium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Thallium.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Xenon.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Xenon.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Zirconium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Zirconium.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Sceptrum.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Sceptrum.ogg \
diff --git a/data/sounds/AudioPackage7.mk b/data/sounds/AudioPackage7.mk
index e4763be..27e349d2 100644
--- a/data/sounds/AudioPackage7.mk
+++ b/data/sounds/AudioPackage7.mk
@@ -8,64 +8,64 @@
LOCAL_PATH:= frameworks/base/data/sounds
PRODUCT_COPY_FILES += \
- $(LOCAL_PATH)/alarms/ogg/Cesium.ogg:system/media/audio/alarms/Cesium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Fermium.ogg:system/media/audio/alarms/Fermium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Hassium.ogg:system/media/audio/alarms/Hassium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Neptunium.ogg:system/media/audio/alarms/Neptunium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Nobelium.ogg:system/media/audio/alarms/Nobelium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Plutonium.ogg:system/media/audio/alarms/Plutonium.ogg \
- $(LOCAL_PATH)/effects/ogg/Effect_Tick.ogg:system/media/audio/ui/Effect_Tick.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressStandard_120.ogg:system/media/audio/ui/KeypressStandard.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressSpacebar_120.ogg:system/media/audio/ui/KeypressSpacebar.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressDelete_120.ogg:system/media/audio/ui/KeypressDelete.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressInvalid_120.ogg:system/media/audio/ui/KeypressInvalid.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressReturn_120.ogg:system/media/audio/ui/KeypressReturn.ogg \
- $(LOCAL_PATH)/effects/material/ogg/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
- $(LOCAL_PATH)/effects/material/ogg/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
- $(LOCAL_PATH)/effects/material/ogg/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
- $(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
- $(LOCAL_PATH)/effects/material/ogg/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
- $(LOCAL_PATH)/effects/ogg/Dock.ogg:system/media/audio/ui/Dock.ogg \
- $(LOCAL_PATH)/effects/ogg/Undock.ogg:system/media/audio/ui/Undock.ogg \
- $(LOCAL_PATH)/effects/ogg/Lock.ogg:system/media/audio/ui/Lock.ogg \
- $(LOCAL_PATH)/effects/ogg/Unlock.ogg:system/media/audio/ui/Unlock.ogg \
- $(LOCAL_PATH)/effects/ogg/Trusted.ogg:system/media/audio/ui/Trusted.ogg \
- $(LOCAL_PATH)/notifications/ogg/Adara.ogg:system/media/audio/notifications/Adara.ogg \
- $(LOCAL_PATH)/notifications/ogg/Arcturus.ogg:system/media/audio/notifications/Arcturus.ogg \
- $(LOCAL_PATH)/notifications/ogg/Bellatrix.ogg:system/media/audio/notifications/Bellatrix.ogg \
- $(LOCAL_PATH)/notifications/ogg/Capella.ogg:system/media/audio/notifications/Capella.ogg \
- $(LOCAL_PATH)/notifications/ogg/CetiAlpha.ogg:system/media/audio/notifications/CetiAlpha.ogg \
- $(LOCAL_PATH)/notifications/ogg/Hojus.ogg:system/media/audio/notifications/Hojus.ogg \
- $(LOCAL_PATH)/notifications/ogg/Lalande.ogg:system/media/audio/notifications/Lalande.ogg \
- $(LOCAL_PATH)/notifications/ogg/Mira.ogg:system/media/audio/notifications/Mira.ogg \
- $(LOCAL_PATH)/notifications/ogg/Polaris.ogg:system/media/audio/notifications/Polaris.ogg \
- $(LOCAL_PATH)/notifications/ogg/Pollux.ogg:system/media/audio/notifications/Pollux.ogg \
- $(LOCAL_PATH)/notifications/ogg/Procyon.ogg:system/media/audio/notifications/Procyon.ogg \
- $(LOCAL_PATH)/notifications/ogg/Proxima.ogg:system/media/audio/notifications/Proxima.ogg \
- $(LOCAL_PATH)/notifications/ogg/Shaula.ogg:system/media/audio/notifications/Shaula.ogg \
- $(LOCAL_PATH)/notifications/ogg/Spica.ogg:system/media/audio/notifications/Spica.ogg \
- $(LOCAL_PATH)/notifications/ogg/Tejat.ogg:system/media/audio/notifications/Tejat.ogg \
- $(LOCAL_PATH)/notifications/ogg/Upsilon.ogg:system/media/audio/notifications/Upsilon.ogg \
- $(LOCAL_PATH)/notifications/ogg/Vega.ogg:system/media/audio/notifications/Vega.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Andromeda.ogg:system/media/audio/ringtones/Andromeda.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Aquila.ogg:system/media/audio/ringtones/Aquila.ogg \
- $(LOCAL_PATH)/ringtones/ogg/ArgoNavis.ogg:system/media/audio/ringtones/ArgoNavis.ogg \
- $(LOCAL_PATH)/ringtones/ogg/CanisMajor.ogg:system/media/audio/ringtones/CanisMajor.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Carina.ogg:system/media/audio/ringtones/Carina.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Centaurus.ogg:system/media/audio/ringtones/Centaurus.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Cygnus.ogg:system/media/audio/ringtones/Cygnus.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Draco.ogg:system/media/audio/ringtones/Draco.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Girtab.ogg:system/media/audio/ringtones/Girtab.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Hydra.ogg:system/media/audio/ringtones/Hydra.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Machina.ogg:system/media/audio/ringtones/Machina.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Orion.ogg:system/media/audio/ringtones/Orion.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Pegasus.ogg:system/media/audio/ringtones/Pegasus.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Perseus.ogg:system/media/audio/ringtones/Perseus.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Pyxis.ogg:system/media/audio/ringtones/Pyxis.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Rigel.ogg:system/media/audio/ringtones/Rigel.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Scarabaeus.ogg:system/media/audio/ringtones/Scarabaeus.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Sceptrum.ogg:system/media/audio/ringtones/Sceptrum.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Solarium.ogg:system/media/audio/ringtones/Solarium.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Themos.ogg:system/media/audio/ringtones/Themos.ogg \
- $(LOCAL_PATH)/ringtones/ogg/UrsaMinor.ogg:system/media/audio/ringtones/UrsaMinor.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Zeta.ogg:system/media/audio/ringtones/Zeta.ogg
+ $(LOCAL_PATH)/alarms/ogg/Cesium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Cesium.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Fermium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Fermium.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Hassium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Hassium.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Neptunium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Neptunium.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Nobelium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Nobelium.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Plutonium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Plutonium.ogg \
+ $(LOCAL_PATH)/effects/ogg/Effect_Tick.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Effect_Tick.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressStandard_120.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressStandard.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressSpacebar_120.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressSpacebar.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressDelete_120.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressDelete.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressInvalid_120.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressInvalid.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressReturn_120.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressReturn.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/VideoRecord.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/VideoStop.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/VideoStop.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/camera_click.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/camera_click.ogg \
+ $(LOCAL_PATH)/effects/ogg/camera_focus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/camera_focus.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/LowBattery.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/LowBattery.ogg \
+ $(LOCAL_PATH)/effects/ogg/Dock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Dock.ogg \
+ $(LOCAL_PATH)/effects/ogg/Undock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Undock.ogg \
+ $(LOCAL_PATH)/effects/ogg/Lock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Lock.ogg \
+ $(LOCAL_PATH)/effects/ogg/Unlock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Unlock.ogg \
+ $(LOCAL_PATH)/effects/ogg/Trusted.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Trusted.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Adara.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Adara.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Arcturus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Arcturus.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Bellatrix.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Bellatrix.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Capella.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Capella.ogg \
+ $(LOCAL_PATH)/notifications/ogg/CetiAlpha.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/CetiAlpha.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Hojus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Hojus.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Lalande.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Lalande.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Mira.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Mira.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Polaris.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Polaris.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Pollux.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Pollux.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Procyon.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Procyon.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Proxima.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Proxima.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Shaula.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Shaula.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Spica.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Spica.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Tejat.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Tejat.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Upsilon.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Upsilon.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Vega.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Vega.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Andromeda.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Andromeda.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Aquila.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Aquila.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/ArgoNavis.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/ArgoNavis.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/CanisMajor.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/CanisMajor.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Carina.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Carina.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Centaurus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Centaurus.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Cygnus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Cygnus.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Draco.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Draco.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Girtab.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Girtab.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Hydra.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Hydra.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Machina.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Machina.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Orion.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Orion.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Pegasus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Pegasus.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Perseus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Perseus.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Pyxis.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Pyxis.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Rigel.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Rigel.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Scarabaeus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Scarabaeus.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Sceptrum.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Sceptrum.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Solarium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Solarium.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Themos.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Themos.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/UrsaMinor.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/UrsaMinor.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Zeta.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Zeta.ogg
diff --git a/data/sounds/AudioPackage7alt.mk b/data/sounds/AudioPackage7alt.mk
index 30e6173..a0f4d89 100644
--- a/data/sounds/AudioPackage7alt.mk
+++ b/data/sounds/AudioPackage7alt.mk
@@ -8,63 +8,63 @@
LOCAL_PATH:= frameworks/base/data/sounds
PRODUCT_COPY_FILES += \
- $(LOCAL_PATH)/alarms/ogg-jp/Argon.ogg:system/media/audio/alarms/Argon.ogg \
- $(LOCAL_PATH)/alarms/ogg-jp/Carbon.ogg:system/media/audio/alarms/Carbon.ogg \
- $(LOCAL_PATH)/alarms/ogg-jp/Helium.ogg:system/media/audio/alarms/Helium.ogg \
- $(LOCAL_PATH)/alarms/ogg-jp/Krypton.ogg:system/media/audio/alarms/Krypton.ogg \
- $(LOCAL_PATH)/alarms/ogg-jp/Neon.ogg:system/media/audio/alarms/Neon.ogg \
- $(LOCAL_PATH)/alarms/ogg-jp/Oxygen.ogg:system/media/audio/alarms/Oxygen.ogg \
- $(LOCAL_PATH)/effects/ogg/Effect_Tick.ogg:system/media/audio/ui/Effect_Tick.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressStandard_120.ogg:system/media/audio/ui/KeypressStandard.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressSpacebar_120.ogg:system/media/audio/ui/KeypressSpacebar.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressDelete_120.ogg:system/media/audio/ui/KeypressDelete.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressInvalid_120.ogg:system/media/audio/ui/KeypressInvalid.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressReturn_120.ogg:system/media/audio/ui/KeypressReturn.ogg \
- $(LOCAL_PATH)/effects/ogg/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
- $(LOCAL_PATH)/effects/ogg/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
- $(LOCAL_PATH)/effects/material/ogg/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
- $(LOCAL_PATH)/effects/ogg/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
- $(LOCAL_PATH)/effects/ogg/Dock.ogg:system/media/audio/ui/Dock.ogg \
- $(LOCAL_PATH)/effects/ogg/Undock.ogg:system/media/audio/ui/Undock.ogg \
- $(LOCAL_PATH)/effects/ogg/Lock.ogg:system/media/audio/ui/Lock.ogg \
- $(LOCAL_PATH)/effects/ogg/Unlock.ogg:system/media/audio/ui/Unlock.ogg \
- $(LOCAL_PATH)/effects/ogg/Trusted.ogg:system/media/audio/ui/Trusted.ogg \
- $(LOCAL_PATH)/notifications/ogg/Adara.ogg:system/media/audio/notifications/Adara.ogg \
- $(LOCAL_PATH)/notifications/ogg/Arcturus.ogg:system/media/audio/notifications/Arcturus.ogg \
- $(LOCAL_PATH)/notifications/ogg/Bellatrix.ogg:system/media/audio/notifications/Bellatrix.ogg \
- $(LOCAL_PATH)/notifications/ogg/Capella.ogg:system/media/audio/notifications/Capella.ogg \
- $(LOCAL_PATH)/notifications/ogg/CetiAlpha.ogg:system/media/audio/notifications/CetiAlpha.ogg \
- $(LOCAL_PATH)/notifications/ogg/Hojus.ogg:system/media/audio/notifications/Hojus.ogg \
- $(LOCAL_PATH)/notifications/ogg/Lalande.ogg:system/media/audio/notifications/Lalande.ogg \
- $(LOCAL_PATH)/notifications/ogg/Mira.ogg:system/media/audio/notifications/Mira.ogg \
- $(LOCAL_PATH)/notifications/ogg/Polaris.ogg:system/media/audio/notifications/Polaris.ogg \
- $(LOCAL_PATH)/notifications/ogg/Pollux.ogg:system/media/audio/notifications/Pollux.ogg \
- $(LOCAL_PATH)/notifications/ogg/Procyon.ogg:system/media/audio/notifications/Procyon.ogg \
- $(LOCAL_PATH)/notifications/ogg/Proxima.ogg:system/media/audio/notifications/Proxima.ogg \
- $(LOCAL_PATH)/notifications/ogg/Shaula.ogg:system/media/audio/notifications/Shaula.ogg \
- $(LOCAL_PATH)/notifications/ogg/Spica.ogg:system/media/audio/notifications/Spica.ogg \
- $(LOCAL_PATH)/notifications/ogg/Tejat.ogg:system/media/audio/notifications/Tejat.ogg \
- $(LOCAL_PATH)/notifications/ogg/Upsilon.ogg:system/media/audio/notifications/Upsilon.ogg \
- $(LOCAL_PATH)/notifications/ogg/Vega.ogg:system/media/audio/notifications/Vega.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Andromeda.ogg:system/media/audio/ringtones/Andromeda.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Aquila.ogg:system/media/audio/ringtones/Aquila.ogg \
- $(LOCAL_PATH)/ringtones/ogg/ArgoNavis.ogg:system/media/audio/ringtones/ArgoNavis.ogg \
- $(LOCAL_PATH)/ringtones/ogg/CanisMajor.ogg:system/media/audio/ringtones/CanisMajor.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Carina.ogg:system/media/audio/ringtones/Carina.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Centaurus.ogg:system/media/audio/ringtones/Centaurus.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Cygnus.ogg:system/media/audio/ringtones/Cygnus.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Draco.ogg:system/media/audio/ringtones/Draco.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Girtab.ogg:system/media/audio/ringtones/Girtab.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Hydra.ogg:system/media/audio/ringtones/Hydra.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Machina.ogg:system/media/audio/ringtones/Machina.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Orion.ogg:system/media/audio/ringtones/Orion.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Pegasus.ogg:system/media/audio/ringtones/Pegasus.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Perseus.ogg:system/media/audio/ringtones/Perseus.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Pyxis.ogg:system/media/audio/ringtones/Pyxis.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Rigel.ogg:system/media/audio/ringtones/Rigel.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Scarabaeus.ogg:system/media/audio/ringtones/Scarabaeus.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Sceptrum.ogg:system/media/audio/ringtones/Sceptrum.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Solarium.ogg:system/media/audio/ringtones/Solarium.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Themos.ogg:system/media/audio/ringtones/Themos.ogg \
- $(LOCAL_PATH)/ringtones/ogg/UrsaMinor.ogg:system/media/audio/ringtones/UrsaMinor.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Zeta.ogg:system/media/audio/ringtones/Zeta.ogg
+ $(LOCAL_PATH)/alarms/ogg-jp/Argon.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Argon.ogg \
+ $(LOCAL_PATH)/alarms/ogg-jp/Carbon.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Carbon.ogg \
+ $(LOCAL_PATH)/alarms/ogg-jp/Helium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Helium.ogg \
+ $(LOCAL_PATH)/alarms/ogg-jp/Krypton.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Krypton.ogg \
+ $(LOCAL_PATH)/alarms/ogg-jp/Neon.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Neon.ogg \
+ $(LOCAL_PATH)/alarms/ogg-jp/Oxygen.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Oxygen.ogg \
+ $(LOCAL_PATH)/effects/ogg/Effect_Tick.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Effect_Tick.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressStandard_120.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressStandard.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressSpacebar_120.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressSpacebar.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressDelete_120.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressDelete.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressInvalid_120.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressInvalid.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressReturn_120.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressReturn.ogg \
+ $(LOCAL_PATH)/effects/ogg/VideoRecord.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/ogg/VideoStop.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/VideoStop.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/camera_click.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/camera_click.ogg \
+ $(LOCAL_PATH)/effects/ogg/LowBattery.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/LowBattery.ogg \
+ $(LOCAL_PATH)/effects/ogg/Dock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Dock.ogg \
+ $(LOCAL_PATH)/effects/ogg/Undock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Undock.ogg \
+ $(LOCAL_PATH)/effects/ogg/Lock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Lock.ogg \
+ $(LOCAL_PATH)/effects/ogg/Unlock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Unlock.ogg \
+ $(LOCAL_PATH)/effects/ogg/Trusted.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Trusted.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Adara.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Adara.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Arcturus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Arcturus.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Bellatrix.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Bellatrix.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Capella.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Capella.ogg \
+ $(LOCAL_PATH)/notifications/ogg/CetiAlpha.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/CetiAlpha.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Hojus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Hojus.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Lalande.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Lalande.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Mira.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Mira.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Polaris.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Polaris.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Pollux.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Pollux.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Procyon.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Procyon.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Proxima.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Proxima.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Shaula.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Shaula.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Spica.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Spica.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Tejat.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Tejat.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Upsilon.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Upsilon.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Vega.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Vega.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Andromeda.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Andromeda.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Aquila.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Aquila.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/ArgoNavis.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/ArgoNavis.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/CanisMajor.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/CanisMajor.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Carina.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Carina.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Centaurus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Centaurus.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Cygnus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Cygnus.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Draco.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Draco.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Girtab.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Girtab.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Hydra.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Hydra.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Machina.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Machina.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Orion.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Orion.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Pegasus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Pegasus.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Perseus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Perseus.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Pyxis.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Pyxis.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Rigel.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Rigel.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Scarabaeus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Scarabaeus.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Sceptrum.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Sceptrum.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Solarium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Solarium.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Themos.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Themos.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/UrsaMinor.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/UrsaMinor.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Zeta.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Zeta.ogg
diff --git a/data/sounds/AudioPackage8.mk b/data/sounds/AudioPackage8.mk
index b38e62d..032b4d2 100644
--- a/data/sounds/AudioPackage8.mk
+++ b/data/sounds/AudioPackage8.mk
@@ -8,66 +8,66 @@
LOCAL_PATH:= frameworks/base/data/sounds
PRODUCT_COPY_FILES += \
- $(LOCAL_PATH)/alarms/ogg/Cesium.ogg:system/media/audio/alarms/Cesium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Fermium.ogg:system/media/audio/alarms/Fermium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Hassium.ogg:system/media/audio/alarms/Hassium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Neptunium.ogg:system/media/audio/alarms/Neptunium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Nobelium.ogg:system/media/audio/alarms/Nobelium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Osmium.ogg:system/media/audio/alarms/Osmium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Plutonium.ogg:system/media/audio/alarms/Plutonium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Promethium.ogg:system/media/audio/alarms/Promethium.ogg \
- $(LOCAL_PATH)/effects/ogg/Effect_Tick.ogg:system/media/audio/ui/Effect_Tick.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressStandard.ogg:system/media/audio/ui/KeypressStandard.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressSpacebar.ogg:system/media/audio/ui/KeypressSpacebar.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressDelete.ogg:system/media/audio/ui/KeypressDelete.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
- $(LOCAL_PATH)/effects/material/ogg/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
- $(LOCAL_PATH)/effects/material/ogg/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
- $(LOCAL_PATH)/effects/material/ogg/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
- $(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
- $(LOCAL_PATH)/effects/material/ogg/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
- $(LOCAL_PATH)/effects/ogg/Dock.ogg:system/media/audio/ui/Dock.ogg \
- $(LOCAL_PATH)/effects/ogg/Undock.ogg:system/media/audio/ui/Undock.ogg \
- $(LOCAL_PATH)/effects/ogg/Lock.ogg:system/media/audio/ui/Lock.ogg \
- $(LOCAL_PATH)/effects/ogg/Unlock.ogg:system/media/audio/ui/Unlock.ogg \
- $(LOCAL_PATH)/effects/ogg/Trusted.ogg:system/media/audio/ui/Trusted.ogg \
- $(LOCAL_PATH)/notifications/ogg/Adara.ogg:system/media/audio/notifications/Adara.ogg \
- $(LOCAL_PATH)/notifications/ogg/Arcturus.ogg:system/media/audio/notifications/Arcturus.ogg \
- $(LOCAL_PATH)/notifications/ogg/Bellatrix.ogg:system/media/audio/notifications/Bellatrix.ogg \
- $(LOCAL_PATH)/notifications/ogg/Capella.ogg:system/media/audio/notifications/Capella.ogg \
- $(LOCAL_PATH)/notifications/ogg/CetiAlpha.ogg:system/media/audio/notifications/CetiAlpha.ogg \
- $(LOCAL_PATH)/notifications/ogg/Hojus.ogg:system/media/audio/notifications/Hojus.ogg \
- $(LOCAL_PATH)/notifications/ogg/Lalande.ogg:system/media/audio/notifications/Lalande.ogg \
- $(LOCAL_PATH)/notifications/ogg/Mira.ogg:system/media/audio/notifications/Mira.ogg \
- $(LOCAL_PATH)/notifications/ogg/Polaris.ogg:system/media/audio/notifications/Polaris.ogg \
- $(LOCAL_PATH)/notifications/ogg/Pollux.ogg:system/media/audio/notifications/Pollux.ogg \
- $(LOCAL_PATH)/notifications/ogg/Procyon.ogg:system/media/audio/notifications/Procyon.ogg \
- $(LOCAL_PATH)/notifications/ogg/Proxima.ogg:system/media/audio/notifications/Proxima.ogg \
- $(LOCAL_PATH)/notifications/ogg/Shaula.ogg:system/media/audio/notifications/Shaula.ogg \
- $(LOCAL_PATH)/notifications/ogg/Spica.ogg:system/media/audio/notifications/Spica.ogg \
- $(LOCAL_PATH)/notifications/ogg/Tejat.ogg:system/media/audio/notifications/Tejat.ogg \
- $(LOCAL_PATH)/notifications/ogg/Upsilon.ogg:system/media/audio/notifications/Upsilon.ogg \
- $(LOCAL_PATH)/notifications/ogg/Vega.ogg:system/media/audio/notifications/Vega.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Andromeda.ogg:system/media/audio/ringtones/Andromeda.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Aquila.ogg:system/media/audio/ringtones/Aquila.ogg \
- $(LOCAL_PATH)/ringtones/ogg/ArgoNavis.ogg:system/media/audio/ringtones/ArgoNavis.ogg \
- $(LOCAL_PATH)/ringtones/ogg/CanisMajor.ogg:system/media/audio/ringtones/CanisMajor.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Carina.ogg:system/media/audio/ringtones/Carina.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Centaurus.ogg:system/media/audio/ringtones/Centaurus.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Cygnus.ogg:system/media/audio/ringtones/Cygnus.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Draco.ogg:system/media/audio/ringtones/Draco.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Girtab.ogg:system/media/audio/ringtones/Girtab.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Hydra.ogg:system/media/audio/ringtones/Hydra.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Machina.ogg:system/media/audio/ringtones/Machina.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Orion.ogg:system/media/audio/ringtones/Orion.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Pegasus.ogg:system/media/audio/ringtones/Pegasus.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Perseus.ogg:system/media/audio/ringtones/Perseus.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Pyxis.ogg:system/media/audio/ringtones/Pyxis.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Rigel.ogg:system/media/audio/ringtones/Rigel.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Scarabaeus.ogg:system/media/audio/ringtones/Scarabaeus.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Sceptrum.ogg:system/media/audio/ringtones/Sceptrum.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Solarium.ogg:system/media/audio/ringtones/Solarium.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Themos.ogg:system/media/audio/ringtones/Themos.ogg \
- $(LOCAL_PATH)/ringtones/ogg/UrsaMinor.ogg:system/media/audio/ringtones/UrsaMinor.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Zeta.ogg:system/media/audio/ringtones/Zeta.ogg
+ $(LOCAL_PATH)/alarms/ogg/Cesium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Cesium.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Fermium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Fermium.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Hassium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Hassium.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Neptunium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Neptunium.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Nobelium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Nobelium.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Osmium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Osmium.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Plutonium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Plutonium.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Promethium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Promethium.ogg \
+ $(LOCAL_PATH)/effects/ogg/Effect_Tick.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Effect_Tick.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressStandard.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressStandard.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressSpacebar.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressSpacebar.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressDelete.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressDelete.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressInvalid.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressInvalid.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressReturn.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressReturn.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/VideoRecord.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/VideoStop.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/VideoStop.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/camera_click.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/camera_click.ogg \
+ $(LOCAL_PATH)/effects/ogg/camera_focus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/camera_focus.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/LowBattery.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/LowBattery.ogg \
+ $(LOCAL_PATH)/effects/ogg/Dock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Dock.ogg \
+ $(LOCAL_PATH)/effects/ogg/Undock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Undock.ogg \
+ $(LOCAL_PATH)/effects/ogg/Lock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Lock.ogg \
+ $(LOCAL_PATH)/effects/ogg/Unlock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Unlock.ogg \
+ $(LOCAL_PATH)/effects/ogg/Trusted.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Trusted.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Adara.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Adara.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Arcturus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Arcturus.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Bellatrix.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Bellatrix.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Capella.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Capella.ogg \
+ $(LOCAL_PATH)/notifications/ogg/CetiAlpha.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/CetiAlpha.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Hojus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Hojus.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Lalande.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Lalande.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Mira.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Mira.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Polaris.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Polaris.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Pollux.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Pollux.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Procyon.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Procyon.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Proxima.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Proxima.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Shaula.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Shaula.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Spica.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Spica.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Tejat.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Tejat.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Upsilon.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Upsilon.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Vega.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Vega.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Andromeda.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Andromeda.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Aquila.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Aquila.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/ArgoNavis.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/ArgoNavis.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/CanisMajor.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/CanisMajor.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Carina.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Carina.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Centaurus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Centaurus.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Cygnus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Cygnus.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Draco.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Draco.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Girtab.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Girtab.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Hydra.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Hydra.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Machina.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Machina.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Orion.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Orion.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Pegasus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Pegasus.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Perseus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Perseus.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Pyxis.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Pyxis.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Rigel.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Rigel.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Scarabaeus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Scarabaeus.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Sceptrum.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Sceptrum.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Solarium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Solarium.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Themos.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Themos.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/UrsaMinor.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/UrsaMinor.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Zeta.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Zeta.ogg
diff --git a/data/sounds/AudioPackage9.mk b/data/sounds/AudioPackage9.mk
index dbe1350..53cc8c0 100644
--- a/data/sounds/AudioPackage9.mk
+++ b/data/sounds/AudioPackage9.mk
@@ -8,43 +8,43 @@
LOCAL_PATH:= frameworks/base/data/sounds
PRODUCT_COPY_FILES += \
- $(LOCAL_PATH)/alarms/ogg/Argon.ogg:system/media/audio/alarms/Argon.ogg \
- $(LOCAL_PATH)/alarms/ogg/Carbon.ogg:system/media/audio/alarms/Carbon.ogg \
- $(LOCAL_PATH)/alarms/ogg/Helium.ogg:system/media/audio/alarms/Helium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Krypton.ogg:system/media/audio/alarms/Krypton.ogg \
- $(LOCAL_PATH)/alarms/ogg/Neon.ogg:system/media/audio/alarms/Neon.ogg \
- $(LOCAL_PATH)/alarms/ogg/Oxygen.ogg:system/media/audio/alarms/Oxygen.ogg \
- $(LOCAL_PATH)/alarms/ogg/Osmium.ogg:system/media/audio/alarms/Osmium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Platinum.ogg:system/media/audio/alarms/Platinum.ogg \
- $(LOCAL_PATH)/effects/ogg/Effect_Tick.ogg:system/media/audio/ui/Effect_Tick.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressStandard.ogg:system/media/audio/ui/KeypressStandard.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressSpacebar.ogg:system/media/audio/ui/KeypressSpacebar.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressDelete.ogg:system/media/audio/ui/KeypressDelete.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
- $(LOCAL_PATH)/effects/material/ogg/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
- $(LOCAL_PATH)/effects/material/ogg/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
- $(LOCAL_PATH)/effects/material/ogg/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
- $(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
- $(LOCAL_PATH)/effects/material/ogg/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
- $(LOCAL_PATH)/effects/ogg/Dock.ogg:system/media/audio/ui/Dock.ogg \
- $(LOCAL_PATH)/effects/ogg/Undock.ogg:system/media/audio/ui/Undock.ogg \
- $(LOCAL_PATH)/effects/ogg/Lock.ogg:system/media/audio/ui/Lock.ogg \
- $(LOCAL_PATH)/effects/ogg/Unlock.ogg:system/media/audio/ui/Unlock.ogg \
- $(LOCAL_PATH)/effects/ogg/Trusted.ogg:system/media/audio/ui/Trusted.ogg \
- $(LOCAL_PATH)/notifications/ogg/Adara.ogg:system/media/audio/notifications/Adara.ogg \
- $(LOCAL_PATH)/notifications/ogg/Alya.ogg:system/media/audio/notifications/Alya.ogg \
- $(LOCAL_PATH)/notifications/ogg/Arcturus.ogg:system/media/audio/notifications/Arcturus.ogg \
- $(LOCAL_PATH)/notifications/ogg/Capella.ogg:system/media/audio/notifications/Capella.ogg \
- $(LOCAL_PATH)/notifications/ogg/CetiAlpha.ogg:system/media/audio/notifications/CetiAlpha.ogg \
- $(LOCAL_PATH)/notifications/ogg/Hojus.ogg:system/media/audio/notifications/Hojus.ogg \
- $(LOCAL_PATH)/notifications/ogg/Mira.ogg:system/media/audio/notifications/Mira.ogg \
- $(LOCAL_PATH)/notifications/ogg/Pollux.ogg:system/media/audio/notifications/Pollux.ogg \
- $(LOCAL_PATH)/notifications/ogg/Procyon.ogg:system/media/audio/notifications/Procyon.ogg \
- $(LOCAL_PATH)/notifications/ogg/Shaula.ogg:system/media/audio/notifications/Shaula.ogg \
- $(LOCAL_PATH)/notifications/ogg/Spica.ogg:system/media/audio/notifications/Spica.ogg \
- $(LOCAL_PATH)/notifications/ogg/Syrma.ogg:system/media/audio/notifications/Syrma.ogg \
- $(LOCAL_PATH)/notifications/ogg/Talitha.ogg:system/media/audio/notifications/Talitha.ogg \
- $(LOCAL_PATH)/notifications/ogg/Tejat.ogg:system/media/audio/notifications/Tejat.ogg \
- $(LOCAL_PATH)/notifications/ogg/Vega.ogg:system/media/audio/notifications/Vega.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Girtab.ogg:system/media/audio/ringtones/Girtab.ogg
+ $(LOCAL_PATH)/alarms/ogg/Argon.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Argon.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Carbon.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Carbon.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Helium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Helium.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Krypton.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Krypton.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Neon.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Neon.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Oxygen.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Oxygen.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Osmium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Osmium.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Platinum.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Platinum.ogg \
+ $(LOCAL_PATH)/effects/ogg/Effect_Tick.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Effect_Tick.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressStandard.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressStandard.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressSpacebar.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressSpacebar.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressDelete.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressDelete.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressInvalid.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressInvalid.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressReturn.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressReturn.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/VideoRecord.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/VideoStop.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/VideoStop.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/camera_click.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/camera_click.ogg \
+ $(LOCAL_PATH)/effects/ogg/camera_focus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/camera_focus.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/LowBattery.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/LowBattery.ogg \
+ $(LOCAL_PATH)/effects/ogg/Dock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Dock.ogg \
+ $(LOCAL_PATH)/effects/ogg/Undock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Undock.ogg \
+ $(LOCAL_PATH)/effects/ogg/Lock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Lock.ogg \
+ $(LOCAL_PATH)/effects/ogg/Unlock.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Unlock.ogg \
+ $(LOCAL_PATH)/effects/ogg/Trusted.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Trusted.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Adara.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Adara.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Alya.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Alya.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Arcturus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Arcturus.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Capella.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Capella.ogg \
+ $(LOCAL_PATH)/notifications/ogg/CetiAlpha.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/CetiAlpha.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Hojus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Hojus.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Mira.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Mira.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Pollux.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Pollux.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Procyon.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Procyon.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Shaula.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Shaula.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Spica.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Spica.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Syrma.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Syrma.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Talitha.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Talitha.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Tejat.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Tejat.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Vega.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Vega.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Girtab.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Girtab.ogg
diff --git a/data/sounds/AudioPackageGo.mk b/data/sounds/AudioPackageGo.mk
index 0296219..e3b27f2 100644
--- a/data/sounds/AudioPackageGo.mk
+++ b/data/sounds/AudioPackageGo.mk
@@ -20,30 +20,30 @@
# Ring_Synth_04 : Flutey Phone
# Alarm_Beep_03 : Beep Beep Beep
PRODUCT_COPY_FILES += \
- $(LOCAL_PATH)/notifications/ogg/Alya.ogg:system/media/audio/notifications/Alya.ogg \
- $(LOCAL_PATH)/notifications/ogg/Argon.ogg:system/media/audio/notifications/Argon.ogg \
- $(LOCAL_PATH)/notifications/Canopus.ogg:system/media/audio/notifications/Canopus.ogg \
- $(LOCAL_PATH)/notifications/Deneb.ogg:system/media/audio/notifications/Deneb.ogg \
- $(LOCAL_PATH)/newwavelabs/Highwire.ogg:system/media/audio/notifications/Highwire.ogg \
- $(LOCAL_PATH)/notifications/ogg/Iridium.ogg:system/media/audio/notifications/Iridium.ogg \
- $(LOCAL_PATH)/notifications/pixiedust.ogg:system/media/audio/notifications/pixiedust.ogg \
- $(LOCAL_PATH)/notifications/ogg/Talitha.ogg:system/media/audio/notifications/Talitha.ogg \
- $(LOCAL_PATH)/Ring_Classic_02.ogg:system/media/audio/ringtones/Ring_Classic_02.ogg \
- $(LOCAL_PATH)/Ring_Synth_02.ogg:system/media/audio/ringtones/Ring_Synth_02.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Cygnus.ogg:system/media/audio/ringtones/Cygnus.ogg \
- $(LOCAL_PATH)/Ring_Digital_02.ogg:system/media/audio/ringtones/Ring_Digital_02.ogg \
- $(LOCAL_PATH)/Ring_Synth_04.ogg:system/media/audio/ringtones/Ring_Synth_04.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Kuma.ogg:system/media/audio/ringtones/Kuma.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Themos.ogg:system/media/audio/ringtones/Themos.ogg \
- $(LOCAL_PATH)/Alarm_Classic.ogg:system/media/audio/alarms/Alarm_Classic.ogg \
- $(LOCAL_PATH)/alarms/ogg/Argon.ogg:system/media/audio/alarms/Argon.ogg \
- $(LOCAL_PATH)/alarms/ogg/Platinum.ogg:system/media/audio/alarms/Platinum.ogg \
- $(LOCAL_PATH)/Alarm_Beep_03.ogg:system/media/audio/alarms/Alarm_Beep_03.ogg \
- $(LOCAL_PATH)/alarms/ogg/Helium.ogg:system/media/audio/alarms/Helium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Oxygen.ogg:system/media/audio/alarms/Oxygen.ogg \
- $(LOCAL_PATH)/effects/ogg/Effect_Tick.ogg:system/media/audio/ui/Effect_Tick.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressStandard.ogg:system/media/audio/ui/KeypressStandard.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressSpacebar.ogg:system/media/audio/ui/KeypressSpacebar.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressDelete.ogg:system/media/audio/ui/KeypressDelete.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Alya.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Alya.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Argon.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Argon.ogg \
+ $(LOCAL_PATH)/notifications/Canopus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Canopus.ogg \
+ $(LOCAL_PATH)/notifications/Deneb.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Deneb.ogg \
+ $(LOCAL_PATH)/newwavelabs/Highwire.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Highwire.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Iridium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Iridium.ogg \
+ $(LOCAL_PATH)/notifications/pixiedust.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/pixiedust.ogg \
+ $(LOCAL_PATH)/notifications/ogg/Talitha.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Talitha.ogg \
+ $(LOCAL_PATH)/Ring_Classic_02.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Ring_Classic_02.ogg \
+ $(LOCAL_PATH)/Ring_Synth_02.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Ring_Synth_02.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Cygnus.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Cygnus.ogg \
+ $(LOCAL_PATH)/Ring_Digital_02.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Ring_Digital_02.ogg \
+ $(LOCAL_PATH)/Ring_Synth_04.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Ring_Synth_04.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Kuma.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Kuma.ogg \
+ $(LOCAL_PATH)/ringtones/ogg/Themos.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Themos.ogg \
+ $(LOCAL_PATH)/Alarm_Classic.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Alarm_Classic.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Argon.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Argon.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Platinum.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Platinum.ogg \
+ $(LOCAL_PATH)/Alarm_Beep_03.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Alarm_Beep_03.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Helium.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Helium.ogg \
+ $(LOCAL_PATH)/alarms/ogg/Oxygen.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Oxygen.ogg \
+ $(LOCAL_PATH)/effects/ogg/Effect_Tick.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Effect_Tick.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressStandard.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressStandard.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressSpacebar.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressSpacebar.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressDelete.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressDelete.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressInvalid.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressInvalid.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressReturn.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressReturn.ogg \
diff --git a/data/sounds/AudioTv.mk b/data/sounds/AudioTv.mk
index 91265af..d0006b7 100644
--- a/data/sounds/AudioTv.mk
+++ b/data/sounds/AudioTv.mk
@@ -15,8 +15,8 @@
LOCAL_PATH := frameworks/base/data/sounds
PRODUCT_COPY_FILES += \
- $(LOCAL_PATH)/effects/ogg/KeypressDelete_120_48k.ogg:system/media/audio/ui/KeypressDelete.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressInvalid_120_48k.ogg:system/media/audio/ui/KeypressInvalid.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressReturn_120_48k.ogg:system/media/audio/ui/KeypressReturn.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressSpacebar_120_48k.ogg:system/media/audio/ui/KeypressSpacebar.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressStandard_120_48k.ogg:system/media/audio/ui/KeypressStandard.ogg
+ $(LOCAL_PATH)/effects/ogg/KeypressDelete_120_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressDelete.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressInvalid_120_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressInvalid.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressReturn_120_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressReturn.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressSpacebar_120_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressSpacebar.ogg \
+ $(LOCAL_PATH)/effects/ogg/KeypressStandard_120_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressStandard.ogg
diff --git a/data/sounds/OriginalAudio.mk b/data/sounds/OriginalAudio.mk
index f683752..4d74d12 100644
--- a/data/sounds/OriginalAudio.mk
+++ b/data/sounds/OriginalAudio.mk
@@ -9,67 +9,67 @@
LOCAL_PATH:= frameworks/base/data/sounds
PRODUCT_COPY_FILES += \
- $(LOCAL_PATH)/F1_MissedCall.ogg:system/media/audio/notifications/F1_MissedCall.ogg \
- $(LOCAL_PATH)/F1_New_MMS.ogg:system/media/audio/notifications/F1_New_MMS.ogg \
- $(LOCAL_PATH)/F1_New_SMS.ogg:system/media/audio/notifications/F1_New_SMS.ogg \
- $(LOCAL_PATH)/Alarm_Buzzer.ogg:system/media/audio/alarms/Alarm_Buzzer.ogg \
- $(LOCAL_PATH)/Alarm_Beep_01.ogg:system/media/audio/alarms/Alarm_Beep_01.ogg \
- $(LOCAL_PATH)/Alarm_Beep_02.ogg:system/media/audio/alarms/Alarm_Beep_02.ogg \
- $(LOCAL_PATH)/Alarm_Classic.ogg:system/media/audio/alarms/Alarm_Classic.ogg \
- $(LOCAL_PATH)/Alarm_Beep_03.ogg:system/media/audio/alarms/Alarm_Beep_03.ogg \
- $(LOCAL_PATH)/Alarm_Rooster_02.ogg:system/media/audio/alarms/Alarm_Rooster_02.ogg \
- $(LOCAL_PATH)/Ring_Classic_02.ogg:system/media/audio/ringtones/Ring_Classic_02.ogg \
- $(LOCAL_PATH)/Ring_Digital_02.ogg:system/media/audio/ringtones/Ring_Digital_02.ogg \
- $(LOCAL_PATH)/Ring_Synth_04.ogg:system/media/audio/ringtones/Ring_Synth_04.ogg \
- $(LOCAL_PATH)/Ring_Synth_02.ogg:system/media/audio/ringtones/Ring_Synth_02.ogg \
- $(LOCAL_PATH)/notifications/Beat_Box_Android.ogg:system/media/audio/notifications/Beat_Box_Android.ogg \
- $(LOCAL_PATH)/notifications/Heaven.ogg:system/media/audio/notifications/Heaven.ogg \
- $(LOCAL_PATH)/notifications/TaDa.ogg:system/media/audio/notifications/TaDa.ogg \
- $(LOCAL_PATH)/notifications/Tinkerbell.ogg:system/media/audio/notifications/Tinkerbell.ogg \
- $(LOCAL_PATH)/effects/Effect_Tick.ogg:system/media/audio/ui/Effect_Tick.ogg \
- $(LOCAL_PATH)/effects/KeypressStandard.ogg:system/media/audio/ui/KeypressStandard.ogg \
- $(LOCAL_PATH)/effects/KeypressSpacebar.ogg:system/media/audio/ui/KeypressSpacebar.ogg \
- $(LOCAL_PATH)/effects/KeypressDelete.ogg:system/media/audio/ui/KeypressDelete.ogg \
- $(LOCAL_PATH)/effects/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
- $(LOCAL_PATH)/effects/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
- $(LOCAL_PATH)/effects/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
- $(LOCAL_PATH)/effects/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
- $(LOCAL_PATH)/newwavelabs/BeatPlucker.ogg:system/media/audio/ringtones/BeatPlucker.ogg \
- $(LOCAL_PATH)/newwavelabs/CaffeineSnake.ogg:system/media/audio/notifications/CaffeineSnake.ogg
+ $(LOCAL_PATH)/F1_MissedCall.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/F1_MissedCall.ogg \
+ $(LOCAL_PATH)/F1_New_MMS.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/F1_New_MMS.ogg \
+ $(LOCAL_PATH)/F1_New_SMS.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/F1_New_SMS.ogg \
+ $(LOCAL_PATH)/Alarm_Buzzer.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Alarm_Buzzer.ogg \
+ $(LOCAL_PATH)/Alarm_Beep_01.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Alarm_Beep_01.ogg \
+ $(LOCAL_PATH)/Alarm_Beep_02.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Alarm_Beep_02.ogg \
+ $(LOCAL_PATH)/Alarm_Classic.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Alarm_Classic.ogg \
+ $(LOCAL_PATH)/Alarm_Beep_03.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Alarm_Beep_03.ogg \
+ $(LOCAL_PATH)/Alarm_Rooster_02.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Alarm_Rooster_02.ogg \
+ $(LOCAL_PATH)/Ring_Classic_02.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Ring_Classic_02.ogg \
+ $(LOCAL_PATH)/Ring_Digital_02.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Ring_Digital_02.ogg \
+ $(LOCAL_PATH)/Ring_Synth_04.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Ring_Synth_04.ogg \
+ $(LOCAL_PATH)/Ring_Synth_02.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Ring_Synth_02.ogg \
+ $(LOCAL_PATH)/notifications/Beat_Box_Android.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Beat_Box_Android.ogg \
+ $(LOCAL_PATH)/notifications/Heaven.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Heaven.ogg \
+ $(LOCAL_PATH)/notifications/TaDa.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/TaDa.ogg \
+ $(LOCAL_PATH)/notifications/Tinkerbell.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Tinkerbell.ogg \
+ $(LOCAL_PATH)/effects/Effect_Tick.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Effect_Tick.ogg \
+ $(LOCAL_PATH)/effects/KeypressStandard.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressStandard.ogg \
+ $(LOCAL_PATH)/effects/KeypressSpacebar.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressSpacebar.ogg \
+ $(LOCAL_PATH)/effects/KeypressDelete.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressDelete.ogg \
+ $(LOCAL_PATH)/effects/KeypressReturn.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressReturn.ogg \
+ $(LOCAL_PATH)/effects/VideoRecord.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/VideoStop.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/VideoStop.ogg \
+ $(LOCAL_PATH)/effects/camera_click.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/camera_click.ogg \
+ $(LOCAL_PATH)/newwavelabs/BeatPlucker.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/BeatPlucker.ogg \
+ $(LOCAL_PATH)/newwavelabs/CaffeineSnake.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/CaffeineSnake.ogg
ifneq ($(MINIMAL_NEWWAVELABS),true)
PRODUCT_COPY_FILES += \
- $(LOCAL_PATH)/newwavelabs/BentleyDubs.ogg:system/media/audio/ringtones/BentleyDubs.ogg \
- $(LOCAL_PATH)/newwavelabs/BirdLoop.ogg:system/media/audio/ringtones/BirdLoop.ogg \
- $(LOCAL_PATH)/newwavelabs/CaribbeanIce.ogg:system/media/audio/ringtones/CaribbeanIce.ogg \
- $(LOCAL_PATH)/newwavelabs/CurveBall.ogg:system/media/audio/ringtones/CurveBall.ogg \
- $(LOCAL_PATH)/newwavelabs/EtherShake.ogg:system/media/audio/ringtones/EtherShake.ogg \
- $(LOCAL_PATH)/newwavelabs/FriendlyGhost.ogg:system/media/audio/ringtones/FriendlyGhost.ogg \
- $(LOCAL_PATH)/newwavelabs/GameOverGuitar.ogg:system/media/audio/ringtones/GameOverGuitar.ogg \
- $(LOCAL_PATH)/newwavelabs/Growl.ogg:system/media/audio/ringtones/Growl.ogg \
- $(LOCAL_PATH)/newwavelabs/InsertCoin.ogg:system/media/audio/ringtones/InsertCoin.ogg \
- $(LOCAL_PATH)/newwavelabs/LoopyLounge.ogg:system/media/audio/ringtones/LoopyLounge.ogg \
- $(LOCAL_PATH)/newwavelabs/LoveFlute.ogg:system/media/audio/ringtones/LoveFlute.ogg \
- $(LOCAL_PATH)/newwavelabs/MidEvilJaunt.ogg:system/media/audio/ringtones/MidEvilJaunt.ogg \
- $(LOCAL_PATH)/newwavelabs/MildlyAlarming.ogg:system/media/audio/ringtones/MildlyAlarming.ogg \
- $(LOCAL_PATH)/newwavelabs/NewPlayer.ogg:system/media/audio/ringtones/NewPlayer.ogg \
- $(LOCAL_PATH)/newwavelabs/Noises1.ogg:system/media/audio/ringtones/Noises1.ogg \
- $(LOCAL_PATH)/newwavelabs/Noises2.ogg:system/media/audio/ringtones/Noises2.ogg \
- $(LOCAL_PATH)/newwavelabs/Noises3.ogg:system/media/audio/ringtones/Noises3.ogg \
- $(LOCAL_PATH)/newwavelabs/OrganDub.ogg:system/media/audio/ringtones/OrganDub.ogg \
- $(LOCAL_PATH)/newwavelabs/RomancingTheTone.ogg:system/media/audio/ringtones/RomancingTheTone.ogg \
- $(LOCAL_PATH)/newwavelabs/SitarVsSitar.ogg:system/media/audio/ringtones/SitarVsSitar.ogg \
- $(LOCAL_PATH)/newwavelabs/SpringyJalopy.ogg:system/media/audio/ringtones/SpringyJalopy.ogg \
- $(LOCAL_PATH)/newwavelabs/Terminated.ogg:system/media/audio/ringtones/Terminated.ogg \
- $(LOCAL_PATH)/newwavelabs/TwirlAway.ogg:system/media/audio/ringtones/TwirlAway.ogg \
- $(LOCAL_PATH)/newwavelabs/VeryAlarmed.ogg:system/media/audio/ringtones/VeryAlarmed.ogg \
- $(LOCAL_PATH)/newwavelabs/World.ogg:system/media/audio/ringtones/World.ogg \
- $(LOCAL_PATH)/newwavelabs/DearDeer.ogg:system/media/audio/notifications/DearDeer.ogg \
- $(LOCAL_PATH)/newwavelabs/DontPanic.ogg:system/media/audio/notifications/DontPanic.ogg \
- $(LOCAL_PATH)/newwavelabs/Highwire.ogg:system/media/audio/notifications/Highwire.ogg \
- $(LOCAL_PATH)/newwavelabs/KzurbSonar.ogg:system/media/audio/notifications/KzurbSonar.ogg \
- $(LOCAL_PATH)/newwavelabs/OnTheHunt.ogg:system/media/audio/notifications/OnTheHunt.ogg \
- $(LOCAL_PATH)/newwavelabs/Voila.ogg:system/media/audio/notifications/Voila.ogg \
- $(LOCAL_PATH)/newwavelabs/CrazyDream.ogg:system/media/audio/ringtones/CrazyDream.ogg \
- $(LOCAL_PATH)/newwavelabs/DreamTheme.ogg:system/media/audio/ringtones/DreamTheme.ogg
+ $(LOCAL_PATH)/newwavelabs/BentleyDubs.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/BentleyDubs.ogg \
+ $(LOCAL_PATH)/newwavelabs/BirdLoop.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/BirdLoop.ogg \
+ $(LOCAL_PATH)/newwavelabs/CaribbeanIce.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/CaribbeanIce.ogg \
+ $(LOCAL_PATH)/newwavelabs/CurveBall.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/CurveBall.ogg \
+ $(LOCAL_PATH)/newwavelabs/EtherShake.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/EtherShake.ogg \
+ $(LOCAL_PATH)/newwavelabs/FriendlyGhost.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/FriendlyGhost.ogg \
+ $(LOCAL_PATH)/newwavelabs/GameOverGuitar.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/GameOverGuitar.ogg \
+ $(LOCAL_PATH)/newwavelabs/Growl.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Growl.ogg \
+ $(LOCAL_PATH)/newwavelabs/InsertCoin.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/InsertCoin.ogg \
+ $(LOCAL_PATH)/newwavelabs/LoopyLounge.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/LoopyLounge.ogg \
+ $(LOCAL_PATH)/newwavelabs/LoveFlute.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/LoveFlute.ogg \
+ $(LOCAL_PATH)/newwavelabs/MidEvilJaunt.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/MidEvilJaunt.ogg \
+ $(LOCAL_PATH)/newwavelabs/MildlyAlarming.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/MildlyAlarming.ogg \
+ $(LOCAL_PATH)/newwavelabs/NewPlayer.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/NewPlayer.ogg \
+ $(LOCAL_PATH)/newwavelabs/Noises1.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Noises1.ogg \
+ $(LOCAL_PATH)/newwavelabs/Noises2.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Noises2.ogg \
+ $(LOCAL_PATH)/newwavelabs/Noises3.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Noises3.ogg \
+ $(LOCAL_PATH)/newwavelabs/OrganDub.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/OrganDub.ogg \
+ $(LOCAL_PATH)/newwavelabs/RomancingTheTone.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/RomancingTheTone.ogg \
+ $(LOCAL_PATH)/newwavelabs/SitarVsSitar.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/SitarVsSitar.ogg \
+ $(LOCAL_PATH)/newwavelabs/SpringyJalopy.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/SpringyJalopy.ogg \
+ $(LOCAL_PATH)/newwavelabs/Terminated.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/Terminated.ogg \
+ $(LOCAL_PATH)/newwavelabs/TwirlAway.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/TwirlAway.ogg \
+ $(LOCAL_PATH)/newwavelabs/VeryAlarmed.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/VeryAlarmed.ogg \
+ $(LOCAL_PATH)/newwavelabs/World.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/World.ogg \
+ $(LOCAL_PATH)/newwavelabs/DearDeer.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/DearDeer.ogg \
+ $(LOCAL_PATH)/newwavelabs/DontPanic.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/DontPanic.ogg \
+ $(LOCAL_PATH)/newwavelabs/Highwire.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Highwire.ogg \
+ $(LOCAL_PATH)/newwavelabs/KzurbSonar.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/KzurbSonar.ogg \
+ $(LOCAL_PATH)/newwavelabs/OnTheHunt.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/OnTheHunt.ogg \
+ $(LOCAL_PATH)/newwavelabs/Voila.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Voila.ogg \
+ $(LOCAL_PATH)/newwavelabs/CrazyDream.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/CrazyDream.ogg \
+ $(LOCAL_PATH)/newwavelabs/DreamTheme.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/DreamTheme.ogg
endif
diff --git a/drm/java/android/drm/DrmManagerClient.java b/drm/java/android/drm/DrmManagerClient.java
index 76eab4a..fcebad3 100644
--- a/drm/java/android/drm/DrmManagerClient.java
+++ b/drm/java/android/drm/DrmManagerClient.java
@@ -831,6 +831,7 @@
* content://media/<table_name>/<row_index> (or)
* file://sdcard/test.mp4
* http://test.com/test.mp4
+ * https://test.com/test.mp4
*
* Here <table_name> shall be "video" or "audio" or "images"
* <row_index> the index of the content in given table
@@ -843,7 +844,7 @@
scheme.equals(ContentResolver.SCHEME_FILE)) {
path = uri.getPath();
- } else if (scheme.equals("http")) {
+ } else if (scheme.equals("http") || scheme.equals("https")) {
path = uri.toString();
} else if (scheme.equals(ContentResolver.SCHEME_CONTENT)) {
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index adab1a9c..022fbdc 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -440,7 +440,8 @@
if (opts == null) return;
if (opts.inBitmap != null && opts.inBitmap.getConfig() == Bitmap.Config.HARDWARE) {
- throw new IllegalArgumentException("Bitmaps with Config.HARWARE are always immutable");
+ throw new IllegalArgumentException(
+ "Bitmaps with Config.HARDWARE are always immutable");
}
if (opts.inMutable && opts.inPreferredConfig == Bitmap.Config.HARDWARE) {
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index 2227cf5..f0efb58 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -1354,9 +1354,9 @@
*/
@NonNull
static ColorSpace get(@IntRange(from = MIN_ID, to = MAX_ID) int index) {
- if (index < 0 || index > Named.values().length) {
+ if (index < 0 || index >= Named.values().length) {
throw new IllegalArgumentException("Invalid ID, must be in the range [0.." +
- Named.values().length + "]");
+ Named.values().length + ")");
}
return sNamedColorSpaces[index];
}
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 9b86b77..25f6775 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -1075,6 +1075,11 @@
continue; // If alias and named family are conflict, use named family.
}
final Typeface base = systemFontMap.get(alias.getToName());
+ if (base == null) {
+ // The missing target is a valid thing, some configuration don't have font files,
+ // e.g. wear devices. Just skip this alias.
+ continue;
+ }
final int weight = alias.getWeight();
final Typeface newFace = weight == 400 ? base :
new Typeface(nativeCreateWeightAlias(base.native_instance, weight));
diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp
index b9860ad..635d0ec 100644
--- a/libs/hwui/HardwareBitmapUploader.cpp
+++ b/libs/hwui/HardwareBitmapUploader.cpp
@@ -34,7 +34,7 @@
namespace android::uirenderer {
static std::mutex sLock{};
-static ThreadBase* sUploadThread = nullptr;
+static sp<ThreadBase> sUploadThread = nullptr;
static renderthread::EglManager sEglManager;
static int sPendingUploads = 0;
static nsecs_t sLastUpload = 0;
@@ -257,4 +257,15 @@
Bitmap::computePalette(bitmap));
}
+void HardwareBitmapUploader::terminate() {
+ std::lock_guard _lock{sLock};
+ LOG_ALWAYS_FATAL_IF(sPendingUploads, "terminate called while uploads in progress");
+ if (sUploadThread) {
+ sUploadThread->requestExit();
+ sUploadThread->join();
+ sUploadThread = nullptr;
+ }
+ sEglManager.destroy();
+}
+
} // namespace android::uirenderer
diff --git a/libs/hwui/HardwareBitmapUploader.h b/libs/hwui/HardwareBitmapUploader.h
index 6298013..40f2b0c 100644
--- a/libs/hwui/HardwareBitmapUploader.h
+++ b/libs/hwui/HardwareBitmapUploader.h
@@ -23,6 +23,7 @@
class HardwareBitmapUploader {
public:
static sk_sp<Bitmap> allocateHardwareBitmap(const SkBitmap& sourceBitmap);
+ static void terminate();
};
} // namespace android::uirenderer
diff --git a/libs/hwui/Matrix.h b/libs/hwui/Matrix.h
index b33cfe2..0c515a4 100644
--- a/libs/hwui/Matrix.h
+++ b/libs/hwui/Matrix.h
@@ -81,7 +81,7 @@
explicit Matrix4(const float* v) { load(v); }
- Matrix4(const SkMatrix& v) { // NOLINT, implicit
+ Matrix4(const SkMatrix& v) { // NOLINT(google-explicit-constructor)
load(v);
}
diff --git a/libs/hwui/Rect.h b/libs/hwui/Rect.h
index d6362ef..24443c8 100644
--- a/libs/hwui/Rect.h
+++ b/libs/hwui/Rect.h
@@ -57,15 +57,15 @@
inline Rect(float width, float height) : left(0.0f), top(0.0f), right(width), bottom(height) {}
- inline Rect(const SkIRect& rect)
- : // NOLINT, implicit
+ inline Rect(const SkIRect& rect) // NOLINT(google-explicit-constructor)
+ :
left(rect.fLeft)
, top(rect.fTop)
, right(rect.fRight)
, bottom(rect.fBottom) {}
- inline Rect(const SkRect& rect)
- : // NOLINT, implicit
+ inline Rect(const SkRect& rect) // NOLINT(google-explicit-constructor)
+ :
left(rect.fLeft)
, top(rect.fTop)
, right(rect.fRight)
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index ab95e69..cc62fdc 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -678,15 +678,15 @@
// Canvas draw operations: Text
// ----------------------------------------------------------------------------
-void SkiaCanvas::drawGlyphs(ReadGlyphFunc glyphFunc, int count, const SkPaint& paint, float x,
+void SkiaCanvas::drawGlyphs(ReadGlyphFunc glyphFunc, int count, const Paint& paint, float x,
float y, float boundsLeft, float boundsTop, float boundsRight,
float boundsBottom, float totalAdvance) {
if (count <= 0 || paint.nothingToDraw()) return;
- SkFont font = SkFont::LEGACY_ExtractFromPaint(paint);
SkPaint paintCopy(paint);
if (mPaintFilter) {
mPaintFilter->filter(&paintCopy);
}
+ SkFont font = SkFont::LEGACY_ExtractFromPaint(paintCopy);
SkASSERT(paintCopy.getTextEncoding() == kGlyphID_SkTextEncoding);
// Stroke with a hairline is drawn on HW with a fill style for compatibility with Android O and
// older.
@@ -708,13 +708,13 @@
}
void SkiaCanvas::drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset,
- const SkPaint& paint, const SkPath& path, size_t start,
+ const Paint& paint, const SkPath& path, size_t start,
size_t end) {
- SkFont font = SkFont::LEGACY_ExtractFromPaint(paint);
SkPaint paintCopy(paint);
if (mPaintFilter) {
mPaintFilter->filter(&paintCopy);
}
+ SkFont font = SkFont::LEGACY_ExtractFromPaint(paintCopy);
SkASSERT(paintCopy.getTextEncoding() == kGlyphID_SkTextEncoding);
const int N = end - start;
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index 4ab0a59..3fe2bce 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -158,11 +158,11 @@
void reset(SkCanvas* skiaCanvas);
void drawDrawable(SkDrawable* drawable) { mCanvas->drawDrawable(drawable); }
- virtual void drawGlyphs(ReadGlyphFunc glyphFunc, int count, const SkPaint& paint, float x,
+ virtual void drawGlyphs(ReadGlyphFunc glyphFunc, int count, const Paint& paint, float x,
float y, float boundsLeft, float boundsTop, float boundsRight,
float boundsBottom, float totalAdvance) override;
virtual void drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset,
- const SkPaint& paint, const SkPath& path, size_t start,
+ const Paint& paint, const SkPath& path, size_t start,
size_t end) override;
/** This class acts as a copy on write SkPaint.
diff --git a/libs/hwui/WebViewFunctorManager.cpp b/libs/hwui/WebViewFunctorManager.cpp
index 5b7ae70..9170d6d 100644
--- a/libs/hwui/WebViewFunctorManager.cpp
+++ b/libs/hwui/WebViewFunctorManager.cpp
@@ -18,6 +18,7 @@
#include <private/hwui/WebViewFunctor.h>
#include "Properties.h"
+#include "renderthread/RenderThread.h"
#include <log/log.h>
#include <utils/Trace.h>
@@ -90,6 +91,10 @@
mHasContext = false;
ATRACE_NAME("WebViewFunctor::onContextDestroyed");
mCallbacks.onContextDestroyed(mFunctor, mData);
+
+ // grContext may be null in unit tests.
+ auto* grContext = renderthread::RenderThread::getInstance().getGrContext();
+ if (grContext) grContext->resetContext();
}
}
diff --git a/libs/hwui/debug/GlesErrorCheckWrapper.h b/libs/hwui/debug/GlesErrorCheckWrapper.h
index ee5cc1f..791400b 100644
--- a/libs/hwui/debug/GlesErrorCheckWrapper.h
+++ b/libs/hwui/debug/GlesErrorCheckWrapper.h
@@ -24,7 +24,7 @@
class GlesErrorCheckWrapper : public GlesDriver {
public:
- GlesErrorCheckWrapper(GlesDriver& base) : mBase(base) {}
+ explicit GlesErrorCheckWrapper(GlesDriver& base) : mBase(base) {}
#define GL_ENTRY(ret, api, ...) virtual ret api##_(__VA_ARGS__) override;
#include "gles_decls.in"
diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp
index a09da6b..277148e 100644
--- a/libs/hwui/hwui/Canvas.cpp
+++ b/libs/hwui/hwui/Canvas.cpp
@@ -38,7 +38,7 @@
canvas->drawRect(left, top, right, bottom, paint);
}
-void Canvas::drawTextDecorations(float x, float y, float length, const SkPaint& paint) {
+void Canvas::drawTextDecorations(float x, float y, float length, const Paint& paint) {
uint32_t flags;
PaintFilter* paintFilter = getPaintFilter();
if (paintFilter) {
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 4c5365d..11e8579 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -303,18 +303,18 @@
static int GetApiLevel() { return sApiLevel; }
protected:
- void drawTextDecorations(float x, float y, float length, const SkPaint& paint);
+ void drawTextDecorations(float x, float y, float length, const Paint& paint);
/**
* glyphFunc: valid only for the duration of the call and should not be cached.
* drawText: count is of glyphs
* totalAdvance: used to define width of text decorations (underlines, strikethroughs).
*/
- virtual void drawGlyphs(ReadGlyphFunc glyphFunc, int count, const SkPaint& paint, float x,
+ virtual void drawGlyphs(ReadGlyphFunc glyphFunc, int count, const Paint& paint, float x,
float y, float boundsLeft, float boundsTop, float boundsRight,
float boundsBottom, float totalAdvance) = 0;
virtual void drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset,
- const SkPaint& paint, const SkPath& path, size_t start,
+ const Paint& paint, const SkPath& path, size_t start,
size_t end) = 0;
static int sApiLevel;
diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h
index c1a3b6d..92ffda9 100644
--- a/libs/hwui/hwui/Paint.h
+++ b/libs/hwui/hwui/Paint.h
@@ -46,7 +46,7 @@
Paint();
Paint(const Paint& paint);
- Paint(const SkPaint& paint); // NOLINT(implicit)
+ Paint(const SkPaint& paint); // NOLINT(google-explicit-constructor)
~Paint();
Paint& operator=(const Paint& other);
diff --git a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
index a5faae7..c3563db 100644
--- a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
@@ -36,8 +36,6 @@
namespace uirenderer {
namespace skiapipeline {
-static std::mutex sLock{};
-static ThreadBase* sGLDrawThread = nullptr;
static renderthread::EglManager sEglManager;
// ScopedDrawRequest makes sure a GL thread is started and EGL context is initialized on it.
@@ -47,32 +45,20 @@
private:
void beginDraw() {
- std::lock_guard _lock{sLock};
-
- if (!sGLDrawThread) {
- sGLDrawThread = new ThreadBase{};
- }
-
- if (!sGLDrawThread->isRunning()) {
- sGLDrawThread->start("GLFunctorThread");
- }
-
if (!sEglManager.hasEglContext()) {
- sGLDrawThread->queue().runSync([]() { sEglManager.initialize(); });
+ sEglManager.initialize();
}
}
};
void VkInteropFunctorDrawable::vkInvokeFunctor(Functor* functor) {
ScopedDrawRequest _drawRequest{};
- sGLDrawThread->queue().runSync([&]() {
- EGLDisplay display = sEglManager.eglDisplay();
- DrawGlInfo::Mode mode = DrawGlInfo::kModeProcessNoContext;
- if (display != EGL_NO_DISPLAY) {
- mode = DrawGlInfo::kModeProcess;
- }
- (*functor)(mode, nullptr);
- });
+ EGLDisplay display = sEglManager.eglDisplay();
+ DrawGlInfo::Mode mode = DrawGlInfo::kModeProcessNoContext;
+ if (display != EGL_NO_DISPLAY) {
+ mode = DrawGlInfo::kModeProcess;
+ }
+ (*functor)(mode, nullptr);
}
#define FENCE_TIMEOUT 2000000000
@@ -113,7 +99,7 @@
// TODO: draw command has completed.
// TODO: A simple but inefficient way is to flush and issue a QueueWaitIdle call. See
// TODO: GrVkGpu::destroyResources() for example.
- bool success = sGLDrawThread->queue().runSync([&]() -> bool {
+ {
ATRACE_FORMAT("WebViewDraw_%dx%d", mFBInfo.width(), mFBInfo.height());
EGLDisplay display = sEglManager.eglDisplay();
LOG_ALWAYS_FATAL_IF(display == EGL_NO_DISPLAY, "Failed to get EGL_DEFAULT_DISPLAY! err=%s",
@@ -125,7 +111,7 @@
if (autoImage.image == EGL_NO_IMAGE_KHR) {
ALOGW("Could not create EGL image, err =%s",
uirenderer::renderthread::EglManager::eglErrorString());
- return false;
+ return;
}
AutoSkiaGlTexture glTexture;
@@ -156,7 +142,7 @@
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
ALOGE("Failed framebuffer check for created target buffer: %s",
GLUtils::getGLFramebufferError());
- return false;
+ return;
}
glDisable(GL_STENCIL_TEST);
@@ -183,11 +169,6 @@
LOG_ALWAYS_FATAL_IF(waitStatus != EGL_CONDITION_SATISFIED_KHR,
"Failed to wait for the fence %#x", eglGetError());
eglDestroySyncKHR(display, glDrawFinishedFence);
- return true;
- });
-
- if (!success) {
- return;
}
SkPaint paint;
@@ -208,15 +189,14 @@
if (auto lp = std::get_if<LegacyFunctor>(&mAnyFunctor)) {
if (lp->listener) {
ScopedDrawRequest _drawRequest{};
- sGLDrawThread->queue().runSync(
- [&]() { lp->listener->onGlFunctorReleased(lp->functor); });
+ lp->listener->onGlFunctorReleased(lp->functor);
}
}
}
void VkInteropFunctorDrawable::syncFunctor(const WebViewSyncData& data) const {
ScopedDrawRequest _drawRequest{};
- sGLDrawThread->queue().runSync([&]() { FunctorDrawable::syncFunctor(data); });
+ FunctorDrawable::syncFunctor(data);
}
} // namespace skiapipeline
diff --git a/libs/hwui/private/hwui/DrawVkInfo.h b/libs/hwui/private/hwui/DrawVkInfo.h
index 019950f..b2351fc 100644
--- a/libs/hwui/private/hwui/DrawVkInfo.h
+++ b/libs/hwui/private/hwui/DrawVkInfo.h
@@ -53,16 +53,16 @@
VkFormat format;
// Input: Color space transfer params
- float G;
- float A;
- float B;
- float C;
- float D;
- float E;
- float F;
+ float g;
+ float a;
+ float b;
+ float c;
+ float d;
+ float e;
+ float f;
// Input: Color space transformation from linear RGB to D50-adapted XYZ
- float matrix[9];
+ float colorSpaceTransform[9];
// Input: current clip rect
int clipLeft;
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index 7acc44c..6c04232 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -30,6 +30,7 @@
#include <gui/Surface.h>
#include <math.h>
#include <set>
+#include <SkMathPriv.h>
namespace android {
namespace uirenderer {
@@ -42,11 +43,6 @@
#define SURFACE_SIZE_MULTIPLIER (12.0f * 4.0f)
#define BACKGROUND_RETENTION_PERCENTAGE (0.5f)
-// for super large fonts we will draw them as paths so no need to keep linearly
-// increasing the font cache size.
-#define FONT_CACHE_MIN_MB (0.5f)
-#define FONT_CACHE_MAX_MB (4.0f)
-
CacheManager::CacheManager(const DisplayInfo& display) : mMaxSurfaceArea(display.w * display.h) {
mVectorDrawableAtlas = new skiapipeline::VectorDrawableAtlas(
mMaxSurfaceArea / 2,
@@ -106,25 +102,10 @@
void CacheManager::configureContext(GrContextOptions* contextOptions, const void* identity, ssize_t size) {
contextOptions->fAllowPathMaskCaching = true;
- float screenMP = mMaxSurfaceArea / 1024.0f / 1024.0f;
- float fontCacheMB = 0;
- float decimalVal = std::modf(screenMP, &fontCacheMB);
-
- // This is a basic heuristic to size the cache to a multiple of 512 KB
- if (decimalVal > 0.8f) {
- fontCacheMB += 1.0f;
- } else if (decimalVal > 0.5f) {
- fontCacheMB += 0.5f;
- }
-
- // set limits on min/max size of the cache
- fontCacheMB = std::max(FONT_CACHE_MIN_MB, std::min(FONT_CACHE_MAX_MB, fontCacheMB));
-
- // We must currently set the size of the text cache based on the size of the
- // display even though we like to be dynamicallysizing it to the size of the window.
- // Skia's implementation doesn't provide a mechanism to resize the font cache due to
- // the potential cost of recreating the glyphs.
- contextOptions->fGlyphCacheTextureMaximumBytes = fontCacheMB * 1024 * 1024;
+ // This sets the maximum size for a single texture atlas in the GPU font cache. If necessary,
+ // the cache can allocate additional textures that are counted against the total cache limits
+ // provided to Skia.
+ contextOptions->fGlyphCacheTextureMaximumBytes = GrNextSizePow2(mMaxSurfaceArea);
if (mTaskManager.canRunTasks()) {
if (!mTaskProcessor.get()) {
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index 56eedff..8cd97ed 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -93,7 +93,9 @@
, mHasWideColorGamutSupport(false) {}
EglManager::~EglManager() {
- destroy();
+ if (hasEglContext()) {
+ ALOGW("~EglManager() leaked an EGL context");
+ }
}
void EglManager::initialize() {
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index 12666b3..5272227 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -123,6 +123,7 @@
friend class RenderProxy;
friend class DummyVsyncSource;
friend class android::uirenderer::TestUtils;
+ friend class android::uirenderer::WebViewFunctor;
RenderThread();
virtual ~RenderThread();
diff --git a/libs/hwui/service/GraphicsStatsService.cpp b/libs/hwui/service/GraphicsStatsService.cpp
index 3d50d2d..8a16b20 100644
--- a/libs/hwui/service/GraphicsStatsService.cpp
+++ b/libs/hwui/service/GraphicsStatsService.cpp
@@ -48,7 +48,7 @@
class FileDescriptor {
public:
- FileDescriptor(int fd) : mFd(fd) {}
+ explicit FileDescriptor(int fd) : mFd(fd) {}
~FileDescriptor() {
if (mFd != -1) {
close(mFd);
@@ -56,7 +56,7 @@
}
}
bool valid() { return mFd != -1; }
- operator int() { return mFd; }
+ operator int() { return mFd; } // NOLINT(google-explicit-constructor)
private:
int mFd;
@@ -64,7 +64,7 @@
class FileOutputStreamLite : public io::ZeroCopyOutputStream {
public:
- FileOutputStreamLite(int fd) : mCopyAdapter(fd), mImpl(&mCopyAdapter) {}
+ explicit FileOutputStreamLite(int fd) : mCopyAdapter(fd), mImpl(&mCopyAdapter) {}
virtual ~FileOutputStreamLite() {}
int GetErrno() { return mCopyAdapter.mErrno; }
@@ -82,7 +82,7 @@
int mFd;
int mErrno = 0;
- FDAdapter(int fd) : mFd(fd) {}
+ explicit FDAdapter(int fd) : mFd(fd) {}
virtual ~FDAdapter() {}
virtual bool Write(const void* buffer, int size) override {
@@ -139,6 +139,7 @@
uint32_t file_version = *reinterpret_cast<uint32_t*>(addr);
if (file_version != sCurrentFileVersion) {
ALOGW("file_version mismatch! expected %d got %d", sCurrentFileVersion, file_version);
+ munmap(addr, sb.st_size);
return false;
}
@@ -150,6 +151,7 @@
ALOGW("Parse failed on '%s' error='%s'", path.c_str(),
output->InitializationErrorString().c_str());
}
+ munmap(addr, sb.st_size);
return success;
}
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index 7aa9b82..16a27598 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -82,7 +82,7 @@
float y) {
auto utf16 = asciiToUtf16(text);
uint32_t length = strlen(text);
- SkPaint glyphPaint(paint);
+ Paint glyphPaint(paint);
glyphPaint.setTextEncoding(kGlyphID_SkTextEncoding);
canvas->drawText(utf16.get(), length, // text buffer
0, length, // draw range
@@ -93,7 +93,7 @@
void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint,
const SkPath& path) {
auto utf16 = asciiToUtf16(text);
- SkPaint glyphPaint(paint);
+ Paint glyphPaint(paint);
glyphPaint.setTextEncoding(kGlyphID_SkTextEncoding);
canvas->drawTextOnPath(utf16.get(), strlen(text), minikin::Bidi::LTR, path, 0, 0, glyphPaint,
nullptr);
diff --git a/libs/hwui/tests/common/scenes/BitmapFillrate.cpp b/libs/hwui/tests/common/scenes/BitmapFillrate.cpp
index 1d3d607..5af7d43 100644
--- a/libs/hwui/tests/common/scenes/BitmapFillrate.cpp
+++ b/libs/hwui/tests/common/scenes/BitmapFillrate.cpp
@@ -31,7 +31,7 @@
class BitmapFillrate : public TestScene {
public:
- BitmapFillrate(BitmapAllocationTestUtils::BitmapAllocator allocator)
+ explicit BitmapFillrate(BitmapAllocationTestUtils::BitmapAllocator allocator)
: TestScene(), mAllocator(allocator) {}
void createContent(int width, int height, Canvas& canvas) override {
@@ -70,4 +70,4 @@
BitmapAllocationTestUtils::BitmapAllocator mAllocator;
std::vector<sp<RenderNode> > mNodes;
-};
\ No newline at end of file
+};
diff --git a/libs/hwui/tests/common/scenes/BitmapShaders.cpp b/libs/hwui/tests/common/scenes/BitmapShaders.cpp
index ad11a1d..5107660 100644
--- a/libs/hwui/tests/common/scenes/BitmapShaders.cpp
+++ b/libs/hwui/tests/common/scenes/BitmapShaders.cpp
@@ -26,7 +26,7 @@
class BitmapShaders : public TestScene {
public:
- BitmapShaders(BitmapAllocationTestUtils::BitmapAllocator allocator)
+ explicit BitmapShaders(BitmapAllocationTestUtils::BitmapAllocator allocator)
: TestScene(), mAllocator(allocator) {}
sp<RenderNode> card;
diff --git a/libs/hwui/tests/common/scenes/TvApp.cpp b/libs/hwui/tests/common/scenes/TvApp.cpp
index a64e844..286f5f1 100644
--- a/libs/hwui/tests/common/scenes/TvApp.cpp
+++ b/libs/hwui/tests/common/scenes/TvApp.cpp
@@ -48,7 +48,7 @@
class TvApp : public TestScene {
public:
- TvApp(BitmapAllocationTestUtils::BitmapAllocator allocator)
+ explicit TvApp(BitmapAllocationTestUtils::BitmapAllocator allocator)
: TestScene(), mAllocator(allocator) {}
sp<RenderNode> mBg;
@@ -232,7 +232,7 @@
class TvAppNoRoundedCorner : public TvApp {
public:
- TvAppNoRoundedCorner(BitmapAllocationTestUtils::BitmapAllocator allocator) : TvApp(allocator) {}
+ explicit TvAppNoRoundedCorner(BitmapAllocationTestUtils::BitmapAllocator allocator) : TvApp(allocator) {}
private:
virtual float roundedCornerRadius() override { return dp(0); }
@@ -240,7 +240,7 @@
class TvAppColorFilter : public TvApp {
public:
- TvAppColorFilter(BitmapAllocationTestUtils::BitmapAllocator allocator) : TvApp(allocator) {}
+ explicit TvAppColorFilter(BitmapAllocationTestUtils::BitmapAllocator allocator) : TvApp(allocator) {}
private:
virtual bool useOverlay() override { return false; }
@@ -248,7 +248,7 @@
class TvAppNoRoundedCornerColorFilter : public TvApp {
public:
- TvAppNoRoundedCornerColorFilter(BitmapAllocationTestUtils::BitmapAllocator allocator)
+ explicit TvAppNoRoundedCornerColorFilter(BitmapAllocationTestUtils::BitmapAllocator allocator)
: TvApp(allocator) {}
private:
diff --git a/libs/hwui/tests/macrobench/main.cpp b/libs/hwui/tests/macrobench/main.cpp
index 524dfb0..174a140 100644
--- a/libs/hwui/tests/macrobench/main.cpp
+++ b/libs/hwui/tests/macrobench/main.cpp
@@ -19,6 +19,8 @@
#include "Properties.h"
#include "hwui/Typeface.h"
+#include "HardwareBitmapUploader.h"
+#include "renderthread/RenderProxy.h"
#include <benchmark/benchmark.h>
#include <getopt.h>
@@ -353,6 +355,9 @@
gBenchmarkReporter->Finalize();
}
+ renderthread::RenderProxy::trimMemory(100);
+ HardwareBitmapUploader::terminate();
+
LeakChecker::checkForLeaks();
return 0;
}
diff --git a/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp b/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp
index 479c462..635429d 100644
--- a/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp
+++ b/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp
@@ -44,7 +44,7 @@
static const int CANVAS_HEIGHT = 100;
class PropertyTestCanvas : public TestCanvasBase {
public:
- PropertyTestCanvas(std::function<void(const SkCanvas&)> callback)
+ explicit PropertyTestCanvas(std::function<void(const SkCanvas&)> callback)
: TestCanvasBase(CANVAS_WIDTH, CANVAS_HEIGHT), mCallback(callback) {}
void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
EXPECT_EQ(mDrawCounter++, 0);
diff --git a/libs/hwui/tests/unit/ThreadBaseTests.cpp b/libs/hwui/tests/unit/ThreadBaseTests.cpp
index 1168ff2..817c1f3 100644
--- a/libs/hwui/tests/unit/ThreadBaseTests.cpp
+++ b/libs/hwui/tests/unit/ThreadBaseTests.cpp
@@ -95,7 +95,7 @@
};
struct Counter {
- Counter(EventCount* count) : mCount(count) { mCount->construct++; }
+ explicit Counter(EventCount* count) : mCount(count) { mCount->construct++; }
Counter(const Counter& other) : mCount(other.mCount) {
if (mCount) mCount->copy++;
@@ -148,4 +148,4 @@
ASSERT_EQ(1, dummyObject->getStrongCount());
ASSERT_EQ(2, lifecycleTestHelper(dummyObject));
ASSERT_EQ(1, dummyObject->getStrongCount());
-}
\ No newline at end of file
+}
diff --git a/libs/hwui/thread/ThreadBase.h b/libs/hwui/thread/ThreadBase.h
index f9de8a5..8cdcc46 100644
--- a/libs/hwui/thread/ThreadBase.h
+++ b/libs/hwui/thread/ThreadBase.h
@@ -27,7 +27,7 @@
namespace android::uirenderer {
-class ThreadBase : protected Thread {
+class ThreadBase : public Thread {
PREVENT_COPY_AND_ASSIGN(ThreadBase);
public:
diff --git a/libs/hwui/utils/LinearAllocator.h b/libs/hwui/utils/LinearAllocator.h
index b401fcf..9c4a1be 100644
--- a/libs/hwui/utils/LinearAllocator.h
+++ b/libs/hwui/utils/LinearAllocator.h
@@ -168,7 +168,7 @@
};
// enable allocators to be constructed from other templated types
template <class U>
- LinearStdAllocator(const LinearStdAllocator<U>& other) // NOLINT(implicit)
+ LinearStdAllocator(const LinearStdAllocator<U>& other) // NOLINT(google-explicit-constructor)
: linearAllocator(other.linearAllocator) {}
T* allocate(size_t num, const void* = 0) {
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 0a90f85..b4f19c9 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -39,7 +39,7 @@
virtual ~WeakLooperCallback() { }
public:
- WeakLooperCallback(const wp<LooperCallback>& callback) :
+ explicit WeakLooperCallback(const wp<LooperCallback>& callback) :
mCallback(callback) {
}
@@ -89,10 +89,6 @@
mLocked.animationPending = false;
- mLocked.displayWidth = -1;
- mLocked.displayHeight = -1;
- mLocked.displayOrientation = DISPLAY_ORIENTATION_0;
-
mLocked.presentation = PRESENTATION_POINTER;
mLocked.presentationChanged = false;
@@ -110,15 +106,6 @@
mLocked.lastFrameUpdatedTime = 0;
mLocked.buttonState = 0;
-
- mPolicy->loadPointerIcon(&mLocked.pointerIcon);
-
- loadResources();
-
- if (mLocked.pointerIcon.isValid()) {
- mLocked.pointerIconChanged = true;
- updatePointerLocked();
- }
}
PointerController::~PointerController() {
@@ -144,23 +131,15 @@
bool PointerController::getBoundsLocked(float* outMinX, float* outMinY,
float* outMaxX, float* outMaxY) const {
- if (mLocked.displayWidth <= 0 || mLocked.displayHeight <= 0) {
+
+ if (!mLocked.viewport.isValid()) {
return false;
}
- *outMinX = 0;
- *outMinY = 0;
- switch (mLocked.displayOrientation) {
- case DISPLAY_ORIENTATION_90:
- case DISPLAY_ORIENTATION_270:
- *outMaxX = mLocked.displayHeight - 1;
- *outMaxY = mLocked.displayWidth - 1;
- break;
- default:
- *outMaxX = mLocked.displayWidth - 1;
- *outMaxY = mLocked.displayHeight - 1;
- break;
- }
+ *outMinX = mLocked.viewport.logicalLeft;
+ *outMinY = mLocked.viewport.logicalTop;
+ *outMaxX = mLocked.viewport.logicalRight - 1;
+ *outMaxY = mLocked.viewport.logicalBottom - 1;
return true;
}
@@ -231,6 +210,12 @@
*outY = mLocked.pointerY;
}
+int32_t PointerController::getDisplayId() const {
+ AutoMutex _l(mLock);
+
+ return mLocked.viewport.displayId;
+}
+
void PointerController::fade(Transition transition) {
AutoMutex _l(mLock);
@@ -355,48 +340,56 @@
void PointerController::reloadPointerResources() {
AutoMutex _l(mLock);
- loadResources();
-
- if (mLocked.presentation == PRESENTATION_POINTER) {
- mLocked.additionalMouseResources.clear();
- mLocked.animationResources.clear();
- mPolicy->loadPointerIcon(&mLocked.pointerIcon);
- mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources,
- &mLocked.animationResources);
- }
-
- mLocked.presentationChanged = true;
+ loadResourcesLocked();
updatePointerLocked();
}
-void PointerController::setDisplayViewport(int32_t width, int32_t height, int32_t orientation) {
- AutoMutex _l(mLock);
+/**
+ * The viewport values for deviceHeight and deviceWidth have already been adjusted for rotation,
+ * so here we are getting the dimensions in the original, unrotated orientation (orientation 0).
+ */
+static void getNonRotatedSize(const DisplayViewport& viewport, int32_t& width, int32_t& height) {
+ width = viewport.deviceWidth;
+ height = viewport.deviceHeight;
- // Adjust to use the display's unrotated coordinate frame.
- if (orientation == DISPLAY_ORIENTATION_90
- || orientation == DISPLAY_ORIENTATION_270) {
- int32_t temp = height;
- height = width;
- width = temp;
+ if (viewport.orientation == DISPLAY_ORIENTATION_90
+ || viewport.orientation == DISPLAY_ORIENTATION_270) {
+ std::swap(width, height);
+ }
+}
+
+void PointerController::setDisplayViewport(const DisplayViewport& viewport) {
+ AutoMutex _l(mLock);
+ if (viewport == mLocked.viewport) {
+ return;
}
- if (mLocked.displayWidth != width || mLocked.displayHeight != height) {
- mLocked.displayWidth = width;
- mLocked.displayHeight = height;
+ const DisplayViewport oldViewport = mLocked.viewport;
+ mLocked.viewport = viewport;
+
+ int32_t oldDisplayWidth, oldDisplayHeight;
+ getNonRotatedSize(oldViewport, oldDisplayWidth, oldDisplayHeight);
+ int32_t newDisplayWidth, newDisplayHeight;
+ getNonRotatedSize(viewport, newDisplayWidth, newDisplayHeight);
+
+ // Reset cursor position to center if size or display changed.
+ if (oldViewport.displayId != viewport.displayId
+ || oldDisplayWidth != newDisplayWidth
+ || oldDisplayHeight != newDisplayHeight) {
float minX, minY, maxX, maxY;
if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
mLocked.pointerX = (minX + maxX) * 0.5f;
mLocked.pointerY = (minY + maxY) * 0.5f;
+ // Reload icon resources for density may be changed.
+ loadResourcesLocked();
} else {
mLocked.pointerX = 0;
mLocked.pointerY = 0;
}
fadeOutAndReleaseAllSpotsLocked();
- }
-
- if (mLocked.displayOrientation != orientation) {
+ } else if (oldViewport.orientation != viewport.orientation) {
// Apply offsets to convert from the pixel top-left corner position to the pixel center.
// This creates an invariant frame of reference that we can easily rotate when
// taking into account that the pointer may be located at fractional pixel offsets.
@@ -405,37 +398,37 @@
float temp;
// Undo the previous rotation.
- switch (mLocked.displayOrientation) {
+ switch (oldViewport.orientation) {
case DISPLAY_ORIENTATION_90:
temp = x;
- x = mLocked.displayWidth - y;
+ x = oldViewport.deviceHeight - y;
y = temp;
break;
case DISPLAY_ORIENTATION_180:
- x = mLocked.displayWidth - x;
- y = mLocked.displayHeight - y;
+ x = oldViewport.deviceWidth - x;
+ y = oldViewport.deviceHeight - y;
break;
case DISPLAY_ORIENTATION_270:
temp = x;
x = y;
- y = mLocked.displayHeight - temp;
+ y = oldViewport.deviceWidth - temp;
break;
}
// Perform the new rotation.
- switch (orientation) {
+ switch (viewport.orientation) {
case DISPLAY_ORIENTATION_90:
temp = x;
x = y;
- y = mLocked.displayWidth - temp;
+ y = viewport.deviceHeight - temp;
break;
case DISPLAY_ORIENTATION_180:
- x = mLocked.displayWidth - x;
- y = mLocked.displayHeight - y;
+ x = viewport.deviceWidth - x;
+ y = viewport.deviceHeight - y;
break;
case DISPLAY_ORIENTATION_270:
temp = x;
- x = mLocked.displayHeight - y;
+ x = viewport.deviceWidth - y;
y = temp;
break;
}
@@ -444,7 +437,6 @@
// and save the results.
mLocked.pointerX = x - 0.5f;
mLocked.pointerY = y - 0.5f;
- mLocked.displayOrientation = orientation;
}
updatePointerLocked();
@@ -614,11 +606,16 @@
mLooper->removeMessages(mHandler, MSG_INACTIVITY_TIMEOUT);
}
-void PointerController::updatePointerLocked() {
+void PointerController::updatePointerLocked() REQUIRES(mLock) {
+ if (!mLocked.viewport.isValid()) {
+ return;
+ }
+
mSpriteController->openTransaction();
mLocked.pointerSprite->setLayer(Sprite::BASE_LAYER_POINTER);
mLocked.pointerSprite->setPosition(mLocked.pointerX, mLocked.pointerY);
+ mLocked.pointerSprite->setDisplayId(mLocked.viewport.displayId);
if (mLocked.pointerAlpha > 0) {
mLocked.pointerSprite->setAlpha(mLocked.pointerAlpha);
@@ -729,8 +726,18 @@
}
}
-void PointerController::loadResources() {
+void PointerController::loadResourcesLocked() REQUIRES(mLock) {
mPolicy->loadPointerResources(&mResources);
+
+ if (mLocked.presentation == PRESENTATION_POINTER) {
+ mLocked.additionalMouseResources.clear();
+ mLocked.animationResources.clear();
+ mPolicy->loadPointerIcon(&mLocked.pointerIcon);
+ mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources,
+ &mLocked.animationResources);
+ }
+
+ mLocked.pointerIconChanged = true;
}
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 7f4e5a5..a32cc42 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -23,6 +23,7 @@
#include <vector>
#include <ui/DisplayInfo.h>
+#include <input/DisplayViewport.h>
#include <input/Input.h>
#include <PointerControllerInterface.h>
#include <utils/BitSet.h>
@@ -96,6 +97,7 @@
virtual int32_t getButtonState() const;
virtual void setPosition(float x, float y);
virtual void getPosition(float* outX, float* outY) const;
+ virtual int32_t getDisplayId() const;
virtual void fade(Transition transition);
virtual void unfade(Transition transition);
@@ -106,7 +108,7 @@
void updatePointerIcon(int32_t iconId);
void setCustomPointerIcon(const SpriteIcon& icon);
- void setDisplayViewport(int32_t width, int32_t height, int32_t orientation);
+ void setDisplayViewport(const DisplayViewport& viewport);
void setInactivityTimeout(InactivityTimeout inactivityTimeout);
void reloadPointerResources();
@@ -156,9 +158,7 @@
size_t animationFrameIndex;
nsecs_t lastFrameUpdatedTime;
- int32_t displayWidth;
- int32_t displayHeight;
- int32_t displayOrientation;
+ DisplayViewport viewport;
InactivityTimeout inactivityTimeout;
@@ -182,7 +182,7 @@
Vector<Spot*> spots;
Vector<sp<Sprite> > recycledSprites;
- } mLocked;
+ } mLocked GUARDED_BY(mLock);
bool getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
void setPositionLocked(float x, float y);
@@ -207,7 +207,7 @@
void fadeOutAndReleaseSpotLocked(Spot* spot);
void fadeOutAndReleaseAllSpotsLocked();
- void loadResources();
+ void loadResourcesLocked();
};
} // namespace android
diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp
index eb2bc98..c1868d3 100644
--- a/libs/input/SpriteController.cpp
+++ b/libs/input/SpriteController.cpp
@@ -144,13 +144,16 @@
}
}
- // Resize sprites if needed.
+ // Resize and/or reparent sprites if needed.
SurfaceComposerClient::Transaction t;
bool needApplyTransaction = false;
for (size_t i = 0; i < numSprites; i++) {
SpriteUpdate& update = updates.editItemAt(i);
+ if (update.state.surfaceControl == nullptr) {
+ continue;
+ }
- if (update.state.surfaceControl != NULL && update.state.wantSurfaceVisible()) {
+ if (update.state.wantSurfaceVisible()) {
int32_t desiredWidth = update.state.icon.bitmap.width();
int32_t desiredHeight = update.state.icon.bitmap.height();
if (update.state.surfaceWidth < desiredWidth
@@ -170,6 +173,12 @@
}
}
}
+
+ // If surface is a new one, we have to set right layer stack.
+ if (update.surfaceChanged || update.state.dirty & DIRTY_DISPLAY_ID) {
+ t.setLayerStack(update.state.surfaceControl, update.state.displayId);
+ needApplyTransaction = true;
+ }
}
if (needApplyTransaction) {
t.apply();
@@ -236,7 +245,7 @@
if (update.state.surfaceControl != NULL && (becomingVisible || becomingHidden
|| (wantSurfaceVisibleAndDrawn && (update.state.dirty & (DIRTY_ALPHA
| DIRTY_POSITION | DIRTY_TRANSFORMATION_MATRIX | DIRTY_LAYER
- | DIRTY_VISIBILITY | DIRTY_HOTSPOT))))) {
+ | DIRTY_VISIBILITY | DIRTY_HOTSPOT | DIRTY_DISPLAY_ID))))) {
needApplyTransaction = true;
if (wantSurfaceVisibleAndDrawn
@@ -445,6 +454,15 @@
}
}
+void SpriteController::SpriteImpl::setDisplayId(int32_t displayId) {
+ AutoMutex _l(mController->mLock);
+
+ if (mLocked.state.displayId != displayId) {
+ mLocked.state.displayId = displayId;
+ invalidateLocked(DIRTY_DISPLAY_ID);
+ }
+}
+
void SpriteController::SpriteImpl::invalidateLocked(uint32_t dirty) {
bool wasDirty = mLocked.state.dirty;
mLocked.state.dirty |= dirty;
diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h
index 31e43e9..5b216f5 100644
--- a/libs/input/SpriteController.h
+++ b/libs/input/SpriteController.h
@@ -125,6 +125,9 @@
/* Sets the sprite transformation matrix. */
virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix) = 0;
+
+ /* Sets the id of the display where the sprite should be shown. */
+ virtual void setDisplayId(int32_t displayId) = 0;
};
/*
@@ -170,6 +173,7 @@
DIRTY_LAYER = 1 << 4,
DIRTY_VISIBILITY = 1 << 5,
DIRTY_HOTSPOT = 1 << 6,
+ DIRTY_DISPLAY_ID = 1 << 7,
};
/* Describes the state of a sprite.
@@ -180,7 +184,7 @@
struct SpriteState {
inline SpriteState() :
dirty(0), visible(false),
- positionX(0), positionY(0), layer(0), alpha(1.0f),
+ positionX(0), positionY(0), layer(0), alpha(1.0f), displayId(ADISPLAY_ID_DEFAULT),
surfaceWidth(0), surfaceHeight(0), surfaceDrawn(false), surfaceVisible(false) {
}
@@ -193,6 +197,7 @@
int32_t layer;
float alpha;
SpriteTransformationMatrix transformationMatrix;
+ int32_t displayId;
sp<SurfaceControl> surfaceControl;
int32_t surfaceWidth;
@@ -225,6 +230,7 @@
virtual void setLayer(int32_t layer);
virtual void setAlpha(float alpha);
virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix);
+ virtual void setDisplayId(int32_t displayId);
inline const SpriteState& getStateLocked() const {
return mLocked.state;
diff --git a/libs/protoutil/include/android/util/EncodedBuffer.h b/libs/protoutil/include/android/util/EncodedBuffer.h
index c84de4c..0b7f6e46 100644
--- a/libs/protoutil/include/android/util/EncodedBuffer.h
+++ b/libs/protoutil/include/android/util/EncodedBuffer.h
@@ -38,13 +38,13 @@
{
public:
EncodedBuffer();
- EncodedBuffer(size_t chunkSize);
+ explicit EncodedBuffer(size_t chunkSize);
~EncodedBuffer();
class Pointer {
public:
Pointer();
- Pointer(size_t chunkSize);
+ explicit Pointer(size_t chunkSize);
size_t pos() const;
size_t index() const;
@@ -161,7 +161,7 @@
friend class iterator;
class iterator {
public:
- iterator(const EncodedBuffer& buffer);
+ explicit iterator(const EncodedBuffer& buffer);
/**
* Returns the number of bytes written in the buffer
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index bdc84da..e795b81 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -89,7 +89,6 @@
List<String> getAllProviders();
List<String> getProviders(in Criteria criteria, boolean enabledOnly);
String getBestProvider(in Criteria criteria, boolean enabledOnly);
- boolean providerMeetsCriteria(String provider, in Criteria criteria);
ProviderProperties getProviderProperties(String provider);
String getNetworkProviderPackage();
void setLocationControllerExtraPackage(String packageName);
@@ -98,9 +97,7 @@
boolean isLocationControllerExtraPackageEnabled();
boolean isProviderEnabledForUser(String provider, int userId);
- boolean setProviderEnabledForUser(String provider, boolean enabled, int userId);
boolean isLocationEnabledForUser(int userId);
- void setLocationEnabledForUser(boolean enabled, int userId);
void addTestProvider(String name, in ProviderProperties properties, String opPackageName);
void removeTestProvider(String provider, String opPackageName);
void setTestProviderLocation(String provider, in Location loc, String opPackageName);
@@ -114,10 +111,6 @@
// --- internal ---
- // --- deprecated ---
- void reportLocation(in Location location, boolean passive);
- void reportLocationBatch(in List<Location> locations);
-
// for reporting callback completion
void locationCallbackFinished(ILocationListener listener);
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index d96597b..59c6a0a 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -43,6 +43,7 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.provider.Settings;
import android.util.Log;
import com.android.internal.location.ProviderProperties;
@@ -1282,11 +1283,13 @@
@SystemApi
@RequiresPermission(WRITE_SECURE_SETTINGS)
public void setLocationEnabledForUser(boolean enabled, UserHandle userHandle) {
- try {
- mService.setLocationEnabledForUser(enabled, userHandle.getIdentifier());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ Settings.Secure.putIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.LOCATION_MODE,
+ enabled
+ ? Settings.Secure.LOCATION_MODE_HIGH_ACCURACY
+ : Settings.Secure.LOCATION_MODE_OFF,
+ userHandle.getIdentifier());
}
/**
@@ -1372,20 +1375,22 @@
* @return true if the value was set, false on database errors
*
* @throws IllegalArgumentException if provider is null
+ * @deprecated Do not manipulate providers individually, use
+ * {@link #setLocationEnabledForUser(boolean, UserHandle)} instead.
* @hide
*/
+ @Deprecated
@SystemApi
@RequiresPermission(WRITE_SECURE_SETTINGS)
public boolean setProviderEnabledForUser(
String provider, boolean enabled, UserHandle userHandle) {
checkProvider(provider);
- try {
- return mService.setProviderEnabledForUser(
- provider, enabled, userHandle.getIdentifier());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return Settings.Secure.setLocationProviderEnabledForUser(
+ mContext.getContentResolver(),
+ provider,
+ enabled,
+ userHandle.getIdentifier());
}
/**
@@ -1578,7 +1583,7 @@
*/
@Deprecated
public void clearTestProviderEnabled(String provider) {
- setTestProviderEnabled(provider, true);
+ setTestProviderEnabled(provider, false);
}
/**
diff --git a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
index b531325..866634e 100644
--- a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
+++ b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
@@ -16,9 +16,6 @@
package com.android.internal.location;
-import java.io.UnsupportedEncodingException;
-import java.util.concurrent.TimeUnit;
-
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -26,20 +23,23 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.location.LocationManager;
import android.location.INetInitiatedListener;
+import android.location.LocationManager;
+import android.os.RemoteException;
import android.os.SystemClock;
-import android.telephony.TelephonyManager;
+import android.os.UserHandle;
import android.telephony.PhoneNumberUtils;
import android.telephony.PhoneStateListener;
-import android.os.RemoteException;
-import android.os.UserHandle;
+import android.telephony.TelephonyManager;
import android.util.Log;
-import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.R;
+import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.telephony.GsmAlphabet;
+import java.io.UnsupportedEncodingException;
+import java.util.concurrent.TimeUnit;
+
/**
* A GPS Network-initiated Handler class used by LocationManager.
*
@@ -92,9 +92,6 @@
public static final int GPS_ENC_SUPL_UCS2 = 3;
public static final int GPS_ENC_UNKNOWN = -1;
- // Limit on SUPL NI emergency mode time extension after emergency sessions ends
- private static final int MAX_EMERGENCY_MODE_EXTENSION_SECONDS = 300; // 5 minute maximum
-
private final Context mContext;
private final TelephonyManager mTelephonyManager;
private final PhoneStateListener mPhoneStateListener;
@@ -252,19 +249,9 @@
}
public void setEmergencyExtensionSeconds(int emergencyExtensionSeconds) {
- if (emergencyExtensionSeconds > MAX_EMERGENCY_MODE_EXTENSION_SECONDS) {
- Log.w(TAG, "emergencyExtensionSeconds " + emergencyExtensionSeconds
- + " too high, reset to " + MAX_EMERGENCY_MODE_EXTENSION_SECONDS);
- emergencyExtensionSeconds = MAX_EMERGENCY_MODE_EXTENSION_SECONDS;
- } else if (emergencyExtensionSeconds < 0) {
- Log.w(TAG, "emergencyExtensionSeconds " + emergencyExtensionSeconds
- + " is negative, reset to zero.");
- emergencyExtensionSeconds = 0;
- }
mEmergencyExtensionMillis = TimeUnit.SECONDS.toMillis(emergencyExtensionSeconds);
}
-
// Handles NI events from HAL
public void handleNiNotification(GpsNiNotification notif) {
if (DEBUG) Log.d(TAG, "in handleNiNotification () :"
diff --git a/location/lib/Android.bp b/location/lib/Android.bp
index b09335c..35f2877 100644
--- a/location/lib/Android.bp
+++ b/location/lib/Android.bp
@@ -18,5 +18,7 @@
name: "com.android.location.provider",
srcs: ["java/**/*.java"],
api_packages: ["com.android.location.provider"],
- metalava_enabled: false,
+ srcs_lib: "framework",
+ srcs_lib_whitelist_dirs: ["location/java"],
+ srcs_lib_whitelist_pkgs: ["com.android.internal.location"],
}
diff --git a/media/java/android/media/MediaItem2.java b/media/java/android/media/MediaItem2.java
index 235325e..c496cf7 100644
--- a/media/java/android/media/MediaItem2.java
+++ b/media/java/android/media/MediaItem2.java
@@ -40,9 +40,8 @@
* <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a>
* for consistent behavior across all devices.
* <p>
- * @hide
*/
-public class MediaItem2 implements Parcelable {
+public final class MediaItem2 implements Parcelable {
private static final String TAG = "MediaItem2";
// intentionally less than long.MAX_VALUE.
@@ -69,7 +68,6 @@
}
};
- // TODO: Use SessionPlayer2.UNKNOWN_TIME instead
private static final long UNKNOWN_TIME = -1;
private final long mStartPositionMs;
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 0057875..fb18c3b6 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -25,8 +25,10 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
+import android.graphics.SurfaceTexture;
+import android.media.SubtitleController.Anchor;
+import android.media.SubtitleTrack.RenderingWidget;
import android.net.Uri;
-import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
@@ -35,30 +37,19 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PersistableBundle;
-import android.os.Process;
import android.os.PowerManager;
+import android.os.Process;
import android.os.SystemProperties;
import android.provider.Settings;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
+import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
-import android.util.ArrayMap;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.widget.VideoView;
-import android.graphics.SurfaceTexture;
-import android.media.AudioManager;
-import android.media.MediaDrm;
-import android.media.MediaFormat;
-import android.media.MediaTimeProvider;
-import android.media.PlaybackParams;
-import android.media.SubtitleController;
-import android.media.SubtitleController.Anchor;
-import android.media.SubtitleData;
-import android.media.SubtitleTrack.RenderingWidget;
-import android.media.SyncParams;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
@@ -72,7 +63,6 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.lang.Runnable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
@@ -2105,9 +2095,11 @@
mOnInfoListener = null;
mOnVideoSizeChangedListener = null;
mOnTimedTextListener = null;
- if (mTimeProvider != null) {
- mTimeProvider.close();
- mTimeProvider = null;
+ synchronized (mTimeProviderLock) {
+ if (mTimeProvider != null) {
+ mTimeProvider.close();
+ mTimeProvider = null;
+ }
}
synchronized(this) {
mSubtitleDataListenerDisabled = false;
@@ -2147,9 +2139,11 @@
if (mSubtitleController != null) {
mSubtitleController.reset();
}
- if (mTimeProvider != null) {
- mTimeProvider.close();
- mTimeProvider = null;
+ synchronized (mTimeProviderLock) {
+ if (mTimeProvider != null) {
+ mTimeProvider.close();
+ mTimeProvider = null;
+ }
}
stayAwake(false);
@@ -2790,12 +2784,17 @@
synchronized (mIndexTrackPairs) {
mIndexTrackPairs.add(Pair.<Integer, SubtitleTrack>create(null, track));
}
- Handler h = mTimeProvider.mEventHandler;
- int what = TimeProvider.NOTIFY;
- int arg1 = TimeProvider.NOTIFY_TRACK_DATA;
- Pair<SubtitleTrack, byte[]> trackData = Pair.create(track, contents.getBytes());
- Message m = h.obtainMessage(what, arg1, 0, trackData);
- h.sendMessage(m);
+ synchronized (mTimeProviderLock) {
+ if (mTimeProvider != null) {
+ Handler h = mTimeProvider.mEventHandler;
+ int what = TimeProvider.NOTIFY;
+ int arg1 = TimeProvider.NOTIFY_TRACK_DATA;
+ Pair<SubtitleTrack, byte[]> trackData =
+ Pair.create(track, contents.getBytes());
+ Message m = h.obtainMessage(what, arg1, 0, trackData);
+ h.sendMessage(m);
+ }
+ }
return MEDIA_INFO_EXTERNAL_METADATA_UPDATE;
}
@@ -3020,12 +3019,17 @@
total += bytes;
}
}
- Handler h = mTimeProvider.mEventHandler;
- int what = TimeProvider.NOTIFY;
- int arg1 = TimeProvider.NOTIFY_TRACK_DATA;
- Pair<SubtitleTrack, byte[]> trackData = Pair.create(track, bos.toByteArray());
- Message m = h.obtainMessage(what, arg1, 0, trackData);
- h.sendMessage(m);
+ synchronized (mTimeProviderLock) {
+ if (mTimeProvider != null) {
+ Handler h = mTimeProvider.mEventHandler;
+ int what = TimeProvider.NOTIFY;
+ int arg1 = TimeProvider.NOTIFY_TRACK_DATA;
+ Pair<SubtitleTrack, byte[]> trackData =
+ Pair.create(track, bos.toByteArray());
+ Message m = h.obtainMessage(what, arg1, 0, trackData);
+ h.sendMessage(m);
+ }
+ }
return MEDIA_INFO_EXTERNAL_METADATA_UPDATE;
} catch (Exception e) {
Log.e(TAG, e.getMessage(), e);
@@ -3308,14 +3312,17 @@
private static final int MEDIA_AUDIO_ROUTING_CHANGED = 10000;
private TimeProvider mTimeProvider;
+ private final Object mTimeProviderLock = new Object();
/** @hide */
@UnsupportedAppUsage
public MediaTimeProvider getMediaTimeProvider() {
- if (mTimeProvider == null) {
- mTimeProvider = new TimeProvider(this);
+ synchronized (mTimeProviderLock) {
+ if (mTimeProvider == null) {
+ mTimeProvider = new TimeProvider(this);
+ }
+ return mTimeProvider;
}
- return mTimeProvider;
}
private class EventHandler extends Handler
diff --git a/media/java/android/media/MediaPlayer2.java b/media/java/android/media/MediaPlayer2.java
index cef6614..df96994 100644
--- a/media/java/android/media/MediaPlayer2.java
+++ b/media/java/android/media/MediaPlayer2.java
@@ -537,6 +537,19 @@
public native long getCurrentPosition();
/**
+ * Gets the duration of the current data source.
+ * Same as {@link #getDuration(DataSourceDesc)} with
+ * {@code dsd = getCurrentDataSource()}.
+ *
+ * @return the duration in milliseconds, if no duration is available
+ * (for example, if streaming live content), -1 is returned.
+ * @throws NullPointerException if current data source is null
+ */
+ public long getDuration() {
+ return getDuration(getCurrentDataSource());
+ }
+
+ /**
* Gets the duration of the dsd.
*
* @param dsd the descriptor of data source of which you want to get duration
@@ -559,6 +572,18 @@
private native long native_getDuration(long srcId);
/**
+ * Gets the buffered media source position of current data source.
+ * Same as {@link #getBufferedPosition(DataSourceDesc)} with
+ * {@code dsd = getCurrentDataSource()}.
+ *
+ * @return the current buffered media source position in milliseconds
+ * @throws NullPointerException if current data source is null
+ */
+ public long getBufferedPosition() {
+ return getBufferedPosition(getCurrentDataSource());
+ }
+
+ /**
* Gets the buffered media source position of given dsd.
* For example a buffering update of 8000 milliseconds when 5000 milliseconds of the content
* has already been played indicates that the next 3000 milliseconds of the
@@ -1309,11 +1334,11 @@
return addTask(new Task(CALL_COMPLETED_SET_WAKE_LOCK, false) {
@Override
void process() {
- boolean washeld = false;
+ boolean wasHeld = false;
if (mWakeLock != null) {
if (mWakeLock.isHeld()) {
- washeld = true;
+ wasHeld = true;
mWakeLock.release();
}
}
@@ -1321,7 +1346,7 @@
mWakeLock = wakeLock;
if (mWakeLock != null) {
mWakeLock.setReferenceCounted(false);
- if (washeld) {
+ if (wasHeld) {
mWakeLock.acquire();
}
}
@@ -2015,6 +2040,21 @@
};
/**
+ * Returns a List of track information of current data source.
+ * Same as {@link #getTrackInfo(DataSourceDesc)} with
+ * {@code dsd = getCurrentDataSource()}.
+ *
+ * @return List of track info. The total number of tracks is the array length.
+ * Must be called again if an external timed text source has been added after
+ * addTimedTextSource method is called.
+ * @throws IllegalStateException if it is called in an invalid state.
+ * @throws NullPointerException if current data source is null
+ */
+ public @NonNull List<TrackInfo> getTrackInfo() {
+ return getTrackInfo(getCurrentDataSource());
+ }
+
+ /**
* Returns a List of track information.
*
* @param dsd the descriptor of data source of which you want to get track info
@@ -2024,7 +2064,6 @@
* @throws IllegalStateException if it is called in an invalid state.
* @throws NullPointerException if dsd is null
*/
-
public @NonNull List<TrackInfo> getTrackInfo(@NonNull DataSourceDesc dsd) {
if (dsd == null) {
throw new NullPointerException("non-null dsd is expected");
@@ -2060,9 +2099,34 @@
}
/**
- * Returns the index of the audio, video, or subtitle track currently selected for playback,
+ * Returns the index of the audio, video, or subtitle track currently selected for playback.
* The return value is an index into the array returned by {@link #getTrackInfo}, and can
- * be used in calls to {@link #selectTrack} or {@link #deselectTrack}.
+ * be used in calls to {@link #selectTrack(int)} or {@link #deselectTrack(int)}.
+ * Same as {@link #getSelectedTrack(DataSourceDesc, int)} with
+ * {@code dsd = getCurrentDataSource()}.
+ *
+ * @param trackType should be one of {@link TrackInfo#MEDIA_TRACK_TYPE_VIDEO},
+ * {@link TrackInfo#MEDIA_TRACK_TYPE_AUDIO}, or
+ * {@link TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE}
+ * @return index of the audio, video, or subtitle track currently selected for playback;
+ * a negative integer is returned when there is no selected track for {@code trackType} or
+ * when {@code trackType} is not one of audio, video, or subtitle.
+ * @throws IllegalStateException if called after {@link #close()}
+ * @throws NullPointerException if current data source is null
+ *
+ * @see #getTrackInfo()
+ * @see #selectTrack(int)
+ * @see #deselectTrack(int)
+ */
+ public int getSelectedTrack(int trackType) {
+ return getSelectedTrack(getCurrentDataSource(), trackType);
+ }
+
+ /**
+ * Returns the index of the audio, video, or subtitle track currently selected for playback.
+ * The return value is an index into the array returned by {@link #getTrackInfo}, and can
+ * be used in calls to {@link #selectTrack(DataSourceDesc, int)} or
+ * {@link #deselectTrack(DataSourceDesc, int)}.
*
* @param dsd the descriptor of data source of which you want to get selected track
* @param trackType should be one of {@link TrackInfo#MEDIA_TRACK_TYPE_VIDEO},
@@ -2074,9 +2138,9 @@
* @throws IllegalStateException if called after {@link #close()}
* @throws NullPointerException if dsd is null
*
- * @see #getTrackInfo
- * @see #selectTrack
- * @see #deselectTrack
+ * @see #getTrackInfo(DataSourceDesc)
+ * @see #selectTrack(DataSourceDesc, int)
+ * @see #deselectTrack(DataSourceDesc, int)
*/
public int getSelectedTrack(@NonNull DataSourceDesc dsd, int trackType) {
if (dsd == null) {
@@ -2100,6 +2164,23 @@
}
/**
+ * Selects a track of current data source.
+ * Same as {@link #selectTrack(DataSourceDesc, int)} with
+ * {@code dsd = getCurrentDataSource()}.
+ *
+ * @param index the index of the track to be selected. The valid range of the index
+ * is 0..total number of track - 1. The total number of tracks as well as the type of
+ * each individual track can be found by calling {@link #getTrackInfo()} method.
+ * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
+ *
+ * @see MediaPlayer2#getTrackInfo()
+ */
+ // This is an asynchronous call.
+ public Object selectTrack(int index) {
+ return selectTrack(getCurrentDataSource(), index);
+ }
+
+ /**
* Selects a track.
* <p>
* If a MediaPlayer2 is in invalid state, it throws an IllegalStateException exception.
@@ -2123,10 +2204,10 @@
* @param dsd the descriptor of data source of which you want to select track
* @param index the index of the track to be selected. The valid range of the index
* is 0..total number of track - 1. The total number of tracks as well as the type of
- * each individual track can be found by calling {@link #getTrackInfo} method.
+ * each individual track can be found by calling {@link #getTrackInfo(DataSourceDesc)} method.
* @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
*
- * @see MediaPlayer2#getTrackInfo
+ * @see MediaPlayer2#getTrackInfo(DataSourceDesc)
*/
// This is an asynchronous call.
public Object selectTrack(@NonNull DataSourceDesc dsd, int index) {
@@ -2139,6 +2220,23 @@
}
/**
+ * Deselect a track of current data source.
+ * Same as {@link #deselectTrack(DataSourceDesc, int)} with
+ * {@code dsd = getCurrentDataSource()}.
+ *
+ * @param index the index of the track to be deselected. The valid range of the index
+ * is 0..total number of tracks - 1. The total number of tracks as well as the type of
+ * each individual track can be found by calling {@link #getTrackInfo()} method.
+ * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
+ *
+ * @see MediaPlayer2#getTrackInfo()
+ */
+ // This is an asynchronous call.
+ public Object deselectTrack(int index) {
+ return deselectTrack(getCurrentDataSource(), index);
+ }
+
+ /**
* Deselect a track.
* <p>
* Currently, the track must be a timed text track and no audio or video tracks can be
@@ -2151,7 +2249,7 @@
* each individual track can be found by calling {@link #getTrackInfo} method.
* @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
*
- * @see MediaPlayer2#getTrackInfo
+ * @see MediaPlayer2#getTrackInfo(DataSourceDesc)
*/
// This is an asynchronous call.
public Object deselectTrack(@NonNull DataSourceDesc dsd, int index) {
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index b160029..25b1df2 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -456,6 +456,9 @@
/** VP8/VORBIS data in a WEBM container */
public static final int WEBM = 9;
+ /** @hide HEIC data in a HEIF container */
+ public static final int HEIF = 10;
+
/** Opus data in a Ogg container */
public static final int OGG = 11;
};
@@ -699,6 +702,12 @@
* is no guarantee that the recorder will have stopped by the time the
* listener is notified.
*
+ * <p>When using MPEG-4 container ({@link #setOutputFormat(int)} with
+ * {@link OutputFormat#MPEG_4}), it is recommended to set maximum duration that fits the use
+ * case. Setting a larger than required duration may result in a larger than needed output file
+ * because of space reserved for MOOV box expecting large movie data in this recording session.
+ * Unused space of MOOV box is turned into FREE box in the output file.</p>
+ *
* @param max_duration_ms the maximum duration in ms (if zero or negative, disables the duration limit)
*
*/
@@ -714,6 +723,12 @@
* is no guarantee that the recorder will have stopped by the time the
* listener is notified.
*
+ * <p>When using MPEG-4 container ({@link #setOutputFormat(int)} with
+ * {@link OutputFormat#MPEG_4}), it is recommended to set maximum filesize that fits the use
+ * case. Setting a larger than required filesize may result in a larger than needed output file
+ * because of space reserved for MOOV box expecting large movie data in this recording session.
+ * Unused space of MOOV box is turned into FREE box in the output file.</p>
+ *
* @param max_filesize_bytes the maximum filesize in bytes (if zero or negative, disables the limit)
*
*/
diff --git a/media/java/android/media/MediaSession2.java b/media/java/android/media/MediaSession2.java
index 4712cdd..1ee851f 100644
--- a/media/java/android/media/MediaSession2.java
+++ b/media/java/android/media/MediaSession2.java
@@ -464,7 +464,21 @@
if (mId == null) {
mId = "";
}
- return new MediaSession2(mContext, mId, mSessionActivity, mCallbackExecutor, mCallback);
+ MediaSession2 session2 = new MediaSession2(mContext, mId, mSessionActivity,
+ mCallbackExecutor, mCallback);
+
+ // Notify framework about the newly create session after the constructor is finished.
+ // Otherwise, framework may access the session before the initialization is finished.
+ try {
+ MediaSessionManager manager = (MediaSessionManager) mContext.getSystemService(
+ Context.MEDIA_SESSION_SERVICE);
+ manager.notifySession2Created(session2.getSessionToken());
+ } catch (Exception e) {
+ session2.close();
+ throw e;
+ }
+
+ return session2;
}
}
diff --git a/media/java/android/media/Session2Command.java b/media/java/android/media/Session2Command.java
index d2a5aae..8b285f2 100644
--- a/media/java/android/media/Session2Command.java
+++ b/media/java/android/media/Session2Command.java
@@ -36,8 +36,6 @@
* Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
* <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a>
* for consistent behavior across all devices.
- * </p>
- * @hide
*/
public final class Session2Command implements Parcelable {
/**
@@ -151,14 +149,17 @@
}
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ if (dest == null) {
+ throw new IllegalArgumentException("parcel shouldn't be null");
+ }
dest.writeInt(mCommandCode);
dest.writeString(mCustomCommand);
dest.writeBundle(mExtras);
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (!(obj instanceof Session2Command)) {
return false;
}
@@ -185,7 +186,7 @@
* @param resultCode result code
* @param resultData result data
*/
- public Result(int resultCode, Bundle resultData) {
+ public Result(int resultCode, @Nullable Bundle resultData) {
mResultCode = resultCode;
mResultData = resultData;
}
@@ -200,6 +201,7 @@
/**
* Returns the result data.
*/
+ @Nullable
public Bundle getResultData() {
return mResultData;
}
diff --git a/media/java/android/media/Session2CommandGroup.java b/media/java/android/media/Session2CommandGroup.java
index 122dfb1..4668ec4 100644
--- a/media/java/android/media/Session2CommandGroup.java
+++ b/media/java/android/media/Session2CommandGroup.java
@@ -130,7 +130,7 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeParcelableArray((Session2Command[]) mCommands.toArray(), 0);
+ dest.writeParcelableArray(mCommands.toArray(new Session2Command[0]), 0);
}
/**
diff --git a/media/java/android/media/Session2Token.aidl b/media/java/android/media/Session2Token.aidl
new file mode 100644
index 0000000..c5980e9
--- /dev/null
+++ b/media/java/android/media/Session2Token.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.media;
+
+parcelable Session2Token;
diff --git a/media/java/android/media/Session2Token.java b/media/java/android/media/Session2Token.java
index 7642faa..e1fff38 100644
--- a/media/java/android/media/Session2Token.java
+++ b/media/java/android/media/Session2Token.java
@@ -102,7 +102,8 @@
private final ComponentName mComponentName;
/**
- * Constructor for the token.
+ * Constructor for the token with type {@link #TYPE_SESSION_SERVICE} or
+ * {@link #TYPE_LIBRARY_SERVICE}.
*
* @param context The context.
* @param serviceComponent The component name of the service.
@@ -119,7 +120,7 @@
final int uid = getUid(manager, serviceComponent.getPackageName());
// TODO: Uncomment below to stop hardcode type.
- final int type = TYPE_SESSION;
+ final int type = TYPE_SESSION_SERVICE;
// final int type;
// if (isInterfaceDeclared(manager, MediaLibraryService2.SERVICE_INTERFACE,
// serviceComponent)) {
@@ -170,7 +171,7 @@
dest.writeString(mPackageName);
dest.writeString(mServiceName);
// TODO: Uncomment below
- //dest.writeStrongBinder(mSessionLink.asBinder());
+ //dest.writeStrongBinder(mSessionLink.getBinder());
dest.writeString(mComponentName == null ? "" : mComponentName.flattenToString());
}
diff --git a/media/java/android/media/session/ControllerCallbackLink.aidl b/media/java/android/media/session/ControllerCallbackLink.aidl
new file mode 100644
index 0000000..8ee8c7d
--- /dev/null
+++ b/media/java/android/media/session/ControllerCallbackLink.aidl
@@ -0,0 +1,18 @@
+/*
+ * 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 android.media.session;
+
+parcelable ControllerCallbackLink;
diff --git a/media/java/android/media/session/ControllerCallbackLink.java b/media/java/android/media/session/ControllerCallbackLink.java
new file mode 100644
index 0000000..a143c9b
--- /dev/null
+++ b/media/java/android/media/session/ControllerCallbackLink.java
@@ -0,0 +1,272 @@
+/*
+ * 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 android.media.session;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.media.MediaMetadata;
+import android.media.session.MediaController.PlaybackInfo;
+import android.media.session.MediaSession.QueueItem;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+import java.util.List;
+
+/**
+ * Handles incoming commands to {@link MediaController.Callback}.
+ * <p>
+ * This API is not generally intended for third party application developers.
+ */
+public final class ControllerCallbackLink implements Parcelable {
+ final CallbackStub mCallbackStub;
+ final ISessionControllerCallback mIControllerCallback;
+
+ /**
+ * Constructor for stub (Callee)
+ * @hide
+ */
+ @SystemApi
+ public ControllerCallbackLink(@NonNull CallbackStub callbackStub) {
+ mCallbackStub = callbackStub;
+ mIControllerCallback = new CallbackStubProxy();
+ }
+
+ /**
+ * Constructor for interface (Caller)
+ */
+ ControllerCallbackLink(Parcel in) {
+ mCallbackStub = null;
+ mIControllerCallback = ISessionControllerCallback.Stub.asInterface(in.readStrongBinder());
+ }
+
+ /**
+ * Notify controller that the connected session is destroyed.
+ */
+ public void notifySessionDestroyed() {
+ try {
+ mIControllerCallback.notifySessionDestroyed();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify controller that the connected session sends an event.
+ *
+ * @param event the name of the event
+ * @param extras the extras included with the event
+ */
+ public void notifyEvent(@NonNull String event, @Nullable Bundle extras) {
+ try {
+ mIControllerCallback.notifyEvent(event, extras);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify controller that the current playback state is changed.
+ *
+ * @param state the new playback state
+ */
+ public void notifyPlaybackStateChanged(@Nullable PlaybackState state) {
+ try {
+ mIControllerCallback.notifyPlaybackStateChanged(state);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify controller that the current metadata is changed.
+ *
+ * @param metadata the new metadata
+ */
+ public void notifyMetadataChanged(@Nullable MediaMetadata metadata) {
+ try {
+ mIControllerCallback.notifyMetadataChanged(metadata);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify controller that the current queue is changed.
+ *
+ * @param queue the new queue
+ */
+ public void notifyQueueChanged(@Nullable List<QueueItem> queue) {
+ try {
+ mIControllerCallback.notifyQueueChanged(queue);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify controller that the current queue title is changed.
+ *
+ * @param title the new queue title
+ */
+ public void notifyQueueTitleChanged(@Nullable CharSequence title) {
+ try {
+ mIControllerCallback.notifyQueueTitleChanged(title);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify controller that the extras are changed.
+ *
+ * @param extras the new extras
+ */
+ public void notifyExtrasChanged(@Nullable Bundle extras) {
+ try {
+ mIControllerCallback.notifyExtrasChanged(extras);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify controller that the playback info is changed.
+ *
+ * @param info the new playback info
+ */
+ public void notifyVolumeInfoChanged(@NonNull PlaybackInfo info) {
+ try {
+ mIControllerCallback.notifyVolumeInfoChanged(info);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /** Gets the binder */
+ @NonNull
+ public IBinder getBinder() {
+ return mIControllerCallback.asBinder();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStrongBinder(mIControllerCallback.asBinder());
+ }
+
+ public static final Parcelable.Creator<ControllerCallbackLink> CREATOR =
+ new Parcelable.Creator<ControllerCallbackLink>() {
+ @Override
+ public ControllerCallbackLink createFromParcel(Parcel in) {
+ return new ControllerCallbackLink(in);
+ }
+
+ @Override
+ public ControllerCallbackLink[] newArray(int size) {
+ return new ControllerCallbackLink[size];
+ }
+ };
+
+ /**
+ * Class for Stub implementation
+ * @hide
+ */
+ @SystemApi
+ public abstract static class CallbackStub {
+ /** Stub method for ISessionControllerCallback.notifySessionDestroyed */
+ public void onSessionDestroyed() {
+ }
+
+ /** Stub method for ISessionControllerCallback.notifyEvent */
+ public void onEvent(@NonNull String event, @Nullable Bundle extras) {
+ }
+
+ /** Stub method for ISessionControllerCallback.notifyPlaybackStateChanged */
+ public void onPlaybackStateChanged(@Nullable PlaybackState state) {
+ }
+
+ /** Stub method for ISessionControllerCallback.notifyMetadataChanged */
+ public void onMetadataChanged(@Nullable MediaMetadata metadata) {
+ }
+
+ /** Stub method for ISessionControllerCallback.notifyQueueChanged */
+ public void onQueueChanged(@Nullable List<QueueItem> queue) {
+ }
+
+ /** Stub method for ISessionControllerCallback.notifyQueueTitleChanged */
+ public void onQueueTitleChanged(@Nullable CharSequence title) {
+ }
+
+ /** Stub method for ISessionControllerCallback.notifyExtrasChanged */
+ public void onExtrasChanged(@Nullable Bundle extras) {
+ }
+
+ /** Stub method for ISessionControllerCallback.notifyVolumeInfoChanged */
+ public void onVolumeInfoChanged(@NonNull PlaybackInfo info) {
+ }
+ }
+
+ private class CallbackStubProxy extends ISessionControllerCallback.Stub {
+ @Override
+ public void notifyEvent(String event, Bundle extras) {
+ mCallbackStub.onEvent(event, extras);
+ }
+
+ @Override
+ public void notifySessionDestroyed() {
+ mCallbackStub.onSessionDestroyed();
+ }
+
+ @Override
+ public void notifyPlaybackStateChanged(PlaybackState state) {
+ mCallbackStub.onPlaybackStateChanged(state);
+ }
+
+ @Override
+ public void notifyMetadataChanged(MediaMetadata metadata) {
+ mCallbackStub.onMetadataChanged(metadata);
+ }
+
+ @Override
+ public void notifyQueueChanged(List<QueueItem> queue) {
+ mCallbackStub.onQueueChanged(queue);
+ }
+
+ @Override
+ public void notifyQueueTitleChanged(CharSequence title) {
+ mCallbackStub.onQueueTitleChanged(title);
+ }
+
+ @Override
+ public void notifyExtrasChanged(Bundle extras) {
+ mCallbackStub.onExtrasChanged(extras);
+ }
+
+ @Override
+ public void notifyVolumeInfoChanged(PlaybackInfo info) {
+ mCallbackStub.onVolumeInfoChanged(info);
+ }
+ }
+}
diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl
index bfc05fa..1524ad9 100644
--- a/media/java/android/media/session/ISession.aidl
+++ b/media/java/android/media/session/ISession.aidl
@@ -16,7 +16,6 @@
package android.media.session;
import android.app.PendingIntent;
-import android.content.pm.ParceledListSlice;
import android.media.AudioAttributes;
import android.media.MediaMetadata;
import android.media.session.ISessionController;
@@ -41,7 +40,8 @@
// These commands are for the TransportPerformer
void setMetadata(in MediaMetadata metadata, long duration, String metadataDescription);
void setPlaybackState(in PlaybackState state);
- void setQueue(in ParceledListSlice queue);
+ // TODO(b/122432476): Replace List with MediaParceledListSlice
+ void setQueue(in List<MediaSession.QueueItem> queue);
void setQueueTitle(CharSequence title);
void setExtras(in Bundle extras);
void setRatingType(int type);
diff --git a/media/java/android/media/session/ISessionCallback.aidl b/media/java/android/media/session/ISessionCallback.aidl
index 626338d..9b86bfc 100644
--- a/media/java/android/media/session/ISessionCallback.aidl
+++ b/media/java/android/media/session/ISessionCallback.aidl
@@ -17,7 +17,7 @@
import android.content.Intent;
import android.media.Rating;
-import android.media.session.ISessionControllerCallback;
+import android.media.session.ControllerCallbackLink;
import android.net.Uri;
import android.os.Bundle;
import android.os.ResultReceiver;
@@ -26,46 +26,46 @@
* @hide
*/
oneway interface ISessionCallback {
- void onCommand(String packageName, int pid, int uid, ISessionControllerCallback caller,
+ void notifyCommand(String packageName, int pid, int uid, in ControllerCallbackLink caller,
String command, in Bundle args, in ResultReceiver cb);
- void onMediaButton(String packageName, int pid, int uid, in Intent mediaButtonIntent,
+ void notifyMediaButton(String packageName, int pid, int uid, in Intent mediaButtonIntent,
int sequenceNumber, in ResultReceiver cb);
- void onMediaButtonFromController(String packageName, int pid, int uid,
- ISessionControllerCallback caller, in Intent mediaButtonIntent);
+ void notifyMediaButtonFromController(String packageName, int pid, int uid,
+ in ControllerCallbackLink caller, in Intent mediaButtonIntent);
// These callbacks are for the TransportPerformer
- void onPrepare(String packageName, int pid, int uid, ISessionControllerCallback caller);
- void onPrepareFromMediaId(String packageName, int pid, int uid,
- ISessionControllerCallback caller, String mediaId, in Bundle extras);
- void onPrepareFromSearch(String packageName, int pid, int uid,
- ISessionControllerCallback caller, String query, in Bundle extras);
- void onPrepareFromUri(String packageName, int pid, int uid, ISessionControllerCallback caller,
+ void notifyPrepare(String packageName, int pid, int uid, in ControllerCallbackLink caller);
+ void notifyPrepareFromMediaId(String packageName, int pid, int uid,
+ in ControllerCallbackLink caller, String mediaId, in Bundle extras);
+ void notifyPrepareFromSearch(String packageName, int pid, int uid,
+ in ControllerCallbackLink caller, String query, in Bundle extras);
+ void notifyPrepareFromUri(String packageName, int pid, int uid,
+ in ControllerCallbackLink caller, in Uri uri, in Bundle extras);
+ void notifyPlay(String packageName, int pid, int uid, in ControllerCallbackLink caller);
+ void notifyPlayFromMediaId(String packageName, int pid, int uid,
+ in ControllerCallbackLink caller, String mediaId, in Bundle extras);
+ void notifyPlayFromSearch(String packageName, int pid, int uid,
+ in ControllerCallbackLink caller, String query, in Bundle extras);
+ void notifyPlayFromUri(String packageName, int pid, int uid, in ControllerCallbackLink caller,
in Uri uri, in Bundle extras);
- void onPlay(String packageName, int pid, int uid, ISessionControllerCallback caller);
- void onPlayFromMediaId(String packageName, int pid, int uid, ISessionControllerCallback caller,
- String mediaId, in Bundle extras);
- void onPlayFromSearch(String packageName, int pid, int uid, ISessionControllerCallback caller,
- String query, in Bundle extras);
- void onPlayFromUri(String packageName, int pid, int uid, ISessionControllerCallback caller,
- in Uri uri, in Bundle extras);
- void onSkipToTrack(String packageName, int pid, int uid, ISessionControllerCallback caller,
+ void notifySkipToTrack(String packageName, int pid, int uid, in ControllerCallbackLink caller,
long id);
- void onPause(String packageName, int pid, int uid, ISessionControllerCallback caller);
- void onStop(String packageName, int pid, int uid, ISessionControllerCallback caller);
- void onNext(String packageName, int pid, int uid, ISessionControllerCallback caller);
- void onPrevious(String packageName, int pid, int uid, ISessionControllerCallback caller);
- void onFastForward(String packageName, int pid, int uid, ISessionControllerCallback caller);
- void onRewind(String packageName, int pid, int uid, ISessionControllerCallback caller);
- void onSeekTo(String packageName, int pid, int uid, ISessionControllerCallback caller,
+ void notifyPause(String packageName, int pid, int uid, in ControllerCallbackLink caller);
+ void notifyStop(String packageName, int pid, int uid, in ControllerCallbackLink caller);
+ void notifyNext(String packageName, int pid, int uid, in ControllerCallbackLink caller);
+ void notifyPrevious(String packageName, int pid, int uid, in ControllerCallbackLink caller);
+ void notifyFastForward(String packageName, int pid, int uid, in ControllerCallbackLink caller);
+ void notifyRewind(String packageName, int pid, int uid, in ControllerCallbackLink caller);
+ void notifySeekTo(String packageName, int pid, int uid, in ControllerCallbackLink caller,
long pos);
- void onRate(String packageName, int pid, int uid, ISessionControllerCallback caller,
+ void notifyRate(String packageName, int pid, int uid, in ControllerCallbackLink caller,
in Rating rating);
- void onCustomAction(String packageName, int pid, int uid, ISessionControllerCallback caller,
+ void notifyCustomAction(String packageName, int pid, int uid, in ControllerCallbackLink caller,
String action, in Bundle args);
// These callbacks are for volume handling
- void onAdjustVolume(String packageName, int pid, int uid, ISessionControllerCallback caller,
+ void notifyAdjustVolume(String packageName, int pid, int uid, in ControllerCallbackLink caller,
int direction);
- void onSetVolumeTo(String packageName, int pid, int uid,
- ISessionControllerCallback caller, int value);
+ void notifySetVolumeTo(String packageName, int pid, int uid,
+ in ControllerCallbackLink caller, int value);
}
diff --git a/media/java/android/media/session/ISessionController.aidl b/media/java/android/media/session/ISessionController.aidl
index e61bf5b..2ba09fd 100644
--- a/media/java/android/media/session/ISessionController.aidl
+++ b/media/java/android/media/session/ISessionController.aidl
@@ -17,12 +17,11 @@
import android.app.PendingIntent;
import android.content.Intent;
-import android.content.pm.ParceledListSlice;
import android.media.MediaMetadata;
import android.media.Rating;
-import android.media.session.ISessionControllerCallback;
+import android.media.session.ControllerCallbackLink;
+import android.media.session.MediaController;
import android.media.session.MediaSession;
-import android.media.session.ParcelableVolumeInfo;
import android.media.session.PlaybackState;
import android.net.Uri;
import android.os.Bundle;
@@ -36,53 +35,53 @@
* @hide
*/
interface ISessionController {
- void sendCommand(String packageName, ISessionControllerCallback caller,
+ void sendCommand(String packageName, in ControllerCallbackLink caller,
String command, in Bundle args, in ResultReceiver cb);
- boolean sendMediaButton(String packageName, ISessionControllerCallback caller,
+ boolean sendMediaButton(String packageName, in ControllerCallbackLink caller,
boolean asSystemService, in KeyEvent mediaButton);
- void registerCallbackListener(String packageName, ISessionControllerCallback cb);
- void unregisterCallbackListener(ISessionControllerCallback cb);
+ void registerCallbackListener(String packageName, in ControllerCallbackLink cb);
+ void unregisterCallbackListener(in ControllerCallbackLink cb);
boolean isTransportControlEnabled();
String getPackageName();
String getTag();
PendingIntent getLaunchPendingIntent();
long getFlags();
- ParcelableVolumeInfo getVolumeAttributes();
+ MediaController.PlaybackInfo getVolumeAttributes();
void adjustVolume(String packageName, String opPackageName,
- in ISessionControllerCallback caller, boolean asSystemService, int direction,
+ in ControllerCallbackLink caller, boolean asSystemService, int direction,
int flags);
- void setVolumeTo(String packageName, String opPackageName, in ISessionControllerCallback caller,
+ void setVolumeTo(String packageName, String opPackageName, in ControllerCallbackLink caller,
int value, int flags);
// These commands are for the TransportControls
- void prepare(String packageName, ISessionControllerCallback caller);
- void prepareFromMediaId(String packageName, ISessionControllerCallback caller,
+ void prepare(String packageName, in ControllerCallbackLink caller);
+ void prepareFromMediaId(String packageName, in ControllerCallbackLink caller,
String mediaId, in Bundle extras);
- void prepareFromSearch(String packageName, ISessionControllerCallback caller,
+ void prepareFromSearch(String packageName, in ControllerCallbackLink caller,
String string, in Bundle extras);
- void prepareFromUri(String packageName, ISessionControllerCallback caller,
+ void prepareFromUri(String packageName, in ControllerCallbackLink caller,
in Uri uri, in Bundle extras);
- void play(String packageName, ISessionControllerCallback caller);
- void playFromMediaId(String packageName, ISessionControllerCallback caller,
+ void play(String packageName, in ControllerCallbackLink caller);
+ void playFromMediaId(String packageName, in ControllerCallbackLink caller,
String mediaId, in Bundle extras);
- void playFromSearch(String packageName, ISessionControllerCallback caller,
+ void playFromSearch(String packageName, in ControllerCallbackLink caller,
String string, in Bundle extras);
- void playFromUri(String packageName, ISessionControllerCallback caller,
+ void playFromUri(String packageName, in ControllerCallbackLink caller,
in Uri uri, in Bundle extras);
- void skipToQueueItem(String packageName, ISessionControllerCallback caller, long id);
- void pause(String packageName, ISessionControllerCallback caller);
- void stop(String packageName, ISessionControllerCallback caller);
- void next(String packageName, ISessionControllerCallback caller);
- void previous(String packageName, ISessionControllerCallback caller);
- void fastForward(String packageName, ISessionControllerCallback caller);
- void rewind(String packageName, ISessionControllerCallback caller);
- void seekTo(String packageName, ISessionControllerCallback caller, long pos);
- void rate(String packageName, ISessionControllerCallback caller, in Rating rating);
- void sendCustomAction(String packageName, ISessionControllerCallback caller,
+ void skipToQueueItem(String packageName, in ControllerCallbackLink caller, long id);
+ void pause(String packageName, in ControllerCallbackLink caller);
+ void stop(String packageName, in ControllerCallbackLink caller);
+ void next(String packageName, in ControllerCallbackLink caller);
+ void previous(String packageName, in ControllerCallbackLink caller);
+ void fastForward(String packageName, in ControllerCallbackLink caller);
+ void rewind(String packageName, in ControllerCallbackLink caller);
+ void seekTo(String packageName, in ControllerCallbackLink caller, long pos);
+ void rate(String packageName, in ControllerCallbackLink caller, in Rating rating);
+ void sendCustomAction(String packageName, in ControllerCallbackLink caller,
String action, in Bundle args);
MediaMetadata getMetadata();
PlaybackState getPlaybackState();
- ParceledListSlice getQueue();
+ List<MediaSession.QueueItem> getQueue();
CharSequence getQueueTitle();
Bundle getExtras();
int getRatingType();
diff --git a/media/java/android/media/session/ISessionControllerCallback.aidl b/media/java/android/media/session/ISessionControllerCallback.aidl
index cf31767..5c02e7c 100644
--- a/media/java/android/media/session/ISessionControllerCallback.aidl
+++ b/media/java/android/media/session/ISessionControllerCallback.aidl
@@ -15,25 +15,24 @@
package android.media.session;
-import android.content.pm.ParceledListSlice;
import android.media.MediaMetadata;
-import android.media.session.ParcelableVolumeInfo;
-import android.media.session.PlaybackState;
+import android.media.session.MediaController;
import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
import android.os.Bundle;
/**
* @hide
*/
oneway interface ISessionControllerCallback {
- void onEvent(String event, in Bundle extras);
- void onSessionDestroyed();
+ void notifyEvent(String event, in Bundle extras);
+ void notifySessionDestroyed();
// These callbacks are for the TransportController
- void onPlaybackStateChanged(in PlaybackState state);
- void onMetadataChanged(in MediaMetadata metadata);
- void onQueueChanged(in ParceledListSlice queue);
- void onQueueTitleChanged(CharSequence title);
- void onExtrasChanged(in Bundle extras);
- void onVolumeInfoChanged(in ParcelableVolumeInfo info);
+ void notifyPlaybackStateChanged(in PlaybackState state);
+ void notifyMetadataChanged(in MediaMetadata metadata);
+ void notifyQueueChanged(in List<MediaSession.QueueItem> queue);
+ void notifyQueueTitleChanged(CharSequence title);
+ void notifyExtrasChanged(in Bundle extras);
+ void notifyVolumeInfoChanged(in MediaController.PlaybackInfo info);
}
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index d6c226f..7ac3ef2 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -17,12 +17,13 @@
import android.content.ComponentName;
import android.media.IRemoteVolumeController;
+import android.media.Session2Token;
import android.media.session.IActiveSessionsListener;
import android.media.session.ICallback;
import android.media.session.IOnMediaKeyListener;
import android.media.session.IOnVolumeKeyLongPressListener;
import android.media.session.ISession;
-import android.media.session.ISessionCallback;
+import android.media.session.SessionCallbackLink;
import android.os.Bundle;
import android.view.KeyEvent;
@@ -31,8 +32,10 @@
* @hide
*/
interface ISessionManager {
- ISession createSession(String packageName, in ISessionCallback cb, String tag, int userId);
+ ISession createSession(String packageName, in SessionCallbackLink cb, String tag, int userId);
+ void notifySession2Created(in Session2Token sessionToken);
List<IBinder> getSessions(in ComponentName compName, int userId);
+ List<Session2Token> getSession2Tokens(int userId);
void dispatchMediaKeyEvent(String packageName, boolean asSystemService, in KeyEvent keyEvent,
boolean needWakeLock);
void dispatchVolumeKeyEvent(String packageName, String opPackageName, boolean asSystemService,
diff --git a/media/java/android/media/session/ParcelableVolumeInfo.aidl b/media/java/android/media/session/MediaController.aidl
similarity index 93%
rename from media/java/android/media/session/ParcelableVolumeInfo.aidl
rename to media/java/android/media/session/MediaController.aidl
index c4250f0..17167f4 100644
--- a/media/java/android/media/session/ParcelableVolumeInfo.aidl
+++ b/media/java/android/media/session/MediaController.aidl
@@ -15,4 +15,4 @@
package android.media.session;
-parcelable ParcelableVolumeInfo;
+parcelable MediaController.PlaybackInfo;
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index 181ee53..a1b8170c 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -21,17 +21,19 @@
import android.annotation.UnsupportedAppUsage;
import android.app.PendingIntent;
import android.content.Context;
-import android.content.pm.ParceledListSlice;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.MediaMetadata;
import android.media.Rating;
import android.media.VolumeProvider;
+import android.media.session.MediaSession.QueueItem;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.text.TextUtils;
@@ -70,7 +72,8 @@
private final MediaSession.Token mToken;
private final Context mContext;
- private final CallbackStub mCbStub = new CallbackStub(this);
+ private final ControllerCallbackLink mCbStub =
+ new ControllerCallbackLink(new CallbackStub(this));
private final ArrayList<MessageHandler> mCallbacks = new ArrayList<MessageHandler>();
private final Object mLock = new Object();
@@ -249,10 +252,7 @@
*/
public @Nullable List<MediaSession.QueueItem> getQueue() {
try {
- ParceledListSlice queue = mSessionBinder.getQueue();
- if (queue != null) {
- return queue.getList();
- }
+ return mSessionBinder.getQueue();
} catch (RemoteException e) {
Log.wtf(TAG, "Error calling getQueue.", e);
}
@@ -327,10 +327,7 @@
*/
public @Nullable PlaybackInfo getPlaybackInfo() {
try {
- ParcelableVolumeInfo result = mSessionBinder.getVolumeAttributes();
- return new PlaybackInfo(result.volumeType, result.audioAttrs, result.controlType,
- result.maxVolume, result.currentVolume);
-
+ return mSessionBinder.getVolumeAttributes();
} catch (RemoteException e) {
Log.wtf(TAG, "Error calling getAudioInfo.", e);
}
@@ -986,15 +983,15 @@
* Holds information about the current playback and how audio is handled for
* this session.
*/
- public static final class PlaybackInfo {
- /**
- * The session uses remote playback.
- */
- public static final int PLAYBACK_TYPE_REMOTE = 2;
+ public static final class PlaybackInfo implements Parcelable {
/**
* The session uses local playback.
*/
public static final int PLAYBACK_TYPE_LOCAL = 1;
+ /**
+ * The session uses remote playback.
+ */
+ public static final int PLAYBACK_TYPE_REMOTE = 2;
private final int mVolumeType;
private final int mVolumeControl;
@@ -1005,12 +1002,20 @@
/**
* @hide
*/
- public PlaybackInfo(int type, AudioAttributes attrs, int control, int max, int current) {
+ public PlaybackInfo(int type, int control, int max, int current, AudioAttributes attrs) {
mVolumeType = type;
- mAudioAttrs = attrs;
mVolumeControl = control;
mMaxVolume = max;
mCurrentVolume = current;
+ mAudioAttrs = attrs;
+ }
+
+ PlaybackInfo(Parcel in) {
+ mVolumeType = in.readInt();
+ mVolumeControl = in.readInt();
+ mMaxVolume = in.readInt();
+ mCurrentVolume = in.readInt();
+ mAudioAttrs = in.readParcelable(null);
}
/**
@@ -1027,18 +1032,6 @@
}
/**
- * Get the audio attributes for this session. The attributes will affect
- * volume handling for the session. When the volume type is
- * {@link PlaybackInfo#PLAYBACK_TYPE_REMOTE} these may be ignored by the
- * remote volume handler.
- *
- * @return The attributes for this session.
- */
- public AudioAttributes getAudioAttributes() {
- return mAudioAttrs;
- }
-
- /**
* Get the type of volume control that can be used. One of:
* <ul>
* <li>{@link VolumeProvider#VOLUME_CONTROL_ABSOLUTE}</li>
@@ -1070,12 +1063,58 @@
public int getCurrentVolume() {
return mCurrentVolume;
}
+
+ /**
+ * Get the audio attributes for this session. The attributes will affect
+ * volume handling for the session. When the volume type is
+ * {@link PlaybackInfo#PLAYBACK_TYPE_REMOTE} these may be ignored by the
+ * remote volume handler.
+ *
+ * @return The attributes for this session.
+ */
+ public AudioAttributes getAudioAttributes() {
+ return mAudioAttrs;
+ }
+
+ @Override
+ public String toString() {
+ return "volumeType=" + mVolumeType + ", volumeControl=" + mVolumeControl
+ + ", maxVolume=" + mMaxVolume + ", currentVolume=" + mCurrentVolume
+ + ", audioAttrs=" + mAudioAttrs;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mVolumeType);
+ dest.writeInt(mVolumeControl);
+ dest.writeInt(mMaxVolume);
+ dest.writeInt(mCurrentVolume);
+ dest.writeParcelable(mAudioAttrs, flags);
+ }
+
+ public static final Parcelable.Creator<PlaybackInfo> CREATOR =
+ new Parcelable.Creator<PlaybackInfo>() {
+ @Override
+ public PlaybackInfo createFromParcel(Parcel in) {
+ return new PlaybackInfo(in);
+ }
+
+ @Override
+ public PlaybackInfo[] newArray(int size) {
+ return new PlaybackInfo[size];
+ }
+ };
}
- private final static class CallbackStub extends ISessionControllerCallback.Stub {
+ private static final class CallbackStub extends ControllerCallbackLink.CallbackStub {
private final WeakReference<MediaController> mController;
- public CallbackStub(MediaController controller) {
+ CallbackStub(MediaController controller) {
mController = new WeakReference<MediaController>(controller);
}
@@ -1112,9 +1151,7 @@
}
@Override
- public void onQueueChanged(ParceledListSlice parceledQueue) {
- List<MediaSession.QueueItem> queue = parceledQueue == null ? null : parceledQueue
- .getList();
+ public void onQueueChanged(List<QueueItem> queue) {
MediaController controller = mController.get();
if (controller != null) {
controller.postMessage(MSG_UPDATE_QUEUE, queue, null);
@@ -1138,15 +1175,12 @@
}
@Override
- public void onVolumeInfoChanged(ParcelableVolumeInfo pvi) {
+ public void onVolumeInfoChanged(PlaybackInfo info) {
MediaController controller = mController.get();
if (controller != null) {
- PlaybackInfo info = new PlaybackInfo(pvi.volumeType, pvi.audioAttrs,
- pvi.controlType, pvi.maxVolume, pvi.currentVolume);
controller.postMessage(MSG_UPDATE_VOLUME, info, null);
}
}
-
}
private final static class MessageHandler extends Handler {
@@ -1154,7 +1188,7 @@
private boolean mRegistered = false;
public MessageHandler(Looper looper, MediaController.Callback cb) {
- super(looper, null, true);
+ super(looper);
mCallback = cb;
}
@@ -1193,6 +1227,7 @@
public void post(int what, Object obj, Bundle data) {
Message msg = obtainMessage(what, obj);
+ msg.setAsynchronous(true);
msg.setData(data);
msg.sendToTarget();
}
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 8962bb7..e07cf15 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -24,7 +24,6 @@
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ParceledListSlice;
import android.media.AudioAttributes;
import android.media.MediaDescription;
import android.media.MediaMetadata;
@@ -130,7 +129,7 @@
private final MediaSession.Token mSessionToken;
private final MediaController mController;
private final ISession mBinder;
- private final CallbackStub mCbStub;
+ private final SessionCallbackLink mCbStub;
// Do not change the name of mCallback. Support lib accesses this by using reflection.
@UnsupportedAppUsage
@@ -173,7 +172,7 @@
}
mMaxBitmapSize = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.config_mediaMetadataBitmapMaxSize);
- mCbStub = new CallbackStub(this);
+ mCbStub = new SessionCallbackLink(new CallbackStub(this));
MediaSessionManager manager = (MediaSessionManager) context
.getSystemService(Context.MEDIA_SESSION_SERVICE);
try {
@@ -467,7 +466,7 @@
*/
public void setQueue(@Nullable List<QueueItem> queue) {
try {
- mBinder.setQueue(queue == null ? null : new ParceledListSlice<QueueItem>(queue));
+ mBinder.setQueue(queue);
} catch (RemoteException e) {
Log.wtf("Dead object in setQueue.", e);
}
@@ -1063,7 +1062,7 @@
/**
* @hide
*/
- public static class CallbackStub extends ISessionCallback.Stub {
+ public static final class CallbackStub extends SessionCallbackLink.CallbackStub {
private WeakReference<MediaSession> mMediaSession;
public CallbackStub(MediaSession session) {
@@ -1071,14 +1070,14 @@
}
private static RemoteUserInfo createRemoteUserInfo(String packageName, int pid, int uid,
- ISessionControllerCallback caller) {
+ ControllerCallbackLink caller) {
return new RemoteUserInfo(packageName, pid, uid,
- caller != null ? caller.asBinder() : null);
+ caller != null ? caller.getBinder() : null);
}
@Override
public void onCommand(String packageName, int pid, int uid,
- ISessionControllerCallback caller, String command, Bundle args, ResultReceiver cb) {
+ ControllerCallbackLink caller, String command, Bundle args, ResultReceiver cb) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchCommand(createRemoteUserInfo(packageName, pid, uid, caller),
@@ -1104,7 +1103,7 @@
@Override
public void onMediaButtonFromController(String packageName, int pid, int uid,
- ISessionControllerCallback caller, Intent mediaButtonIntent) {
+ ControllerCallbackLink caller, Intent mediaButtonIntent) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchMediaButton(createRemoteUserInfo(packageName, pid, uid, caller),
@@ -1114,7 +1113,7 @@
@Override
public void onPrepare(String packageName, int pid, int uid,
- ISessionControllerCallback caller) {
+ ControllerCallbackLink caller) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchPrepare(createRemoteUserInfo(packageName, pid, uid, caller));
@@ -1123,7 +1122,7 @@
@Override
public void onPrepareFromMediaId(String packageName, int pid, int uid,
- ISessionControllerCallback caller, String mediaId,
+ ControllerCallbackLink caller, String mediaId,
Bundle extras) {
MediaSession session = mMediaSession.get();
if (session != null) {
@@ -1134,7 +1133,7 @@
@Override
public void onPrepareFromSearch(String packageName, int pid, int uid,
- ISessionControllerCallback caller, String query,
+ ControllerCallbackLink caller, String query,
Bundle extras) {
MediaSession session = mMediaSession.get();
if (session != null) {
@@ -1145,7 +1144,7 @@
@Override
public void onPrepareFromUri(String packageName, int pid, int uid,
- ISessionControllerCallback caller, Uri uri, Bundle extras) {
+ ControllerCallbackLink caller, Uri uri, Bundle extras) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchPrepareFromUri(createRemoteUserInfo(packageName, pid, uid, caller),
@@ -1155,7 +1154,7 @@
@Override
public void onPlay(String packageName, int pid, int uid,
- ISessionControllerCallback caller) {
+ ControllerCallbackLink caller) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchPlay(createRemoteUserInfo(packageName, pid, uid, caller));
@@ -1164,7 +1163,7 @@
@Override
public void onPlayFromMediaId(String packageName, int pid, int uid,
- ISessionControllerCallback caller, String mediaId,
+ ControllerCallbackLink caller, String mediaId,
Bundle extras) {
MediaSession session = mMediaSession.get();
if (session != null) {
@@ -1175,7 +1174,7 @@
@Override
public void onPlayFromSearch(String packageName, int pid, int uid,
- ISessionControllerCallback caller, String query,
+ ControllerCallbackLink caller, String query,
Bundle extras) {
MediaSession session = mMediaSession.get();
if (session != null) {
@@ -1186,7 +1185,7 @@
@Override
public void onPlayFromUri(String packageName, int pid, int uid,
- ISessionControllerCallback caller, Uri uri, Bundle extras) {
+ ControllerCallbackLink caller, Uri uri, Bundle extras) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchPlayFromUri(createRemoteUserInfo(packageName, pid, uid, caller),
@@ -1196,7 +1195,7 @@
@Override
public void onSkipToTrack(String packageName, int pid, int uid,
- ISessionControllerCallback caller, long id) {
+ ControllerCallbackLink caller, long id) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchSkipToItem(createRemoteUserInfo(packageName, pid, uid, caller), id);
@@ -1205,7 +1204,7 @@
@Override
public void onPause(String packageName, int pid, int uid,
- ISessionControllerCallback caller) {
+ ControllerCallbackLink caller) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchPause(createRemoteUserInfo(packageName, pid, uid, caller));
@@ -1214,7 +1213,7 @@
@Override
public void onStop(String packageName, int pid, int uid,
- ISessionControllerCallback caller) {
+ ControllerCallbackLink caller) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchStop(createRemoteUserInfo(packageName, pid, uid, caller));
@@ -1223,7 +1222,7 @@
@Override
public void onNext(String packageName, int pid, int uid,
- ISessionControllerCallback caller) {
+ ControllerCallbackLink caller) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchNext(createRemoteUserInfo(packageName, pid, uid, caller));
@@ -1232,7 +1231,7 @@
@Override
public void onPrevious(String packageName, int pid, int uid,
- ISessionControllerCallback caller) {
+ ControllerCallbackLink caller) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchPrevious(createRemoteUserInfo(packageName, pid, uid, caller));
@@ -1241,7 +1240,7 @@
@Override
public void onFastForward(String packageName, int pid, int uid,
- ISessionControllerCallback caller) {
+ ControllerCallbackLink caller) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchFastForward(createRemoteUserInfo(packageName, pid, uid, caller));
@@ -1250,7 +1249,7 @@
@Override
public void onRewind(String packageName, int pid, int uid,
- ISessionControllerCallback caller) {
+ ControllerCallbackLink caller) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchRewind(createRemoteUserInfo(packageName, pid, uid, caller));
@@ -1259,7 +1258,7 @@
@Override
public void onSeekTo(String packageName, int pid, int uid,
- ISessionControllerCallback caller, long pos) {
+ ControllerCallbackLink caller, long pos) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchSeekTo(createRemoteUserInfo(packageName, pid, uid, caller), pos);
@@ -1267,7 +1266,7 @@
}
@Override
- public void onRate(String packageName, int pid, int uid, ISessionControllerCallback caller,
+ public void onRate(String packageName, int pid, int uid, ControllerCallbackLink caller,
Rating rating) {
MediaSession session = mMediaSession.get();
if (session != null) {
@@ -1277,7 +1276,7 @@
@Override
public void onCustomAction(String packageName, int pid, int uid,
- ISessionControllerCallback caller, String action, Bundle args) {
+ ControllerCallbackLink caller, String action, Bundle args) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchCustomAction(createRemoteUserInfo(packageName, pid, uid, caller),
@@ -1287,7 +1286,7 @@
@Override
public void onAdjustVolume(String packageName, int pid, int uid,
- ISessionControllerCallback caller, int direction) {
+ ControllerCallbackLink caller, int direction) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchAdjustVolume(createRemoteUserInfo(packageName, pid, uid, caller),
@@ -1297,7 +1296,7 @@
@Override
public void onSetVolumeTo(String packageName, int pid, int uid,
- ISessionControllerCallback caller, int value) {
+ ControllerCallbackLink caller, int value) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchSetVolumeTo(createRemoteUserInfo(packageName, pid, uid, caller),
@@ -1453,7 +1452,7 @@
private RemoteUserInfo mCurrentControllerInfo;
public CallbackMessageHandler(Looper looper, MediaSession.Callback callback) {
- super(looper, null, true);
+ super(looper);
mCallback = callback;
mCallback.mHandler = this;
}
@@ -1461,6 +1460,7 @@
public void post(RemoteUserInfo caller, int what, Object obj, Bundle data, long delayMs) {
Pair<RemoteUserInfo, Object> objWithCaller = Pair.create(caller, obj);
Message msg = obtainMessage(what, objWithCaller);
+ msg.setAsynchronous(true);
msg.setData(data);
if (delayMs > 0) {
sendMessageDelayed(msg, delayMs);
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index 4221d66..56ea484 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -26,6 +26,8 @@
import android.content.Context;
import android.media.AudioManager;
import android.media.IRemoteVolumeController;
+import android.media.MediaSession2;
+import android.media.Session2Token;
import android.media.browse.MediaBrowser;
import android.os.Handler;
import android.os.IBinder;
@@ -96,12 +98,36 @@
* @return The binder object from the system
* @hide
*/
- public @NonNull ISession createSession(@NonNull MediaSession.CallbackStub cbStub,
+ public @NonNull ISession createSession(@NonNull SessionCallbackLink cbStub,
@NonNull String tag, int userId) throws RemoteException {
return mService.createSession(mContext.getPackageName(), cbStub, tag, userId);
}
/**
+ * Notifies that a new {@link MediaSession2} with type {@link Session2Token#TYPE_SESSION} is
+ * created.
+ * <p>
+ * Do not use this API directly, but create a new instance through the
+ * {@link MediaSession2.Builder} instead.
+ *
+ * @param token newly created session2 token
+ * @hide
+ */
+ public void notifySession2Created(@NonNull Session2Token token) {
+ if (token == null) {
+ throw new IllegalArgumentException("token shouldn't be null");
+ }
+ if (token.getType() != Session2Token.TYPE_SESSION) {
+ throw new IllegalArgumentException("token's type should be TYPE_SESSION");
+ }
+ try {
+ mService.notifySession2Created(token);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Get a list of controllers for all ongoing sessions. The controllers will
* be provided in priority order with the most important controller at index
* 0.
@@ -153,6 +179,44 @@
}
/**
+ * Gets a list of {@link Session2Token} with type {@link Session2Token#TYPE_SESSION} for the
+ * current user.
+ * <p>
+ * Although this API can be used without any restriction, each session owners can accept or
+ * reject your uses of {@link MediaSession2}.
+ *
+ * @return A list of {@link Session2Token}.
+ * @hide
+ */
+ // TODO: unhide
+ @NonNull
+ public List<Session2Token> getSession2Tokens() {
+ return getSession2Tokens(UserHandle.myUserId());
+ }
+
+ /**
+ * Gets a list of {@link Session2Token} with type {@link Session2Token#TYPE_SESSION} for the
+ * given user.
+ * <p>
+ * If you want to get tokens for another user, you must hold the
+ * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission.
+ *
+ * @param userId The user id to fetch sessions for.
+ * @return A list of {@link Session2Token}
+ * @hide
+ */
+ // TODO: unhide
+ @NonNull
+ public List<Session2Token> getSession2Tokens(int userId) {
+ try {
+ return mService.getSession2Tokens(userId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to get session tokens", e);
+ }
+ return new ArrayList<>();
+ }
+
+ /**
* Add a listener to be notified when the list of active sessions
* changes.This requires the
* android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by
diff --git a/media/java/android/media/session/ParcelableVolumeInfo.java b/media/java/android/media/session/ParcelableVolumeInfo.java
deleted file mode 100644
index f59c975..0000000
--- a/media/java/android/media/session/ParcelableVolumeInfo.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/* Copyright 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 android.media.session;
-
-import android.media.AudioAttributes;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * Convenience class for passing information about the audio configuration of a
- * session. The public implementation is {@link MediaController.PlaybackInfo}.
- *
- * @hide
- */
-public class ParcelableVolumeInfo implements Parcelable {
- public int volumeType;
- public AudioAttributes audioAttrs;
- public int controlType;
- public int maxVolume;
- public int currentVolume;
-
- public ParcelableVolumeInfo(int volumeType, AudioAttributes audioAttrs, int controlType,
- int maxVolume,
- int currentVolume) {
- this.volumeType = volumeType;
- this.audioAttrs = audioAttrs;
- this.controlType = controlType;
- this.maxVolume = maxVolume;
- this.currentVolume = currentVolume;
- }
-
- public ParcelableVolumeInfo(Parcel from) {
- volumeType = from.readInt();
- controlType = from.readInt();
- maxVolume = from.readInt();
- currentVolume = from.readInt();
- audioAttrs = AudioAttributes.CREATOR.createFromParcel(from);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(volumeType);
- dest.writeInt(controlType);
- dest.writeInt(maxVolume);
- dest.writeInt(currentVolume);
- audioAttrs.writeToParcel(dest, flags);
- }
-
-
- public static final Parcelable.Creator<ParcelableVolumeInfo> CREATOR
- = new Parcelable.Creator<ParcelableVolumeInfo>() {
- @Override
- public ParcelableVolumeInfo createFromParcel(Parcel in) {
- return new ParcelableVolumeInfo(in);
- }
-
- @Override
- public ParcelableVolumeInfo[] newArray(int size) {
- return new ParcelableVolumeInfo[size];
- }
- };
-}
diff --git a/media/java/android/media/session/SessionCallbackLink.aidl b/media/java/android/media/session/SessionCallbackLink.aidl
new file mode 100644
index 0000000..c489e5b
--- /dev/null
+++ b/media/java/android/media/session/SessionCallbackLink.aidl
@@ -0,0 +1,18 @@
+/*
+ * 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 android.media.session;
+
+parcelable SessionCallbackLink;
diff --git a/media/java/android/media/session/SessionCallbackLink.java b/media/java/android/media/session/SessionCallbackLink.java
new file mode 100644
index 0000000..7547bff
--- /dev/null
+++ b/media/java/android/media/session/SessionCallbackLink.java
@@ -0,0 +1,776 @@
+/*
+ * 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 android.media.session;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Intent;
+import android.media.Rating;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+
+/**
+ * Handles incoming commands to {@link MediaSession.Callback}.
+ * <p>
+ * This API is not generally intended for third party application developers.
+ */
+public final class SessionCallbackLink implements Parcelable {
+ final CallbackStub mCallbackStub;
+ final ISessionCallback mISessionCallback;
+
+ /**
+ * Constructor for stub (Callee)
+ */
+ SessionCallbackLink(@NonNull CallbackStub callbackStub) {
+ mCallbackStub = callbackStub;
+ mISessionCallback = new CallbackStubProxy();
+ }
+
+ /**
+ * Constructor for interface (Caller)
+ */
+ SessionCallbackLink(Parcel in) {
+ mCallbackStub = null;
+ mISessionCallback = ISessionCallback.Stub.asInterface(in.readStrongBinder());
+ }
+
+ /**
+ * Notify session that a controller sends a command.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param command the name of the command
+ * @param args the arguments included with the command
+ * @param cb the result receiver for getting the result of the command
+ */
+ public void notifyCommand(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, @NonNull String command,
+ @Nullable Bundle args, @Nullable ResultReceiver cb) {
+ try {
+ mISessionCallback.notifyCommand(packageName, pid, uid, caller, command, args, cb);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that the android system sends a media button event.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param mediaButtonIntent the media button intent
+ * @param sequenceNumber the sequence number of this call
+ * @param cb the result receiver for getting the result of the command
+ */
+ public void notifyMediaButton(@NonNull String packageName, int pid, int uid,
+ @NonNull Intent mediaButtonIntent, int sequenceNumber,
+ @Nullable ResultReceiver cb) {
+ try {
+ mISessionCallback.notifyMediaButton(packageName, pid, uid, mediaButtonIntent,
+ sequenceNumber, cb);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller sends a media button event.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param mediaButtonIntent the media button intent
+ */
+ public void notifyMediaButtonFromController(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, @NonNull Intent mediaButtonIntent) {
+ try {
+ mISessionCallback.notifyMediaButtonFromController(packageName, pid, uid, caller,
+ mediaButtonIntent);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller requests preparing media.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ */
+ public void notifyPrepare(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller) {
+ try {
+ mISessionCallback.notifyPrepare(packageName, pid, uid, caller);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller requests preparing media from given media ID.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param mediaId the ID of the media
+ * @param extras the extras included with this request.
+ */
+ public void notifyPrepareFromMediaId(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, @NonNull String mediaId,
+ @Nullable Bundle extras) {
+ try {
+ mISessionCallback.notifyPrepareFromMediaId(packageName, pid, uid, caller, mediaId,
+ extras);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller requests preparing media from given search query.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param query the search query
+ * @param extras the extras included with this request.
+ */
+ public void notifyPrepareFromSearch(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, @NonNull String query,
+ @Nullable Bundle extras) {
+ try {
+ mISessionCallback.notifyPrepareFromSearch(packageName, pid, uid, caller, query, extras);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller requests preparing media from given uri.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param uri the uri of the media
+ * @param extras the extras included with this request.
+ */
+ public void notifyPrepareFromUri(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, @NonNull Uri uri, @Nullable Bundle extras) {
+ try {
+ mISessionCallback.notifyPrepareFromUri(packageName, pid, uid, caller, uri, extras);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller requests playing media.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ */
+ public void notifyPlay(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller) {
+ try {
+ mISessionCallback.notifyPlay(packageName, pid, uid, caller);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller requests playing media from given media ID.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param mediaId the ID of the media
+ * @param extras the extras included with this request.
+ */
+ public void notifyPlayFromMediaId(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, @NonNull String mediaId,
+ @Nullable Bundle extras) {
+ try {
+ mISessionCallback.notifyPlayFromMediaId(packageName, pid, uid, caller, mediaId, extras);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller requests playing media from given search query.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param query the search query
+ * @param extras the extras included with this request.
+ */
+ public void notifyPlayFromSearch(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, @NonNull String query,
+ @Nullable Bundle extras) {
+ try {
+ mISessionCallback.notifyPlayFromSearch(packageName, pid, uid, caller, query, extras);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller requests playing media from given uri.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param uri the uri of the media
+ * @param extras the extras included with this request.
+ */
+ public void notifyPlayFromUri(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, @NonNull Uri uri, @Nullable Bundle extras) {
+ try {
+ mISessionCallback.notifyPlayFromUri(packageName, pid, uid, caller, uri, extras);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller requests skipping to the queue item with given ID.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param id the queue id of the item
+ */
+ public void notifySkipToTrack(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, long id) {
+ try {
+ mISessionCallback.notifySkipToTrack(packageName, pid, uid, caller, id);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller requests pausing media.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ */
+ public void notifyPause(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller) {
+ try {
+ mISessionCallback.notifyPause(packageName, pid, uid, caller);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller requests stopping media.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ */
+ public void notifyStop(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller) {
+ try {
+ mISessionCallback.notifyStop(packageName, pid, uid, caller);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller requests skipping to the next queue item.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ */
+ public void notifyNext(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller) {
+ try {
+ mISessionCallback.notifyNext(packageName, pid, uid, caller);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller requests skipping to the previous queue item.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ */
+ public void notifyPrevious(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller) {
+ try {
+ mISessionCallback.notifyPrevious(packageName, pid, uid, caller);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller requests fast-forwarding.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ */
+ public void notifyFastForward(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller) {
+ try {
+ mISessionCallback.notifyFastForward(packageName, pid, uid, caller);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller requests rewinding.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ */
+ public void notifyRewind(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller) {
+ try {
+ mISessionCallback.notifyRewind(packageName, pid, uid, caller);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller requests seeking to the specific position.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param pos the position to move to, in milliseconds
+ */
+ public void notifySeekTo(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, long pos) {
+ try {
+ mISessionCallback.notifySeekTo(packageName, pid, uid, caller, pos);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller requests rating of the current media.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param rating the rating of the current media
+ */
+ public void notifyRate(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, @NonNull Rating rating) {
+ try {
+ mISessionCallback.notifyRate(packageName, pid, uid, caller, rating);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller sends a custom action.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param action the name of the action
+ * @param args the arguments included with this action
+ */
+ public void notifyCustomAction(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, @NonNull String action, @Nullable Bundle args) {
+ try {
+ mISessionCallback.notifyCustomAction(packageName, pid, uid, caller, action, args);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller requests adjusting volume.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param direction the direction of the volume change.
+ */
+ public void notifyAdjustVolume(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, int direction) {
+ try {
+ mISessionCallback.notifyAdjustVolume(packageName, pid, uid, caller, direction);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Notify session that a controller requests setting volume.
+ *
+ * @param packageName the package name of the controller
+ * @param pid the pid of the controller
+ * @param uid the uid of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param value the volume value to set
+ */
+ public void notifySetVolumeTo(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, int value) {
+ try {
+ mISessionCallback.notifySetVolumeTo(packageName, pid, uid, caller, value);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /** Gets the binder */
+ @NonNull
+ public IBinder getBinder() {
+ return mISessionCallback.asBinder();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStrongBinder(mISessionCallback.asBinder());
+ }
+
+ public static final Parcelable.Creator<SessionCallbackLink> CREATOR =
+ new Parcelable.Creator<SessionCallbackLink>() {
+ @Override
+ public SessionCallbackLink createFromParcel(Parcel in) {
+ return new SessionCallbackLink(in);
+ }
+
+ @Override
+ public SessionCallbackLink[] newArray(int size) {
+ return new SessionCallbackLink[size];
+ }
+ };
+
+ /**
+ * Class for Stub implementation
+ */
+ abstract static class CallbackStub {
+ /** Stub method for ISessionCallback.notifyCommand */
+ public void onCommand(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, @NonNull String command,
+ @Nullable Bundle args, @Nullable ResultReceiver cb) {
+ }
+
+ /** Stub method for ISessionCallback.notifyMediaButton */
+ public void onMediaButton(@NonNull String packageName, int pid, int uid,
+ @NonNull Intent mediaButtonIntent, int sequenceNumber,
+ @Nullable ResultReceiver cb) {
+ }
+
+ /** Stub method for ISessionCallback.notifyMediaButtonFromController */
+ public void onMediaButtonFromController(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, @NonNull Intent mediaButtonIntent) {
+ }
+
+ /** Stub method for ISessionCallback.notifyPrepare */
+ public void onPrepare(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller) {
+ }
+
+ /** Stub method for ISessionCallback.notifyPrepareFromMediaId */
+ public void onPrepareFromMediaId(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, @NonNull String mediaId,
+ @Nullable Bundle extras) {
+ }
+
+ /** Stub method for ISessionCallback.notifyPrepareFromSearch */
+ public void onPrepareFromSearch(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, String query, @Nullable Bundle extras) {
+ }
+
+ /** Stub method for ISessionCallback.notifyPrepareFromUri */
+ public void onPrepareFromUri(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, @NonNull Uri uri, @Nullable Bundle extras) {
+ }
+
+ /** Stub method for ISessionCallback.notifyPlay */
+ public void onPlay(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller) {
+ }
+
+ /** Stub method for ISessionCallback.notifyPlayFromMediaId */
+ public void onPlayFromMediaId(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, @NonNull String mediaId,
+ @Nullable Bundle extras) {
+ }
+
+ /** Stub method for ISessionCallback.notifyPlayFromSearch */
+ public void onPlayFromSearch(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, String query, @Nullable Bundle extras) {
+ }
+
+ /** Stub method for ISessionCallback.notifyPlayFromUri */
+ public void onPlayFromUri(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, @NonNull Uri uri, @Nullable Bundle extras) {
+ }
+
+ /** Stub method for ISessionCallback.notifySkipToTrack */
+ public void onSkipToTrack(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, long id) {
+ }
+
+ /** Stub method for ISessionCallback.notifyPause */
+ public void onPause(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller) {
+ }
+
+ /** Stub method for ISessionCallback.notifyStop */
+ public void onStop(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller) {
+ }
+
+ /** Stub method for ISessionCallback.notifyNext */
+ public void onNext(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller) {
+ }
+
+ /** Stub method for ISessionCallback.notifyPrevious */
+ public void onPrevious(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller) {
+ }
+
+ /** Stub method for ISessionCallback.notifyFastForward */
+ public void onFastForward(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller) {
+ }
+
+ /** Stub method for ISessionCallback.notifyRewind */
+ public void onRewind(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller) {
+ }
+
+ /** Stub method for ISessionCallback.notifySeekTo */
+ public void onSeekTo(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, long pos) {
+ }
+
+ /** Stub method for ISessionCallback.notifyRate */
+ public void onRate(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, @NonNull Rating rating) {
+ }
+
+ /** Stub method for ISessionCallback.notifyCustomAction */
+ public void onCustomAction(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, @NonNull String action,
+ @Nullable Bundle args) {
+ }
+
+ /** Stub method for ISessionCallback.notifyAdjustVolume */
+ public void onAdjustVolume(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, int direction) {
+ }
+
+ /** Stub method for ISessionCallback.notifySetVolumeTo */
+ public void onSetVolumeTo(@NonNull String packageName, int pid, int uid,
+ @NonNull ControllerCallbackLink caller, int value) {
+ }
+ }
+
+ private class CallbackStubProxy extends ISessionCallback.Stub {
+ @Override
+ public void notifyCommand(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, String command, Bundle args, ResultReceiver cb) {
+ mCallbackStub.onCommand(packageName, pid, uid, caller, command, args, cb);
+ }
+
+ @Override
+ public void notifyMediaButton(String packageName, int pid, int uid,
+ Intent mediaButtonIntent, int sequenceNumber, ResultReceiver cb) {
+ mCallbackStub.onMediaButton(packageName, pid, uid, mediaButtonIntent, sequenceNumber,
+ cb);
+ }
+
+ @Override
+ public void notifyMediaButtonFromController(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, Intent mediaButtonIntent) {
+ mCallbackStub.onMediaButtonFromController(packageName, pid, uid, caller,
+ mediaButtonIntent);
+ }
+
+ @Override
+ public void notifyPrepare(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ mCallbackStub.onPrepare(packageName, pid, uid, caller);
+ }
+
+ @Override
+ public void notifyPrepareFromMediaId(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, String mediaId, Bundle extras) {
+ mCallbackStub.onPrepareFromMediaId(packageName, pid, uid, caller, mediaId, extras);
+ }
+
+ @Override
+ public void notifyPrepareFromSearch(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, String query, Bundle extras) {
+ mCallbackStub.onPrepareFromSearch(packageName, pid, uid, caller, query, extras);
+ }
+
+ @Override
+ public void notifyPrepareFromUri(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, Uri uri, Bundle extras) {
+ mCallbackStub.onPrepareFromUri(packageName, pid, uid, caller, uri, extras);
+ }
+
+ @Override
+ public void notifyPlay(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ mCallbackStub.onPlay(packageName, pid, uid, caller);
+ }
+
+ @Override
+ public void notifyPlayFromMediaId(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, String mediaId, Bundle extras) {
+ mCallbackStub.onPlayFromMediaId(packageName, pid, uid, caller, mediaId, extras);
+ }
+
+ @Override
+ public void notifyPlayFromSearch(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, String query, Bundle extras) {
+ mCallbackStub.onPlayFromSearch(packageName, pid, uid, caller, query, extras);
+ }
+
+ @Override
+ public void notifyPlayFromUri(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, Uri uri, Bundle extras) {
+ mCallbackStub.onPlayFromUri(packageName, pid, uid, caller, uri, extras);
+ }
+
+ @Override
+ public void notifySkipToTrack(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, long id) {
+ mCallbackStub.onSkipToTrack(packageName, pid, uid, caller, id);
+ }
+
+ @Override
+ public void notifyPause(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ mCallbackStub.onPause(packageName, pid, uid, caller);
+ }
+
+ @Override
+ public void notifyStop(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ mCallbackStub.onStop(packageName, pid, uid, caller);
+ }
+
+ @Override
+ public void notifyNext(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ mCallbackStub.onNext(packageName, pid, uid, caller);
+ }
+
+ @Override
+ public void notifyPrevious(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ mCallbackStub.onPrevious(packageName, pid, uid, caller);
+ }
+
+ @Override
+ public void notifyFastForward(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ mCallbackStub.onFastForward(packageName, pid, uid, caller);
+ }
+
+ @Override
+ public void notifyRewind(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ mCallbackStub.onRewind(packageName, pid, uid, caller);
+ }
+
+ @Override
+ public void notifySeekTo(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, long pos) {
+ mCallbackStub.onSeekTo(packageName, pid, uid, caller, pos);
+ }
+
+ @Override
+ public void notifyRate(String packageName, int pid, int uid, ControllerCallbackLink caller,
+ Rating rating) {
+ mCallbackStub.onRate(packageName, pid, uid, caller, rating);
+ }
+
+ @Override
+ public void notifyCustomAction(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, String action, Bundle args) {
+ mCallbackStub.onCustomAction(packageName, pid, uid, caller, action, args);
+ }
+
+ @Override
+ public void notifyAdjustVolume(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, int direction) {
+ mCallbackStub.onAdjustVolume(packageName, pid, uid, caller, direction);
+ }
+
+ @Override
+ public void notifySetVolumeTo(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, int value) {
+ mCallbackStub.onSetVolumeTo(packageName, pid, uid, caller, value);
+ }
+ }
+}
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index f75f69b..d6b6339 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -102,7 +102,6 @@
"libhidlbase",
"libhidlmemory",
- "libpowermanager", // Used by JWakeLock. Will be replace with public SDJ API.
"libmediametrics", // Used by MediaMetrics. Will be replaced with stable C API.
"libbinder", // Used by JWakeLock and MediaMetrics.
@@ -111,6 +110,7 @@
// NDK or NDK-compliant
"libandroid",
+ "libbinder_ndk",
"libmediandk",
"libnativehelper_compat_libc++",
"liblog",
diff --git a/media/jni/android_media_MediaPlayer2.cpp b/media/jni/android_media_MediaPlayer2.cpp
index 7e6a8ab..9b4e730 100644
--- a/media/jni/android_media_MediaPlayer2.cpp
+++ b/media/jni/android_media_MediaPlayer2.cpp
@@ -86,7 +86,8 @@
// ----------------------------------------------------------------------------
struct fields_t {
- jfieldID context;
+ jfieldID context; // passed from Java to native, used for creating JWakeLock
+ jfieldID nativeContext; // mNativeContext in MediaPlayer2.java
jfieldID surface_texture;
jmethodID post_event;
@@ -225,21 +226,21 @@
static sp<MediaPlayer2> getMediaPlayer(JNIEnv* env, jobject thiz)
{
Mutex::Autolock l(sLock);
- MediaPlayer2* const p = (MediaPlayer2*)env->GetLongField(thiz, fields.context);
+ MediaPlayer2* const p = (MediaPlayer2*)env->GetLongField(thiz, fields.nativeContext);
return sp<MediaPlayer2>(p);
}
static sp<MediaPlayer2> setMediaPlayer(JNIEnv* env, jobject thiz, const sp<MediaPlayer2>& player)
{
Mutex::Autolock l(sLock);
- sp<MediaPlayer2> old = (MediaPlayer2*)env->GetLongField(thiz, fields.context);
+ sp<MediaPlayer2> old = (MediaPlayer2*)env->GetLongField(thiz, fields.nativeContext);
if (player.get()) {
player->incStrong((void*)setMediaPlayer);
}
if (old != 0) {
old->decStrong((void*)setMediaPlayer);
}
- env->SetLongField(thiz, fields.context, (jlong)player.get());
+ env->SetLongField(thiz, fields.nativeContext, (jlong)player.get());
return old;
}
@@ -955,11 +956,16 @@
return;
}
- fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
+ fields.context = env->GetFieldID(clazz, "mContext", "Landroid/content/Context;");
if (fields.context == NULL) {
return;
}
+ fields.nativeContext = env->GetFieldID(clazz, "mNativeContext", "J");
+ if (fields.nativeContext == NULL) {
+ return;
+ }
+
fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative",
"(Ljava/lang/Object;JIII[B)V");
if (fields.post_event == NULL) {
@@ -1013,7 +1019,8 @@
jint sessionId, jobject weak_this)
{
ALOGV("native_setup");
- sp<MediaPlayer2> mp = MediaPlayer2::Create(sessionId);
+ jobject context = env->GetObjectField(thiz, fields.context);
+ sp<MediaPlayer2> mp = MediaPlayer2::Create(sessionId, context);
if (mp == NULL) {
jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
return;
diff --git a/media/lib/signer/Android.bp b/media/lib/signer/Android.bp
index 8c43683..44f8725 100644
--- a/media/lib/signer/Android.bp
+++ b/media/lib/signer/Android.bp
@@ -18,5 +18,7 @@
name: "com.android.mediadrm.signer",
srcs: ["java/**/*.java"],
api_packages: ["com.android.mediadrm.signer"],
- metalava_enabled: false,
+ srcs_lib: "framework",
+ srcs_lib_whitelist_dirs: ["media/java"],
+ srcs_lib_whitelist_pkgs: ["android.media"],
}
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 5cfb09b..fdcfc44 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -83,6 +83,10 @@
include_dirs: ["bionic/libc/dns/include"],
version_script: "libandroid.map.txt",
+ stubs: {
+ symbol_file: "libandroid.map.txt",
+ versions: ["29"],
+ },
}
// Network library.
diff --git a/native/android/net.c b/native/android/net.c
index 4cac371..a8104fc 100644
--- a/native/android/net.c
+++ b/native/android/net.c
@@ -84,26 +84,28 @@
return android_getaddrinfofornet(node, service, hints, netid, 0, res);
}
-int android_res_nquery(net_handle_t network, const char *dname, int ns_class, int ns_type) {
+int android_res_nquery(net_handle_t network, const char *dname,
+ int ns_class, int ns_type, enum ResNsendFlags flags) {
unsigned netid;
if (!getnetidfromhandle(network, &netid)) {
return -ENONET;
}
- return resNetworkQuery(netid, dname, ns_class, ns_type);
+ return resNetworkQuery(netid, dname, ns_class, ns_type, flags);
}
int android_res_nresult(int fd, int *rcode, uint8_t *answer, size_t anslen) {
return resNetworkResult(fd, rcode, answer, anslen);
}
-int android_res_nsend(net_handle_t network, const uint8_t *msg, size_t msglen) {
+int android_res_nsend(net_handle_t network, const uint8_t *msg, size_t msglen,
+ enum ResNsendFlags flags) {
unsigned netid;
if (!getnetidfromhandle(network, &netid)) {
return -ENONET;
}
- return resNetworkSend(netid, msg, msglen);
+ return resNetworkSend(netid, msg, msglen, flags);
}
void android_res_cancel(int nsend_fd) {
diff --git a/native/webview/plat_support/Android.bp b/native/webview/plat_support/Android.bp
index 0936256..88decc8 100644
--- a/native/webview/plat_support/Android.bp
+++ b/native/webview/plat_support/Android.bp
@@ -24,7 +24,6 @@
srcs: [
"draw_functor.cpp",
"draw_gl_functor.cpp",
- "draw_vk_functor.cpp",
"functor_utils.cpp",
"jni_entry_point.cpp",
"graphics_utils.cpp",
diff --git a/native/webview/plat_support/draw_functor.cpp b/native/webview/plat_support/draw_functor.cpp
index 820bac5..6c1ceab 100644
--- a/native/webview/plat_support/draw_functor.cpp
+++ b/native/webview/plat_support/draw_functor.cpp
@@ -82,13 +82,9 @@
.onDestroyed = &onDestroyed,
};
if (!callbacks_initialized) {
- switch (uirenderer::WebViewFunctor_queryPlatformRenderMode()) {
- case uirenderer::RenderMode::OpenGL_ES:
- webview_functor_callbacks.gles.draw = &draw_gl;
- break;
- case uirenderer::RenderMode::Vulkan:
- break;
- }
+ // Under uirenderer::RenderMode::Vulkan, whether gles or vk union should
+ // be populated should match whether the vk-gl interop is used.
+ webview_functor_callbacks.gles.draw = &draw_gl;
callbacks_initialized = true;
}
SupportData* support = new SupportData{
diff --git a/native/webview/plat_support/draw_vk.h b/native/webview/plat_support/draw_vk.h
deleted file mode 100644
index 6b7d8d0..0000000
--- a/native/webview/plat_support/draw_vk.h
+++ /dev/null
@@ -1,125 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-//
-//******************************************************************************
-// This is a copy of the coresponding android_webview/public/browser header.
-// Any changes to the interface should be made there.
-//
-// The purpose of having the copy is twofold:
-// - it removes the need to have Chromium sources present in the tree in order
-// to build the plat_support library,
-// - it captures API that the corresponding Android release supports.
-//******************************************************************************
-
-#ifndef ANDROID_WEBVIEW_PUBLIC_BROWSER_DRAW_VK_H_
-#define ANDROID_WEBVIEW_PUBLIC_BROWSER_DRAW_VK_H_
-
-#include <vulkan/vulkan.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-static const int kAwDrawVKInfoVersion = 1;
-
-// Holds the information required to trigger initialization of the Vulkan
-// functor.
-struct InitParams {
- // All params are input
- VkInstance instance;
- VkPhysicalDevice physical_device;
- VkDevice device;
- VkQueue queue;
- uint32_t graphics_queue_index;
- uint32_t instance_version;
- const char* const* enabled_extension_names;
- // Only one of device_features and device_features_2 should be non-null.
- // If both are null then no features are enabled.
- VkPhysicalDeviceFeatures* device_features;
- VkPhysicalDeviceFeatures2* device_features_2;
-};
-
-// Holds the information required to trigger an Vulkan composite operation.
-struct CompositeParams {
- // Input: current width/height of destination surface.
- int width;
- int height;
-
- // Input: is the render target a FBO
- bool is_layer;
-
- // Input: current transform matrix
- float transform[16];
-
- // Input WebView should do its main compositing draws into this. It cannot do
- // anything that would require stopping the render pass.
- VkCommandBuffer secondary_command_buffer;
-
- // Input: The main color attachment index where secondary_command_buffer will
- // eventually be submitted.
- uint32_t color_attachment_index;
-
- // Input: A render pass which will be compatible to the one which the
- // secondary_command_buffer will be submitted into.
- VkRenderPass compatible_render_pass;
-
- // Input: Format of the destination surface.
- VkFormat format;
-
- // Input: Color space transfer params
- float G;
- float A;
- float B;
- float C;
- float D;
- float E;
- float F;
-
- // Input: Color space transformation from linear RGB to D50-adapted XYZ
- float matrix[9];
-
- // Input: current clip rect
- int clip_left;
- int clip_top;
- int clip_right;
- int clip_bottom;
-};
-
-// Holds the information for the post-submission callback of main composite
-// draw.
-struct PostCompositeParams {
- // Input: Fence for the composite command buffer to signal it has finished its
- // work on the GPU.
- int fd;
-};
-
-// Holds the information required to trigger an Vulkan operation.
-struct AwDrawVKInfo {
- int version; // The AwDrawVKInfo this struct was built with.
-
- // Input: tells the draw function what action to perform.
- enum Mode {
- kModeInit = 0,
- kModeReInit = 1,
- kModePreComposite = 2,
- kModeComposite = 3,
- kModePostComposite = 4,
- kModeSync = 5,
- } mode;
-
- // Input: The parameters for the functor being called
- union ParamUnion {
- struct InitParams init_params;
- struct CompositeParams composite_params;
- struct PostCompositeParams post_composite_params;
- } info;
-};
-
-typedef void(AwDrawVKFunction)(long view_context, AwDrawVKInfo* draw_info);
-
-#ifdef __cplusplus
-} // extern "C"
-#endif
-
-#endif // ANDROID_WEBVIEW_PUBLIC_BROWSER_DRAW_VK_H_
diff --git a/native/webview/plat_support/draw_vk_functor.cpp b/native/webview/plat_support/draw_vk_functor.cpp
deleted file mode 100644
index eab1340..0000000
--- a/native/webview/plat_support/draw_vk_functor.cpp
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * 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.
- */
-
-// Provides a webviewchromium glue layer adapter from the internal Android
-// Vulkan Functor data types into the types the chromium stack expects, and
-// back.
-
-#define LOG_TAG "webviewchromium_plat_support"
-
-#include "draw_fn.h"
-#include "draw_vk.h"
-
-#include <jni.h>
-#include <private/hwui/DrawVkInfo.h>
-#include <utils/Functor.h>
-#include <utils/Log.h>
-
-#include "functor_utils.h"
-
-#define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
-
-namespace android {
-namespace {
-
-AwDrawVKFunction* g_aw_drawvk_function = NULL;
-
-class DrawVKFunctor : public Functor {
- public:
- explicit DrawVKFunctor(jlong view_context) : view_context_(view_context) {}
- ~DrawVKFunctor() override {}
-
- // Functor
- status_t operator ()(int what, void* data) override {
- using uirenderer::DrawVkInfo;
- if (!g_aw_drawvk_function) {
- ALOGE("Cannot draw: no DrawVK Function installed");
- return DrawVkInfo::kStatusDone;
- }
-
- AwDrawVKInfo aw_info;
- aw_info.version = kAwDrawVKInfoVersion;
- switch (what) {
- case DrawVkInfo::kModeComposite: {
- aw_info.mode = AwDrawVKInfo::kModeComposite;
- DrawVkInfo* vk_info = reinterpret_cast<DrawVkInfo*>(data);
-
- // Map across the input values.
- CompositeParams& params = aw_info.info.composite_params;
- params.width = vk_info->width;
- params.height = vk_info->height;
- params.is_layer = vk_info->isLayer;
- for (size_t i = 0; i < 16; i++) {
- params.transform[i] = vk_info->transform[i];
- }
- params.secondary_command_buffer = vk_info->secondaryCommandBuffer;
- params.color_attachment_index = vk_info->colorAttachmentIndex;
- params.compatible_render_pass = vk_info->compatibleRenderPass;
- params.format = vk_info->format;
- params.G = vk_info->G;
- params.A = vk_info->A;
- params.B = vk_info->B;
- params.C = vk_info->C;
- params.D = vk_info->D;
- params.E = vk_info->E;
- params.F = vk_info->F;
- for (size_t i = 0; i < 9; i++) {
- params.matrix[i] = vk_info->matrix[i];
- }
- params.clip_left = vk_info->clipLeft;
- params.clip_top = vk_info->clipTop;
- params.clip_right = vk_info->clipRight;
- params.clip_bottom = vk_info->clipBottom;
-
- break;
- }
- case DrawVkInfo::kModePostComposite:
- break;
- case DrawVkInfo::kModeSync:
- aw_info.mode = AwDrawVKInfo::kModeSync;
- break;
- default:
- ALOGE("Unexpected DrawVKInfo type %d", what);
- return DrawVkInfo::kStatusDone;
- }
-
- // Invoke the DrawVK method.
- g_aw_drawvk_function(view_context_, &aw_info);
-
- return DrawVkInfo::kStatusDone;
- }
-
- private:
- intptr_t view_context_;
-};
-
-jlong CreateVKFunctor(JNIEnv*, jclass, jlong view_context) {
- RaiseFileNumberLimit();
- return reinterpret_cast<jlong>(new DrawVKFunctor(view_context));
-}
-
-void DestroyVKFunctor(JNIEnv*, jclass, jlong functor) {
- delete reinterpret_cast<DrawVKFunctor*>(functor);
-}
-
-void SetChromiumAwDrawVKFunction(JNIEnv*, jclass, jlong draw_function) {
- g_aw_drawvk_function = reinterpret_cast<AwDrawVKFunction*>(draw_function);
-}
-
-const char kClassName[] = "com/android/webview/chromium/DrawVKFunctor";
-const JNINativeMethod kJniMethods[] = {
- { "nativeCreateVKFunctor", "(J)J",
- reinterpret_cast<void*>(CreateVKFunctor) },
- { "nativeDestroyVKFunctor", "(J)V",
- reinterpret_cast<void*>(DestroyVKFunctor) },
- { "nativeSetChromiumAwDrawVKFunction", "(J)V",
- reinterpret_cast<void*>(SetChromiumAwDrawVKFunction) },
-};
-
-} // namespace
-
-void RegisterDrawVKFunctor(JNIEnv* env) {
- jclass clazz = env->FindClass(kClassName);
- LOG_ALWAYS_FATAL_IF(!clazz, "Unable to find class '%s'", kClassName);
-
- int res = env->RegisterNatives(clazz, kJniMethods, NELEM(kJniMethods));
- LOG_ALWAYS_FATAL_IF(res < 0, "register native methods failed: res=%d", res);
-}
-
-} // namespace android
diff --git a/packages/AppPredictionLib/Android.bp b/packages/AppPredictionLib/Android.bp
new file mode 100644
index 0000000..e0f4ded
--- /dev/null
+++ b/packages/AppPredictionLib/Android.bp
@@ -0,0 +1,24 @@
+// 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_library {
+ name: "app_prediction",
+
+ sdk_version: "system_current",
+ min_sdk_version: "system_current",
+
+ srcs: [
+ "src/**/*.java",
+ ],
+}
diff --git a/packages/AppPredictionLib/AndroidManifest.xml b/packages/AppPredictionLib/AndroidManifest.xml
new file mode 100644
index 0000000..b992788
--- /dev/null
+++ b/packages/AppPredictionLib/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.app.prediction">
+</manifest>
diff --git a/packages/AppPredictionLib/src/com/android/app/prediction/Constants.java b/packages/AppPredictionLib/src/com/android/app/prediction/Constants.java
new file mode 100644
index 0000000..0993c9a
--- /dev/null
+++ b/packages/AppPredictionLib/src/com/android/app/prediction/Constants.java
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+
+package com.android.app.prediction;
+
+/**
+ * Constants to be used with {@link android.app.prediction.AppPredictor}.
+ */
+public class Constants {
+
+ /**
+ * UI surface for predictions displayed on the user's home screen
+ */
+ public static final String UI_SURFACE_HOME = "home";
+
+ /**
+ * UI surface for predictions displayed on the recents/task switcher view
+ */
+ public static final String UI_SURFACE_RECENTS = "recents";
+
+ /**
+ * UI surface for predictions displayed on the share sheet.
+ */
+ public static final String UI_SURFACE_SHARE = "share";
+
+ /**
+ * Location constant when an app target or shortcut is started from the apps list
+ */
+ public static final String LAUNCH_LOCATION_APPS_LIST = "apps_list";
+
+ /**
+ * Location constant when an app target or shortcut is started from the user's home screen
+ */
+ public static final String LAUNCH_LOCATION_APPS_HOME = "home";
+
+ /**
+ * Location constant when an app target or shortcut is started from task switcher
+ */
+ public static final String LAUNCH_LOCATION_APPS_RECENTS = "recents";
+
+ /**
+ * Location constant when an app target or shortcut is started in the share sheet while it is
+ * in collapsed state (showing a limited set of result).
+ */
+ public static final String LAUNCH_LOCATION_APPS_SHARE_COLLAPSED = "share_collapsed";
+
+ /**
+ * Location constant when an app target or shortcut is started in the share sheet while it is
+ * in expended state and showing all the results.
+ */
+ public static final String LAUNCH_LOCATION_APPS_SHARE_EXPANDED = "shared_expanded";
+
+ /**
+ * Location constant when an app target or shortcut is started in the share sheet when the
+ * target is displayed as a placeholder for an deprecated object.
+ */
+ public static final String LAUNCH_LOCATION_APPS_SHARE_LEGACY = "share_legacy";
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java
index 62502ef..6d960d7 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java
@@ -18,8 +18,8 @@
import android.content.Context;
-import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
/** Auto-specific implementation of {@link NotificationInterruptionStateProvider}. */
public class CarNotificationInterruptionStateProvider extends
@@ -29,7 +29,7 @@
}
@Override
- public boolean shouldHeadsUp(NotificationData.Entry entry) {
+ public boolean shouldHeadsUp(NotificationEntry entry) {
// Because space is usually constrained in the auto use-case, there should not be a
// pinned notification when the shade has been expanded. Ensure this by not pinning any
// notification if the shade is already opened.
diff --git a/packages/CarrierDefaultApp/tests/unit/Android.mk b/packages/CarrierDefaultApp/tests/unit/Android.mk
index 8e3785e..4c638811 100644
--- a/packages/CarrierDefaultApp/tests/unit/Android.mk
+++ b/packages/CarrierDefaultApp/tests/unit/Android.mk
@@ -21,7 +21,7 @@
LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common android.test.base
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test mockito-target-minus-junit4
+LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules mockito-target-minus-junit4
# Include all test java files.
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/packages/CarrierDefaultApp/tests/unit/AndroidManifest.xml b/packages/CarrierDefaultApp/tests/unit/AndroidManifest.xml
index 3a06a09..7a26d95 100644
--- a/packages/CarrierDefaultApp/tests/unit/AndroidManifest.xml
+++ b/packages/CarrierDefaultApp/tests/unit/AndroidManifest.xml
@@ -21,7 +21,7 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.carrierdefaultapp"
android:label="CarrierDefaultApp Unit Test Cases">
</instrumentation>
diff --git a/packages/ExtServices/src/android/ext/services/notification/Assistant.java b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
index af52c00..fab1bcc 100644
--- a/packages/ExtServices/src/android/ext/services/notification/Assistant.java
+++ b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
@@ -366,9 +366,9 @@
}
@Override
- public void onNotificationDirectReply(@NonNull String key) {
- if (DEBUG) Log.i(TAG, "onNotificationDirectReply " + key);
- mSmartActionsHelper.onNotificationDirectReply(key);
+ public void onNotificationDirectReplied(@NonNull String key) {
+ if (DEBUG) Log.i(TAG, "onNotificationDirectReplied " + key);
+ mSmartActionsHelper.onNotificationDirectReplied(key);
}
@Override
diff --git a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
index 56c4158..a9d8f62 100644
--- a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
+++ b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
@@ -165,7 +165,7 @@
}
}
- void onNotificationDirectReply(@NonNull String key) {
+ void onNotificationDirectReplied(@NonNull String key) {
if (mTextClassifier == null) {
return;
}
@@ -383,8 +383,7 @@
remoteAction.getIcon(),
remoteAction.getTitle(),
remoteAction.getActionIntent())
- .setSemanticAction(
- Notification.Action.SEMANTIC_ACTION_CONTEXTUAL_SUGGESTION)
+ .setContextual(true)
.addExtras(Bundle.forPair(KEY_ACTION_TYPE, classification.getEntity(0)))
.build();
actions.add(action);
diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java
index da382a0..7d74788 100644
--- a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java
+++ b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java
@@ -241,7 +241,7 @@
when(mNotificationEntry.getNotification()).thenReturn(notification);
mSmartActionsHelper.suggestReplies(mNotificationEntry);
- mSmartActionsHelper.onNotificationDirectReply(NOTIFICATION_KEY);
+ mSmartActionsHelper.onNotificationDirectReplied(NOTIFICATION_KEY);
ArgumentCaptor<TextClassifierEvent> argumentCaptor =
ArgumentCaptor.forClass(TextClassifierEvent.class);
diff --git a/packages/NetworkStack/Android.bp b/packages/NetworkStack/Android.bp
index 55bb517..2f7d599 100644
--- a/packages/NetworkStack/Android.bp
+++ b/packages/NetworkStack/Android.bp
@@ -21,7 +21,11 @@
installable: true,
srcs: [
"src/**/*.java",
+ ":services-networkstack-shared-srcs",
],
+ static_libs: [
+ "dhcp-packet-lib",
+ ]
}
// Updatable network stack packaged as an application
diff --git a/packages/NetworkStack/AndroidManifest.xml b/packages/NetworkStack/AndroidManifest.xml
index d1c5cb6..0b0f1ec 100644
--- a/packages/NetworkStack/AndroidManifest.xml
+++ b/packages/NetworkStack/AndroidManifest.xml
@@ -17,13 +17,16 @@
*/
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.google.android.networkstack"
+ package="com.android.mainline.networkstack"
android:sharedUserId="android.uid.networkstack">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+ <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
+ <uses-permission android:name="android.permission.NETWORK_SETTINGS" />
<!-- Launch captive portal app as specific user -->
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+ <uses-permission android:name="android.permission.NETWORK_STACK" />
<application
android:label="NetworkStack"
android:defaultToDeviceProtectedStorage="true"
diff --git a/services/net/java/android/net/dhcp/DhcpLease.java b/packages/NetworkStack/src/android/net/dhcp/DhcpLease.java
similarity index 90%
rename from services/net/java/android/net/dhcp/DhcpLease.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpLease.java
index 6cdd2aa..6849cfa 100644
--- a/services/net/java/android/net/dhcp/DhcpLease.java
+++ b/packages/NetworkStack/src/android/net/dhcp/DhcpLease.java
@@ -58,6 +58,11 @@
mHostname = hostname;
}
+ /**
+ * Get the clientId associated with this lease, if any.
+ *
+ * <p>If the lease is not associated to a clientId, this returns null.
+ */
@Nullable
public byte[] getClientId() {
if (mClientId == null) {
@@ -97,6 +102,11 @@
(hostname == null ? mHostname : hostname));
}
+ /**
+ * Determine whether this lease matches a client with the specified parameters.
+ * @param clientId clientId of the client if any, or null otherwise.
+ * @param hwAddr Hardware address of the client.
+ */
public boolean matchesClient(@Nullable byte[] clientId, @NonNull MacAddress hwAddr) {
if (mClientId != null) {
return Arrays.equals(mClientId, clientId);
@@ -110,7 +120,7 @@
if (!(obj instanceof DhcpLease)) {
return false;
}
- final DhcpLease other = (DhcpLease)obj;
+ final DhcpLease other = (DhcpLease) obj;
return Arrays.equals(mClientId, other.mClientId)
&& mHwAddr.equals(other.mHwAddr)
&& mNetAddr.equals(other.mNetAddr)
diff --git a/services/net/java/android/net/dhcp/DhcpLeaseRepository.java b/packages/NetworkStack/src/android/net/dhcp/DhcpLeaseRepository.java
similarity index 98%
rename from services/net/java/android/net/dhcp/DhcpLeaseRepository.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpLeaseRepository.java
index 2dda421..0d298de 100644
--- a/services/net/java/android/net/dhcp/DhcpLeaseRepository.java
+++ b/packages/NetworkStack/src/android/net/dhcp/DhcpLeaseRepository.java
@@ -21,7 +21,8 @@
import static android.net.NetworkUtils.prefixLengthToV4NetmaskIntHTH;
import static android.net.dhcp.DhcpLease.EXPIRATION_NEVER;
import static android.net.dhcp.DhcpLease.inet4AddrToString;
-import static android.net.util.NetworkConstants.IPV4_ADDR_BITS;
+
+import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_BITS;
import static java.lang.Math.min;
@@ -29,8 +30,8 @@
import android.annotation.Nullable;
import android.net.IpPrefix;
import android.net.MacAddress;
-import android.net.util.SharedLog;
import android.net.dhcp.DhcpServer.Clock;
+import android.net.util.SharedLog;
import android.util.ArrayMap;
import java.net.Inet4Address;
@@ -117,7 +118,7 @@
*/
private final LinkedHashMap<Inet4Address, Long> mDeclinedAddrs = new LinkedHashMap<>();
- public DhcpLeaseRepository(@NonNull IpPrefix prefix, @NonNull Set<Inet4Address> reservedAddrs,
+ DhcpLeaseRepository(@NonNull IpPrefix prefix, @NonNull Set<Inet4Address> reservedAddrs,
long leaseTimeMs, @NonNull SharedLog log, @NonNull Clock clock) {
updateParams(prefix, reservedAddrs, leaseTimeMs);
mLog = log;
@@ -250,8 +251,8 @@
// reqAddr null (RENEWING/REBINDING): client renewing its own lease for clientAddr.
// reqAddr set with sid not set (INIT-REBOOT): client verifying configuration.
// In both cases, throw if clientAddr or reqAddr does not match the known lease.
- throw new InvalidAddressException("Incorrect address for client in " +
- (reqAddr != null ? "INIT-REBOOT" : "RENEWING/REBINDING"));
+ throw new InvalidAddressException("Incorrect address for client in "
+ + (reqAddr != null ? "INIT-REBOOT" : "RENEWING/REBINDING"));
}
}
diff --git a/services/net/java/android/net/dhcp/DhcpPacketListener.java b/packages/NetworkStack/src/android/net/dhcp/DhcpPacketListener.java
similarity index 79%
rename from services/net/java/android/net/dhcp/DhcpPacketListener.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpPacketListener.java
index 6f620c5..dce8b61 100644
--- a/services/net/java/android/net/dhcp/DhcpPacketListener.java
+++ b/packages/NetworkStack/src/android/net/dhcp/DhcpPacketListener.java
@@ -32,32 +32,32 @@
*/
abstract class DhcpPacketListener extends FdEventsReader<DhcpPacketListener.Payload> {
static final class Payload {
- final byte[] bytes = new byte[DhcpPacket.MAX_LENGTH];
- Inet4Address srcAddr;
- int srcPort;
+ protected final byte[] mBytes = new byte[DhcpPacket.MAX_LENGTH];
+ protected Inet4Address mSrcAddr;
+ protected int mSrcPort;
}
- public DhcpPacketListener(@NonNull Handler handler) {
+ DhcpPacketListener(@NonNull Handler handler) {
super(handler, new Payload());
}
@Override
protected int recvBufSize(@NonNull Payload buffer) {
- return buffer.bytes.length;
+ return buffer.mBytes.length;
}
@Override
protected final void handlePacket(@NonNull Payload recvbuf, int length) {
- if (recvbuf.srcAddr == null) {
+ if (recvbuf.mSrcAddr == null) {
return;
}
try {
- final DhcpPacket packet = DhcpPacket.decodeFullPacket(recvbuf.bytes, length,
+ final DhcpPacket packet = DhcpPacket.decodeFullPacket(recvbuf.mBytes, length,
DhcpPacket.ENCAP_BOOTP);
- onReceive(packet, recvbuf.srcAddr, recvbuf.srcPort);
+ onReceive(packet, recvbuf.mSrcAddr, recvbuf.mSrcPort);
} catch (DhcpPacket.ParseException e) {
- logParseError(recvbuf.bytes, length, e);
+ logParseError(recvbuf.mBytes, length, e);
}
}
@@ -66,11 +66,11 @@
throws Exception {
final InetSocketAddress addr = new InetSocketAddress();
final int read = Os.recvfrom(
- fd, packetBuffer.bytes, 0, packetBuffer.bytes.length, 0 /* flags */, addr);
+ fd, packetBuffer.mBytes, 0, packetBuffer.mBytes.length, 0 /* flags */, addr);
// Buffers with null srcAddr will be dropped in handlePacket()
- packetBuffer.srcAddr = inet4AddrOrNull(addr);
- packetBuffer.srcPort = addr.getPort();
+ packetBuffer.mSrcAddr = inet4AddrOrNull(addr);
+ packetBuffer.mSrcPort = addr.getPort();
return read;
}
diff --git a/services/net/java/android/net/dhcp/DhcpServer.java b/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java
similarity index 78%
rename from services/net/java/android/net/dhcp/DhcpServer.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpServer.java
index 35d29e7..14e2936 100644
--- a/services/net/java/android/net/dhcp/DhcpServer.java
+++ b/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java
@@ -23,7 +23,8 @@
import static android.net.dhcp.DhcpPacket.DHCP_HOST_NAME;
import static android.net.dhcp.DhcpPacket.DHCP_SERVER;
import static android.net.dhcp.DhcpPacket.ENCAP_BOOTP;
-import static android.net.dhcp.DhcpPacket.INFINITE_LEASE;
+import static android.net.dhcp.IDhcpServer.STATUS_INVALID_ARGUMENT;
+import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
import static android.system.OsConstants.AF_INET;
import static android.system.OsConstants.IPPROTO_UDP;
import static android.system.OsConstants.SOCK_DGRAM;
@@ -32,21 +33,28 @@
import static android.system.OsConstants.SO_BROADCAST;
import static android.system.OsConstants.SO_REUSEADDR;
+import static com.android.server.util.NetworkStackConstants.INFINITE_LEASE;
+import static com.android.server.util.PermissionUtil.checkNetworkStackCallingPermission;
+
import static java.lang.Integer.toUnsignedLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.net.INetworkStackStatusCallback;
import android.net.MacAddress;
import android.net.NetworkUtils;
import android.net.TrafficStats;
import android.net.util.SharedLog;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
+import android.os.RemoteException;
import android.os.SystemClock;
import android.system.ErrnoException;
import android.system.Os;
import android.text.TextUtils;
+import android.util.Pair;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.HexDump;
@@ -70,7 +78,7 @@
* on the looper asynchronously.
* @hide
*/
-public class DhcpServer {
+public class DhcpServer extends IDhcpServer.Stub {
private static final String REPO_TAG = "Repository";
// Lease time to transmit to client instead of a negative time in case a lease expired before
@@ -82,7 +90,7 @@
private static final int CMD_UPDATE_PARAMS = 3;
@NonNull
- private final ServerHandler mHandler;
+ private final HandlerThread mHandlerThread;
@NonNull
private final String mIfName;
@NonNull
@@ -93,14 +101,25 @@
private final Dependencies mDeps;
@NonNull
private final Clock mClock;
- @NonNull
- private final DhcpPacketListener mPacketListener;
@Nullable
+ private volatile ServerHandler mHandler;
+
+ // Accessed only on the handler thread
+ @Nullable
+ private DhcpPacketListener mPacketListener;
+ @Nullable
private FileDescriptor mSocket;
@NonNull
private DhcpServingParams mServingParams;
+ /**
+ * Clock to be used by DhcpServer to track time for lease expiration.
+ *
+ * <p>The clock should track time as may be measured by clients obtaining a lease. It does not
+ * need to be monotonous across restarts of the server as long as leases are cleared when the
+ * server is stopped.
+ */
public static class Clock {
/**
* @see SystemClock#elapsedRealtime()
@@ -110,15 +129,51 @@
}
}
+ /**
+ * Dependencies for the DhcpServer. Useful to be mocked in tests.
+ */
public interface Dependencies {
+ /**
+ * Send a packet to the specified datagram socket.
+ *
+ * @param fd File descriptor of the socket.
+ * @param buffer Data to be sent.
+ * @param dst Destination address of the packet.
+ */
void sendPacket(@NonNull FileDescriptor fd, @NonNull ByteBuffer buffer,
@NonNull InetAddress dst) throws ErrnoException, IOException;
+
+ /**
+ * Create a DhcpLeaseRepository for the server.
+ * @param servingParams Parameters used to serve DHCP requests.
+ * @param log Log to be used by the repository.
+ * @param clock Clock that the repository must use to track time.
+ */
DhcpLeaseRepository makeLeaseRepository(@NonNull DhcpServingParams servingParams,
@NonNull SharedLog log, @NonNull Clock clock);
+
+ /**
+ * Create a packet listener that will send packets to be processed.
+ */
DhcpPacketListener makePacketListener();
+
+ /**
+ * Create a clock that the server will use to track time.
+ */
Clock makeClock();
+
+ /**
+ * Add an entry to the ARP cache table.
+ * @param fd Datagram socket file descriptor that must use the new entry.
+ */
void addArpEntry(@NonNull Inet4Address ipv4Addr, @NonNull MacAddress ethAddr,
@NonNull String ifname, @NonNull FileDescriptor fd) throws IOException;
+
+ /**
+ * Verify that the caller is allowed to call public methods on DhcpServer.
+ * @throws SecurityException The caller is not allowed to call public methods on DhcpServer.
+ */
+ void checkCaller() throws SecurityException;
}
private class DependenciesImpl implements Dependencies {
@@ -134,7 +189,7 @@
return new DhcpLeaseRepository(
DhcpServingParams.makeIpPrefix(servingParams.serverAddr),
servingParams.excludedAddrs,
- servingParams.dhcpLeaseTimeSecs*1000, log.forSubComponent(REPO_TAG), clock);
+ servingParams.dhcpLeaseTimeSecs * 1000, log.forSubComponent(REPO_TAG), clock);
}
@Override
@@ -152,6 +207,11 @@
@NonNull String ifname, @NonNull FileDescriptor fd) throws IOException {
NetworkUtils.addArpEntry(ipv4Addr, ethAddr, ifname, fd);
}
+
+ @Override
+ public void checkCaller() {
+ checkNetworkStackCallingPermission();
+ }
}
private static class MalformedPacketException extends Exception {
@@ -160,41 +220,62 @@
}
}
- public DhcpServer(@NonNull Looper looper, @NonNull String ifName,
+ public DhcpServer(@NonNull String ifName,
@NonNull DhcpServingParams params, @NonNull SharedLog log) {
- this(looper, ifName, params, log, null);
+ this(new HandlerThread(DhcpServer.class.getSimpleName() + "." + ifName),
+ ifName, params, log, null);
}
@VisibleForTesting
- DhcpServer(@NonNull Looper looper, @NonNull String ifName,
+ DhcpServer(@NonNull HandlerThread handlerThread, @NonNull String ifName,
@NonNull DhcpServingParams params, @NonNull SharedLog log,
@Nullable Dependencies deps) {
if (deps == null) {
deps = new DependenciesImpl();
}
- mHandler = new ServerHandler(looper);
+ mHandlerThread = handlerThread;
mIfName = ifName;
mServingParams = params;
mLog = log;
mDeps = deps;
mClock = deps.makeClock();
- mPacketListener = deps.makePacketListener();
mLeaseRepo = deps.makeLeaseRepository(mServingParams, mLog, mClock);
}
/**
* Start listening for and responding to packets.
+ *
+ * <p>It is not legal to call this method more than once; in particular the server cannot be
+ * restarted after being stopped.
*/
- public void start() {
- mHandler.sendEmptyMessage(CMD_START_DHCP_SERVER);
+ @Override
+ public void start(@Nullable INetworkStackStatusCallback cb) {
+ mDeps.checkCaller();
+ mHandlerThread.start();
+ mHandler = new ServerHandler(mHandlerThread.getLooper());
+ sendMessage(CMD_START_DHCP_SERVER, cb);
}
/**
* Update serving parameters. All subsequently received requests will be handled with the new
* parameters, and current leases that are incompatible with the new parameters are dropped.
*/
- public void updateParams(@NonNull DhcpServingParams params) {
- sendMessage(CMD_UPDATE_PARAMS, params);
+ @Override
+ public void updateParams(@Nullable DhcpServingParamsParcel params,
+ @Nullable INetworkStackStatusCallback cb) throws RemoteException {
+ mDeps.checkCaller();
+ final DhcpServingParams parsedParams;
+ try {
+ // throws InvalidParameterException with null params
+ parsedParams = DhcpServingParams.fromParcelableObject(params);
+ } catch (DhcpServingParams.InvalidParameterException e) {
+ mLog.e("Invalid parameters sent to DhcpServer", e);
+ if (cb != null) {
+ cb.onStatusAvailable(STATUS_INVALID_ARGUMENT);
+ }
+ return;
+ }
+ sendMessage(CMD_UPDATE_PARAMS, new Pair<>(parsedParams, cb));
}
/**
@@ -203,38 +284,63 @@
* <p>As the server is stopped asynchronously, some packets may still be processed shortly after
* calling this method.
*/
- public void stop() {
- mHandler.sendEmptyMessage(CMD_STOP_DHCP_SERVER);
+ @Override
+ public void stop(@Nullable INetworkStackStatusCallback cb) {
+ mDeps.checkCaller();
+ sendMessage(CMD_STOP_DHCP_SERVER, cb);
}
private void sendMessage(int what, @Nullable Object obj) {
+ if (mHandler == null) {
+ mLog.e("Attempting to send a command to stopped DhcpServer: " + what);
+ return;
+ }
mHandler.sendMessage(mHandler.obtainMessage(what, obj));
}
private class ServerHandler extends Handler {
- public ServerHandler(@NonNull Looper looper) {
+ ServerHandler(@NonNull Looper looper) {
super(looper);
}
@Override
public void handleMessage(@NonNull Message msg) {
+ final INetworkStackStatusCallback cb;
switch (msg.what) {
case CMD_UPDATE_PARAMS:
- final DhcpServingParams params = (DhcpServingParams) msg.obj;
+ final Pair<DhcpServingParams, INetworkStackStatusCallback> pair =
+ (Pair<DhcpServingParams, INetworkStackStatusCallback>) msg.obj;
+ final DhcpServingParams params = pair.first;
mServingParams = params;
mLeaseRepo.updateParams(
DhcpServingParams.makeIpPrefix(mServingParams.serverAddr),
params.excludedAddrs,
params.dhcpLeaseTimeSecs);
+
+ cb = pair.second;
break;
case CMD_START_DHCP_SERVER:
- // This is a no-op if the listener is already started
+ mPacketListener = mDeps.makePacketListener();
mPacketListener.start();
+ cb = (INetworkStackStatusCallback) msg.obj;
break;
case CMD_STOP_DHCP_SERVER:
- // This is a no-op if the listener was not started
- mPacketListener.stop();
+ if (mPacketListener != null) {
+ mPacketListener.stop();
+ mPacketListener = null;
+ }
+ mHandlerThread.quitSafely();
+ cb = (INetworkStackStatusCallback) msg.obj;
break;
+ default:
+ return;
+ }
+ if (cb != null) {
+ try {
+ cb.onStatusAvailable(STATUS_SUCCESS);
+ } catch (RemoteException e) {
+ mLog.e("Could not send status back to caller", e);
+ }
}
}
}
@@ -496,22 +602,24 @@
}
private class PacketListener extends DhcpPacketListener {
- public PacketListener() {
+ PacketListener() {
super(mHandler);
}
@Override
- protected void onReceive(DhcpPacket packet, Inet4Address srcAddr, int srcPort) {
+ protected void onReceive(@NonNull DhcpPacket packet, @NonNull Inet4Address srcAddr,
+ int srcPort) {
processPacket(packet, srcPort);
}
@Override
- protected void logError(String msg, Exception e) {
+ protected void logError(@NonNull String msg, Exception e) {
mLog.e("Error receiving packet: " + msg, e);
}
@Override
- protected void logParseError(byte[] packet, int length, DhcpPacket.ParseException e) {
+ protected void logParseError(@NonNull byte[] packet, int length,
+ @NonNull DhcpPacket.ParseException e) {
mLog.e("Error parsing packet", e);
}
@@ -533,7 +641,7 @@
return mSocket;
} catch (IOException | ErrnoException e) {
mLog.e("Error creating UDP socket", e);
- DhcpServer.this.stop();
+ DhcpServer.this.stop(null);
return null;
} finally {
TrafficStats.setThreadStatsTag(oldTag);
diff --git a/services/net/java/android/net/dhcp/DhcpServingParams.java b/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java
similarity index 73%
rename from services/net/java/android/net/dhcp/DhcpServingParams.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java
index df15ba1..f38888a 100644
--- a/services/net/java/android/net/dhcp/DhcpServingParams.java
+++ b/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java
@@ -17,13 +17,16 @@
package android.net.dhcp;
import static android.net.NetworkUtils.getPrefixMaskAsInet4Address;
-import static android.net.dhcp.DhcpPacket.INFINITE_LEASE;
-import static android.net.util.NetworkConstants.IPV4_MAX_MTU;
-import static android.net.util.NetworkConstants.IPV4_MIN_MTU;
+import static android.net.NetworkUtils.intToInet4AddressHTH;
+
+import static com.android.server.util.NetworkStackConstants.INFINITE_LEASE;
+import static com.android.server.util.NetworkStackConstants.IPV4_MAX_MTU;
+import static com.android.server.util.NetworkStackConstants.IPV4_MIN_MTU;
import static java.lang.Integer.toUnsignedLong;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.NetworkUtils;
@@ -103,6 +106,41 @@
this.metered = metered;
}
+ /**
+ * Create parameters from a stable AIDL-compatible parcel.
+ * @throws InvalidParameterException The parameters parcelable is null or invalid.
+ */
+ public static DhcpServingParams fromParcelableObject(@Nullable DhcpServingParamsParcel parcel)
+ throws InvalidParameterException {
+ if (parcel == null) {
+ throw new InvalidParameterException("Null serving parameters");
+ }
+ final LinkAddress serverAddr = new LinkAddress(
+ intToInet4AddressHTH(parcel.serverAddr),
+ parcel.serverAddrPrefixLength);
+ return new Builder()
+ .setServerAddr(serverAddr)
+ .setDefaultRouters(toInet4AddressSet(parcel.defaultRouters))
+ .setDnsServers(toInet4AddressSet(parcel.dnsServers))
+ .setExcludedAddrs(toInet4AddressSet(parcel.excludedAddrs))
+ .setDhcpLeaseTimeSecs(parcel.dhcpLeaseTimeSecs)
+ .setLinkMtu(parcel.linkMtu)
+ .setMetered(parcel.metered)
+ .build();
+ }
+
+ private static Set<Inet4Address> toInet4AddressSet(@Nullable int[] addrs) {
+ if (addrs == null) {
+ return new HashSet<>(0);
+ }
+
+ final HashSet<Inet4Address> res = new HashSet<>();
+ for (int addr : addrs) {
+ res.add(intToInet4AddressHTH(addr));
+ }
+ return res;
+ }
+
@NonNull
public Inet4Address getServerInet4Addr() {
return (Inet4Address) serverAddr.getAddress();
@@ -134,13 +172,13 @@
* of the parameters.
*/
public static class Builder {
- private LinkAddress serverAddr;
- private Set<Inet4Address> defaultRouters;
- private Set<Inet4Address> dnsServers;
- private Set<Inet4Address> excludedAddrs;
- private long dhcpLeaseTimeSecs;
- private int linkMtu = MTU_UNSET;
- private boolean metered;
+ private LinkAddress mServerAddr;
+ private Set<Inet4Address> mDefaultRouters;
+ private Set<Inet4Address> mDnsServers;
+ private Set<Inet4Address> mExcludedAddrs;
+ private long mDhcpLeaseTimeSecs;
+ private int mLinkMtu = MTU_UNSET;
+ private boolean mMetered;
/**
* Set the server address and served prefix for the DHCP server.
@@ -148,7 +186,7 @@
* <p>This parameter is required.
*/
public Builder setServerAddr(@NonNull LinkAddress serverAddr) {
- this.serverAddr = serverAddr;
+ this.mServerAddr = serverAddr;
return this;
}
@@ -159,7 +197,7 @@
* always be set explicitly before building the {@link DhcpServingParams}.
*/
public Builder setDefaultRouters(@NonNull Set<Inet4Address> defaultRouters) {
- this.defaultRouters = defaultRouters;
+ this.mDefaultRouters = defaultRouters;
return this;
}
@@ -189,7 +227,7 @@
* {@link DhcpServingParams}.
*/
public Builder setDnsServers(@NonNull Set<Inet4Address> dnsServers) {
- this.dnsServers = dnsServers;
+ this.mDnsServers = dnsServers;
return this;
}
@@ -219,7 +257,7 @@
* and do not need to be set here.
*/
public Builder setExcludedAddrs(@NonNull Set<Inet4Address> excludedAddrs) {
- this.excludedAddrs = excludedAddrs;
+ this.mExcludedAddrs = excludedAddrs;
return this;
}
@@ -239,7 +277,7 @@
* <p>This parameter is required.
*/
public Builder setDhcpLeaseTimeSecs(long dhcpLeaseTimeSecs) {
- this.dhcpLeaseTimeSecs = dhcpLeaseTimeSecs;
+ this.mDhcpLeaseTimeSecs = dhcpLeaseTimeSecs;
return this;
}
@@ -250,7 +288,7 @@
* is optional and defaults to {@link #MTU_UNSET}.
*/
public Builder setLinkMtu(int linkMtu) {
- this.linkMtu = linkMtu;
+ this.mLinkMtu = linkMtu;
return this;
}
@@ -260,7 +298,7 @@
* <p>If not set, the default value is false.
*/
public Builder setMetered(boolean metered) {
- this.metered = metered;
+ this.mMetered = metered;
return this;
}
@@ -274,54 +312,57 @@
*/
@NonNull
public DhcpServingParams build() throws InvalidParameterException {
- if (serverAddr == null) {
+ if (mServerAddr == null) {
throw new InvalidParameterException("Missing serverAddr");
}
- if (defaultRouters == null) {
+ if (mDefaultRouters == null) {
throw new InvalidParameterException("Missing defaultRouters");
}
- if (dnsServers == null) {
+ if (mDnsServers == null) {
// Empty set is OK, but enforce explicitly setting it
throw new InvalidParameterException("Missing dnsServers");
}
- if (dhcpLeaseTimeSecs <= 0 || dhcpLeaseTimeSecs > toUnsignedLong(INFINITE_LEASE)) {
- throw new InvalidParameterException("Invalid lease time: " + dhcpLeaseTimeSecs);
+ if (mDhcpLeaseTimeSecs <= 0 || mDhcpLeaseTimeSecs > toUnsignedLong(INFINITE_LEASE)) {
+ throw new InvalidParameterException("Invalid lease time: " + mDhcpLeaseTimeSecs);
}
- if (linkMtu != MTU_UNSET && (linkMtu < IPV4_MIN_MTU || linkMtu > IPV4_MAX_MTU)) {
- throw new InvalidParameterException("Invalid link MTU: " + linkMtu);
+ if (mLinkMtu != MTU_UNSET && (mLinkMtu < IPV4_MIN_MTU || mLinkMtu > IPV4_MAX_MTU)) {
+ throw new InvalidParameterException("Invalid link MTU: " + mLinkMtu);
}
- if (!serverAddr.isIPv4()) {
+ if (!mServerAddr.isIPv4()) {
throw new InvalidParameterException("serverAddr must be IPv4");
}
- if (serverAddr.getPrefixLength() < MIN_PREFIX_LENGTH
- || serverAddr.getPrefixLength() > MAX_PREFIX_LENGTH) {
+ if (mServerAddr.getPrefixLength() < MIN_PREFIX_LENGTH
+ || mServerAddr.getPrefixLength() > MAX_PREFIX_LENGTH) {
throw new InvalidParameterException("Prefix length is not in supported range");
}
- final IpPrefix prefix = makeIpPrefix(serverAddr);
- for (Inet4Address addr : defaultRouters) {
+ final IpPrefix prefix = makeIpPrefix(mServerAddr);
+ for (Inet4Address addr : mDefaultRouters) {
if (!prefix.contains(addr)) {
throw new InvalidParameterException(String.format(
- "Default router %s is not in server prefix %s", addr, serverAddr));
+ "Default router %s is not in server prefix %s", addr, mServerAddr));
}
}
final Set<Inet4Address> excl = new HashSet<>();
- if (excludedAddrs != null) {
- excl.addAll(excludedAddrs);
+ if (mExcludedAddrs != null) {
+ excl.addAll(mExcludedAddrs);
}
- excl.add((Inet4Address) serverAddr.getAddress());
- excl.addAll(defaultRouters);
- excl.addAll(dnsServers);
+ excl.add((Inet4Address) mServerAddr.getAddress());
+ excl.addAll(mDefaultRouters);
+ excl.addAll(mDnsServers);
- return new DhcpServingParams(serverAddr,
- Collections.unmodifiableSet(new HashSet<>(defaultRouters)),
- Collections.unmodifiableSet(new HashSet<>(dnsServers)),
+ return new DhcpServingParams(mServerAddr,
+ Collections.unmodifiableSet(new HashSet<>(mDefaultRouters)),
+ Collections.unmodifiableSet(new HashSet<>(mDnsServers)),
Collections.unmodifiableSet(excl),
- dhcpLeaseTimeSecs, linkMtu, metered);
+ mDhcpLeaseTimeSecs, mLinkMtu, mMetered);
}
}
+ /**
+ * Utility method to create an IpPrefix with the address and prefix length of a LinkAddress.
+ */
@NonNull
static IpPrefix makeIpPrefix(@NonNull LinkAddress addr) {
return new IpPrefix(addr.getAddress(), addr.getPrefixLength());
diff --git a/packages/NetworkStack/src/android/net/util/SharedLog.java b/packages/NetworkStack/src/android/net/util/SharedLog.java
new file mode 100644
index 0000000..4fabf10
--- /dev/null
+++ b/packages/NetworkStack/src/android/net/util/SharedLog.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.text.TextUtils;
+import android.util.LocalLog;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.StringJoiner;
+
+
+/**
+ * Class to centralize logging functionality for tethering.
+ *
+ * All access to class methods other than dump() must be on the same thread.
+ *
+ * @hide
+ */
+public class SharedLog {
+ private static final int DEFAULT_MAX_RECORDS = 500;
+ private static final String COMPONENT_DELIMITER = ".";
+
+ private enum Category {
+ NONE,
+ ERROR,
+ MARK,
+ WARN,
+ };
+
+ private final LocalLog mLocalLog;
+ // The tag to use for output to the system log. This is not output to the
+ // LocalLog because that would be redundant.
+ private final String mTag;
+ // The component (or subcomponent) of a system that is sharing this log.
+ // This can grow in depth if components call forSubComponent() to obtain
+ // their SharedLog instance. The tag is not included in the component for
+ // brevity.
+ private final String mComponent;
+
+ public SharedLog(String tag) {
+ this(DEFAULT_MAX_RECORDS, tag);
+ }
+
+ public SharedLog(int maxRecords, String tag) {
+ this(new LocalLog(maxRecords), tag, tag);
+ }
+
+ private SharedLog(LocalLog localLog, String tag, String component) {
+ mLocalLog = localLog;
+ mTag = tag;
+ mComponent = component;
+ }
+
+ public String getTag() {
+ return mTag;
+ }
+
+ /**
+ * Create a SharedLog based on this log with an additional component prefix on each logged line.
+ */
+ public SharedLog forSubComponent(String component) {
+ if (!isRootLogInstance()) {
+ component = mComponent + COMPONENT_DELIMITER + component;
+ }
+ return new SharedLog(mLocalLog, mTag, component);
+ }
+
+ /**
+ * Dump the contents of this log.
+ *
+ * <p>This method may be called on any thread.
+ */
+ public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ mLocalLog.readOnlyLocalLog().dump(fd, writer, args);
+ }
+
+ //////
+ // Methods that both log an entry and emit it to the system log.
+ //////
+
+ /**
+ * Log an error due to an exception. This does not include the exception stacktrace.
+ *
+ * <p>The log entry will be also added to the system log.
+ * @see #e(String, Throwable)
+ */
+ public void e(Exception e) {
+ Log.e(mTag, record(Category.ERROR, e.toString()));
+ }
+
+ /**
+ * Log an error message.
+ *
+ * <p>The log entry will be also added to the system log.
+ */
+ public void e(String msg) {
+ Log.e(mTag, record(Category.ERROR, msg));
+ }
+
+ /**
+ * Log an error due to an exception, with the exception stacktrace if provided.
+ *
+ * <p>The error and exception message appear in the shared log, but the stacktrace is only
+ * logged in general log output (logcat). The log entry will be also added to the system log.
+ */
+ public void e(@NonNull String msg, @Nullable Throwable exception) {
+ if (exception == null) {
+ e(msg);
+ return;
+ }
+ Log.e(mTag, record(Category.ERROR, msg + ": " + exception.getMessage()), exception);
+ }
+
+ /**
+ * Log an informational message.
+ *
+ * <p>The log entry will be also added to the system log.
+ */
+ public void i(String msg) {
+ Log.i(mTag, record(Category.NONE, msg));
+ }
+
+ /**
+ * Log a warning message.
+ *
+ * <p>The log entry will be also added to the system log.
+ */
+ public void w(String msg) {
+ Log.w(mTag, record(Category.WARN, msg));
+ }
+
+ //////
+ // Methods that only log an entry (and do NOT emit to the system log).
+ //////
+
+ /**
+ * Log a general message to be only included in the in-memory log.
+ *
+ * <p>The log entry will *not* be added to the system log.
+ */
+ public void log(String msg) {
+ record(Category.NONE, msg);
+ }
+
+ /**
+ * Log a general, formatted message to be only included in the in-memory log.
+ *
+ * <p>The log entry will *not* be added to the system log.
+ * @see String#format(String, Object...)
+ */
+ public void logf(String fmt, Object... args) {
+ log(String.format(fmt, args));
+ }
+
+ /**
+ * Log a message with MARK level.
+ *
+ * <p>The log entry will *not* be added to the system log.
+ */
+ public void mark(String msg) {
+ record(Category.MARK, msg);
+ }
+
+ private String record(Category category, String msg) {
+ final String entry = logLine(category, msg);
+ mLocalLog.log(entry);
+ return entry;
+ }
+
+ private String logLine(Category category, String msg) {
+ final StringJoiner sj = new StringJoiner(" ");
+ if (!isRootLogInstance()) sj.add("[" + mComponent + "]");
+ if (category != Category.NONE) sj.add(category.toString());
+ return sj.add(msg).toString();
+ }
+
+ // Check whether this SharedLog instance is nominally the top level in
+ // a potential hierarchy of shared logs (the root of a tree),
+ // or is a subcomponent within the hierarchy.
+ private boolean isRootLogInstance() {
+ return TextUtils.isEmpty(mComponent) || mComponent.equals(mTag);
+ }
+}
diff --git a/services/net/java/android/net/util/Stopwatch.java b/packages/NetworkStack/src/android/net/util/Stopwatch.java
similarity index 78%
rename from services/net/java/android/net/util/Stopwatch.java
rename to packages/NetworkStack/src/android/net/util/Stopwatch.java
index cb15ee5..c316699 100644
--- a/services/net/java/android/net/util/Stopwatch.java
+++ b/packages/NetworkStack/src/android/net/util/Stopwatch.java
@@ -38,9 +38,9 @@
return (isStarted() && !isStopped());
}
- // Returning |this| makes possible the following usage pattern:
- //
- // Stopwatch s = new Stopwatch().start();
+ /**
+ * Start the Stopwatch.
+ */
public Stopwatch start() {
if (!isStarted()) {
mStartTimeMs = SystemClock.elapsedRealtime();
@@ -48,7 +48,10 @@
return this;
}
- // Returns the total time recorded, in milliseconds, or 0 if not started.
+ /**
+ * Stop the Stopwatch.
+ * @return the total time recorded, in milliseconds, or 0 if not started.
+ */
public long stop() {
if (isRunning()) {
mStopTimeMs = SystemClock.elapsedRealtime();
@@ -57,9 +60,11 @@
return (mStopTimeMs - mStartTimeMs);
}
- // Returns the total time recorded to date, in milliseconds.
- // If the Stopwatch is not running, returns the same value as stop(),
- // i.e. either the total time recorded before stopping or 0.
+ /**
+ * Return the total time recorded to date, in milliseconds.
+ * If the Stopwatch is not running, returns the same value as stop(),
+ * i.e. either the total time recorded before stopping or 0.
+ */
public long lap() {
if (isRunning()) {
return (SystemClock.elapsedRealtime() - mStartTimeMs);
@@ -68,6 +73,9 @@
}
}
+ /**
+ * Reset the Stopwatch. It will be stopped when this method returns.
+ */
public void reset() {
mStartTimeMs = 0;
mStopTimeMs = 0;
diff --git a/packages/NetworkStack/src/com/android/server/NetworkStackService.java b/packages/NetworkStack/src/com/android/server/NetworkStackService.java
index 5afaf58..057012d 100644
--- a/packages/NetworkStack/src/com/android/server/NetworkStackService.java
+++ b/packages/NetworkStack/src/com/android/server/NetworkStackService.java
@@ -16,18 +16,40 @@
package com.android.server;
-import static android.os.Binder.getCallingUid;
+import static android.net.dhcp.IDhcpServer.STATUS_INVALID_ARGUMENT;
+import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
+import static android.net.dhcp.IDhcpServer.STATUS_UNKNOWN_ERROR;
+
+import static com.android.server.util.PermissionUtil.checkNetworkStackCallingPermission;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Service;
+import android.content.Context;
import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.INetworkMonitor;
+import android.net.INetworkMonitorCallbacks;
import android.net.INetworkStackConnector;
+import android.net.Network;
+import android.net.NetworkRequest;
+import android.net.PrivateDnsConfigParcel;
+import android.net.dhcp.DhcpServer;
+import android.net.dhcp.DhcpServingParams;
+import android.net.dhcp.DhcpServingParamsParcel;
+import android.net.dhcp.IDhcpServerCallbacks;
+import android.net.shared.PrivateDnsConfig;
+import android.net.util.SharedLog;
import android.os.IBinder;
-import android.os.Process;
+import android.os.RemoteException;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.connectivity.NetworkMonitor;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayDeque;
/**
* Android service used to start the network stack when bound to via an intent.
@@ -43,32 +65,163 @@
* <p>On platforms where the network stack runs in the system server process, this method may
* be called directly instead of obtaining the connector by binding to the service.
*/
- public static IBinder makeConnector() {
- return new NetworkStackConnector();
+ public static IBinder makeConnector(Context context) {
+ return new NetworkStackConnector(context);
}
@NonNull
@Override
public IBinder onBind(Intent intent) {
- return makeConnector();
+ return makeConnector(this);
}
private static class NetworkStackConnector extends INetworkStackConnector.Stub {
- // TODO: makeDhcpServer(), etc. will go here.
+ private static final int NUM_VALIDATION_LOG_LINES = 20;
+ private final Context mContext;
+ private final ConnectivityManager mCm;
+
+ private static final int MAX_VALIDATION_LOGS = 10;
+ @GuardedBy("mValidationLogs")
+ private final ArrayDeque<SharedLog> mValidationLogs = new ArrayDeque<>(MAX_VALIDATION_LOGS);
+
+ private SharedLog addValidationLogs(Network network, String name) {
+ final SharedLog log = new SharedLog(NUM_VALIDATION_LOG_LINES, network + " - " + name);
+ synchronized (mValidationLogs) {
+ while (mValidationLogs.size() >= MAX_VALIDATION_LOGS) {
+ mValidationLogs.removeLast();
+ }
+ mValidationLogs.addFirst(log);
+ }
+ return log;
+ }
+
+ NetworkStackConnector(Context context) {
+ mContext = context;
+ mCm = context.getSystemService(ConnectivityManager.class);
+ }
+
+ @NonNull
+ private final SharedLog mLog = new SharedLog(TAG);
+
+ @Override
+ public void makeDhcpServer(@NonNull String ifName, @NonNull DhcpServingParamsParcel params,
+ @NonNull IDhcpServerCallbacks cb) throws RemoteException {
+ checkNetworkStackCallingPermission();
+ final DhcpServer server;
+ try {
+ server = new DhcpServer(
+ ifName,
+ DhcpServingParams.fromParcelableObject(params),
+ mLog.forSubComponent(ifName + ".DHCP"));
+ } catch (DhcpServingParams.InvalidParameterException e) {
+ mLog.e("Invalid DhcpServingParams", e);
+ cb.onDhcpServerCreated(STATUS_INVALID_ARGUMENT, null);
+ return;
+ } catch (Exception e) {
+ mLog.e("Unknown error starting DhcpServer", e);
+ cb.onDhcpServerCreated(STATUS_UNKNOWN_ERROR, null);
+ return;
+ }
+ cb.onDhcpServerCreated(STATUS_SUCCESS, server);
+ }
+
+ @Override
+ public void makeNetworkMonitor(int netId, String name, INetworkMonitorCallbacks cb)
+ throws RemoteException {
+ final Network network = new Network(netId, false /* privateDnsBypass */);
+ final NetworkRequest defaultRequest = mCm.getDefaultRequest();
+ final SharedLog log = addValidationLogs(network, name);
+ final NetworkMonitor nm = new NetworkMonitor(
+ mContext, cb, network, defaultRequest, log);
+ cb.onNetworkMonitorCreated(new NetworkMonitorImpl(nm));
+ }
@Override
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
@Nullable String[] args) {
- checkCaller();
- fout.println("NetworkStack logs:");
- // TODO: dump logs here
+ checkNetworkStackCallingPermission();
+ final IndentingPrintWriter pw = new IndentingPrintWriter(fout, " ");
+ pw.println("NetworkStack logs:");
+ mLog.dump(fd, pw, args);
+
+ pw.println();
+ pw.println("Validation logs (most recent first):");
+ synchronized (mValidationLogs) {
+ for (SharedLog p : mValidationLogs) {
+ pw.println(p.getTag());
+ pw.increaseIndent();
+ p.dump(fd, pw, args);
+ pw.decreaseIndent();
+ }
+ }
}
}
- private static void checkCaller() {
- // TODO: check that the calling PID is the system server.
- if (getCallingUid() != Process.SYSTEM_UID && getCallingUid() != Process.ROOT_UID) {
- throw new SecurityException("Invalid caller: " + getCallingUid());
+ private static class NetworkMonitorImpl extends INetworkMonitor.Stub {
+ private final NetworkMonitor mNm;
+
+ NetworkMonitorImpl(NetworkMonitor nm) {
+ mNm = nm;
+ }
+
+ @Override
+ public void start() {
+ checkNetworkStackCallingPermission();
+ mNm.start();
+ }
+
+ @Override
+ public void launchCaptivePortalApp() {
+ checkNetworkStackCallingPermission();
+ mNm.launchCaptivePortalApp();
+ }
+
+ @Override
+ public void forceReevaluation(int uid) {
+ checkNetworkStackCallingPermission();
+ mNm.forceReevaluation(uid);
+ }
+
+ @Override
+ public void notifyPrivateDnsChanged(PrivateDnsConfigParcel config) {
+ checkNetworkStackCallingPermission();
+ mNm.notifyPrivateDnsSettingsChanged(PrivateDnsConfig.fromParcel(config));
+ }
+
+ @Override
+ public void notifyDnsResponse(int returnCode) {
+ checkNetworkStackCallingPermission();
+ mNm.notifyDnsResponse(returnCode);
+ }
+
+ @Override
+ public void notifySystemReady() {
+ checkNetworkStackCallingPermission();
+ mNm.notifySystemReady();
+ }
+
+ @Override
+ public void notifyNetworkConnected() {
+ checkNetworkStackCallingPermission();
+ mNm.notifyNetworkConnected();
+ }
+
+ @Override
+ public void notifyNetworkDisconnected() {
+ checkNetworkStackCallingPermission();
+ mNm.notifyNetworkDisconnected();
+ }
+
+ @Override
+ public void notifyLinkPropertiesChanged() {
+ checkNetworkStackCallingPermission();
+ mNm.notifyLinkPropertiesChanged();
+ }
+
+ @Override
+ public void notifyNetworkCapabilitiesChanged() {
+ checkNetworkStackCallingPermission();
+ mNm.notifyNetworkCapabilitiesChanged();
}
}
}
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
similarity index 81%
rename from services/core/java/com/android/server/connectivity/NetworkMonitor.java
rename to packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
index 9684f4c..94ea1b9 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
@@ -21,6 +21,11 @@
import static android.net.CaptivePortal.APP_RETURN_WANTED_AS_IS;
import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_PROBE_SPEC;
import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.metrics.ValidationProbeEvent.DNS_FAILURE;
import static android.net.metrics.ValidationProbeEvent.DNS_SUCCESS;
import static android.net.metrics.ValidationProbeEvent.PROBE_FALLBACK;
@@ -35,6 +40,9 @@
import android.net.CaptivePortal;
import android.net.ConnectivityManager;
import android.net.ICaptivePortal;
+import android.net.INetworkMonitor;
+import android.net.INetworkMonitorCallbacks;
+import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
@@ -46,11 +54,14 @@
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.NetworkEvent;
import android.net.metrics.ValidationProbeEvent;
+import android.net.shared.NetworkMonitorUtils;
+import android.net.shared.PrivateDnsConfig;
+import android.net.util.SharedLog;
import android.net.util.Stopwatch;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
-import android.os.Handler;
import android.os.Message;
+import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
@@ -65,8 +76,6 @@
import android.telephony.CellInfoWcdma;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
-import android.util.LocalLog;
-import android.util.LocalLog.ReadOnlyLocalLog;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -75,7 +84,6 @@
import com.android.internal.util.RingBufferIndices;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
-import com.android.server.connectivity.DnsManager.PrivateDnsConfig;
import java.io.IOException;
import java.net.HttpURLConnection;
@@ -104,9 +112,7 @@
// Default configuration values for captive portal detection probes.
// TODO: append a random length parameter to the default HTTPS url.
// TODO: randomize browser version ids in the default User-Agent String.
- private static final String DEFAULT_HTTPS_URL = "https://www.google.com/generate_204";
- private static final String DEFAULT_HTTP_URL =
- "http://connectivitycheck.gstatic.com/generate_204";
+ private static final String DEFAULT_HTTPS_URL = "https://www.google.com/generate_204";
private static final String DEFAULT_FALLBACK_URL = "http://www.google.com/gen_204";
private static final String DEFAULT_OTHER_FALLBACK_URLS =
"http://play.googleapis.com/generate_204";
@@ -126,51 +132,30 @@
private static final int DEFAULT_DATA_STALL_EVALUATION_TYPES =
(1 << DATA_STALL_EVALUATION_TYPE_DNS);
- static enum EvaluationResult {
+ enum EvaluationResult {
VALIDATED(true),
CAPTIVE_PORTAL(false);
- final boolean isValidated;
+ final boolean mIsValidated;
EvaluationResult(boolean isValidated) {
- this.isValidated = isValidated;
+ this.mIsValidated = isValidated;
}
}
- static enum ValidationStage {
+ enum ValidationStage {
FIRST_VALIDATION(true),
REVALIDATION(false);
- final boolean isFirstValidation;
+ final boolean mIsFirstValidation;
ValidationStage(boolean isFirstValidation) {
- this.isFirstValidation = isFirstValidation;
+ this.mIsFirstValidation = isFirstValidation;
}
}
- // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED.
- // The network should be used as a default internet connection. It was found to be:
- // 1. a functioning network providing internet access, or
- // 2. a captive portal and the user decided to use it as is.
- public static final int NETWORK_TEST_RESULT_VALID = 0;
- // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED.
- // The network should not be used as a default internet connection. It was found to be:
- // 1. a captive portal and the user is prompted to sign-in, or
- // 2. a captive portal and the user did not want to use it, or
- // 3. a broken network (e.g. DNS failed, connect failed, HTTP request failed).
- public static final int NETWORK_TEST_RESULT_INVALID = 1;
-
private static final int BASE = Protocol.BASE_NETWORK_MONITOR;
-
/**
- * Inform NetworkMonitor that their network is connected.
+ * ConnectivityService has sent a notification to indicate that network has connected.
* Initiates Network Validation.
*/
- public static final int CMD_NETWORK_CONNECTED = BASE + 1;
-
- /**
- * Inform ConnectivityService that the network has been tested.
- * obj = String representing URL that Internet probe was redirect to, if it was redirected.
- * arg1 = One of the NETWORK_TESTED_RESULT_* constants.
- * arg2 = NetID.
- */
- public static final int EVENT_NETWORK_TESTED = BASE + 2;
+ private static final int CMD_NETWORK_CONNECTED = BASE + 1;
/**
* Message to self indicating it's time to evaluate a network's connectivity.
@@ -179,9 +164,9 @@
private static final int CMD_REEVALUATE = BASE + 6;
/**
- * Inform NetworkMonitor that the network has disconnected.
+ * ConnectivityService has sent a notification to indicate that network has disconnected.
*/
- public static final int CMD_NETWORK_DISCONNECTED = BASE + 7;
+ private static final int CMD_NETWORK_DISCONNECTED = BASE + 7;
/**
* Force evaluation even if it has succeeded in the past.
@@ -199,21 +184,13 @@
private static final int CMD_CAPTIVE_PORTAL_APP_FINISHED = BASE + 9;
/**
- * Request ConnectivityService display provisioning notification.
- * arg1 = Whether to make the notification visible.
- * arg2 = NetID.
- * obj = Intent to be launched when notification selected by user, null if !arg1.
- */
- public static final int EVENT_PROVISIONING_NOTIFICATION = BASE + 10;
-
- /**
* Message indicating sign-in app should be launched.
* Sent by mLaunchCaptivePortalAppBroadcastReceiver when the
* user touches the sign in notification, or sent by
* ConnectivityService when the user touches the "sign into
* network" button in the wifi access point detail page.
*/
- public static final int CMD_LAUNCH_CAPTIVE_PORTAL_APP = BASE + 11;
+ private static final int CMD_LAUNCH_CAPTIVE_PORTAL_APP = BASE + 11;
/**
* Retest network to see if captive portal is still in place.
@@ -234,7 +211,6 @@
* validation phase is completed.
*/
private static final int CMD_PRIVATE_DNS_SETTINGS_CHANGED = BASE + 13;
- public static final int EVENT_PRIVATE_DNS_CONFIG_RESOLVED = BASE + 14;
private static final int CMD_EVALUATE_PRIVATE_DNS = BASE + 15;
/**
@@ -251,7 +227,7 @@
// Start mReevaluateDelayMs at this value and double.
private static final int INITIAL_REEVALUATE_DELAY_MS = 1000;
- private static final int MAX_REEVALUATE_DELAY_MS = 10*60*1000;
+ private static final int MAX_REEVALUATE_DELAY_MS = 10 * 60 * 1000;
// Before network has been evaluated this many times, ignore repeated reevaluate requests.
private static final int IGNORE_REEVALUATE_ATTEMPTS = 5;
private int mReevaluateToken = 0;
@@ -261,25 +237,18 @@
// Stop blaming UID that requested re-evaluation after this many attempts.
private static final int BLAME_FOR_EVALUATION_ATTEMPTS = 5;
// Delay between reevaluations once a captive portal has been found.
- private static final int CAPTIVE_PORTAL_REEVALUATE_DELAY_MS = 10*60*1000;
-
- private static final int NUM_VALIDATION_LOG_LINES = 20;
+ private static final int CAPTIVE_PORTAL_REEVALUATE_DELAY_MS = 10 * 60 * 1000;
private String mPrivateDnsProviderHostname = "";
- public static boolean isValidationRequired(
- NetworkCapabilities dfltNetCap, NetworkCapabilities nc) {
- // TODO: Consider requiring validation for DUN networks.
- return dfltNetCap.satisfiedByNetworkCapabilities(nc);
- }
-
private final Context mContext;
- private final Handler mConnectivityServiceHandler;
- private final NetworkAgentInfo mNetworkAgentInfo;
+ private final INetworkMonitorCallbacks mCallback;
private final Network mNetwork;
+ private final Network mNonPrivateDnsBypassNetwork;
private final int mNetId;
private final TelephonyManager mTelephonyManager;
private final WifiManager mWifiManager;
+ private final ConnectivityManager mCm;
private final NetworkRequest mDefaultRequest;
private final IpConnectivityLog mMetricsLog;
private final Dependencies mDependencies;
@@ -292,6 +261,9 @@
@Nullable
private final CaptivePortalProbeSpec[] mCaptivePortalFallbackSpecs;
+ private NetworkCapabilities mNetworkCapabilities;
+ private LinkProperties mLinkProperties;
+
@VisibleForTesting
protected boolean mIsCaptivePortalCheckEnabled;
@@ -304,7 +276,7 @@
// Avoids surfacing "Sign in to network" notification.
private boolean mDontDisplaySigninNotification = false;
- public boolean systemReady = false;
+ private volatile boolean mSystemReady = false;
private final State mDefaultState = new DefaultState();
private final State mValidatedState = new ValidatedState();
@@ -317,7 +289,7 @@
private CustomIntentReceiver mLaunchCaptivePortalAppBroadcastReceiver = null;
- private final LocalLog validationLogs = new LocalLog(NUM_VALIDATION_LOG_LINES);
+ private final SharedLog mValidationLogs;
private final Stopwatch mEvaluationTimer = new Stopwatch();
@@ -328,6 +300,7 @@
private final Random mRandom;
private int mNextFallbackUrlIndex = 0;
+
private int mReevaluateDelayMs = INITIAL_REEVALUATE_DELAY_MS;
private int mEvaluateAttempts = 0;
private volatile int mProbeToken = 0;
@@ -338,17 +311,18 @@
private final DnsStallDetector mDnsStallDetector;
private long mLastProbeTime;
- public NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo,
- NetworkRequest defaultRequest) {
- this(context, handler, networkAgentInfo, defaultRequest, new IpConnectivityLog(),
+ public NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network,
+ NetworkRequest defaultRequest, SharedLog validationLog) {
+ this(context, cb, network, defaultRequest, new IpConnectivityLog(), validationLog,
Dependencies.DEFAULT);
}
@VisibleForTesting
- protected NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo,
- NetworkRequest defaultRequest, IpConnectivityLog logger, Dependencies deps) {
+ protected NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network,
+ NetworkRequest defaultRequest, IpConnectivityLog logger, SharedLog validationLogs,
+ Dependencies deps) {
// Add suffix indicating which NetworkMonitor we're talking about.
- super(TAG + networkAgentInfo.name());
+ super(TAG + "/" + network.netId);
// Logs with a tag of the form given just above, e.g.
// <timestamp> 862 2402 D NetworkMonitor/NetworkAgentInfo [WIFI () - 100]: ...
@@ -356,15 +330,18 @@
mContext = context;
mMetricsLog = logger;
- mConnectivityServiceHandler = handler;
+ mValidationLogs = validationLogs;
+ mCallback = cb;
mDependencies = deps;
- mNetworkAgentInfo = networkAgentInfo;
- mNetwork = deps.getNetwork(networkAgentInfo).getPrivateDnsBypassingCopy();
+ mNonPrivateDnsBypassNetwork = network;
+ mNetwork = deps.getPrivateDnsBypassNetwork(network);
mNetId = mNetwork.netId;
mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ mCm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
mDefaultRequest = defaultRequest;
+ // CHECKSTYLE:OFF IndentationCheck
addState(mDefaultState);
addState(mMaybeNotifyState, mDefaultState);
addState(mEvaluatingState, mMaybeNotifyState);
@@ -374,12 +351,13 @@
addState(mEvaluatingPrivateDnsState, mDefaultState);
addState(mValidatedState, mDefaultState);
setInitialState(mDefaultState);
+ // CHECKSTYLE:ON IndentationCheck
mIsCaptivePortalCheckEnabled = getIsCaptivePortalCheckEnabled();
mUseHttps = getUseHttpsValidation();
mCaptivePortalUserAgent = getCaptivePortalUserAgent();
mCaptivePortalHttpsUrl = makeURL(getCaptivePortalServerHttpsUrl());
- mCaptivePortalHttpUrl = makeURL(getCaptivePortalServerHttpUrl(deps, context));
+ mCaptivePortalHttpUrl = makeURL(deps.getCaptivePortalServerHttpUrl(context));
mCaptivePortalFallbackUrls = makeCaptivePortalFallbackUrls();
mCaptivePortalFallbackSpecs = makeCaptivePortalFallbackProbeSpecs();
mRandom = deps.getRandom();
@@ -390,13 +368,34 @@
mDataStallValidDnsTimeThreshold = getDataStallValidDnsTimeThreshold();
mDataStallEvaluationType = getDataStallEvalutionType();
- start();
+ // mLinkProperties and mNetworkCapbilities must never be null or we will NPE.
+ // Provide empty objects in case we are started and the network disconnects before
+ // we can ever fetch them.
+ // TODO: Delete ASAP.
+ mLinkProperties = new LinkProperties();
+ mNetworkCapabilities = new NetworkCapabilities();
+ mNetworkCapabilities.clearAll();
}
+ /**
+ * Request the NetworkMonitor to reevaluate the network.
+ */
public void forceReevaluation(int responsibleUid) {
sendMessage(CMD_FORCE_REEVALUATION, responsibleUid, 0);
}
+ /**
+ * Send a notification to NetworkMonitor indicating that there was a DNS query response event.
+ * @param returnCode the DNS return code of the response.
+ */
+ public void notifyDnsResponse(int returnCode) {
+ sendMessage(EVENT_DNS_NOTIFICATION, returnCode);
+ }
+
+ /**
+ * Send a notification to NetworkMonitor indicating that private DNS settings have changed.
+ * @param newCfg The new private DNS configuration.
+ */
public void notifyPrivateDnsSettingsChanged(PrivateDnsConfig newCfg) {
// Cancel any outstanding resolutions.
removeMessages(CMD_PRIVATE_DNS_SETTINGS_CHANGED);
@@ -404,9 +403,75 @@
sendMessage(CMD_PRIVATE_DNS_SETTINGS_CHANGED, newCfg);
}
+ /**
+ * Send a notification to NetworkMonitor indicating that the system is ready.
+ */
+ public void notifySystemReady() {
+ // No need to run on the handler thread: mSystemReady is volatile and read only once on the
+ // isCaptivePortal() thread.
+ mSystemReady = true;
+ }
+
+ /**
+ * Send a notification to NetworkMonitor indicating that the network is now connected.
+ */
+ public void notifyNetworkConnected() {
+ sendMessage(CMD_NETWORK_CONNECTED);
+ }
+
+ /**
+ * Send a notification to NetworkMonitor indicating that the network is now disconnected.
+ */
+ public void notifyNetworkDisconnected() {
+ sendMessage(CMD_NETWORK_DISCONNECTED);
+ }
+
+ /**
+ * Send a notification to NetworkMonitor indicating that link properties have changed.
+ */
+ public void notifyLinkPropertiesChanged() {
+ getHandler().post(() -> {
+ updateLinkProperties();
+ });
+ }
+
+ private void updateLinkProperties() {
+ final LinkProperties lp = mCm.getLinkProperties(mNetwork);
+ // If null, we should soon get a message that the network was disconnected, and will stop.
+ if (lp != null) {
+ // TODO: send LinkProperties parceled in notifyLinkPropertiesChanged() and start().
+ mLinkProperties = lp;
+ }
+ }
+
+ /**
+ * Send a notification to NetworkMonitor indicating that network capabilities have changed.
+ */
+ public void notifyNetworkCapabilitiesChanged() {
+ getHandler().post(() -> {
+ updateNetworkCapabilities();
+ });
+ }
+
+ private void updateNetworkCapabilities() {
+ final NetworkCapabilities nc = mCm.getNetworkCapabilities(mNetwork);
+ // If null, we should soon get a message that the network was disconnected, and will stop.
+ if (nc != null) {
+ // TODO: send NetworkCapabilities parceled in notifyNetworkCapsChanged() and start().
+ mNetworkCapabilities = nc;
+ }
+ }
+
+ /**
+ * Request the captive portal application to be launched.
+ */
+ public void launchCaptivePortalApp() {
+ sendMessage(CMD_LAUNCH_CAPTIVE_PORTAL_APP);
+ }
+
@Override
protected void log(String s) {
- if (DBG) Log.d(TAG + "/" + mNetworkAgentInfo.name(), s);
+ if (DBG) Log.d(TAG + "/" + mNetwork.netId, s);
}
private void validationLog(int probeType, Object url, String msg) {
@@ -416,11 +481,7 @@
private void validationLog(String s) {
if (DBG) log(s);
- validationLogs.log(s);
- }
-
- public ReadOnlyLocalLog getValidationLogs() {
- return validationLogs.readOnlyLocalLog();
+ mValidationLogs.log(s);
}
private ValidationStage validationStage() {
@@ -428,20 +489,46 @@
}
private boolean isValidationRequired() {
- return isValidationRequired(
- mDefaultRequest.networkCapabilities, mNetworkAgentInfo.networkCapabilities);
+ return NetworkMonitorUtils.isValidationRequired(
+ mDefaultRequest.networkCapabilities, mNetworkCapabilities);
}
- private void notifyNetworkTestResultInvalid(Object obj) {
- mConnectivityServiceHandler.sendMessage(obtainMessage(
- EVENT_NETWORK_TESTED, NETWORK_TEST_RESULT_INVALID, mNetId, obj));
+ private void notifyNetworkTested(int result, @Nullable String redirectUrl) {
+ try {
+ mCallback.notifyNetworkTested(result, redirectUrl);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error sending network test result", e);
+ }
+ }
+
+ private void showProvisioningNotification(String action) {
+ try {
+ mCallback.showProvisioningNotification(action);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error showing provisioning notification", e);
+ }
+ }
+
+ private void hideProvisioningNotification() {
+ try {
+ mCallback.hideProvisioningNotification();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error hiding provisioning notification", e);
+ }
}
// DefaultState is the parent of all States. It exists only to handle CMD_* messages but
// does not entail any real state (hence no enter() or exit() routines).
private class DefaultState extends State {
@Override
+ public void enter() {
+ // TODO: have those passed parceled in start() and remove this
+ updateLinkProperties();
+ updateNetworkCapabilities();
+ }
+
+ @Override
public boolean processMessage(Message message) {
switch (message.what) {
case CMD_NETWORK_CONNECTED:
@@ -492,7 +579,7 @@
case APP_RETURN_UNWANTED:
mDontDisplaySigninNotification = true;
mUserDoesNotWant = true;
- notifyNetworkTestResultInvalid(null);
+ notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, null);
// TODO: Should teardown network.
mUidResponsibleForReeval = 0;
transitionTo(mEvaluatingState);
@@ -556,8 +643,7 @@
public void enter() {
maybeLogEvaluationResult(
networkEventType(validationStage(), EvaluationResult.VALIDATED));
- mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED,
- NETWORK_TEST_RESULT_VALID, mNetId, null));
+ notifyNetworkTested(INetworkMonitor.NETWORK_TEST_RESULT_VALID, null);
mValidations++;
}
@@ -626,8 +712,7 @@
@Override
public void exit() {
- Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 0, mNetId, null);
- mConnectivityServiceHandler.sendMessage(message);
+ hideProvisioningNotification();
}
}
@@ -655,8 +740,9 @@
public boolean processMessage(Message message) {
switch (message.what) {
case CMD_REEVALUATE:
- if (message.arg1 != mReevaluateToken || mUserDoesNotWant)
+ if (message.arg1 != mReevaluateToken || mUserDoesNotWant) {
return HANDLED;
+ }
// Don't bother validating networks that don't satisfy the default request.
// This includes:
// - VPNs which can be considered explicitly desired by the user and the
@@ -743,9 +829,7 @@
CMD_LAUNCH_CAPTIVE_PORTAL_APP);
}
// Display the sign in notification.
- Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 1, mNetId,
- mLaunchCaptivePortalAppBroadcastReceiver.getPendingIntent());
- mConnectivityServiceHandler.sendMessage(message);
+ showProvisioningNotification(mLaunchCaptivePortalAppBroadcastReceiver.mAction);
// Retest for captive portal occasionally.
sendMessageDelayed(CMD_CAPTIVE_PORTAL_RECHECK, 0 /* no UID */,
CAPTIVE_PORTAL_REEVALUATE_DELAY_MS);
@@ -813,9 +897,9 @@
}
private boolean isStrictModeHostnameResolved() {
- return (mPrivateDnsConfig != null) &&
- mPrivateDnsConfig.hostname.equals(mPrivateDnsProviderHostname) &&
- (mPrivateDnsConfig.ips.length > 0);
+ return (mPrivateDnsConfig != null)
+ && mPrivateDnsConfig.hostname.equals(mPrivateDnsProviderHostname)
+ && (mPrivateDnsConfig.ips.length > 0);
}
private void resolveStrictModeHostname() {
@@ -831,12 +915,15 @@
}
private void notifyPrivateDnsConfigResolved() {
- mConnectivityServiceHandler.sendMessage(obtainMessage(
- EVENT_PRIVATE_DNS_CONFIG_RESOLVED, 0, mNetId, mPrivateDnsConfig));
+ try {
+ mCallback.notifyPrivateDnsConfigResolved(mPrivateDnsConfig.toParcel());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error sending private DNS config resolved notification", e);
+ }
}
private void handlePrivateDnsEvaluationFailure() {
- notifyNetworkTestResultInvalid(null);
+ notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, null);
// Queue up a re-evaluation with backoff.
//
@@ -852,12 +939,12 @@
private boolean sendPrivateDnsProbe() {
// q.v. system/netd/server/dns/DnsTlsTransport.cpp
- final String ONE_TIME_HOSTNAME_SUFFIX = "-dnsotls-ds.metric.gstatic.com";
- final String host = UUID.randomUUID().toString().substring(0, 8) +
- ONE_TIME_HOSTNAME_SUFFIX;
+ final String oneTimeHostnameSuffix = "-dnsotls-ds.metric.gstatic.com";
+ final String host = UUID.randomUUID().toString().substring(0, 8)
+ + oneTimeHostnameSuffix;
final Stopwatch watch = new Stopwatch().start();
try {
- final InetAddress[] ips = mNetworkAgentInfo.network().getAllByName(host);
+ final InetAddress[] ips = mNonPrivateDnsBypassNetwork.getAllByName(host);
final long time = watch.stop();
final String strIps = Arrays.toString(ips);
final boolean success = (ips != null && ips.length > 0);
@@ -907,12 +994,12 @@
// state (even if no Private DNS validation required).
transitionTo(mEvaluatingPrivateDnsState);
} else if (probeResult.isPortal()) {
- notifyNetworkTestResultInvalid(probeResult.redirectUrl);
+ notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, probeResult.redirectUrl);
mLastPortalProbeResult = probeResult;
transitionTo(mCaptivePortalState);
} else {
logNetworkEvent(NetworkEvent.NETWORK_VALIDATION_FAILED);
- notifyNetworkTestResultInvalid(probeResult.redirectUrl);
+ notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, probeResult.redirectUrl);
transitionTo(mWaitingForNextProbeState);
}
return HANDLED;
@@ -966,7 +1053,7 @@
// most one per address family. This ensures we only wait up to 20 seconds for TCP connections
// to complete, regardless of how many IP addresses a host has.
private static class OneAddressPerFamilyNetwork extends Network {
- public OneAddressPerFamilyNetwork(Network network) {
+ OneAddressPerFamilyNetwork(Network network) {
// Always bypass Private DNS.
super(network.getPrivateDnsBypassingCopy());
}
@@ -988,19 +1075,20 @@
}
}
- public boolean getIsCaptivePortalCheckEnabled() {
+ private boolean getIsCaptivePortalCheckEnabled() {
String symbol = Settings.Global.CAPTIVE_PORTAL_MODE;
int defaultValue = Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT;
int mode = mDependencies.getSetting(mContext, symbol, defaultValue);
return mode != Settings.Global.CAPTIVE_PORTAL_MODE_IGNORE;
}
- public boolean getUseHttpsValidation() {
+ private boolean getUseHttpsValidation() {
return mDependencies.getSetting(mContext, Settings.Global.CAPTIVE_PORTAL_USE_HTTPS, 1) == 1;
}
- public boolean getWifiScansAlwaysAvailableDisabled() {
- return mDependencies.getSetting(mContext, Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 0;
+ private boolean getWifiScansAlwaysAvailableDisabled() {
+ return mDependencies.getSetting(
+ mContext, Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 0;
}
private String getCaptivePortalServerHttpsUrl() {
@@ -1031,15 +1119,6 @@
DEFAULT_DATA_STALL_EVALUATION_TYPES);
}
- // Static for direct access by ConnectivityService
- public static String getCaptivePortalServerHttpUrl(Context context) {
- return getCaptivePortalServerHttpUrl(Dependencies.DEFAULT, context);
- }
-
- public static String getCaptivePortalServerHttpUrl(Dependencies deps, Context context) {
- return deps.getSetting(context, Settings.Global.CAPTIVE_PORTAL_HTTP_URL, DEFAULT_HTTP_URL);
- }
-
private URL[] makeCaptivePortalFallbackUrls() {
try {
String separator = ",";
@@ -1135,7 +1214,7 @@
// 3. PAC scripts are sometimes used to block or restrict Internet access and may in
// fact block fetching of the generate_204 URL which would lead to false negative
// results for network validation.
- final ProxyInfo proxyInfo = mNetworkAgentInfo.linkProperties.getHttpProxy();
+ final ProxyInfo proxyInfo = mLinkProperties.getHttpProxy();
if (proxyInfo != null && !Uri.EMPTY.equals(proxyInfo.getPacFileUrl())) {
pacUrl = makeURL(proxyInfo.getPacFileUrl().toString());
if (pacUrl == null) {
@@ -1246,10 +1325,10 @@
// Time how long it takes to get a response to our request
long responseTimestamp = SystemClock.elapsedRealtime();
- validationLog(probeType, url, "time=" + (responseTimestamp - requestTimestamp) + "ms" +
- " ret=" + httpResponseCode +
- " request=" + requestHeader +
- " headers=" + urlConnection.getHeaderFields());
+ validationLog(probeType, url, "time=" + (responseTimestamp - requestTimestamp) + "ms"
+ + " ret=" + httpResponseCode
+ + " request=" + requestHeader
+ + " headers=" + urlConnection.getHeaderFields());
// NOTE: We may want to consider an "HTTP/1.0 204" response to be a captive
// portal. The only example of this seen so far was a captive portal. For
// the time being go with prior behavior of assuming it's not a captive
@@ -1267,7 +1346,7 @@
// sign-in to an empty page. Probably the result of a broken transparent proxy.
// See http://b/9972012.
validationLog(probeType, url,
- "200 response with Content-length=0 interpreted as 204 response.");
+ "200 response with Content-length=0 interpreted as 204 response.");
httpResponseCode = CaptivePortalProbeResult.SUCCESS_CODE;
} else if (urlConnection.getContentLengthLong() == -1) {
// When no Content-length (default value == -1), attempt to read a byte from the
@@ -1309,7 +1388,7 @@
private final boolean mIsHttps;
private volatile CaptivePortalProbeResult mResult = CaptivePortalProbeResult.FAILED;
- public ProbeThread(boolean isHttps) {
+ ProbeThread(boolean isHttps) {
mIsHttps = isHttps;
}
@@ -1407,99 +1486,98 @@
return;
}
- if (!systemReady) {
+ if (!mSystemReady) {
return;
}
Intent latencyBroadcast =
- new Intent(ConnectivityConstants.ACTION_NETWORK_CONDITIONS_MEASURED);
- switch (mNetworkAgentInfo.networkInfo.getType()) {
- case ConnectivityManager.TYPE_WIFI:
- WifiInfo currentWifiInfo = mWifiManager.getConnectionInfo();
- if (currentWifiInfo != null) {
- // NOTE: getSSID()'s behavior changed in API 17; before that, SSIDs were not
- // surrounded by double quotation marks (thus violating the Javadoc), but this
- // was changed to match the Javadoc in API 17. Since clients may have started
- // sanitizing the output of this method since API 17 was released, we should
- // not change it here as it would become impossible to tell whether the SSID is
- // simply being surrounded by quotes due to the API, or whether those quotes
- // are actually part of the SSID.
- latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_SSID,
- currentWifiInfo.getSSID());
- latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_BSSID,
- currentWifiInfo.getBSSID());
- } else {
- if (VDBG) logw("network info is TYPE_WIFI but no ConnectionInfo found");
- return;
- }
- break;
- case ConnectivityManager.TYPE_MOBILE:
- latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_NETWORK_TYPE,
- mTelephonyManager.getNetworkType());
- List<CellInfo> info = mTelephonyManager.getAllCellInfo();
- if (info == null) return;
- int numRegisteredCellInfo = 0;
- for (CellInfo cellInfo : info) {
- if (cellInfo.isRegistered()) {
- numRegisteredCellInfo++;
- if (numRegisteredCellInfo > 1) {
- if (VDBG) logw("more than one registered CellInfo." +
- " Can't tell which is active. Bailing.");
- return;
+ new Intent(NetworkMonitorUtils.ACTION_NETWORK_CONDITIONS_MEASURED);
+ if (mNetworkCapabilities.hasTransport(TRANSPORT_WIFI)) {
+ WifiInfo currentWifiInfo = mWifiManager.getConnectionInfo();
+ if (currentWifiInfo != null) {
+ // NOTE: getSSID()'s behavior changed in API 17; before that, SSIDs were not
+ // surrounded by double quotation marks (thus violating the Javadoc), but this
+ // was changed to match the Javadoc in API 17. Since clients may have started
+ // sanitizing the output of this method since API 17 was released, we should
+ // not change it here as it would become impossible to tell whether the SSID is
+ // simply being surrounded by quotes due to the API, or whether those quotes
+ // are actually part of the SSID.
+ latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_SSID,
+ currentWifiInfo.getSSID());
+ latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_BSSID,
+ currentWifiInfo.getBSSID());
+ } else {
+ if (VDBG) logw("network info is TYPE_WIFI but no ConnectionInfo found");
+ return;
+ }
+ latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CONNECTIVITY_TYPE, TYPE_WIFI);
+ } else if (mNetworkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
+ latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_NETWORK_TYPE,
+ mTelephonyManager.getNetworkType());
+ List<CellInfo> info = mTelephonyManager.getAllCellInfo();
+ if (info == null) return;
+ int numRegisteredCellInfo = 0;
+ for (CellInfo cellInfo : info) {
+ if (cellInfo.isRegistered()) {
+ numRegisteredCellInfo++;
+ if (numRegisteredCellInfo > 1) {
+ if (VDBG) {
+ logw("more than one registered CellInfo."
+ + " Can't tell which is active. Bailing.");
}
- if (cellInfo instanceof CellInfoCdma) {
- CellIdentityCdma cellId = ((CellInfoCdma) cellInfo).getCellIdentity();
- latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CELL_ID, cellId);
- } else if (cellInfo instanceof CellInfoGsm) {
- CellIdentityGsm cellId = ((CellInfoGsm) cellInfo).getCellIdentity();
- latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CELL_ID, cellId);
- } else if (cellInfo instanceof CellInfoLte) {
- CellIdentityLte cellId = ((CellInfoLte) cellInfo).getCellIdentity();
- latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CELL_ID, cellId);
- } else if (cellInfo instanceof CellInfoWcdma) {
- CellIdentityWcdma cellId = ((CellInfoWcdma) cellInfo).getCellIdentity();
- latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CELL_ID, cellId);
- } else {
- if (VDBG) logw("Registered cellinfo is unrecognized");
- return;
- }
+ return;
+ }
+ if (cellInfo instanceof CellInfoCdma) {
+ CellIdentityCdma cellId = ((CellInfoCdma) cellInfo).getCellIdentity();
+ latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CELL_ID, cellId);
+ } else if (cellInfo instanceof CellInfoGsm) {
+ CellIdentityGsm cellId = ((CellInfoGsm) cellInfo).getCellIdentity();
+ latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CELL_ID, cellId);
+ } else if (cellInfo instanceof CellInfoLte) {
+ CellIdentityLte cellId = ((CellInfoLte) cellInfo).getCellIdentity();
+ latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CELL_ID, cellId);
+ } else if (cellInfo instanceof CellInfoWcdma) {
+ CellIdentityWcdma cellId = ((CellInfoWcdma) cellInfo).getCellIdentity();
+ latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CELL_ID, cellId);
+ } else {
+ if (VDBG) logw("Registered cellinfo is unrecognized");
+ return;
}
}
- break;
- default:
- return;
+ }
+ latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CONNECTIVITY_TYPE, TYPE_MOBILE);
+ } else {
+ return;
}
- latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CONNECTIVITY_TYPE,
- mNetworkAgentInfo.networkInfo.getType());
- latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_RESPONSE_RECEIVED,
+ latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_RESPONSE_RECEIVED,
responseReceived);
- latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_REQUEST_TIMESTAMP_MS,
+ latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_REQUEST_TIMESTAMP_MS,
requestTimestampMs);
if (responseReceived) {
- latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_IS_CAPTIVE_PORTAL,
+ latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_IS_CAPTIVE_PORTAL,
isCaptivePortal);
- latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_RESPONSE_TIMESTAMP_MS,
+ latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_RESPONSE_TIMESTAMP_MS,
responseTimestampMs);
}
mContext.sendBroadcastAsUser(latencyBroadcast, UserHandle.CURRENT,
- ConnectivityConstants.PERMISSION_ACCESS_NETWORK_CONDITIONS);
+ NetworkMonitorUtils.PERMISSION_ACCESS_NETWORK_CONDITIONS);
}
private void logNetworkEvent(int evtype) {
- int[] transports = mNetworkAgentInfo.networkCapabilities.getTransportTypes();
+ int[] transports = mNetworkCapabilities.getTransportTypes();
mMetricsLog.log(mNetId, transports, new NetworkEvent(evtype));
}
private int networkEventType(ValidationStage s, EvaluationResult r) {
- if (s.isFirstValidation) {
- if (r.isValidated) {
+ if (s.mIsFirstValidation) {
+ if (r.mIsValidated) {
return NetworkEvent.NETWORK_FIRST_VALIDATION_SUCCESS;
} else {
return NetworkEvent.NETWORK_FIRST_VALIDATION_PORTAL_FOUND;
}
} else {
- if (r.isValidated) {
+ if (r.mIsValidated) {
return NetworkEvent.NETWORK_REVALIDATION_SUCCESS;
} else {
return NetworkEvent.NETWORK_REVALIDATION_PORTAL_FOUND;
@@ -1509,15 +1587,15 @@
private void maybeLogEvaluationResult(int evtype) {
if (mEvaluationTimer.isRunning()) {
- int[] transports = mNetworkAgentInfo.networkCapabilities.getTransportTypes();
+ int[] transports = mNetworkCapabilities.getTransportTypes();
mMetricsLog.log(mNetId, transports, new NetworkEvent(evtype, mEvaluationTimer.stop()));
mEvaluationTimer.reset();
}
}
private void logValidationProbe(long durationMs, int probeType, int probeResult) {
- int[] transports = mNetworkAgentInfo.networkCapabilities.getTransportTypes();
- boolean isFirstValidation = validationStage().isFirstValidation;
+ int[] transports = mNetworkCapabilities.getTransportTypes();
+ boolean isFirstValidation = validationStage().mIsFirstValidation;
ValidationProbeEvent ev = new ValidationProbeEvent();
ev.probeType = ValidationProbeEvent.makeProbeType(probeType, isFirstValidation);
ev.returnCode = probeResult;
@@ -1526,19 +1604,36 @@
}
@VisibleForTesting
- public static class Dependencies {
- public Network getNetwork(NetworkAgentInfo networkAgentInfo) {
- return new OneAddressPerFamilyNetwork(networkAgentInfo.network());
+ static class Dependencies {
+ public Network getPrivateDnsBypassNetwork(Network network) {
+ return new OneAddressPerFamilyNetwork(network);
}
public Random getRandom() {
return new Random();
}
+ /**
+ * Get the captive portal server HTTP URL that is configured on the device.
+ */
+ public String getCaptivePortalServerHttpUrl(Context context) {
+ return NetworkMonitorUtils.getCaptivePortalServerHttpUrl(context);
+ }
+
+ /**
+ * Get the value of a global integer setting.
+ * @param symbol Name of the setting
+ * @param defaultValue Value to return if the setting is not defined.
+ */
public int getSetting(Context context, String symbol, int defaultValue) {
return Settings.Global.getInt(context.getContentResolver(), symbol, defaultValue);
}
+ /**
+ * Get the value of a global String setting.
+ * @param symbol Name of the setting
+ * @param defaultValue Value to return if the setting is not defined.
+ */
public String getSetting(Context context, String symbol, String defaultValue) {
final String value = Settings.Global.getString(context.getContentResolver(), symbol);
return value != null ? value : defaultValue;
@@ -1645,7 +1740,7 @@
boolean result = false;
// Reevaluation will generate traffic. Thus, set a minimal reevaluation timer to limit the
// possible traffic cost in metered network.
- if (mNetworkAgentInfo.networkCapabilities.isMetered()
+ if (mNetworkCapabilities.isMetered()
&& (SystemClock.elapsedRealtime() - getLastProbeTime()
< mDataStallMinEvaluateTime)) {
return false;
diff --git a/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java b/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java
new file mode 100644
index 0000000..bb5900c
--- /dev/null
+++ b/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+package com.android.server.util;
+
+/**
+ * Network constants used by the network stack.
+ */
+public final class NetworkStackConstants {
+
+ /**
+ * IPv4 constants.
+ *
+ * See also:
+ * - https://tools.ietf.org/html/rfc791
+ */
+ public static final int IPV4_ADDR_BITS = 32;
+ public static final int IPV4_MIN_MTU = 68;
+ public static final int IPV4_MAX_MTU = 65_535;
+
+ /**
+ * DHCP constants.
+ *
+ * See also:
+ * - https://tools.ietf.org/html/rfc2131
+ */
+ public static final int INFINITE_LEASE = 0xffffffff;
+
+ private NetworkStackConstants() {
+ throw new UnsupportedOperationException("This class is not to be instantiated");
+ }
+}
diff --git a/packages/NetworkStack/src/com/android/server/util/PermissionUtil.java b/packages/NetworkStack/src/com/android/server/util/PermissionUtil.java
new file mode 100644
index 0000000..733f873
--- /dev/null
+++ b/packages/NetworkStack/src/com/android/server/util/PermissionUtil.java
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+package com.android.server.util;
+
+import static android.os.Binder.getCallingUid;
+
+import android.os.Process;
+
+/**
+ * Utility class to check calling permissions on the network stack.
+ */
+public final class PermissionUtil {
+
+ /**
+ * Check that the caller is allowed to communicate with the network stack.
+ * @throws SecurityException The caller is not allowed to communicate with the network stack.
+ */
+ public static void checkNetworkStackCallingPermission() {
+ // TODO: check that the calling PID is the system server.
+ if (getCallingUid() != Process.SYSTEM_UID && getCallingUid() != Process.ROOT_UID) {
+ throw new SecurityException("Invalid caller: " + getCallingUid());
+ }
+ }
+
+ private PermissionUtil() {
+ throw new UnsupportedOperationException("This class is not to be instantiated");
+ }
+}
diff --git a/packages/NetworkStack/tests/Android.bp b/packages/NetworkStack/tests/Android.bp
new file mode 100644
index 0000000..bd7ff2a
--- /dev/null
+++ b/packages/NetworkStack/tests/Android.bp
@@ -0,0 +1,35 @@
+//
+// 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: "NetworkStackTests",
+ srcs: ["src/**/*.java"],
+ static_libs: [
+ "android-support-test",
+ "mockito-target-extended-minus-junit4",
+ "NetworkStackLib",
+ "testables",
+ ],
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ ],
+ jni_libs: [
+ // For mockito extended
+ "libdexmakerjvmtiagent",
+ "libstaticjvmtiagent",
+ ]
+}
\ No newline at end of file
diff --git a/packages/NetworkStack/tests/AndroidManifest.xml b/packages/NetworkStack/tests/AndroidManifest.xml
new file mode 100644
index 0000000..8b8474f
--- /dev/null
+++ b/packages/NetworkStack/tests/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.networkstack.tests">
+ <application android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+ </application>
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.server.networkstack.tests"
+ android:label="Networking service tests">
+ </instrumentation>
+</manifest>
\ No newline at end of file
diff --git a/packages/NetworkStack/tests/AndroidTest.xml b/packages/NetworkStack/tests/AndroidTest.xml
new file mode 100644
index 0000000..6b08b57
--- /dev/null
+++ b/packages/NetworkStack/tests/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Runs Tests for NetworkStack">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="NetworkStackTests.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="framework-base-presubmit" />
+ <option name="test-tag" value="NetworkStackTests" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.server.networkstack.tests" />
+ <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
\ No newline at end of file
diff --git a/tests/net/java/android/net/dhcp/DhcpLeaseRepositoryTest.java b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpLeaseRepositoryTest.java
similarity index 99%
rename from tests/net/java/android/net/dhcp/DhcpLeaseRepositoryTest.java
rename to packages/NetworkStack/tests/src/android/net/dhcp/DhcpLeaseRepositoryTest.java
index 7f8e7b5..51d50d9 100644
--- a/tests/net/java/android/net/dhcp/DhcpLeaseRepositoryTest.java
+++ b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpLeaseRepositoryTest.java
@@ -16,6 +16,7 @@
package android.net.dhcp;
+import static android.net.InetAddresses.parseNumericAddress;
import static android.net.dhcp.DhcpLease.HOSTNAME_NONE;
import static android.net.dhcp.DhcpLeaseRepository.CLIENTID_UNSPEC;
import static android.net.dhcp.DhcpLeaseRepository.INETADDR_UNSPEC;
@@ -29,14 +30,13 @@
import static org.mockito.Mockito.when;
import static java.lang.String.format;
-import static java.net.InetAddress.parseNumericAddress;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.IpPrefix;
import android.net.MacAddress;
-import android.net.util.SharedLog;
import android.net.dhcp.DhcpServer.Clock;
+import android.net.util.SharedLog;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -126,7 +126,7 @@
mRepo.updateParams(new IpPrefix(TEST_SERVER_ADDR, 28), TEST_EXCL_SET, TEST_LEASE_TIME_MS);
// /28 should have 16 addresses, 14 w/o the first/last, 11 w/o excluded addresses
- requestAddresses((byte)11);
+ requestAddresses((byte) 11);
try {
mRepo.getOffer(null, TEST_MAC_2,
diff --git a/tests/net/java/android/net/dhcp/DhcpServerTest.java b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpServerTest.java
similarity index 89%
rename from tests/net/java/android/net/dhcp/DhcpServerTest.java
rename to packages/NetworkStack/tests/src/android/net/dhcp/DhcpServerTest.java
index ab9bd84..d4c1e2e 100644
--- a/tests/net/java/android/net/dhcp/DhcpServerTest.java
+++ b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpServerTest.java
@@ -16,11 +16,13 @@
package android.net.dhcp;
+import static android.net.InetAddresses.parseNumericAddress;
import static android.net.dhcp.DhcpPacket.DHCP_CLIENT;
import static android.net.dhcp.DhcpPacket.DHCP_HOST_NAME;
import static android.net.dhcp.DhcpPacket.ENCAP_BOOTP;
import static android.net.dhcp.DhcpPacket.INADDR_ANY;
import static android.net.dhcp.DhcpPacket.INADDR_BROADCAST;
+import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
@@ -33,14 +35,14 @@
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import static java.net.InetAddress.parseNumericAddress;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.net.INetworkStackStatusCallback;
import android.net.LinkAddress;
import android.net.MacAddress;
import android.net.dhcp.DhcpLeaseRepository.InvalidAddressException;
@@ -48,9 +50,11 @@
import android.net.dhcp.DhcpServer.Clock;
import android.net.dhcp.DhcpServer.Dependencies;
import android.net.util.SharedLog;
-import android.os.test.TestLooper;
+import android.os.HandlerThread;
import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
import org.junit.After;
import org.junit.Before;
@@ -67,10 +71,10 @@
import java.util.HashSet;
import java.util.Set;
-@RunWith(AndroidJUnit4.class)
+@RunWith(AndroidTestingRunner.class)
@SmallTest
+@RunWithLooper
public class DhcpServerTest {
- private static final String PROP_DEXMAKER_SHARE_CLASSLOADER = "dexmaker.share_classloader";
private static final String TEST_IFACE = "testiface";
private static final Inet4Address TEST_SERVER_ADDR = parseAddr("192.168.0.2");
@@ -113,18 +117,25 @@
private ArgumentCaptor<Inet4Address> mResponseDstAddrCaptor;
@NonNull
- private TestLooper mLooper;
+ private HandlerThread mHandlerThread;
+ @NonNull
+ private TestableLooper mLooper;
@NonNull
private DhcpServer mServer;
@Nullable
private String mPrevShareClassloaderProp;
+ private final INetworkStackStatusCallback mAssertSuccessCallback =
+ new INetworkStackStatusCallback.Stub() {
+ @Override
+ public void onStatusAvailable(int statusCode) {
+ assertEquals(STATUS_SUCCESS, statusCode);
+ }
+ };
+
@Before
public void setUp() throws Exception {
- // Allow mocking package-private classes
- mPrevShareClassloaderProp = System.getProperty(PROP_DEXMAKER_SHARE_CLASSLOADER);
- System.setProperty(PROP_DEXMAKER_SHARE_CLASSLOADER, "true");
MockitoAnnotations.initMocks(this);
when(mDeps.makeLeaseRepository(any(), any(), any())).thenReturn(mRepository);
@@ -143,20 +154,22 @@
.setExcludedAddrs(TEST_EXCLUDED_ADDRS)
.build();
- mLooper = new TestLooper();
- mServer = new DhcpServer(mLooper.getLooper(), TEST_IFACE, servingParams,
+ mLooper = TestableLooper.get(this);
+ mHandlerThread = spy(new HandlerThread("TestDhcpServer"));
+ when(mHandlerThread.getLooper()).thenReturn(mLooper.getLooper());
+ mServer = new DhcpServer(mHandlerThread, TEST_IFACE, servingParams,
new SharedLog(DhcpServerTest.class.getSimpleName()), mDeps);
- mServer.start();
- mLooper.dispatchAll();
+ mServer.start(mAssertSuccessCallback);
+ mLooper.processAllMessages();
}
@After
- public void tearDown() {
- // Calling stop() several times is not an issue
- mServer.stop();
- System.setProperty(PROP_DEXMAKER_SHARE_CLASSLOADER,
- (mPrevShareClassloaderProp == null ? "" : mPrevShareClassloaderProp));
+ public void tearDown() throws Exception {
+ mServer.stop(mAssertSuccessCallback);
+ mLooper.processMessages(1);
+ verify(mPacketListener, times(1)).stop();
+ verify(mHandlerThread, times(1)).quitSafely();
}
@Test
@@ -165,13 +178,6 @@
}
@Test
- public void testStop() throws Exception {
- mServer.stop();
- mLooper.dispatchAll();
- verify(mPacketListener, times(1)).stop();
- }
-
- @Test
public void testDiscover() throws Exception {
// TODO: refactor packet construction to eliminate unnecessary/confusing/duplicate fields
when(mRepository.getOffer(isNull() /* clientId */, eq(TEST_CLIENT_MAC),
diff --git a/tests/net/java/android/net/dhcp/DhcpServingParamsTest.java b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpServingParamsTest.java
similarity index 74%
rename from tests/net/java/android/net/dhcp/DhcpServingParamsTest.java
rename to packages/NetworkStack/tests/src/android/net/dhcp/DhcpServingParamsTest.java
index b6a4073..3ca0564 100644
--- a/tests/net/java/android/net/dhcp/DhcpServingParamsTest.java
+++ b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpServingParamsTest.java
@@ -16,17 +16,18 @@
package android.net.dhcp;
+import static android.net.InetAddresses.parseNumericAddress;
+import static android.net.NetworkUtils.inet4AddressToIntHTH;
import static android.net.dhcp.DhcpServingParams.MTU_UNSET;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
-import static java.net.InetAddress.parseNumericAddress;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.LinkAddress;
+import android.net.NetworkUtils;
import android.net.dhcp.DhcpServingParams.InvalidParameterException;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -35,8 +36,10 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.lang.reflect.Modifier;
import java.net.Inet4Address;
import java.util.Arrays;
+import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
@@ -56,6 +59,7 @@
private static final int TEST_MTU = 1500;
private static final Set<Inet4Address> TEST_EXCLUDED_ADDRS = new HashSet<>(
Arrays.asList(parseAddr("192.168.0.200"), parseAddr("192.168.0.201")));
+ private static final boolean TEST_METERED = true;
@Before
public void setUp() {
@@ -65,7 +69,8 @@
.setDnsServers(TEST_DNS_SERVERS)
.setServerAddr(TEST_LINKADDR)
.setLinkMtu(TEST_MTU)
- .setExcludedAddrs(TEST_EXCLUDED_ADDRS);
+ .setExcludedAddrs(TEST_EXCLUDED_ADDRS)
+ .setMetered(TEST_METERED);
}
@Test
@@ -91,6 +96,7 @@
assertEquals(TEST_DNS_SERVERS, params.dnsServers);
assertEquals(TEST_LINKADDR, params.serverAddr);
assertEquals(TEST_MTU, params.linkMtu);
+ assertEquals(TEST_METERED, params.metered);
assertContains(params.excludedAddrs, TEST_EXCLUDED_ADDRS);
assertContains(params.excludedAddrs, TEST_DEFAULT_ROUTERS);
@@ -159,6 +165,44 @@
mBuilder.setDefaultRouters(parseAddr("192.168.254.254")).build();
}
+ @Test
+ public void testFromParcelableObject() throws InvalidParameterException {
+ final DhcpServingParams params = mBuilder.build();
+ final DhcpServingParamsParcel parcel = new DhcpServingParamsParcel();
+ parcel.defaultRouters = toIntArray(TEST_DEFAULT_ROUTERS);
+ parcel.dhcpLeaseTimeSecs = TEST_LEASE_TIME_SECS;
+ parcel.dnsServers = toIntArray(TEST_DNS_SERVERS);
+ parcel.serverAddr = inet4AddressToIntHTH(TEST_SERVER_ADDR);
+ parcel.serverAddrPrefixLength = TEST_LINKADDR.getPrefixLength();
+ parcel.linkMtu = TEST_MTU;
+ parcel.excludedAddrs = toIntArray(TEST_EXCLUDED_ADDRS);
+ parcel.metered = TEST_METERED;
+ final DhcpServingParams parceled = DhcpServingParams.fromParcelableObject(parcel);
+
+ assertEquals(params.defaultRouters, parceled.defaultRouters);
+ assertEquals(params.dhcpLeaseTimeSecs, parceled.dhcpLeaseTimeSecs);
+ assertEquals(params.dnsServers, parceled.dnsServers);
+ assertEquals(params.serverAddr, parceled.serverAddr);
+ assertEquals(params.linkMtu, parceled.linkMtu);
+ assertEquals(params.excludedAddrs, parceled.excludedAddrs);
+ assertEquals(params.metered, parceled.metered);
+
+ // Ensure that we do not miss any field if added in the future
+ final long numFields = Arrays.stream(DhcpServingParams.class.getDeclaredFields())
+ .filter(f -> !Modifier.isStatic(f.getModifiers()))
+ .count();
+ assertEquals(7, numFields);
+ }
+
+ @Test(expected = InvalidParameterException.class)
+ public void testFromParcelableObject_NullArgument() throws InvalidParameterException {
+ DhcpServingParams.fromParcelableObject(null);
+ }
+
+ private static int[] toIntArray(Collection<Inet4Address> addrs) {
+ return addrs.stream().mapToInt(NetworkUtils::inet4AddressToIntHTH).toArray();
+ }
+
private static <T> void assertContains(@NonNull Set<T> set, @NonNull Set<T> subset) {
for (final T elem : subset) {
assertContains(set, elem);
diff --git a/tests/net/java/com/android/server/connectivity/NetworkMonitorTest.java b/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java
similarity index 68%
rename from tests/net/java/com/android/server/connectivity/NetworkMonitorTest.java
rename to packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java
index 6e07b26..d31fa77 100644
--- a/tests/net/java/com/android/server/connectivity/NetworkMonitorTest.java
+++ b/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java
@@ -16,6 +16,14 @@
package com.android.server.connectivity;
+import static android.net.ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN;
+import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL;
+import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID;
+import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_VALID;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+
+import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -24,13 +32,21 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.content.Intent;
+import android.net.CaptivePortal;
import android.net.ConnectivityManager;
+import android.net.INetworkMonitorCallbacks;
+import android.net.InetAddresses;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
@@ -38,9 +54,12 @@
import android.net.NetworkRequest;
import android.net.captiveportal.CaptivePortalProbeResult;
import android.net.metrics.IpConnectivityLog;
+import android.net.util.SharedLog;
import android.net.wifi.WifiManager;
+import android.os.ConditionVariable;
import android.os.Handler;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.provider.Settings;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -50,8 +69,10 @@
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;
+import org.mockito.Spy;
import java.io.IOException;
import java.net.HttpURLConnection;
@@ -68,21 +89,23 @@
private static final String LOCATION_HEADER = "location";
private @Mock Context mContext;
- private @Mock Handler mHandler;
private @Mock IpConnectivityLog mLogger;
- private @Mock NetworkAgentInfo mAgent;
- private @Mock NetworkAgentInfo mNotMeteredAgent;
+ private @Mock SharedLog mValidationLogger;
private @Mock NetworkInfo mNetworkInfo;
- private @Mock NetworkRequest mRequest;
+ private @Mock ConnectivityManager mCm;
private @Mock TelephonyManager mTelephony;
private @Mock WifiManager mWifi;
- private @Mock Network mNetwork;
private @Mock HttpURLConnection mHttpConnection;
private @Mock HttpURLConnection mHttpsConnection;
private @Mock HttpURLConnection mFallbackConnection;
private @Mock HttpURLConnection mOtherFallbackConnection;
private @Mock Random mRandom;
private @Mock NetworkMonitor.Dependencies mDependencies;
+ private @Mock INetworkMonitorCallbacks mCallbacks;
+ private @Spy Network mNetwork = new Network(TEST_NETID);
+ private NetworkRequest mRequest;
+
+ private static final int TEST_NETID = 4242;
private static final String TEST_HTTP_URL = "http://www.google.com/gen_204";
private static final String TEST_HTTPS_URL = "https://www.google.com/gen_204";
@@ -93,33 +116,37 @@
private static final int RETURN_CODE_DNS_SUCCESS = 0;
private static final int RETURN_CODE_DNS_TIMEOUT = 255;
+ private static final int HANDLER_TIMEOUT_MS = 1000;
+
+ private static final LinkProperties TEST_LINKPROPERTIES = new LinkProperties();
+
+ private static final NetworkCapabilities METERED_CAPABILITIES = new NetworkCapabilities()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_INTERNET);
+
+ private static final NetworkCapabilities NOT_METERED_CAPABILITIES = new NetworkCapabilities()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+
+ private static final NetworkCapabilities NO_INTERNET_CAPABILITIES = new NetworkCapabilities()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+
@Before
public void setUp() throws IOException {
MockitoAnnotations.initMocks(this);
- mAgent.linkProperties = new LinkProperties();
- mAgent.networkCapabilities = new NetworkCapabilities()
- .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
- mAgent.networkInfo = mNetworkInfo;
-
- mNotMeteredAgent.linkProperties = new LinkProperties();
- mNotMeteredAgent.networkCapabilities = new NetworkCapabilities()
- .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
- .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
- mNotMeteredAgent.networkInfo = mNetworkInfo;
-
- when(mAgent.network()).thenReturn(mNetwork);
- when(mDependencies.getNetwork(any())).thenReturn(mNetwork);
+ when(mDependencies.getPrivateDnsBypassNetwork(any())).thenReturn(mNetwork);
when(mDependencies.getRandom()).thenReturn(mRandom);
when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_MODE), anyInt()))
.thenReturn(Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT);
when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_USE_HTTPS),
anyInt())).thenReturn(1);
- when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_HTTP_URL),
- anyString())).thenReturn(TEST_HTTP_URL);
+ when(mDependencies.getCaptivePortalServerHttpUrl(any())).thenReturn(TEST_HTTP_URL);
when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_HTTPS_URL),
anyString())).thenReturn(TEST_HTTPS_URL);
- when(mNetwork.getPrivateDnsBypassingCopy()).thenReturn(mNetwork);
+ doReturn(mNetwork).when(mNetwork).getPrivateDnsBypassingCopy();
+ when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(mCm);
when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephony);
when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mWifi);
@@ -129,7 +156,7 @@
setFallbackSpecs(null); // Test with no fallback spec by default
when(mRandom.nextInt()).thenReturn(0);
- when(mNetwork.openConnection(any())).then((invocation) -> {
+ doAnswer((invocation) -> {
URL url = invocation.getArgument(0);
switch(url.toString()) {
case TEST_HTTP_URL:
@@ -144,12 +171,20 @@
fail("URL not mocked: " + url.toString());
return null;
}
- });
+ }).when(mNetwork).openConnection(any());
when(mHttpConnection.getRequestProperties()).thenReturn(new ArrayMap<>());
when(mHttpsConnection.getRequestProperties()).thenReturn(new ArrayMap<>());
- when(mNetwork.getAllByName(any())).thenReturn(new InetAddress[] {
- InetAddress.parseNumericAddress("192.168.0.0")
- });
+ doReturn(new InetAddress[] {
+ InetAddresses.parseNumericAddress("192.168.0.0")
+ }).when(mNetwork).getAllByName(any());
+
+ mRequest = new NetworkRequest.Builder()
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ .build();
+ // Default values. Individual tests can override these.
+ when(mCm.getLinkProperties(any())).thenReturn(TEST_LINKPROPERTIES);
+ when(mCm.getNetworkCapabilities(any())).thenReturn(METERED_CAPABILITIES);
setMinDataStallEvaluateInterval(500);
setDataStallEvaluationType(1 << DATA_STALL_EVALUATION_TYPE_DNS);
@@ -160,10 +195,10 @@
private class WrappedNetworkMonitor extends NetworkMonitor {
private long mProbeTime = 0;
- WrappedNetworkMonitor(Context context, Handler handler,
- NetworkAgentInfo networkAgentInfo, NetworkRequest defaultRequest,
+ WrappedNetworkMonitor(Context context, Network network, NetworkRequest defaultRequest,
IpConnectivityLog logger, Dependencies deps) {
- super(context, handler, networkAgentInfo, defaultRequest, logger, deps);
+ super(context, mCallbacks, network, defaultRequest, logger,
+ new SharedLog("test_nm"), deps);
}
@Override
@@ -176,19 +211,39 @@
}
}
- WrappedNetworkMonitor makeMeteredWrappedNetworkMonitor() {
- return new WrappedNetworkMonitor(
- mContext, mHandler, mAgent, mRequest, mLogger, mDependencies);
+ private WrappedNetworkMonitor makeMeteredWrappedNetworkMonitor() {
+ final WrappedNetworkMonitor nm = new WrappedNetworkMonitor(
+ mContext, mNetwork, mRequest, mLogger, mDependencies);
+ when(mCm.getNetworkCapabilities(any())).thenReturn(METERED_CAPABILITIES);
+ nm.start();
+ waitForIdle(nm.getHandler());
+ return nm;
}
- WrappedNetworkMonitor makeNotMeteredWrappedNetworkMonitor() {
- return new WrappedNetworkMonitor(
- mContext, mHandler, mNotMeteredAgent, mRequest, mLogger, mDependencies);
+ private WrappedNetworkMonitor makeNotMeteredWrappedNetworkMonitor() {
+ final WrappedNetworkMonitor nm = new WrappedNetworkMonitor(
+ mContext, mNetwork, mRequest, mLogger, mDependencies);
+ when(mCm.getNetworkCapabilities(any())).thenReturn(NOT_METERED_CAPABILITIES);
+ nm.start();
+ waitForIdle(nm.getHandler());
+ return nm;
}
- NetworkMonitor makeMonitor() {
- return new NetworkMonitor(
- mContext, mHandler, mAgent, mRequest, mLogger, mDependencies);
+ private NetworkMonitor makeMonitor() {
+ final NetworkMonitor nm = new NetworkMonitor(
+ mContext, mCallbacks, mNetwork, mRequest, mLogger, mValidationLogger,
+ mDependencies);
+ nm.start();
+ waitForIdle(nm.getHandler());
+ return nm;
+ }
+
+ private void waitForIdle(Handler handler) {
+ final ConditionVariable cv = new ConditionVariable(false);
+ handler.post(cv::open);
+ if (!cv.block(HANDLER_TIMEOUT_MS)) {
+ fail("Timed out waiting for handler");
+ }
}
@Test
@@ -319,6 +374,15 @@
}
@Test
+ public void testIsCaptivePortal_IgnorePortals() throws IOException {
+ setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_IGNORE);
+ setSslException(mHttpsConnection);
+ setPortal302(mHttpConnection);
+
+ assertNotPortal(makeMonitor().isCaptivePortal());
+ }
+
+ @Test
public void testIsDataStall_EvaluationDisabled() {
setDataStallEvaluationType(0);
WrappedNetworkMonitor wrappedMonitor = makeMeteredWrappedNetworkMonitor();
@@ -390,6 +454,63 @@
assertFalse(wrappedMonitor.isDataStall());
}
+ @Test
+ public void testBrokenNetworkNotValidated() throws Exception {
+ setSslException(mHttpsConnection);
+ setStatus(mHttpConnection, 500);
+ setStatus(mFallbackConnection, 404);
+ when(mCm.getNetworkCapabilities(any())).thenReturn(METERED_CAPABILITIES);
+
+ final NetworkMonitor nm = makeMonitor();
+ nm.notifyNetworkConnected();
+
+ verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
+ .notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, null);
+ }
+
+ @Test
+ public void testNoInternetCapabilityValidated() throws Exception {
+ when(mCm.getNetworkCapabilities(any())).thenReturn(NO_INTERNET_CAPABILITIES);
+
+ final NetworkMonitor nm = makeMonitor();
+ nm.notifyNetworkConnected();
+
+ verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
+ .notifyNetworkTested(NETWORK_TEST_RESULT_VALID, null);
+ verify(mNetwork, never()).openConnection(any());
+ }
+
+ @Test
+ public void testLaunchCaptivePortalApp() throws Exception {
+ setSslException(mHttpsConnection);
+ setPortal302(mHttpConnection);
+
+ final NetworkMonitor nm = makeMonitor();
+ nm.notifyNetworkConnected();
+
+ verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
+ .showProvisioningNotification(any());
+
+ // Check that startCaptivePortalApp sends the expected intent.
+ nm.launchCaptivePortalApp();
+
+ final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mContext, timeout(HANDLER_TIMEOUT_MS).times(1))
+ .startActivityAsUser(intentCaptor.capture(), eq(UserHandle.CURRENT));
+ final Intent intent = intentCaptor.getValue();
+ assertEquals(ACTION_CAPTIVE_PORTAL_SIGN_IN, intent.getAction());
+ final Network network = intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK);
+ assertEquals(TEST_NETID, network.netId);
+
+ // Have the app report that the captive portal is dismissed, and check that we revalidate.
+ setStatus(mHttpsConnection, 204);
+ setStatus(mHttpConnection, 204);
+ final CaptivePortal captivePortal = intent.getParcelableExtra(EXTRA_CAPTIVE_PORTAL);
+ captivePortal.reportCaptivePortalDismissed();
+ verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
+ .notifyNetworkTested(NETWORK_TEST_RESULT_VALID, null);
+ }
+
private void makeDnsTimeoutEvent(WrappedNetworkMonitor wrappedMonitor, int count) {
for (int i = 0; i < count; i++) {
wrappedMonitor.getDnsStallDetector().accumulateConsecutiveDnsTimeoutCount(
@@ -440,6 +561,11 @@
eq(Settings.Global.CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS), any())).thenReturn(specs);
}
+ private void setCaptivePortalMode(int mode) {
+ when(mDependencies.getSetting(any(),
+ eq(Settings.Global.CAPTIVE_PORTAL_MODE), anyInt())).thenReturn(mode);
+ }
+
private void assertPortal(CaptivePortalProbeResult result) {
assertTrue(result.isPortal());
assertFalse(result.isFailed());
@@ -459,12 +585,12 @@
}
private void setSslException(HttpURLConnection connection) throws IOException {
- when(connection.getResponseCode()).thenThrow(new SSLHandshakeException("Invalid cert"));
+ doThrow(new SSLHandshakeException("Invalid cert")).when(connection).getResponseCode();
}
private void set302(HttpURLConnection connection, String location) throws IOException {
setStatus(connection, 302);
- when(connection.getHeaderField(LOCATION_HEADER)).thenReturn(location);
+ doReturn(location).when(connection).getHeaderField(LOCATION_HEADER);
}
private void setPortal302(HttpURLConnection connection) throws IOException {
@@ -472,7 +598,7 @@
}
private void setStatus(HttpURLConnection connection, int status) throws IOException {
- when(connection.getResponseCode()).thenReturn(status);
+ doReturn(status).when(connection).getResponseCode();
}
}
diff --git a/packages/NetworkStack/tests/src/com/android/server/util/SharedLogTest.java b/packages/NetworkStack/tests/src/com/android/server/util/SharedLogTest.java
new file mode 100644
index 0000000..07ad3123
--- /dev/null
+++ b/packages/NetworkStack/tests/src/com/android/server/util/SharedLogTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.net.util.SharedLog;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintWriter;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class SharedLogTest {
+ private static final String TIMESTAMP_PATTERN = "\\d{2}:\\d{2}:\\d{2}";
+ private static final String TIMESTAMP = "HH:MM:SS";
+
+ @Test
+ public void testBasicOperation() {
+ final SharedLog logTop = new SharedLog("top");
+ logTop.mark("first post!");
+
+ final SharedLog logLevel2a = logTop.forSubComponent("twoA");
+ final SharedLog logLevel2b = logTop.forSubComponent("twoB");
+ logLevel2b.e("2b or not 2b");
+ logLevel2b.e("No exception", null);
+ logLevel2b.e("Wait, here's one", new Exception("Test"));
+ logLevel2a.w("second post?");
+
+ final SharedLog logLevel3 = logLevel2a.forSubComponent("three");
+ logTop.log("still logging");
+ logLevel3.log("3 >> 2");
+ logLevel2a.mark("ok: last post");
+
+ final String[] expected = {
+ " - MARK first post!",
+ " - [twoB] ERROR 2b or not 2b",
+ " - [twoB] ERROR No exception",
+ // No stacktrace in shared log, only in logcat
+ " - [twoB] ERROR Wait, here's one: Test",
+ " - [twoA] WARN second post?",
+ " - still logging",
+ " - [twoA.three] 3 >> 2",
+ " - [twoA] MARK ok: last post",
+ };
+ // Verify the logs are all there and in the correct order.
+ verifyLogLines(expected, logTop);
+
+ // In fact, because they all share the same underlying LocalLog,
+ // every subcomponent SharedLog's dump() is identical.
+ verifyLogLines(expected, logLevel2a);
+ verifyLogLines(expected, logLevel2b);
+ verifyLogLines(expected, logLevel3);
+ }
+
+ private static void verifyLogLines(String[] expected, SharedLog log) {
+ final ByteArrayOutputStream ostream = new ByteArrayOutputStream();
+ final PrintWriter pw = new PrintWriter(ostream, true);
+ log.dump(null, pw, null);
+
+ final String dumpOutput = ostream.toString();
+ assertTrue(dumpOutput != null);
+ assertTrue(!"".equals(dumpOutput));
+
+ final String[] lines = dumpOutput.split("\n");
+ assertEquals(expected.length, lines.length);
+
+ for (int i = 0; i < expected.length; i++) {
+ String got = lines[i];
+ String want = expected[i];
+ assertTrue(String.format("'%s' did not contain '%s'", got, want), got.endsWith(want));
+ assertTrue(String.format("'%s' did not contain a %s timestamp", got, TIMESTAMP),
+ got.replaceFirst(TIMESTAMP_PATTERN, TIMESTAMP).contains(TIMESTAMP));
+ }
+ }
+}
diff --git a/packages/SettingsLib/BarChartPreference/src/com/android/settingslib/widget/BarChartPreference.java b/packages/SettingsLib/BarChartPreference/src/com/android/settingslib/widget/BarChartPreference.java
index eed66e9..d332bac 100644
--- a/packages/SettingsLib/BarChartPreference/src/com/android/settingslib/widget/BarChartPreference.java
+++ b/packages/SettingsLib/BarChartPreference/src/com/android/settingslib/widget/BarChartPreference.java
@@ -77,7 +77,7 @@
*/
public class BarChartPreference extends Preference {
- static final int MAXIMUM_BAR_VIEWS = 4;
+ public static final int MAXIMUM_BAR_VIEWS = 4;
private static final String TAG = "BarChartPreference";
private static final int[] BAR_VIEWS = {
R.id.bar_view1,
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 7dcc3ac..03c6205 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1137,9 +1137,6 @@
<!-- The notice header of Third-party licenses. not translatable -->
<string name="notice_header" translatable="false"></string>
- <!-- UI debug setting: opt in to use updated graphics driver? [CHAR LIMIT=100] -->
- <string name="gup_dev_opt_in_app_summary">Opt in app to use Game Update Package in developement</string>
-
<!-- Name of the phone device [CHAR LIMIT=NONE] -->
<string name="media_transfer_phone_device_name">Phone speaker</string>
</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 6abe76a..595aeb3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -1,5 +1,7 @@
package com.android.settingslib;
+import static android.telephony.ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN;
+
import android.annotation.ColorInt;
import android.content.Context;
import android.content.Intent;
@@ -429,12 +431,14 @@
// and do not support voice service, and on these SIM cards, we
// want to show signal bars for data service as well as the "no
// service" or "emergency calls only" text that indicates that voice
- // is not available.
+ // is not available. Note that we ignore the IWLAN service state
+ // because that state indicates the use of VoWIFI and not cell service
int state = serviceState.getState();
int dataState = serviceState.getDataRegState();
if (state == ServiceState.STATE_OUT_OF_SERVICE
|| state == ServiceState.STATE_EMERGENCY_ONLY) {
- if (dataState == ServiceState.STATE_IN_SERVICE) {
+ if (dataState == ServiceState.STATE_IN_SERVICE
+ && serviceState.getDataNetworkType() != RIL_RADIO_TECHNOLOGY_IWLAN) {
return ServiceState.STATE_IN_SERVICE;
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
index 58feef5..24d7011 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
@@ -35,7 +35,6 @@
public class A2dpProfile implements LocalBluetoothProfile {
private static final String TAG = "A2dpProfile";
- private static boolean V = false;
private Context mContext;
@@ -60,7 +59,6 @@
implements BluetoothProfile.ServiceListener {
public void onServiceConnected(int profile, BluetoothProfile proxy) {
- if (V) Log.d(TAG,"Bluetooth service connected");
mService = (BluetoothA2dp) proxy;
// We just bound to the service, so refresh the UI for any connected A2DP devices.
List<BluetoothDevice> deviceList = mService.getConnectedDevices();
@@ -79,7 +77,6 @@
}
public void onServiceDisconnected(int profile) {
- if (V) Log.d(TAG,"Bluetooth service disconnected");
mIsProfileReady=false;
}
}
@@ -302,7 +299,7 @@
}
protected void finalize() {
- if (V) Log.d(TAG, "finalize()");
+ Log.d(TAG, "finalize()");
if (mService != null) {
try {
BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.A2DP,
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
index 988062de..873dd1a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
@@ -55,7 +55,6 @@
implements BluetoothProfile.ServiceListener {
public void onServiceConnected(int profile, BluetoothProfile proxy) {
- Log.d(TAG, "Bluetooth service connected");
mService = (BluetoothA2dpSink) proxy;
// We just bound to the service, so refresh the UI for any connected A2DP devices.
List<BluetoothDevice> deviceList = mService.getConnectedDevices();
@@ -74,7 +73,6 @@
}
public void onServiceDisconnected(int profile) {
- Log.d(TAG, "Bluetooth service disconnected");
mIsProfileReady=false;
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
index 62507f5..6b6df9b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
@@ -58,7 +58,6 @@
implements BluetoothProfile.ServiceListener {
public void onServiceConnected(int profile, BluetoothProfile proxy) {
- Log.d(TAG,"Bluetooth service connected");
mService = (BluetoothHeadset) proxy;
// We just bound to the service, so refresh the UI for any connected HFP devices.
List<BluetoothDevice> deviceList = mService.getConnectedDevices();
@@ -80,7 +79,6 @@
}
public void onServiceDisconnected(int profile) {
- Log.d(TAG,"Bluetooth service disconnected");
mProfileManager.callServiceDisconnectedListeners();
mIsProfileReady=false;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
index adb5ab3..577d98d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
@@ -51,7 +51,6 @@
implements BluetoothProfile.ServiceListener {
public void onServiceConnected(int profile, BluetoothProfile proxy) {
- if (V) Log.d(TAG,"Bluetooth service connected");
mService = (BluetoothHearingAid) proxy;
// We just bound to the service, so refresh the UI for any connected HearingAid devices.
List<BluetoothDevice> deviceList = mService.getConnectedDevices();
@@ -77,7 +76,6 @@
}
public void onServiceDisconnected(int profile) {
- if (V) Log.d(TAG,"Bluetooth service disconnected");
mIsProfileReady=false;
}
}
@@ -234,7 +232,7 @@
}
protected void finalize() {
- if (V) Log.d(TAG, "finalize()");
+ Log.d(TAG, "finalize()");
if (mService != null) {
try {
BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.HEARING_AID,
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
index 4879144..c6bb2b3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
@@ -59,7 +59,6 @@
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
- Log.d(TAG, "Bluetooth service connected");
mService = (BluetoothHeadsetClient) proxy;
// We just bound to the service, so refresh the UI for any connected HFP devices.
List<BluetoothDevice> deviceList = mService.getConnectedDevices();
@@ -80,7 +79,6 @@
@Override
public void onServiceDisconnected(int profile) {
- Log.d(TAG, "Bluetooth service disconnected");
mIsProfileReady=false;
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java
index 61e5b6b..4dc050c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java
@@ -58,7 +58,6 @@
implements BluetoothProfile.ServiceListener {
public void onServiceConnected(int profile, BluetoothProfile proxy) {
- Log.d(TAG, "Bluetooth service connected :-), profile:" + profile);
mService = (BluetoothHidDevice) proxy;
// We just bound to the service, so refresh the UI for any connected HID devices.
List<BluetoothDevice> deviceList = mService.getConnectedDevices();
@@ -78,7 +77,6 @@
}
public void onServiceDisconnected(int profile) {
- Log.d(TAG, "Bluetooth service disconnected, profile:" + profile);
mIsProfileReady = false;
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
index 75d16db..ca840d9a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
@@ -33,7 +33,6 @@
*/
public class HidProfile implements LocalBluetoothProfile {
private static final String TAG = "HidProfile";
- private static boolean V = true;
private BluetoothHidHost mService;
private boolean mIsProfileReady;
@@ -51,7 +50,6 @@
implements BluetoothProfile.ServiceListener {
public void onServiceConnected(int profile, BluetoothProfile proxy) {
- if (V) Log.d(TAG,"Bluetooth service connected");
mService = (BluetoothHidHost) proxy;
// We just bound to the service, so refresh the UI for any connected HID devices.
List<BluetoothDevice> deviceList = mService.getConnectedDevices();
@@ -70,7 +68,6 @@
}
public void onServiceDisconnected(int profile) {
- if (V) Log.d(TAG,"Bluetooth service disconnected");
mIsProfileReady=false;
}
}
@@ -186,7 +183,7 @@
}
protected void finalize() {
- if (V) Log.d(TAG, "finalize()");
+ Log.d(TAG, "finalize()");
if (mService != null) {
try {
BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.HID_HOST,
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
index 1e22f44..6acdcac 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
@@ -59,7 +59,6 @@
implements BluetoothProfile.ServiceListener {
public void onServiceConnected(int profile, BluetoothProfile proxy) {
- Log.d(TAG, "Bluetooth service connected, profile:" + profile);
mService = (BluetoothMapClient) proxy;
// We just bound to the service, so refresh the UI for any connected MAP devices.
List<BluetoothDevice> deviceList = mService.getConnectedDevices();
@@ -81,7 +80,6 @@
}
public void onServiceDisconnected(int profile) {
- Log.d(TAG, "Bluetooth service disconnected, profile:" + profile);
mProfileManager.callServiceDisconnectedListeners();
mIsProfileReady=false;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
index 7582024..28975d4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
@@ -58,7 +58,6 @@
implements BluetoothProfile.ServiceListener {
public void onServiceConnected(int profile, BluetoothProfile proxy) {
- Log.d(TAG, "Bluetooth service connected");
mService = (BluetoothMap) proxy;
// We just bound to the service, so refresh the UI for any connected MAP devices.
List<BluetoothDevice> deviceList = mService.getConnectedDevices();
@@ -80,7 +79,6 @@
}
public void onServiceDisconnected(int profile) {
- Log.d(TAG, "Bluetooth service disconnected");
mProfileManager.callServiceDisconnectedListeners();
mIsProfileReady=false;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java
index 7b81162..2d0a090 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java
@@ -34,7 +34,6 @@
*/
public class PanProfile implements LocalBluetoothProfile {
private static final String TAG = "PanProfile";
- private static boolean V = true;
private BluetoothPan mService;
private boolean mIsProfileReady;
@@ -53,13 +52,11 @@
implements BluetoothProfile.ServiceListener {
public void onServiceConnected(int profile, BluetoothProfile proxy) {
- if (V) Log.d(TAG,"Bluetooth service connected");
mService = (BluetoothPan) proxy;
mIsProfileReady=true;
}
public void onServiceDisconnected(int profile) {
- if (V) Log.d(TAG,"Bluetooth service disconnected");
mIsProfileReady=false;
}
}
@@ -173,7 +170,7 @@
}
protected void finalize() {
- if (V) Log.d(TAG, "finalize()");
+ Log.d(TAG, "finalize()");
if (mService != null) {
try {
BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.PAN, mService);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
index 1f15601..4672393 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
@@ -55,7 +55,6 @@
implements BluetoothProfile.ServiceListener {
public void onServiceConnected(int profile, BluetoothProfile proxy) {
- Log.d(TAG, "Bluetooth service connected, profile:" + profile);
mService = (BluetoothPbapClient) proxy;
// We just bound to the service, so refresh the UI for any connected PBAP devices.
List<BluetoothDevice> deviceList = mService.getConnectedDevices();
@@ -74,7 +73,6 @@
}
public void onServiceDisconnected(int profile) {
- Log.d(TAG, "Bluetooth service disconnected, profile:" + profile);
mIsProfileReady = false;
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
index adef0841..1b3c453 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
@@ -33,7 +33,6 @@
*/
public class PbapServerProfile implements LocalBluetoothProfile {
private static final String TAG = "PbapServerProfile";
- private static boolean V = true;
private BluetoothPbap mService;
private boolean mIsProfileReady;
@@ -56,13 +55,11 @@
implements BluetoothPbap.ServiceListener {
public void onServiceConnected(BluetoothPbap proxy) {
- if (V) Log.d(TAG,"Bluetooth service connected");
mService = (BluetoothPbap) proxy;
mIsProfileReady=true;
}
public void onServiceDisconnected() {
- if (V) Log.d(TAG,"Bluetooth service disconnected");
mIsProfileReady=false;
}
}
@@ -142,7 +139,7 @@
}
protected void finalize() {
- if (V) Log.d(TAG, "finalize()");
+ Log.d(TAG, "finalize()");
if (mService != null) {
try {
mService.close();
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
index b4acc48..ea2ebde 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
@@ -57,7 +57,6 @@
implements BluetoothProfile.ServiceListener {
public void onServiceConnected(int profile, BluetoothProfile proxy) {
- Log.d(TAG, "Bluetooth service connected, profile:" + profile);
mService = (BluetoothSap) proxy;
// We just bound to the service, so refresh the UI for any connected SAP devices.
List<BluetoothDevice> deviceList = mService.getConnectedDevices();
@@ -79,7 +78,6 @@
}
public void onServiceDisconnected(int profile) {
- Log.d(TAG, "Bluetooth service disconnected, profile:" + profile);
mProfileManager.callServiceDisconnectedListeners();
mIsProfileReady=false;
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
index 594d767..86f0438 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
@@ -247,6 +247,15 @@
}
@Test
+ public void isInService_voiceOutOfServiceDataInServiceOnIwLan_returnFalse() {
+ when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+ when(mServiceState.getDataNetworkType())
+ .thenReturn(ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN);
+ when(mServiceState.getDataRegState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+ assertThat(Utils.isInService(mServiceState)).isFalse();
+ }
+
+ @Test
public void isInService_voiceOutOfServiceDataOutOfService_returnFalse() {
when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
when(mServiceState.getDataRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 526efcb..bcf37ff 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -708,6 +708,9 @@
Settings.Global.GUP_DEV_OPT_IN_APPS,
GlobalSettingsProto.Gpu.GUP_DEV_OPT_IN_APPS);
dumpSetting(s, p,
+ Settings.Global.GUP_DEV_OPT_OUT_APPS,
+ GlobalSettingsProto.Gpu.GUP_DEV_OPT_OUT_APPS);
+ dumpSetting(s, p,
Settings.Global.GUP_BLACK_LIST,
GlobalSettingsProto.Gpu.GUP_BLACK_LIST);
p.end(gpuToken);
@@ -1554,6 +1557,10 @@
Settings.Global.ZRAM_ENABLED,
GlobalSettingsProto.ZRAM_ENABLED);
+ dumpSetting(s, p,
+ Global.APP_OPS_CONSTANTS,
+ GlobalSettingsProto.APP_OPS_CONSTANTS);
+
p.end(token);
// Please insert new settings using the same order as in GlobalSettingsProto.
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index bff2c84..fa95bf2 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -134,6 +134,8 @@
<uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
<uses-permission android:name="android.permission.MANAGE_AUTO_FILL" />
<uses-permission android:name="android.permission.MANAGE_CONTENT_CAPTURE" />
+ <uses-permission android:name="android.permission.MANAGE_CONTENT_SUGGESTIONS" />
+ <uses-permission android:name="android.permission.MANAGE_APP_PREDICTIONS" />
<uses-permission android:name="android.permission.NETWORK_SETTINGS" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.SET_TIME" />
@@ -162,6 +164,8 @@
<uses-permission android:name="android.permission.SUSPEND_APPS" />
<uses-permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND" />
+ <uses-permission android:name="android.permission.MANAGE_APPOPS" />
+
<application android:label="@string/app_label"
android:defaultToDeviceProtectedStorage="true"
android:directBootAware="true">
diff --git a/packages/SystemUI/res/drawable/biometric_dialog_bg.xml b/packages/SystemUI/res/drawable/biometric_dialog_bg.xml
index d041556..0c6d57d 100644
--- a/packages/SystemUI/res/drawable/biometric_dialog_bg.xml
+++ b/packages/SystemUI/res/drawable/biometric_dialog_bg.xml
@@ -18,7 +18,7 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="?android:attr/colorBackgroundFloating" />
- <corners android:radius="1dp"
+ <corners
android:topLeftRadius="@dimen/biometric_dialog_corner_size"
android:topRightRadius="@dimen/biometric_dialog_corner_size"
android:bottomLeftRadius="@dimen/biometric_dialog_corner_size"
diff --git a/packages/SystemUI/res/drawable/bubble_expanded_header_bg.xml b/packages/SystemUI/res/drawable/bubble_expanded_header_bg.xml
new file mode 100644
index 0000000..26bf981
--- /dev/null
+++ b/packages/SystemUI/res/drawable/bubble_expanded_header_bg.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <shape android:shape="rectangle">
+ <solid android:color="?android:attr/colorBackgroundFloating"/>
+ <corners
+ android:topLeftRadius="@dimen/corner_size"
+ android:topRightRadius="@dimen/corner_size"/>
+ </shape>
+ </item>
+ <item android:gravity="bottom">
+ <shape>
+ <size android:height="1dp"/>
+ <solid android:color="?android:attr/textColorSecondary" />
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/battery_percentage_view.xml b/packages/SystemUI/res/layout/battery_percentage_view.xml
index e52aa14..b9b1bb1 100644
--- a/packages/SystemUI/res/layout/battery_percentage_view.xml
+++ b/packages/SystemUI/res/layout/battery_percentage_view.xml
@@ -22,7 +22,7 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:singleLine="true"
- android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock"
+ android:textAppearance="@style/TextAppearance.StatusBar.Clock"
android:textColor="?android:attr/textColorPrimary"
android:gravity="center_vertical|start"
android:paddingStart="@dimen/battery_level_padding_start"
diff --git a/packages/SystemUI/res/layout/bubble_expanded_view.xml b/packages/SystemUI/res/layout/bubble_expanded_view.xml
index b2307e7..1aeb52c 100644
--- a/packages/SystemUI/res/layout/bubble_expanded_view.xml
+++ b/packages/SystemUI/res/layout/bubble_expanded_view.xml
@@ -20,11 +20,23 @@
android:layout_width="match_parent"
android:id="@+id/bubble_expanded_view">
- <!-- TODO: header -->
-
<View
android:id="@+id/pointer_view"
android:layout_width="@dimen/bubble_pointer_width"
android:layout_height="@dimen/bubble_pointer_height"
/>
+
+ <TextView
+ android:id="@+id/bubble_content_header"
+ android:background="@drawable/bubble_expanded_header_bg"
+ android:textAppearance="@*android:style/TextAppearance.Material.Title"
+ android:textSize="18sp"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/bubble_expanded_header_height"
+ android:gravity="start|center_vertical"
+ android:singleLine="true"
+ android:paddingLeft="@dimen/bubble_expanded_header_horizontal_padding"
+ android:paddingRight="@dimen/bubble_expanded_header_horizontal_padding"
+ />
+
</com.android.systemui.bubbles.BubbleExpandedViewContainer>
diff --git a/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml b/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml
index 307b538..5bcc1b3 100644
--- a/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml
+++ b/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml
@@ -39,7 +39,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/monitoring_title_device_owned"
- style="@android:style/TextAppearance.Material.Title"
+ style="@style/TextAppearance.DeviceManagementDialog.Title"
android:textColor="?android:attr/textColorPrimary"
android:paddingBottom="@dimen/qs_footer_dialog_subtitle_padding"
/>
@@ -64,7 +64,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/monitoring_subtitle_ca_certificate"
- style="@android:style/TextAppearance.Material.Title"
+ style="@style/TextAppearance.DeviceManagementDialog.Title"
android:textColor="?android:attr/textColorPrimary"
android:paddingBottom="@dimen/qs_footer_dialog_subtitle_padding"
/>
@@ -89,7 +89,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/monitoring_subtitle_network_logging"
- style="@android:style/TextAppearance.Material.Title"
+ style="@style/TextAppearance.DeviceManagementDialog.Title"
android:textColor="?android:attr/textColorPrimary"
android:paddingBottom="@dimen/qs_footer_dialog_subtitle_padding"
/>
@@ -114,7 +114,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/monitoring_subtitle_vpn"
- style="@android:style/TextAppearance.Material.Title"
+ style="@style/TextAppearance.DeviceManagementDialog.Title"
android:textColor="?android:attr/textColorPrimary"
android:paddingBottom="@dimen/qs_footer_dialog_subtitle_padding"
/>
diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
index 4e0cbe0..ed18dc7 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -24,14 +24,13 @@
android:layout_gravity="@integer/notification_panel_layout_gravity"
android:background="@android:color/transparent"
android:baselineAligned="false"
- android:clickable="true"
+ android:clickable="false"
android:clipChildren="false"
android:clipToPadding="false"
android:paddingTop="0dp"
android:paddingEnd="0dp"
android:paddingStart="0dp"
- android:elevation="4dp"
- android:importantForAccessibility="no" >
+ android:elevation="4dp" >
<include layout="@layout/quick_status_bar_header_system_icons" />
diff --git a/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml b/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml
index 22b8d2f..4b65b6a 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml
@@ -24,6 +24,7 @@
android:clipToPadding="false"
android:gravity="center"
android:orientation="horizontal"
+ android:clickable="true"
android:paddingStart="@dimen/status_bar_padding_start"
android:paddingEnd="@dimen/status_bar_padding_end" >
diff --git a/packages/SystemUI/res/layout/signal_cluster_view.xml b/packages/SystemUI/res/layout/signal_cluster_view.xml
deleted file mode 100644
index cfa372b..0000000
--- a/packages/SystemUI/res/layout/signal_cluster_view.xml
+++ /dev/null
@@ -1,129 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-**
-** Copyright 2011, 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.
-*/
--->
-<!-- extends LinearLayout -->
-<com.android.systemui.statusbar.SignalClusterView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/signal_cluster"
- android:layout_height="match_parent"
- android:layout_width="wrap_content"
- android:gravity="center_vertical"
- android:orientation="horizontal"
- android:paddingEnd="@dimen/signal_cluster_battery_padding"
- >
- <ImageView
- android:id="@+id/vpn"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:paddingEnd="6dp"
- android:src="@drawable/stat_sys_vpn_ic"
- android:tint="@color/background_protect_secondary"
- android:contentDescription="@string/accessibility_vpn_on"
- />
- <FrameLayout
- android:id="@+id/ethernet_combo"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- >
- <com.android.systemui.statusbar.AlphaOptimizedImageView
- android:theme="?attr/lightIconTheme"
- android:id="@+id/ethernet"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- />
- <com.android.systemui.statusbar.AlphaOptimizedImageView
- android:theme="?attr/darkIconTheme"
- android:id="@+id/ethernet_dark"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:alpha="0.0"
- />
- </FrameLayout>
- <FrameLayout
- android:layout_height="17dp"
- android:layout_width="wrap_content">
- <ImageView
- android:id="@+id/wifi_in"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:src="@drawable/ic_activity_down"
- android:visibility="gone"
- android:paddingEnd="2dp"
- />
- <ImageView
- android:id="@+id/wifi_out"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:src="@drawable/ic_activity_up"
- android:paddingEnd="2dp"
- android:visibility="gone"
- />
- </FrameLayout>
- <FrameLayout
- android:id="@+id/wifi_combo"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- >
- <com.android.systemui.statusbar.AlphaOptimizedImageView
- android:theme="?attr/lightIconTheme"
- android:id="@+id/wifi_signal"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- />
- <com.android.systemui.statusbar.AlphaOptimizedImageView
- android:theme="?attr/darkIconTheme"
- android:id="@+id/wifi_signal_dark"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:alpha="0.0"
- />
- <ImageView
- android:id="@+id/wifi_inout"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- />
- </FrameLayout>
- <View
- android:id="@+id/wifi_signal_spacer"
- android:layout_width="@dimen/status_bar_wifi_signal_spacer_width"
- android:layout_height="4dp"
- android:visibility="gone"
- />
- <ViewStub
- android:id="@+id/connected_device_signals_stub"
- android:layout="@layout/connected_device_signal"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
- <LinearLayout
- android:id="@+id/mobile_signal_group"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- >
- </LinearLayout>
- <View
- android:id="@+id/wifi_airplane_spacer"
- android:layout_width="@dimen/status_bar_airplane_spacer_width"
- android:layout_height="4dp"
- android:visibility="gone"
- />
- <ImageView
- android:id="@+id/airplane"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- />
-</com.android.systemui.statusbar.SignalClusterView>
diff --git a/packages/SystemUI/res/layout/super_status_bar.xml b/packages/SystemUI/res/layout/super_status_bar.xml
index 34c208a..02062bb 100644
--- a/packages/SystemUI/res/layout/super_status_bar.xml
+++ b/packages/SystemUI/res/layout/super_status_bar.xml
@@ -43,6 +43,14 @@
android:visibility="invisible" />
</com.android.systemui.statusbar.BackDropView>
+ <com.android.systemui.wallpaper.AodMaskView
+ android:id="@+id/aod_mask"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:importantForAccessibility="no"
+ android:visibility="invisible"
+ sysui:ignoreRightInset="true" />
+
<com.android.systemui.statusbar.ScrimView
android:id="@+id/scrim_behind"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 889db66..7cc5524 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -449,6 +449,11 @@
better (narrower) line-break for a double-line smart reply button. -->
<integer name="config_smart_replies_in_notifications_max_squeeze_remeasure_attempts">3</integer>
+ <!-- Smart replies in notifications: Whether by default tapping on a choice should let the user
+ edit the input before it is sent to the app. Developers can override this via
+ RemoteInput.Builder.setEditChoicesBeforeSending. -->
+ <bool name="config_smart_replies_in_notifications_edit_choices_before_sending">false</bool>
+
<!-- Screenshot editing default activity. Must handle ACTION_EDIT image/png intents.
Blank sends the user to the Chooser first.
This name is in the ComponentName flattened format (package/class) -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 06df0e7..10e5f74 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -997,4 +997,8 @@
<dimen name="bubble_pointer_width">6dp</dimen>
<!-- Extra padding around the dismiss target for bubbles -->
<dimen name="bubble_dismiss_slop">16dp</dimen>
+ <!-- Height of the header within the expanded view. -->
+ <dimen name="bubble_expanded_header_height">48dp</dimen>
+ <!-- Left and right padding applied to the header. -->
+ <dimen name="bubble_expanded_header_horizontal_padding">24dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index dac20b5..633f868 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -102,12 +102,17 @@
<item type="id" name="action_snooze_assistant_suggestion_1"/>
<item type="id" name="action_snooze"/>
- <!-- For StatusBarIconContainer to tag its icon views -->
+ <!-- For StatusIconContainer to tag its icon views -->
<item type="id" name="status_bar_view_state_tag" />
<item type="id" name="display_cutout" />
<!-- Optional cancel button on Keyguard -->
<item type="id" name="cancel_button"/>
+
+ <!-- AodMaskView transition tag -->
+ <item type="id" name="aod_mask_transition_progress_tag" />
+ <item type="id" name="aod_mask_transition_progress_end_tag" />
+ <item type="id" name="aod_mask_transition_progress_start_tag" />
</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 8a5a69b..22a0b36 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -125,7 +125,7 @@
<style name="TextAppearance.StatusBar.Clock" parent="@*android:style/TextAppearance.StatusBar.Icon">
<item name="android:textSize">@dimen/status_bar_clock_size</item>
- <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
<item name="android:textColor">@color/status_bar_clock_color</item>
</style>
@@ -265,6 +265,10 @@
<item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
</style>
+ <style name="TextAppearance.DeviceManagementDialog.Title" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle">
+ <item name="android:gravity">center</item>
+ </style>
+
<style name="BaseBrightnessDialogContainer" parent="@style/Theme.SystemUI">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index f3bdbae..078947c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -76,4 +76,10 @@
*/
float getWindowCornerRadius() = 10;
+ /**
+ * If device supports live rounded corners on windows.
+ * This might be turned off for performance reasons
+ */
+ boolean supportsRoundedCornersOnWindows() = 11;
+
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
index c0a1d89..e6acfbe 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
@@ -16,14 +16,10 @@
package com.android.systemui.shared.system;
+import android.graphics.HardwareRenderer;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.view.Surface;
-import android.view.SurfaceControl;
-import android.view.SurfaceControl.Transaction;
-import android.view.SyncRtSurfaceTransactionApplier;
-import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
-import android.view.ThreadedRenderer;
import android.view.View;
import android.view.ViewRootImpl;
@@ -31,20 +27,21 @@
/**
* Helper class to apply surface transactions in sync with RenderThread.
+ *
+ * NOTE: This is a modification of {@link android.view.SyncRtSurfaceTransactionApplier}, we can't
+ * currently reference that class from the shared lib as it is hidden.
*/
public class SyncRtSurfaceTransactionApplierCompat {
- private final SyncRtSurfaceTransactionApplier mApplier;
+ private final Surface mTargetSurface;
+ private final ViewRootImpl mTargetViewRootImpl;
/**
* @param targetView The view in the surface that acts as synchronization anchor.
*/
public SyncRtSurfaceTransactionApplierCompat(View targetView) {
- mApplier = new SyncRtSurfaceTransactionApplier(targetView);
- }
-
- private SyncRtSurfaceTransactionApplierCompat(SyncRtSurfaceTransactionApplier applier) {
- mApplier = applier;
+ mTargetViewRootImpl = targetView != null ? targetView.getViewRootImpl() : null;
+ mTargetSurface = mTargetViewRootImpl != null ? mTargetViewRootImpl.mSurface : null;
}
/**
@@ -53,38 +50,74 @@
* @param params The surface parameters to apply. DO NOT MODIFY the list after passing into
* this method to avoid synchronization issues.
*/
- public void scheduleApply(final SurfaceParams... params) {
- mApplier.scheduleApply(convert(params));
- }
-
- private SyncRtSurfaceTransactionApplier.SurfaceParams[] convert(SurfaceParams[] params) {
- SyncRtSurfaceTransactionApplier.SurfaceParams[] result =
- new SyncRtSurfaceTransactionApplier.SurfaceParams[params.length];
- for (int i = 0; i < params.length; i++) {
- result[i] = params[i].mParams;
+ public void scheduleApply(final SyncRtSurfaceTransactionApplierCompat.SurfaceParams... params) {
+ if (mTargetViewRootImpl == null) {
+ return;
}
- return result;
+ mTargetViewRootImpl.registerRtFrameCallback(new HardwareRenderer.FrameDrawingCallback() {
+ @Override
+ public void onFrameDraw(long frame) {
+ if (mTargetSurface == null || !mTargetSurface.isValid()) {
+ return;
+ }
+ TransactionCompat t = new TransactionCompat();
+ for (int i = params.length - 1; i >= 0; i--) {
+ SyncRtSurfaceTransactionApplierCompat.SurfaceParams surfaceParams =
+ params[i];
+ SurfaceControlCompat surface = surfaceParams.surface;
+ t.deferTransactionUntil(surface, mTargetSurface, frame);
+ applyParams(t, surfaceParams);
+ }
+ t.setEarlyWakeup();
+ t.apply();
+ }
+ });
+
+ // Make sure a frame gets scheduled.
+ mTargetViewRootImpl.getView().invalidate();
}
- public static void applyParams(TransactionCompat t, SurfaceParams params) {
- SyncRtSurfaceTransactionApplier.applyParams(t.mTransaction, params.mParams, t.mTmpValues);
+ public static void applyParams(TransactionCompat t,
+ SyncRtSurfaceTransactionApplierCompat.SurfaceParams params) {
+ t.setMatrix(params.surface, params.matrix);
+ t.setWindowCrop(params.surface, params.windowCrop);
+ t.setAlpha(params.surface, params.alpha);
+ t.setLayer(params.surface, params.layer);
+ t.setCornerRadius(params.surface, params.cornerRadius);
+ t.show(params.surface);
}
+ /**
+ * Creates an instance of SyncRtSurfaceTransactionApplier, deferring until the target view is
+ * attached if necessary.
+ */
public static void create(final View targetView,
final Consumer<SyncRtSurfaceTransactionApplierCompat> callback) {
- SyncRtSurfaceTransactionApplier.create(targetView,
- new Consumer<SyncRtSurfaceTransactionApplier>() {
- @Override
- public void accept(SyncRtSurfaceTransactionApplier applier) {
- callback.accept(new SyncRtSurfaceTransactionApplierCompat(applier));
- }
- });
+ if (targetView == null) {
+ // No target view, no applier
+ callback.accept(null);
+ } else if (targetView.getViewRootImpl() != null) {
+ // Already attached, we're good to go
+ callback.accept(new SyncRtSurfaceTransactionApplierCompat(targetView));
+ } else {
+ // Haven't been attached before we can get the view root
+ targetView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ targetView.removeOnAttachStateChangeListener(this);
+ callback.accept(new SyncRtSurfaceTransactionApplierCompat(targetView));
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ // Do nothing
+ }
+ });
+ }
}
public static class SurfaceParams {
- private final SyncRtSurfaceTransactionApplier.SurfaceParams mParams;
-
/**
* Constructs surface parameters to be applied when the current view state gets pushed to
* RenderThread.
@@ -96,8 +129,19 @@
*/
public SurfaceParams(SurfaceControlCompat surface, float alpha, Matrix matrix,
Rect windowCrop, int layer, float cornerRadius) {
- mParams = new SyncRtSurfaceTransactionApplier.SurfaceParams(surface.mSurfaceControl,
- alpha, matrix, windowCrop, layer, cornerRadius);
+ this.surface = surface;
+ this.alpha = alpha;
+ this.matrix = new Matrix(matrix);
+ this.windowCrop = new Rect(windowCrop);
+ this.layer = layer;
+ this.cornerRadius = cornerRadius;
}
+
+ public final SurfaceControlCompat surface;
+ public final float alpha;
+ final float cornerRadius;
+ public final Matrix matrix;
+ public final Rect windowCrop;
+ public final int layer;
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java
index 2aba3fa..af32f48 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java
@@ -82,6 +82,11 @@
return this;
}
+ public TransactionCompat setCornerRadius(SurfaceControlCompat surfaceControl, float radius) {
+ mTransaction.setCornerRadius(surfaceControl.mSurfaceControl, radius);
+ return this;
+ }
+
public TransactionCompat deferTransactionUntil(SurfaceControlCompat surfaceControl,
IBinder handle, long frameNumber) {
mTransaction.deferTransactionUntil(surfaceControl.mSurfaceControl, handle, frameNumber);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 570d351..17cc1d5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -14,11 +14,13 @@
import com.android.systemui.Dependency;
import com.android.systemui.plugins.ClockPlugin;
-import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.policy.ExtensionController;
+import com.android.systemui.statusbar.policy.ExtensionController.Extension;
-import java.util.Objects;
import java.util.TimeZone;
+import java.util.function.Consumer;
/**
* Switch to show plugin clock when plugin is connected, otherwise it will show default clock.
@@ -45,43 +47,37 @@
* or not to show it below the alternate clock.
*/
private View mKeyguardStatusArea;
+ /**
+ * Used to select between plugin or default implementations of ClockPlugin interface.
+ */
+ private Extension<ClockPlugin> mClockExtension;
+ /**
+ * Consumer that accepts the a new ClockPlugin implementation when the Extension reloads.
+ */
+ private final Consumer<ClockPlugin> mClockPluginConsumer = plugin -> setClockPlugin(plugin);
+ /**
+ * Maintain state so that a newly connected plugin can be initialized.
+ */
+ private float mDarkAmount;
- private final PluginListener<ClockPlugin> mClockPluginListener =
- new PluginListener<ClockPlugin>() {
+ private final StatusBarStateController.StateListener mStateListener =
+ new StatusBarStateController.StateListener() {
@Override
- public void onPluginConnected(ClockPlugin plugin, Context pluginContext) {
- disconnectPlugin();
- View smallClockView = plugin.getView();
- if (smallClockView != null) {
- // For now, assume that the most recently connected plugin is the
- // selected clock face. In the future, the user should be able to
- // pick a clock face from the available plugins.
- mSmallClockFrame.addView(smallClockView, -1,
- new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT));
- initPluginParams();
- mClockView.setVisibility(View.GONE);
+ public void onStateChanged(int newState) {
+ if (mBigClockContainer == null) {
+ return;
}
- View bigClockView = plugin.getBigClockView();
- if (bigClockView != null && mBigClockContainer != null) {
- mBigClockContainer.addView(bigClockView);
- mBigClockContainer.setVisibility(View.VISIBLE);
- }
- if (!plugin.shouldShowStatusArea()) {
- mKeyguardStatusArea.setVisibility(View.GONE);
- }
- mClockPlugin = plugin;
- }
-
- @Override
- public void onPluginDisconnected(ClockPlugin plugin) {
- if (Objects.equals(plugin, mClockPlugin)) {
- disconnectPlugin();
- mClockView.setVisibility(View.VISIBLE);
- mKeyguardStatusArea.setVisibility(View.VISIBLE);
+ if (newState == StatusBarState.SHADE) {
+ if (mBigClockContainer.getVisibility() == View.VISIBLE) {
+ mBigClockContainer.setVisibility(View.INVISIBLE);
+ }
+ } else {
+ if (mBigClockContainer.getVisibility() == View.INVISIBLE) {
+ mBigClockContainer.setVisibility(View.VISIBLE);
+ }
}
}
- };
+ };
public KeyguardClockSwitch(Context context) {
this(context, null);
@@ -102,14 +98,60 @@
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- Dependency.get(PluginManager.class).addPluginListener(mClockPluginListener,
- ClockPlugin.class);
+ mClockExtension = Dependency.get(ExtensionController.class).newExtension(ClockPlugin.class)
+ .withPlugin(ClockPlugin.class)
+ .withCallback(mClockPluginConsumer)
+ .build();
+ Dependency.get(StatusBarStateController.class).addCallback(mStateListener);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- Dependency.get(PluginManager.class).removePluginListener(mClockPluginListener);
+ mClockExtension.destroy();
+ Dependency.get(StatusBarStateController.class).removeCallback(mStateListener);
+ }
+
+ private void setClockPlugin(ClockPlugin plugin) {
+ // Disconnect from existing plugin.
+ if (mClockPlugin != null) {
+ View smallClockView = mClockPlugin.getView();
+ if (smallClockView != null && smallClockView.getParent() == mSmallClockFrame) {
+ mSmallClockFrame.removeView(smallClockView);
+ }
+ if (mBigClockContainer != null) {
+ mBigClockContainer.removeAllViews();
+ mBigClockContainer.setVisibility(View.GONE);
+ }
+ mClockPlugin = null;
+ }
+ if (plugin == null) {
+ mClockView.setVisibility(View.VISIBLE);
+ mKeyguardStatusArea.setVisibility(View.VISIBLE);
+ return;
+ }
+ // Attach small and big clock views to hierarchy.
+ View smallClockView = plugin.getView();
+ if (smallClockView != null) {
+ mSmallClockFrame.addView(smallClockView, -1,
+ new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ mClockView.setVisibility(View.GONE);
+ }
+ View bigClockView = plugin.getBigClockView();
+ if (bigClockView != null && mBigClockContainer != null) {
+ mBigClockContainer.addView(bigClockView);
+ mBigClockContainer.setVisibility(View.VISIBLE);
+ }
+ // Hide default clock.
+ if (!plugin.shouldShowStatusArea()) {
+ mKeyguardStatusArea.setVisibility(View.GONE);
+ }
+ // Initialize plugin parameters.
+ mClockPlugin = plugin;
+ mClockPlugin.setStyle(getPaint().getStyle());
+ mClockPlugin.setTextColor(getCurrentTextColor());
+ mClockPlugin.setDarkAmount(mDarkAmount);
}
/**
@@ -171,6 +213,7 @@
* @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
*/
public void setDarkAmount(float darkAmount) {
+ mDarkAmount = darkAmount;
if (mClockPlugin != null) {
mClockPlugin.setDarkAmount(darkAmount);
}
@@ -210,32 +253,13 @@
}
}
- /**
- * When plugin changes, set all kept parameters into newer plugin.
- */
- private void initPluginParams() {
- if (mClockPlugin != null) {
- mClockPlugin.setStyle(getPaint().getStyle());
- mClockPlugin.setTextColor(getCurrentTextColor());
- }
- }
-
- private void disconnectPlugin() {
- if (mClockPlugin != null) {
- View smallClockView = mClockPlugin.getView();
- if (smallClockView != null) {
- mSmallClockFrame.removeView(smallClockView);
- }
- if (mBigClockContainer != null) {
- mBigClockContainer.removeAllViews();
- mBigClockContainer.setVisibility(View.GONE);
- }
- mClockPlugin = null;
- }
+ @VisibleForTesting (otherwise = VisibleForTesting.NONE)
+ Consumer<ClockPlugin> getClockPluginConsumer() {
+ return mClockPluginConsumer;
}
@VisibleForTesting (otherwise = VisibleForTesting.NONE)
- PluginListener getClockPluginListener() {
- return mClockPluginListener;
+ StatusBarStateController.StateListener getStateListener() {
+ return mStateListener;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index 6864ea1..8e273ef 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -40,7 +40,6 @@
import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.LayoutInflater;
-import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -56,7 +55,6 @@
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
-import com.android.systemui.statusbar.policy.IconLogger;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
import com.android.systemui.util.Utils.DisableStateTracker;
@@ -276,9 +274,6 @@
public void onTuningChanged(String key, String newValue) {
if (StatusBarIconController.ICON_BLACKLIST.equals(key)) {
ArraySet<String> icons = StatusBarIconController.getIconBlacklist(newValue);
- boolean hidden = icons.contains(mSlotBattery);
- Dependency.get(IconLogger.class).onIconVisibility(mSlotBattery, !hidden);
- setVisibility(hidden ? View.GONE : View.VISIBLE);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 3cc9bb6..ec6ecc6 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -56,12 +56,12 @@
import com.android.systemui.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.notification.NotificationAlertingManager;
-import com.android.systemui.statusbar.notification.NotificationData.KeyguardEnvironment;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationFilter;
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
import com.android.systemui.statusbar.notification.NotificationRowBinder;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationData.KeyguardEnvironment;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -86,7 +86,6 @@
import com.android.systemui.statusbar.policy.ExtensionController;
import com.android.systemui.statusbar.policy.FlashlightController;
import com.android.systemui.statusbar.policy.HotspotController;
-import com.android.systemui.statusbar.policy.IconLogger;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
import com.android.systemui.statusbar.policy.LocationController;
import com.android.systemui.statusbar.policy.NetworkController;
@@ -217,7 +216,6 @@
@Inject Lazy<LeakDetector> mLeakDetector;
@Inject Lazy<LeakReporter> mLeakReporter;
@Inject Lazy<GarbageMonitor> mGarbageMonitor;
- @Inject Lazy<IconLogger> mIconLogger;
@Inject Lazy<TunerService> mTunerService;
@Inject Lazy<StatusBarWindowController> mStatusBarWindowController;
@Inject Lazy<DarkIconDispatcher> mDarkIconDispatcher;
@@ -392,8 +390,6 @@
mProviders.put(PowerUI.WarningsUI.class, mWarningsUI::get);
- mProviders.put(IconLogger.class, mIconLogger::get);
-
mProviders.put(LightBarController.class, mLightBarController::get);
mProviders.put(IWindowManager.class, mIWindowManager::get);
diff --git a/packages/SystemUI/src/com/android/systemui/DependencyBinder.java b/packages/SystemUI/src/com/android/systemui/DependencyBinder.java
index f324a05b..ce9c637 100644
--- a/packages/SystemUI/src/com/android/systemui/DependencyBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/DependencyBinder.java
@@ -46,8 +46,6 @@
import com.android.systemui.statusbar.policy.FlashlightControllerImpl;
import com.android.systemui.statusbar.policy.HotspotController;
import com.android.systemui.statusbar.policy.HotspotControllerImpl;
-import com.android.systemui.statusbar.policy.IconLogger;
-import com.android.systemui.statusbar.policy.IconLoggerImpl;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
import com.android.systemui.statusbar.policy.KeyguardMonitorImpl;
import com.android.systemui.statusbar.policy.LocationController;
@@ -133,11 +131,6 @@
/**
*/
@Binds
- public abstract IconLogger provideIconLogger(IconLoggerImpl loggerImpl);
-
- /**
- */
- @Binds
public abstract CastController provideCastController(CastControllerImpl controllerImpl);
/**
diff --git a/packages/SystemUI/src/com/android/systemui/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/DependencyProvider.java
index dac977a..88e32cb 100644
--- a/packages/SystemUI/src/com/android/systemui/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/DependencyProvider.java
@@ -44,8 +44,6 @@
import com.android.systemui.statusbar.NavigationBarController;
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
-import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DataSaverController;
import com.android.systemui.statusbar.policy.NetworkController;
@@ -141,12 +139,6 @@
@Singleton
@Provides
- public ShadeController provideShadeController(Context context) {
- return SysUiServiceProvider.getComponent(context, StatusBar.class);
- }
-
- @Singleton
- @Provides
public SensorPrivacyManager provideSensorPrivacyManager(Context context) {
return context.getSystemService(SensorPrivacyManager.class);
}
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
index 151a6b0..96b62ac 100644
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
@@ -24,9 +24,9 @@
import android.util.Log;
import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -49,26 +49,21 @@
mForegroundServiceController = foregroundServiceController;
notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
@Override
- public void onPendingEntryAdded(NotificationData.Entry entry) {
+ public void onPendingEntryAdded(NotificationEntry entry) {
addNotification(entry.notification, entry.importance);
}
@Override
- public void onEntryUpdated(NotificationData.Entry entry) {
+ public void onPostEntryUpdated(NotificationEntry entry) {
updateNotification(entry.notification, entry.importance);
}
@Override
public void onEntryRemoved(
- NotificationData.Entry entry,
- String key,
- StatusBarNotification old,
+ NotificationEntry entry,
NotificationVisibility visibility,
- boolean lifetimeExtended,
boolean removedByUser) {
- if (entry != null && !lifetimeExtended) {
- removeNotification(entry.notification);
- }
+ removeNotification(entry.notification);
}
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 073516e..d0111cb 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -40,9 +40,9 @@
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
import com.android.systemui.statusbar.ScrimView;
-import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.collection.NotificationData;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl;
@@ -51,6 +51,7 @@
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.ScrimState;
+import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -208,6 +209,12 @@
return true;
}
+ @Singleton
+ @Provides
+ public ShadeController provideShadeController(Context context) {
+ return SysUiServiceProvider.getComponent(context, StatusBar.class);
+ }
+
@Module
protected static class ContextHolder {
private Context mContext;
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java
index 9f363f6..7e5b426 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java
@@ -25,12 +25,20 @@
private int mUid;
private String mPackageName;
private long mTimeStarted;
+ private String mState;
public AppOpItem(int code, int uid, String packageName, long timeStarted) {
this.mCode = code;
this.mUid = uid;
this.mPackageName = packageName;
this.mTimeStarted = timeStarted;
+ mState = new StringBuilder()
+ .append("AppOpItem(")
+ .append("Op code=").append(code).append(", ")
+ .append("UID=").append(uid).append(", ")
+ .append("Package name=").append(packageName)
+ .append(")")
+ .toString();
}
public int getCode() {
@@ -48,4 +56,9 @@
public long getTimeStarted() {
return mTimeStarted;
}
+
+ @Override
+ public String toString() {
+ return mState;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index e3bcb37..c013df3 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -29,7 +29,10 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Dumpable;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@@ -47,7 +50,7 @@
@Singleton
public class AppOpsControllerImpl implements AppOpsController,
AppOpsManager.OnOpActiveChangedListener,
- AppOpsManager.OnOpNotedListener {
+ AppOpsManager.OnOpNotedListener, Dumpable {
private static final long NOTED_OP_TIME_DELAY_MS = 5000;
private static final String TAG = "AppOpsControllerImpl";
@@ -271,6 +274,22 @@
}
}
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("AppOpsController state:");
+ pw.println(" Active Items:");
+ for (int i = 0; i < mActiveItems.size(); i++) {
+ final AppOpItem item = mActiveItems.get(i);
+ pw.print(" "); pw.println(item.toString());
+ }
+ pw.println(" Noted Items:");
+ for (int i = 0; i < mNotedItems.size(); i++) {
+ final AppOpItem item = mNotedItems.get(i);
+ pw.print(" "); pw.println(item.toString());
+ }
+
+ }
+
protected final class H extends Handler {
H(Looper looper) {
super(looper);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java
index 3167b9e..016b8fe 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java
@@ -33,9 +33,6 @@
import com.android.systemui.SystemUI;
import com.android.systemui.statusbar.CommandQueue;
-import java.util.HashMap;
-import java.util.Map;
-
/**
* Receives messages sent from AuthenticationClient and shows the appropriate biometric UI (e.g.
* BiometricDialogView).
@@ -52,10 +49,8 @@
private static final int MSG_BUTTON_NEGATIVE = 6;
private static final int MSG_USER_CANCELED = 7;
private static final int MSG_BUTTON_POSITIVE = 8;
- private static final int MSG_BIOMETRIC_SHOW_TRY_AGAIN = 9;
- private static final int MSG_TRY_AGAIN_PRESSED = 10;
+ private static final int MSG_TRY_AGAIN_PRESSED = 9;
- private Map<Integer, BiometricDialogView> mDialogs; // BiometricAuthenticator type, view
private SomeArgs mCurrentDialogArgs;
private BiometricDialogView mCurrentDialog;
private WindowManager mWindowManager;
@@ -63,21 +58,22 @@
private boolean mDialogShowing;
private Callback mCallback = new Callback();
- private boolean mTryAgainShowing; // No good place to save state before config change :/
- private boolean mConfirmShowing; // No good place to save state before config change :/
-
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch(msg.what) {
case MSG_SHOW_DIALOG:
- handleShowDialog((SomeArgs) msg.obj, false /* skipAnimation */);
+ handleShowDialog((SomeArgs) msg.obj, false /* skipAnimation */,
+ null /* savedState */);
break;
case MSG_BIOMETRIC_AUTHENTICATED:
- handleBiometricAuthenticated();
+ handleBiometricAuthenticated((boolean) msg.obj);
break;
case MSG_BIOMETRIC_HELP:
- handleBiometricHelp((String) msg.obj);
+ SomeArgs args = (SomeArgs) msg.obj;
+ handleBiometricHelp((String) args.arg1 /* message */,
+ (boolean) args.arg2 /* requireTryAgain */);
+ args.recycle();
break;
case MSG_BIOMETRIC_ERROR:
handleBiometricError((String) msg.obj);
@@ -94,9 +90,6 @@
case MSG_BUTTON_POSITIVE:
handleButtonPositive();
break;
- case MSG_BIOMETRIC_SHOW_TRY_AGAIN:
- handleShowTryAgain();
- break;
case MSG_TRY_AGAIN_PRESSED:
handleTryAgainPressed();
break;
@@ -137,30 +130,22 @@
@Override
public void start() {
- createDialogs();
-
- if (!mDialogs.isEmpty()) {
+ final PackageManager pm = mContext.getPackageManager();
+ if (pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)
+ || pm.hasSystemFeature(PackageManager.FEATURE_FACE)
+ || pm.hasSystemFeature(PackageManager.FEATURE_IRIS)) {
getComponent(CommandQueue.class).addCallback(this);
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
}
}
- private void createDialogs() {
- final PackageManager pm = mContext.getPackageManager();
- mDialogs = new HashMap<>();
- if (pm.hasSystemFeature(PackageManager.FEATURE_FACE)) {
- mDialogs.put(BiometricAuthenticator.TYPE_FACE, new FaceDialogView(mContext, mCallback));
- }
- if (pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
- mDialogs.put(BiometricAuthenticator.TYPE_FINGERPRINT,
- new FingerprintDialogView(mContext, mCallback));
- }
- }
-
@Override
public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
int type, boolean requireConfirmation, int userId) {
- if (DEBUG) Log.d(TAG, "showBiometricDialog, type: " + type);
+ if (DEBUG) {
+ Log.d(TAG, "showBiometricDialog, type: " + type
+ + ", requireConfirmation: " + requireConfirmation);
+ }
// Remove these messages as they are part of the previous client
mHandler.removeMessages(MSG_BIOMETRIC_ERROR);
mHandler.removeMessages(MSG_BIOMETRIC_HELP);
@@ -176,15 +161,18 @@
}
@Override
- public void onBiometricAuthenticated() {
- if (DEBUG) Log.d(TAG, "onBiometricAuthenticated");
- mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED).sendToTarget();
+ public void onBiometricAuthenticated(boolean authenticated) {
+ if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: " + authenticated);
+ mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED, authenticated).sendToTarget();
}
@Override
public void onBiometricHelp(String message) {
if (DEBUG) Log.d(TAG, "onBiometricHelp: " + message);
- mHandler.obtainMessage(MSG_BIOMETRIC_HELP, message).sendToTarget();
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = message;
+ args.arg2 = false; // requireTryAgain
+ mHandler.obtainMessage(MSG_BIOMETRIC_HELP, args).sendToTarget();
}
@Override
@@ -199,16 +187,21 @@
mHandler.obtainMessage(MSG_HIDE_DIALOG, false /* userCanceled */).sendToTarget();
}
- @Override
- public void showBiometricTryAgain() {
- if (DEBUG) Log.d(TAG, "showBiometricTryAgain");
- mHandler.obtainMessage(MSG_BIOMETRIC_SHOW_TRY_AGAIN).sendToTarget();
- }
-
- private void handleShowDialog(SomeArgs args, boolean skipAnimation) {
+ private void handleShowDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
mCurrentDialogArgs = args;
final int type = args.argi1;
- mCurrentDialog = mDialogs.get(type);
+
+ if (type == BiometricAuthenticator.TYPE_FINGERPRINT) {
+ mCurrentDialog = new FingerprintDialogView(mContext, mCallback);
+ } else if (type == BiometricAuthenticator.TYPE_FACE) {
+ mCurrentDialog = new FaceDialogView(mContext, mCallback);
+ } else {
+ Log.e(TAG, "Unsupported type: " + type);
+ }
+
+ if (savedState != null) {
+ mCurrentDialog.restoreState(savedState);
+ }
if (DEBUG) Log.d(TAG, "handleShowDialog, isAnimatingAway: "
+ mCurrentDialog.isAnimatingAway() + " type: " + type);
@@ -224,32 +217,36 @@
mCurrentDialog.setRequireConfirmation((boolean) args.arg3);
mCurrentDialog.setUserId(args.argi2);
mCurrentDialog.setSkipIntro(skipAnimation);
- mCurrentDialog.setPendingTryAgain(mTryAgainShowing);
- mCurrentDialog.setPendingConfirm(mConfirmShowing);
mWindowManager.addView(mCurrentDialog, mCurrentDialog.getLayoutParams());
mDialogShowing = true;
}
- private void handleBiometricAuthenticated() {
- if (DEBUG) Log.d(TAG, "handleBiometricAuthenticated");
+ private void handleBiometricAuthenticated(boolean authenticated) {
+ if (DEBUG) Log.d(TAG, "handleBiometricAuthenticated: " + authenticated);
- mCurrentDialog.announceForAccessibility(
- mContext.getResources()
- .getText(mCurrentDialog.getAuthenticatedAccessibilityResourceId()));
- if (mCurrentDialog.requiresConfirmation()) {
- mConfirmShowing = true;
- mCurrentDialog.showConfirmationButton(true /* show */);
+ if (authenticated) {
+ mCurrentDialog.announceForAccessibility(
+ mContext.getResources()
+ .getText(mCurrentDialog.getAuthenticatedAccessibilityResourceId()));
+ if (mCurrentDialog.requiresConfirmation()) {
+ mCurrentDialog.showConfirmationButton(true /* show */);
+ } else {
+ mCurrentDialog.updateState(BiometricDialogView.STATE_AUTHENTICATED);
+ mHandler.postDelayed(() -> {
+ handleHideDialog(false /* userCanceled */);
+ }, mCurrentDialog.getDelayAfterAuthenticatedDurationMs());
+ }
} else {
- mCurrentDialog.updateState(BiometricDialogView.STATE_AUTHENTICATED);
- mHandler.postDelayed(() -> {
- handleHideDialog(false /* userCanceled */);
- }, mCurrentDialog.getDelayAfterAuthenticatedDurationMs());
+ handleBiometricHelp(mContext.getResources()
+ .getString(com.android.internal.R.string.biometric_not_recognized),
+ true /* requireTryAgain */);
+ mCurrentDialog.showTryAgainButton(true /* show */);
}
}
- private void handleBiometricHelp(String message) {
+ private void handleBiometricHelp(String message, boolean requireTryAgain) {
if (DEBUG) Log.d(TAG, "handleBiometricHelp: " + message);
- mCurrentDialog.showHelpMessage(message);
+ mCurrentDialog.showHelpMessage(message, requireTryAgain);
}
private void handleBiometricError(String error) {
@@ -258,7 +255,6 @@
if (DEBUG) Log.d(TAG, "Dialog already dismissed");
return;
}
- mTryAgainShowing = false;
mCurrentDialog.showErrorMessage(error);
}
@@ -279,8 +275,6 @@
}
mReceiver = null;
mDialogShowing = false;
- mConfirmShowing = false;
- mTryAgainShowing = false;
mCurrentDialog.startDismiss();
}
@@ -294,7 +288,6 @@
} catch (RemoteException e) {
Log.e(TAG, "Remote exception when handling negative button", e);
}
- mTryAgainShowing = false;
handleHideDialog(false /* userCanceled */);
}
@@ -308,25 +301,16 @@
} catch (RemoteException e) {
Log.e(TAG, "Remote exception when handling positive button", e);
}
- mConfirmShowing = false;
handleHideDialog(false /* userCanceled */);
}
private void handleUserCanceled() {
- mTryAgainShowing = false;
- mConfirmShowing = false;
handleHideDialog(true /* userCanceled */);
}
- private void handleShowTryAgain() {
- mCurrentDialog.showTryAgainButton(true /* show */);
- mTryAgainShowing = true;
- }
-
private void handleTryAgainPressed() {
try {
mCurrentDialog.clearTemporaryMessage();
- mTryAgainShowing = false;
mReceiver.onTryAgainPressed();
} catch (RemoteException e) {
Log.e(TAG, "RemoteException when handling try again", e);
@@ -337,13 +321,20 @@
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
final boolean wasShowing = mDialogShowing;
+
+ // Save the state of the current dialog (buttons showing, etc)
+ final Bundle savedState = new Bundle();
+ if (mCurrentDialog != null) {
+ mCurrentDialog.onSaveState(savedState);
+ }
+
if (mDialogShowing) {
mCurrentDialog.forceRemove();
mDialogShowing = false;
}
- createDialogs();
+
if (wasShowing) {
- handleShowDialog(mCurrentDialogArgs, true /* skipAnimation */);
+ handleShowDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java
index 9934bfd..b8c69c80 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java
@@ -56,12 +56,15 @@
private static final String TAG = "BiometricDialogView";
+ private static final String KEY_TRY_AGAIN_VISIBILITY = "key_try_again_visibility";
+ private static final String KEY_CONFIRM_VISIBILITY = "key_confirm_visibility";
+
private static final int ANIMATION_DURATION_SHOW = 250; // ms
private static final int ANIMATION_DURATION_AWAY = 350; // ms
private static final int MSG_CLEAR_MESSAGE = 1;
- protected static final int STATE_NONE = 0;
+ protected static final int STATE_IDLE = 0;
protected static final int STATE_AUTHENTICATING = 1;
protected static final int STATE_ERROR = 2;
protected static final int STATE_PENDING_CONFIRMATION = 3;
@@ -78,12 +81,19 @@
private final float mDialogWidth;
private final DialogViewCallback mCallback;
- private ViewGroup mLayout;
- private final Button mPositiveButton;
- private final Button mNegativeButton;
- private final TextView mErrorText;
+ protected final ViewGroup mLayout;
+ protected final LinearLayout mDialog;
+ protected final TextView mTitleText;
+ protected final TextView mSubtitleText;
+ protected final TextView mDescriptionText;
+ protected final ImageView mBiometricIcon;
+ protected final TextView mErrorText;
+ protected final Button mPositiveButton;
+ protected final Button mNegativeButton;
+ protected final Button mTryAgainButton;
+
private Bundle mBundle;
- private final LinearLayout mDialog;
+
private int mLastState;
private boolean mAnimatingAway;
private boolean mWasForceRemoved;
@@ -91,15 +101,13 @@
protected boolean mRequireConfirmation;
private int mUserId; // used to determine if we should show work background
- private boolean mPendingShowTryAgain;
- private boolean mPendingShowConfirm;
-
protected abstract int getHintStringResourceId();
protected abstract int getAuthenticatedAccessibilityResourceId();
protected abstract int getIconDescriptionResourceId();
protected abstract Drawable getAnimationForTransition(int oldState, int newState);
protected abstract boolean shouldAnimateForTransition(int oldState, int newState);
protected abstract int getDelayAfterAuthenticatedDurationMs();
+ protected abstract boolean shouldGrayAreaDismissDialog();
private final Runnable mShowAnimationRunnable = new Runnable() {
@Override
@@ -124,7 +132,7 @@
public void handleMessage(Message msg) {
switch(msg.what) {
case MSG_CLEAR_MESSAGE:
- handleClearMessage();
+ handleClearMessage((boolean) msg.obj /* requireTryAgain */);
break;
default:
Log.e(TAG, "Unhandled message: " + msg.what);
@@ -158,10 +166,6 @@
mLayout = (ViewGroup) factory.inflate(R.layout.biometric_dialog, this, false);
addView(mLayout);
- mDialog = mLayout.findViewById(R.id.dialog);
-
- mErrorText = mLayout.findViewById(R.id.error);
-
mLayout.setOnKeyListener(new View.OnKeyListener() {
boolean downPressed = false;
@Override
@@ -184,12 +188,19 @@
final View space = mLayout.findViewById(R.id.space);
final View leftSpace = mLayout.findViewById(R.id.left_space);
final View rightSpace = mLayout.findViewById(R.id.right_space);
- final ImageView icon = mLayout.findViewById(R.id.biometric_icon);
- final Button tryAgain = mLayout.findViewById(R.id.button_try_again);
+
+ mDialog = mLayout.findViewById(R.id.dialog);
+ mTitleText = mLayout.findViewById(R.id.title);
+ mSubtitleText = mLayout.findViewById(R.id.subtitle);
+ mDescriptionText = mLayout.findViewById(R.id.description);
+ mBiometricIcon = mLayout.findViewById(R.id.biometric_icon);
+ mErrorText = mLayout.findViewById(R.id.error);
mNegativeButton = mLayout.findViewById(R.id.button2);
mPositiveButton = mLayout.findViewById(R.id.button1);
+ mTryAgainButton = mLayout.findViewById(R.id.button_try_again);
- icon.setContentDescription(getResources().getString(getIconDescriptionResourceId()));
+ mBiometricIcon.setContentDescription(
+ getResources().getString(getIconDescriptionResourceId()));
setDismissesDialog(space);
setDismissesDialog(leftSpace);
@@ -206,8 +217,9 @@
}, getDelayAfterAuthenticatedDurationMs());
});
- tryAgain.setOnClickListener((View v) -> {
+ mTryAgainButton.setOnClickListener((View v) -> {
showTryAgainButton(false /* show */);
+ handleClearMessage(false /* requireTryAgain */);
mCallback.onTryAgainPressed();
});
@@ -215,15 +227,17 @@
mLayout.requestFocus();
}
+ public void onSaveState(Bundle bundle) {
+ bundle.putInt(KEY_TRY_AGAIN_VISIBILITY, mTryAgainButton.getVisibility());
+ bundle.putInt(KEY_CONFIRM_VISIBILITY, mPositiveButton.getVisibility());
+ }
+
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
mErrorText.setText(getHintStringResourceId());
- final TextView title = mLayout.findViewById(R.id.title);
- final TextView subtitle = mLayout.findViewById(R.id.subtitle);
- final TextView description = mLayout.findViewById(R.id.description);
final ImageView backgroundView = mLayout.findViewById(R.id.background);
if (mUserManager.isManagedProfile(mUserId)) {
@@ -244,36 +258,34 @@
mDialog.getLayoutParams().width = (int) mDialogWidth;
}
- mLastState = STATE_NONE;
+ mLastState = STATE_IDLE;
updateState(STATE_AUTHENTICATING);
CharSequence titleText = mBundle.getCharSequence(BiometricPrompt.KEY_TITLE);
- title.setText(titleText);
- title.setSelected(true);
+ mTitleText.setVisibility(View.VISIBLE);
+ mTitleText.setText(titleText);
+ mTitleText.setSelected(true);
final CharSequence subtitleText = mBundle.getCharSequence(BiometricPrompt.KEY_SUBTITLE);
if (TextUtils.isEmpty(subtitleText)) {
- subtitle.setVisibility(View.GONE);
+ mSubtitleText.setVisibility(View.GONE);
} else {
- subtitle.setVisibility(View.VISIBLE);
- subtitle.setText(subtitleText);
+ mSubtitleText.setVisibility(View.VISIBLE);
+ mSubtitleText.setText(subtitleText);
}
final CharSequence descriptionText =
mBundle.getCharSequence(BiometricPrompt.KEY_DESCRIPTION);
if (TextUtils.isEmpty(descriptionText)) {
- description.setVisibility(View.GONE);
+ mDescriptionText.setVisibility(View.GONE);
} else {
- description.setVisibility(View.VISIBLE);
- description.setText(descriptionText);
+ mDescriptionText.setVisibility(View.VISIBLE);
+ mDescriptionText.setText(descriptionText);
}
mNegativeButton.setText(mBundle.getCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT));
- showTryAgainButton(mPendingShowTryAgain);
- showConfirmationButton(mPendingShowConfirm);
-
if (mWasForceRemoved || mSkipIntro) {
// Show the dialog immediately
mLayout.animate().cancel();
@@ -302,8 +314,7 @@
? (AnimatedVectorDrawable) icon
: null;
- final ImageView imageView = getLayout().findViewById(R.id.biometric_icon);
- imageView.setImageDrawable(icon);
+ mBiometricIcon.setImageDrawable(icon);
if (animation != null && shouldAnimateForTransition(lastState, newState)) {
animation.forceAnimationOnUI();
@@ -314,7 +325,7 @@
private void setDismissesDialog(View v) {
v.setClickable(true);
v.setOnTouchListener((View view, MotionEvent event) -> {
- if (mLastState != STATE_AUTHENTICATED) {
+ if (mLastState != STATE_AUTHENTICATED && shouldGrayAreaDismissDialog()) {
mCallback.onUserCanceled();
}
return true;
@@ -331,11 +342,9 @@
mWindowManager.removeView(BiometricDialogView.this);
mAnimatingAway = false;
// Set the icons / text back to normal state
- handleClearMessage();
+ handleClearMessage(false /* requireTryAgain */);
showTryAgainButton(false /* show */);
- mPendingShowTryAgain = false;
- mPendingShowConfirm = false;
- updateState(STATE_NONE);
+ updateState(STATE_IDLE);
}
};
@@ -412,35 +421,42 @@
return mLayout;
}
- // Clears the temporary message and shows the help message.
- private void handleClearMessage() {
- updateState(STATE_AUTHENTICATING);
- mErrorText.setText(getHintStringResourceId());
- mErrorText.setTextColor(mTextColor);
+ // Clears the temporary message and shows the help message. If requireTryAgain is true,
+ // we will start the authenticating state again.
+ private void handleClearMessage(boolean requireTryAgain) {
+ if (!requireTryAgain) {
+ updateState(STATE_AUTHENTICATING);
+ mErrorText.setText(getHintStringResourceId());
+ mErrorText.setTextColor(mTextColor);
+ mErrorText.setVisibility(View.VISIBLE);
+ } else {
+ updateState(STATE_IDLE);
+ mErrorText.setVisibility(View.INVISIBLE);
+ }
}
// Shows an error/help message
- private void showTemporaryMessage(String message) {
+ private void showTemporaryMessage(String message, boolean requireTryAgain) {
mHandler.removeMessages(MSG_CLEAR_MESSAGE);
updateState(STATE_ERROR);
mErrorText.setText(message);
mErrorText.setTextColor(mErrorColor);
mErrorText.setContentDescription(message);
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CLEAR_MESSAGE),
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CLEAR_MESSAGE, requireTryAgain),
BiometricPrompt.HIDE_DIALOG_DELAY);
}
public void clearTemporaryMessage() {
mHandler.removeMessages(MSG_CLEAR_MESSAGE);
- mHandler.obtainMessage(MSG_CLEAR_MESSAGE).sendToTarget();
+ mHandler.obtainMessage(MSG_CLEAR_MESSAGE, false /* requireTryAgain */).sendToTarget();
}
- public void showHelpMessage(String message) {
- showTemporaryMessage(message);
+ public void showHelpMessage(String message, boolean requireTryAgain) {
+ showTemporaryMessage(message, requireTryAgain);
}
public void showErrorMessage(String error) {
- showTemporaryMessage(error);
+ showTemporaryMessage(error, false /* requireTryAgain */);
showTryAgainButton(false /* show */);
mCallback.onErrorShown();
}
@@ -459,22 +475,16 @@
}
public void showTryAgainButton(boolean show) {
- final Button tryAgain = mLayout.findViewById(R.id.button_try_again);
if (show) {
- tryAgain.setVisibility(View.VISIBLE);
+ mTryAgainButton.setVisibility(View.VISIBLE);
} else {
- tryAgain.setVisibility(View.GONE);
+ mTryAgainButton.setVisibility(View.GONE);
}
}
- // Set the state before the window is attached, so we know if the dialog should be started
- // with or without the button. This is because there's no good onPause signal
- public void setPendingTryAgain(boolean show) {
- mPendingShowTryAgain = show;
- }
-
- public void setPendingConfirm(boolean show) {
- mPendingShowConfirm = show;
+ public void restoreState(Bundle bundle) {
+ mTryAgainButton.setVisibility(bundle.getInt(KEY_TRY_AGAIN_VISIBILITY));
+ mPositiveButton.setVisibility(bundle.getInt(KEY_CONFIRM_VISIBILITY));
}
public WindowManager.LayoutParams getLayoutParams() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java
index de3f947..359cb04 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java
@@ -16,8 +16,18 @@
package com.android.systemui.biometrics;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
import android.content.Context;
+import android.graphics.Outline;
import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.view.ViewOutlineProvider;
import com.android.systemui.R;
@@ -28,13 +38,243 @@
*/
public class FaceDialogView extends BiometricDialogView {
+ private static final String TAG = "FaceDialogView";
+ private static final String KEY_DIALOG_SIZE = "key_dialog_size";
+
private static final int HIDE_DIALOG_DELAY = 500; // ms
+ private static final int IMPLICIT_Y_PADDING = 16; // dp
+ private static final int GROW_DURATION = 150; // ms
+ private static final int TEXT_ANIMATE_DISTANCE = 32; // dp
+
+ private static final int SIZE_UNKNOWN = 0;
+ private static final int SIZE_SMALL = 1;
+ private static final int SIZE_GROWING = 2;
+ private static final int SIZE_BIG = 3;
+
+ private int mSize;
+ private float mIconOriginalY;
+ private DialogOutlineProvider mOutlineProvider = new DialogOutlineProvider();
+
+ private final class DialogOutlineProvider extends ViewOutlineProvider {
+
+ float mY;
+
+ @Override
+ public void getOutline(View view, Outline outline) {
+ outline.setRoundRect(
+ 0 /* left */,
+ (int) mY, /* top */
+ mDialog.getWidth() /* right */,
+ mDialog.getBottom(), /* bottom */
+ getResources().getDimension(R.dimen.biometric_dialog_corner_size));
+ }
+
+ int calculateSmall() {
+ final float padding = dpToPixels(IMPLICIT_Y_PADDING);
+ return mDialog.getHeight() - mBiometricIcon.getHeight() - 2 * (int) padding;
+ }
+
+ void setOutlineY(float y) {
+ mY = y;
+ }
+ }
public FaceDialogView(Context context,
DialogViewCallback callback) {
super(context, callback);
}
+ private void updateSize(int newSize) {
+ final float padding = dpToPixels(IMPLICIT_Y_PADDING);
+ final float iconSmallPositionY = mDialog.getHeight() - mBiometricIcon.getHeight() - padding;
+
+ if (newSize == SIZE_SMALL) {
+ // These fields are required and/or always hold a spot on the UI, so should be set to
+ // INVISIBLE so they keep their position
+ mTitleText.setVisibility(View.INVISIBLE);
+ mErrorText.setVisibility(View.INVISIBLE);
+ mNegativeButton.setVisibility(View.INVISIBLE);
+
+ // These fields are optional, so set them to gone or invisible depending on their
+ // usage. If they're empty, they're already set to GONE in BiometricDialogView.
+ if (!TextUtils.isEmpty(mSubtitleText.getText())) {
+ mSubtitleText.setVisibility(View.INVISIBLE);
+ }
+ if (!TextUtils.isEmpty(mDescriptionText.getText())) {
+ mDescriptionText.setVisibility(View.INVISIBLE);
+ }
+
+ // Move the biometric icon to the small spot
+ mBiometricIcon.setY(iconSmallPositionY);
+
+ // Clip the dialog to the small size
+ mDialog.setOutlineProvider(mOutlineProvider);
+ mOutlineProvider.setOutlineY(mOutlineProvider.calculateSmall());
+
+ mDialog.setClipToOutline(true);
+ mDialog.invalidateOutline();
+
+ mSize = newSize;
+ } else if (mSize == SIZE_SMALL && newSize == SIZE_BIG) {
+ mSize = SIZE_GROWING;
+
+ // Animate the outline
+ final ValueAnimator outlineAnimator =
+ ValueAnimator.ofFloat(mOutlineProvider.calculateSmall(), 0);
+ outlineAnimator.addUpdateListener((animation) -> {
+ final float y = (float) animation.getAnimatedValue();
+ mOutlineProvider.setOutlineY(y);
+ mDialog.invalidateOutline();
+ });
+
+ // Animate the icon back to original big position
+ final ValueAnimator iconAnimator =
+ ValueAnimator.ofFloat(iconSmallPositionY, mIconOriginalY);
+ iconAnimator.addUpdateListener((animation) -> {
+ final float y = (float) animation.getAnimatedValue();
+ mBiometricIcon.setY(y);
+ });
+
+ // Animate the error text so it slides up with the icon
+ final ValueAnimator textSlideAnimator =
+ ValueAnimator.ofFloat(dpToPixels(TEXT_ANIMATE_DISTANCE), 0);
+ textSlideAnimator.addUpdateListener((animation) -> {
+ final float y = (float) animation.getAnimatedValue();
+ mErrorText.setTranslationY(y);
+ });
+
+ // Opacity animator for things that should fade in (title, subtitle, details, negative
+ // button)
+ final ValueAnimator opacityAnimator = ValueAnimator.ofFloat(0, 1);
+ opacityAnimator.addUpdateListener((animation) -> {
+ final float opacity = (float) animation.getAnimatedValue();
+
+ // These fields are required and/or always hold a spot on the UI
+ mTitleText.setAlpha(opacity);
+ mErrorText.setAlpha(opacity);
+ mNegativeButton.setAlpha(opacity);
+ mTryAgainButton.setAlpha(opacity);
+
+ // These fields are optional, so only animate them if they're supposed to be showing
+ if (!TextUtils.isEmpty(mSubtitleText.getText())) {
+ mSubtitleText.setAlpha(opacity);
+ }
+ if (!TextUtils.isEmpty(mDescriptionText.getText())) {
+ mDescriptionText.setAlpha(opacity);
+ }
+ });
+
+ // Choreograph together
+ final AnimatorSet as = new AnimatorSet();
+ as.setDuration(GROW_DURATION);
+ as.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+ // Set the visibility of opacity-animating views back to VISIBLE
+ mTitleText.setVisibility(View.VISIBLE);
+ mErrorText.setVisibility(View.VISIBLE);
+ mNegativeButton.setVisibility(View.VISIBLE);
+ mTryAgainButton.setVisibility(View.VISIBLE);
+
+ if (!TextUtils.isEmpty(mSubtitleText.getText())) {
+ mSubtitleText.setVisibility(View.VISIBLE);
+ }
+ if (!TextUtils.isEmpty(mDescriptionText.getText())) {
+ mDescriptionText.setVisibility(View.VISIBLE);
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ mSize = SIZE_BIG;
+ }
+ });
+ as.play(outlineAnimator).with(iconAnimator).with(opacityAnimator)
+ .with(textSlideAnimator);
+ as.start();
+ } else if (mSize == SIZE_BIG) {
+ mDialog.setClipToOutline(false);
+ mDialog.invalidateOutline();
+
+ mBiometricIcon.setY(mIconOriginalY);
+
+ mSize = newSize;
+ }
+ }
+
+ @Override
+ public void onSaveState(Bundle bundle) {
+ super.onSaveState(bundle);
+ bundle.putInt(KEY_DIALOG_SIZE, mSize);
+ }
+
+ @Override
+ public void restoreState(Bundle bundle) {
+ super.restoreState(bundle);
+ // Keep in mind that this happens before onAttachedToWindow()
+ mSize = bundle.getInt(KEY_DIALOG_SIZE);
+ }
+
+ /**
+ * Do small/big layout here instead of onAttachedToWindow, since:
+ * 1) We need the big layout to be measured, etc for small -> big animation
+ * 2) We need the dialog measurements to know where to move the biometric icon to
+ *
+ * BiometricDialogView already sets the views to their default big state, so here we only
+ * need to hide the ones that are unnecessary.
+ */
+ @Override
+ public void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+
+ if (mIconOriginalY == 0) {
+ mIconOriginalY = mBiometricIcon.getY();
+ }
+
+ // UNKNOWN means size hasn't been set yet. First time we create the dialog.
+ // onLayout can happen when visibility of views change (during animation, etc).
+ if (mSize != SIZE_UNKNOWN) {
+ // Probably not the cleanest way to do this, but since dialog is big by default,
+ // and small dialogs can persist across orientation changes, we need to set it to
+ // small size here again.
+ if (mSize == SIZE_SMALL) {
+ updateSize(SIZE_SMALL);
+ }
+ return;
+ }
+
+ // If we don't require confirmation, show the small dialog first (until errors occur).
+ if (!requiresConfirmation()) {
+ updateSize(SIZE_SMALL);
+ } else {
+ updateSize(SIZE_BIG);
+ }
+ }
+
+ @Override
+ public void showErrorMessage(String error) {
+ super.showErrorMessage(error);
+
+ // All error messages will cause the dialog to go from small -> big. Error messages
+ // are messages such as lockout, auth failed, etc.
+ if (mSize == SIZE_SMALL) {
+ updateSize(SIZE_BIG);
+ }
+ }
+
+ @Override
+ public void showTryAgainButton(boolean show) {
+ if (show && mSize == SIZE_SMALL) {
+ // Do not call super, we will nicely animate the alpha together with the rest
+ // of the elements in here.
+ updateSize(SIZE_BIG);
+ } else {
+ super.showTryAgainButton(show);
+ }
+ }
+
@Override
protected int getHintStringResourceId() {
return R.string.face_dialog_looking_for_face;
@@ -56,7 +296,9 @@
@Override
protected boolean shouldAnimateForTransition(int oldState, int newState) {
- if (oldState == STATE_NONE && newState == STATE_AUTHENTICATING) {
+ if (oldState == STATE_ERROR && newState == STATE_IDLE) {
+ return true;
+ } else if (oldState == STATE_IDLE && newState == STATE_AUTHENTICATING) {
return false;
} else if (oldState == STATE_AUTHENTICATING && newState == STATE_ERROR) {
return true;
@@ -78,9 +320,19 @@
}
@Override
+ protected boolean shouldGrayAreaDismissDialog() {
+ if (mSize == SIZE_SMALL) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
protected Drawable getAnimationForTransition(int oldState, int newState) {
int iconRes;
- if (oldState == STATE_NONE && newState == STATE_AUTHENTICATING) {
+ if (oldState == STATE_ERROR && newState == STATE_IDLE) {
+ iconRes = R.drawable.face_dialog_error_to_face;
+ } else if (oldState == STATE_IDLE && newState == STATE_AUTHENTICATING) {
iconRes = R.drawable.face_dialog_face_to_error;
} else if (oldState == STATE_AUTHENTICATING && newState == STATE_ERROR) {
iconRes = R.drawable.face_dialog_face_to_error;
@@ -97,4 +349,14 @@
}
return mContext.getDrawable(iconRes);
}
+
+ private float dpToPixels(float dp) {
+ return dp * ((float) mContext.getResources().getDisplayMetrics().densityDpi
+ / DisplayMetrics.DENSITY_DEFAULT);
+ }
+
+ private float pixelsToDp(float pixels) {
+ return pixels / ((float) mContext.getResources().getDisplayMetrics().densityDpi
+ / DisplayMetrics.DENSITY_DEFAULT);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java
index 1a6cee2..d63836b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java
@@ -49,7 +49,7 @@
@Override
protected boolean shouldAnimateForTransition(int oldState, int newState) {
- if (oldState == STATE_NONE && newState == STATE_AUTHENTICATING) {
+ if (oldState == STATE_IDLE && newState == STATE_AUTHENTICATING) {
return false;
} else if (oldState == STATE_AUTHENTICATING && newState == STATE_ERROR) {
return true;
@@ -68,9 +68,15 @@
}
@Override
+ protected boolean shouldGrayAreaDismissDialog() {
+ // Fingerprint dialog always dismisses when region outside the dialog is tapped
+ return true;
+ }
+
+ @Override
protected Drawable getAnimationForTransition(int oldState, int newState) {
int iconRes;
- if (oldState == STATE_NONE && newState == STATE_AUTHENTICATING) {
+ if (oldState == STATE_IDLE && newState == STATE_AUTHENTICATING) {
iconRes = R.drawable.fingerprint_dialog_fp_to_error;
} else if (oldState == STATE_AUTHENTICATING && newState == STATE_ERROR) {
iconRes = R.drawable.fingerprint_dialog_fp_to_error;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 881aa18..9f3ff78 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -23,18 +23,26 @@
import static com.android.systemui.bubbles.BubbleMovementHelper.EDGE_OVERLAP;
import android.app.Notification;
+import android.app.PendingIntent;
import android.content.Context;
+import android.content.pm.ActivityInfo;
import android.graphics.Point;
import android.graphics.Rect;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
+import android.util.Log;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.NotificationEntryListener;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.phone.StatusBarWindowController;
import java.util.ArrayList;
@@ -57,47 +65,33 @@
private static final String TAG = "BubbleController";
// Enables some subset of notifs to automatically become bubbles
- public static final boolean DEBUG_ENABLE_AUTO_BUBBLE = false;
+ private static final boolean DEBUG_ENABLE_AUTO_BUBBLE = false;
// When a bubble is dismissed, recreate it as a notification
- public static final boolean DEBUG_DEMOTE_TO_NOTIF = false;
+ private static final boolean DEBUG_DEMOTE_TO_NOTIF = false;
// Secure settings
private static final String ENABLE_AUTO_BUBBLE_MESSAGES = "experiment_autobubble_messaging";
private static final String ENABLE_AUTO_BUBBLE_ONGOING = "experiment_autobubble_ongoing";
private static final String ENABLE_AUTO_BUBBLE_ALL = "experiment_autobubble_all";
+ private static final String ENABLE_BUBBLE_ACTIVITY_VIEW = "experiment_bubble_activity_view";
+ private static final String ENABLE_BUBBLE_CONTENT_INTENT = "experiment_bubble_content_intent";
- private Context mContext;
- private BubbleDismissListener mDismissListener;
+ private final Context mContext;
+ private final NotificationEntryManager mNotificationEntryManager;
private BubbleStateChangeListener mStateChangeListener;
private BubbleExpandListener mExpandListener;
- private Map<String, BubbleView> mBubbles = new HashMap<>();
+ private final Map<String, BubbleView> mBubbles = new HashMap<>();
private BubbleStackView mStackView;
- private Point mDisplaySize;
+ private final Point mDisplaySize;
// Bubbles get added to the status bar view
- @VisibleForTesting
- protected StatusBarWindowController mStatusBarWindowController;
+ private final StatusBarWindowController mStatusBarWindowController;
// Used for determining view rect for touch interaction
private Rect mTempRect = new Rect();
/**
- * Listener to find out about bubble / bubble stack dismissal events.
- */
- public interface BubbleDismissListener {
- /**
- * Called when the entire stack of bubbles is dismissed by the user.
- */
- void onStackDismissed();
-
- /**
- * Called when a specific bubble is dismissed by the user.
- */
- void onBubbleDismissed(String key);
- }
-
- /**
* Listener to be notified when some states of the bubbles change.
*/
public interface BubbleStateChangeListener {
@@ -123,17 +117,13 @@
@Inject
public BubbleController(Context context, StatusBarWindowController statusBarWindowController) {
mContext = context;
+ mNotificationEntryManager = Dependency.get(NotificationEntryManager.class);
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
mDisplaySize = new Point();
wm.getDefaultDisplay().getSize(mDisplaySize);
mStatusBarWindowController = statusBarWindowController;
- }
- /**
- * Set a listener to be notified of bubble dismissal events.
- */
- public void setDismissListener(BubbleDismissListener listener) {
- mDismissListener = listener;
+ mNotificationEntryManager.addNotificationEntryListener(mEntryListener);
}
/**
@@ -180,7 +170,7 @@
/**
* Tell the stack of bubbles to be dismissed, this will remove all of the bubbles in the stack.
*/
- public void dismissStack() {
+ void dismissStack() {
if (mStackView == null) {
return;
}
@@ -190,16 +180,14 @@
for (String key: mBubbles.keySet()) {
removeBubble(key);
}
- if (mDismissListener != null) {
- mDismissListener.onStackDismissed();
- }
+ mNotificationEntryManager.updateNotifications();
updateBubblesShowing();
}
/**
* Adds a bubble associated with the provided notification entry or updates it if it exists.
*/
- public void addBubble(NotificationData.Entry notif) {
+ public void addBubble(NotificationEntry notif) {
if (mBubbles.containsKey(notif.key)) {
// It's an update
BubbleView bubble = mBubbles.get(notif.key);
@@ -208,6 +196,9 @@
// It's new
BubbleView bubble = new BubbleView(mContext);
bubble.setNotif(notif);
+ if (shouldUseActivityView(mContext)) {
+ bubble.setAppOverlayIntent(getAppOverlayIntent(notif));
+ }
mBubbles.put(bubble.getKey(), bubble);
boolean setPosition = mStackView != null && mStackView.getVisibility() != VISIBLE;
@@ -235,21 +226,54 @@
}
}
+ @Nullable
+ private PendingIntent getAppOverlayIntent(NotificationEntry notif) {
+ Notification notification = notif.notification.getNotification();
+ if (canLaunchInActivityView(notification.getAppOverlayIntent())) {
+ return notification.getAppOverlayIntent();
+ } else if (shouldUseContentIntent(mContext)
+ && canLaunchInActivityView(notification.contentIntent)) {
+ Log.d(TAG, "[addBubble " + notif.key
+ + "]: No appOverlayIntent, using contentIntent.");
+ return notification.contentIntent;
+ }
+ Log.d(TAG, "[addBubble " + notif.key + "]: No supported intent for ActivityView.");
+ return null;
+ }
+
/**
* Removes the bubble associated with the {@param uri}.
*/
- public void removeBubble(String key) {
+ void removeBubble(String key) {
BubbleView bv = mBubbles.get(key);
if (mStackView != null && bv != null) {
mStackView.removeBubble(bv);
+ bv.destroyActivityView(mStackView);
bv.getEntry().setBubbleDismissed(true);
}
- if (mDismissListener != null) {
- mDismissListener.onBubbleDismissed(key);
+
+ NotificationEntry entry = mNotificationEntryManager.getNotificationData().get(key);
+ if (entry != null) {
+ entry.setBubbleDismissed(true);
+ if (!DEBUG_DEMOTE_TO_NOTIF) {
+ mNotificationEntryManager.performRemoveNotification(entry.notification);
+ }
}
+ mNotificationEntryManager.updateNotifications();
+
updateBubblesShowing();
}
+ @SuppressWarnings("FieldCanBeLocal")
+ private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
+ @Override
+ public void onPendingEntryAdded(NotificationEntry entry) {
+ if (shouldAutoBubble(mContext, entry)) {
+ entry.setIsBubble(true);
+ }
+ }
+ };
+
private void updateBubblesShowing() {
boolean hasBubblesShowing = false;
for (BubbleView bv : mBubbles.values()) {
@@ -277,16 +301,17 @@
}
ArrayList<BubbleView> viewsToRemove = new ArrayList<>();
for (BubbleView bv : mBubbles.values()) {
- NotificationData.Entry entry = bv.getEntry();
+ NotificationEntry entry = bv.getEntry();
if (entry != null) {
if (entry.isRowRemoved() || entry.isBubbleDismissed() || entry.isRowDismissed()) {
viewsToRemove.add(bv);
}
}
}
- for (BubbleView view : viewsToRemove) {
- mBubbles.remove(view.getKey());
- mStackView.removeBubble(view);
+ for (BubbleView bubbleView : viewsToRemove) {
+ mBubbles.remove(bubbleView.getKey());
+ mStackView.removeBubble(bubbleView);
+ bubbleView.destroyActivityView(mStackView);
}
if (mStackView != null) {
mStackView.setVisibility(visible ? VISIBLE : INVISIBLE);
@@ -308,8 +333,19 @@
return mTempRect;
}
+ private boolean canLaunchInActivityView(PendingIntent intent) {
+ if (intent == null) {
+ return false;
+ }
+ ActivityInfo info =
+ intent.getIntent().resolveActivityInfo(mContext.getPackageManager(), 0);
+ return info != null
+ && ActivityInfo.isResizeableMode(info.resizeMode)
+ && (info.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) != 0;
+ }
+
@VisibleForTesting
- public BubbleStackView getStackView() {
+ BubbleStackView getStackView() {
return mStackView;
}
@@ -317,7 +353,7 @@
/**
* Gets an appropriate starting point to position the bubble stack.
*/
- public static Point getStartPoint(int size, Point displaySize) {
+ private static Point getStartPoint(int size, Point displaySize) {
final int x = displaySize.x - size + EDGE_OVERLAP;
final int y = displaySize.y / 4;
return new Point(x, y);
@@ -326,7 +362,7 @@
/**
* Gets an appropriate position for the bubble when the stack is expanded.
*/
- public static Point getExpandPoint(BubbleStackView view, int size, Point displaySize) {
+ static Point getExpandPoint(BubbleStackView view, int size, Point displaySize) {
// Same place for now..
return new Point(EDGE_OVERLAP, size);
}
@@ -334,7 +370,7 @@
/**
* Whether the notification should bubble or not.
*/
- public static boolean shouldAutoBubble(Context context, NotificationData.Entry entry) {
+ private static boolean shouldAutoBubble(Context context, NotificationEntry entry) {
if (entry.isBubbleDismissed()) {
return false;
}
@@ -380,4 +416,14 @@
return Settings.Secure.getInt(context.getContentResolver(),
ENABLE_AUTO_BUBBLE_ALL, 0) != 0;
}
+
+ private static boolean shouldUseActivityView(Context context) {
+ return Settings.Secure.getInt(context.getContentResolver(),
+ ENABLE_BUBBLE_ACTIVITY_VIEW, 0) != 0;
+ }
+
+ private static boolean shouldUseContentIntent(Context context) {
+ return Settings.Secure.getInt(context.getContentResolver(),
+ ENABLE_BUBBLE_CONTENT_INTENT, 0) != 0;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java
index e28d96b..badefe1 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java
@@ -24,6 +24,7 @@
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
+import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.recents.TriangleShape;
@@ -35,6 +36,8 @@
// The triangle pointing to the expanded view
private View mPointerView;
+ // The view displayed between the pointer and the expanded view
+ private TextView mHeaderView;
// The view that is being displayed for the expanded state
private View mExpandedView;
@@ -68,6 +71,7 @@
TriangleShape.create(width, height, true /* pointUp */));
triangleDrawable.setTint(Color.WHITE); // TODO: dark mode
mPointerView.setBackground(triangleDrawable);
+ mHeaderView = findViewById(R.id.bubble_content_header);
}
/**
@@ -80,9 +84,19 @@
}
/**
+ * Set the text displayed within the header.
+ */
+ public void setHeaderText(CharSequence text) {
+ mHeaderView.setText(text);
+ }
+
+ /**
* Set the view to display for the expanded state. Passing null will clear the view.
*/
public void setExpandedView(View view) {
+ if (mExpandedView == view) {
+ return;
+ }
if (mExpandedView != null) {
removeView(mExpandedView);
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index dfd18b2..3280a33 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -21,10 +21,13 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
+import android.app.ActivityView;
+import android.app.PendingIntent;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.RectF;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -40,7 +43,7 @@
import com.android.internal.widget.ViewClippingUtil;
import com.android.systemui.R;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
import com.android.systemui.statusbar.notification.stack.ViewState;
@@ -50,6 +53,7 @@
*/
public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.FloatingView {
+ private static final String TAG = "BubbleStackView";
private Point mDisplaySize;
private FrameLayout mBubbleContainer;
@@ -59,6 +63,7 @@
private int mBubblePadding;
private boolean mIsExpanded;
+ private int mExpandedBubbleHeight;
private BubbleView mExpandedBubble;
private Point mCollapsedPosition;
private BubbleTouchHandler mTouchHandler;
@@ -106,6 +111,7 @@
mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size);
mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding);
+ mExpandedBubbleHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height);
mDisplaySize = new Point();
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getSize(mDisplaySize);
@@ -247,7 +253,7 @@
* @param bubbleView the view to update in the stack.
* @param entry the entry to update it with.
*/
- public void updateBubble(BubbleView bubbleView, NotificationData.Entry entry) {
+ public void updateBubble(BubbleView bubbleView, NotificationEntry entry) {
// TODO - move to top of bubble stack, make it show its update if it makes sense
bubbleView.update(entry);
if (bubbleView.equals(mExpandedBubble)) {
@@ -389,27 +395,63 @@
}
private void updateExpandedBubble() {
- if (mExpandedBubble != null) {
+ if (mExpandedBubble == null) {
+ return;
+ }
+
+ if (mExpandedBubble.hasAppOverlayIntent()) {
+ ActivityView expandedView = mExpandedBubble.getActivityView();
+ expandedView.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, mExpandedBubbleHeight));
+
+ final PendingIntent intent = mExpandedBubble.getAppOverlayIntent();
+ mExpandedViewContainer.setHeaderText(intent.getIntent().getComponent().toShortString());
+ mExpandedViewContainer.setExpandedView(expandedView);
+ expandedView.setCallback(new ActivityView.StateCallback() {
+ @Override
+ public void onActivityViewReady(ActivityView view) {
+ Log.d(TAG, "onActivityViewReady("
+ + mExpandedBubble.getEntry().key + "): " + view);
+ view.startActivity(intent);
+ }
+
+ @Override
+ public void onActivityViewDestroyed(ActivityView view) {
+ NotificationEntry entry = mExpandedBubble.getEntry();
+ Log.d(TAG, "onActivityViewDestroyed(key="
+ + ((entry != null) ? entry.key : "(none)") + "): " + view);
+ }
+ });
+ } else {
ExpandableNotificationRow row = mExpandedBubble.getRowView();
- if (!row.equals(mExpandedViewContainer.getChildAt(0))) {
+ if (!row.equals(mExpandedViewContainer.getExpandedView())) {
// Different expanded view than what we have
mExpandedViewContainer.setExpandedView(null);
}
- int pointerPosition = mExpandedBubble.getPosition().x
- + (mExpandedBubble.getWidth() / 2);
- mExpandedViewContainer.setPointerPosition(pointerPosition);
mExpandedViewContainer.setExpandedView(row);
+ mExpandedViewContainer.setHeaderText(null);
}
+ int pointerPosition = mExpandedBubble.getPosition().x
+ + (mExpandedBubble.getWidth() / 2);
+ mExpandedViewContainer.setPointerPosition(pointerPosition);
}
private void applyCurrentState() {
+ Log.d(TAG, "applyCurrentState: mIsExpanded=" + mIsExpanded);
+
mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE);
if (!mIsExpanded) {
mExpandedViewContainer.setExpandedView(null);
} else {
mExpandedViewContainer.setTranslationY(mBubbleContainer.getHeight());
- ExpandableNotificationRow row = mExpandedBubble.getRowView();
- applyRowState(row);
+ View expandedView = mExpandedViewContainer.getExpandedView();
+ if (expandedView instanceof ActivityView) {
+ if (expandedView.isAttachedToWindow()) {
+ ((ActivityView) expandedView).onLocationChanged();
+ }
+ } else {
+ applyRowState(mExpandedBubble.getRowView());
+ }
}
int bubbsCount = mBubbleContainer.getChildCount();
for (int i = 0; i < bubbsCount; i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
index 88030ee..96b2dba 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
@@ -33,7 +33,7 @@
* Handles interpreting touches on a {@link BubbleStackView}. This includes expanding, collapsing,
* dismissing, and flings.
*/
-public class BubbleTouchHandler implements View.OnTouchListener {
+class BubbleTouchHandler implements View.OnTouchListener {
private BubbleController mController = Dependency.get(BubbleController.class);
private PipDismissViewController mDismissViewController;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
index 6c47aac..c1bbb93 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
@@ -16,7 +16,9 @@
package com.android.systemui.bubbles;
+import android.app.ActivityView;
import android.app.Notification;
+import android.app.PendingIntent;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Point;
@@ -25,27 +27,31 @@
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.OvalShape;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import com.android.internal.util.ContrastColorUtil;
import com.android.systemui.R;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
/**
* A floating object on the screen that has a collapsed and expanded state.
*/
-public class BubbleView extends LinearLayout implements BubbleTouchHandler.FloatingView {
+class BubbleView extends LinearLayout implements BubbleTouchHandler.FloatingView {
private static final String TAG = "BubbleView";
private Context mContext;
private View mIconView;
- private NotificationData.Entry mEntry;
+ private NotificationEntry mEntry;
private int mBubbleSize;
private int mIconSize;
+ private PendingIntent mAppOverlayIntent;
+ private ActivityView mActivityView;
public BubbleView(Context context) {
this(context, null);
@@ -72,7 +78,7 @@
*
* @param entry the notification to display as a bubble.
*/
- public void setNotif(NotificationData.Entry entry) {
+ public void setNotif(NotificationEntry entry) {
removeAllViews();
// TODO: migrate to inflater
mIconView = new ImageView(mContext);
@@ -89,7 +95,7 @@
/**
* Updates the UI based on the entry.
*/
- public void update(NotificationData.Entry entry) {
+ public void update(NotificationEntry entry) {
mEntry = entry;
Notification n = entry.notification.getNotification();
Icon ic = n.getLargeIcon() != null ? n.getLargeIcon() : n.getSmallIcon();
@@ -112,17 +118,56 @@
/**
* @return the notification entry associated with this bubble.
*/
- public NotificationData.Entry getEntry() {
+ public NotificationEntry getEntry() {
return mEntry;
}
/**
- * @return the view to display when the bubble is expanded.
+ * @return the view to display notification content when the bubble is expanded.
*/
public ExpandableNotificationRow getRowView() {
return mEntry.getRow();
}
+ /**
+ * @return a view used to display app overlay content when expanded.
+ */
+ public ActivityView getActivityView() {
+ if (mActivityView == null) {
+ mActivityView = new ActivityView(mContext);
+ Log.d(TAG, "[getActivityView] created: " + mActivityView);
+ }
+ return mActivityView;
+ }
+
+ /**
+ * Removes and releases an ActivityView if one was previously created for this bubble.
+ */
+ public void destroyActivityView(ViewGroup tmpParent) {
+ if (mActivityView == null) {
+ return;
+ }
+ // HACK: Only release if initialized. There's no way to know if the ActivityView has
+ // been initialized. Calling release() if it hasn't been initialized will crash.
+
+ if (!mActivityView.isAttachedToWindow()) {
+ // HACK: release() will crash if the view is not attached.
+
+ mActivityView.setVisibility(View.GONE);
+ tmpParent.addView(mActivityView, new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+ }
+ try {
+ mActivityView.release();
+ } catch (IllegalStateException ex) {
+ Log.e(TAG, "ActivityView either already released, or not yet initialized.", ex);
+ }
+
+ ((ViewGroup) mActivityView.getParent()).removeView(mActivityView);
+ mActivityView = null;
+ }
+
@Override
public void setPosition(int x, int y) {
setTranslationX(x);
@@ -162,4 +207,20 @@
lp.height = mBubbleSize;
v.setLayoutParams(lp);
}
+
+ /**
+ * @return whether an ActivityView should be used to display the content of this Bubble
+ */
+ public boolean hasAppOverlayIntent() {
+ return mAppOverlayIntent != null;
+ }
+
+ public PendingIntent getAppOverlayIntent() {
+ return mAppOverlayIntent;
+
+ }
+
+ public void setAppOverlayIntent(PendingIntent intent) {
+ mAppOverlayIntent = intent;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 6a0e8ad..c4c8bc7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -50,9 +50,9 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.policy.NextAlarmController;
import com.android.systemui.statusbar.policy.NextAlarmControllerImpl;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -68,7 +68,7 @@
*/
public class KeyguardSliceProvider extends SliceProvider implements
NextAlarmController.NextAlarmChangeCallback, ZenModeController.Callback,
- NotificationMediaManager.MediaListener {
+ NotificationMediaManager.MediaListener, StatusBarStateController.StateListener {
private static final StyleSpan BOLD_STYLE = new StyleSpan(Typeface.BOLD);
public static final String KEYGUARD_SLICE_URI = "content://com.android.systemui.keyguard/main";
@@ -109,7 +109,9 @@
private AlarmManager.AlarmClockInfo mNextAlarmInfo;
private PendingIntent mPendingIntent;
protected NotificationMediaManager mMediaManager;
+ private StatusBarStateController mStatusBarStateController;
protected MediaMetadata mMediaMetaData;
+ protected boolean mDozing;
/**
* Receiver responsible for time ticking and updating the date format.
@@ -167,9 +169,20 @@
mMediaUri = Uri.parse(KEYGUARD_MEDIA_URI);
}
- public void initDependencies() {
- mMediaManager = Dependency.get(NotificationMediaManager.class);
+ /**
+ * Initialize dependencies that don't exist during {@link android.content.ContentProvider}
+ * instantiation.
+ *
+ * @param mediaManager {@link NotificationMediaManager} singleton.
+ * @param statusBarStateController {@link StatusBarStateController} singleton.
+ */
+ public void initDependencies(
+ NotificationMediaManager mediaManager,
+ StatusBarStateController statusBarStateController) {
+ mMediaManager = mediaManager;
mMediaManager.addCallback(this);
+ mStatusBarStateController = statusBarStateController;
+ mStatusBarStateController.addCallback(this);
}
@AnyThread
@@ -179,7 +192,7 @@
Slice slice;
synchronized (this) {
ListBuilder builder = new ListBuilder(getContext(), mSliceUri, ListBuilder.INFINITY);
- if (mMediaMetaData != null) {
+ if (needsMediaLocked()) {
addMediaLocked(builder);
} else {
builder.addRow(new RowBuilder(mDateUri).setTitle(mLastText));
@@ -193,6 +206,10 @@
return slice;
}
+ protected boolean needsMediaLocked() {
+ return mMediaMetaData != null && mDozing;
+ }
+
protected void addMediaLocked(ListBuilder listBuilder) {
if (mMediaMetaData != null) {
SpannableStringBuilder builder = new SpannableStringBuilder();
@@ -209,7 +226,7 @@
}
RowBuilder mediaBuilder = new RowBuilder(mMediaUri).setTitle(builder);
- Icon notificationIcon = mMediaManager.getMediaIcon();
+ Icon notificationIcon = mMediaManager == null ? null : mMediaManager.getMediaIcon();
if (notificationIcon != null) {
IconCompat icon = IconCompat.createFromIcon(notificationIcon);
mediaBuilder.addEndItem(icon, ListBuilder.ICON_IMAGE);
@@ -389,13 +406,35 @@
@Override
public void onMetadataChanged(MediaMetadata metadata) {
+ final boolean notify;
synchronized (this) {
+ boolean neededMedia = needsMediaLocked();
mMediaMetaData = metadata;
+ notify = neededMedia != needsMediaLocked();
}
- notifyChange();
+ if (notify) {
+ notifyChange();
+ }
}
protected void notifyChange() {
mContentResolver.notifyChange(mSliceUri, null /* observer */);
}
+
+ @Override
+ public void onDozingChanged(boolean isDozing) {
+ final boolean notify;
+ synchronized (this) {
+ boolean neededMedia = needsMediaLocked();
+ mDozing = isDozing;
+ notify = neededMedia != needsMediaLocked();
+ }
+ if (notify) {
+ notifyChange();
+ }
+ }
+
+ @Override
+ public void onStateChanged(int newState) {
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 323cf1f..f14495b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -1843,6 +1843,13 @@
*/
private void handleHide() {
Trace.beginSection("KeyguardViewMediator#handleHide");
+
+ // It's possible that the device was unlocked in a dream state. It's time to wake up.
+ if (mAodShowing) {
+ PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ pm.wakeUp(SystemClock.uptimeMillis(), "com.android.systemui:BOUNCER_DOZING");
+ }
+
synchronized (KeyguardViewMediator.this) {
if (DEBUG) Log.d(TAG, "handleHide");
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java
index 1e0d4d0..b09d6e1 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java
@@ -36,8 +36,7 @@
private Handler mHandler;
private IActivityManager mActivityManager;
private AppOpsManager mAppOpsManager;
-
- private PipMotionHelper mMotionHelper;
+ private Callback mCallback;
private AppOpsManager.OnOpChangedListener mAppOpsChangedListener = new OnOpChangedListener() {
@Override
@@ -52,7 +51,7 @@
if (appInfo.packageName.equals(topPipActivityInfo.first.getPackageName()) &&
mAppOpsManager.checkOpNoThrow(OP_PICTURE_IN_PICTURE, appInfo.uid,
packageName) != MODE_ALLOWED) {
- mHandler.post(() -> mMotionHelper.dismissPip());
+ mHandler.post(() -> mCallback.dismissPip());
}
}
} catch (NameNotFoundException e) {
@@ -63,12 +62,12 @@
};
public PipAppOpsListener(Context context, IActivityManager activityManager,
- PipMotionHelper motionHelper) {
+ Callback callback) {
mContext = context;
mHandler = new Handler(mContext.getMainLooper());
mActivityManager = activityManager;
mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
- mMotionHelper = motionHelper;
+ mCallback = callback;
}
public void onActivityPinned(String packageName) {
@@ -89,4 +88,10 @@
private void unregisterAppOpsListener() {
mAppOpsManager.stopWatchingMode(mAppOpsChangedListener);
}
-}
\ No newline at end of file
+
+ /** Callback for PipAppOpsListener to request changes to the PIP window. */
+ public interface Callback {
+ /** Dismisses the PIP window. */
+ void dismissPip();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
index 3858356..82aa473 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
@@ -55,7 +55,7 @@
/**
* A helper to animate and manipulate the PiP.
*/
-public class PipMotionHelper implements Handler.Callback {
+public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Callback {
private static final String TAG = "PipMotionHelper";
private static final boolean DEBUG = false;
@@ -172,7 +172,8 @@
/**
* Dismisses the pinned stack.
*/
- void dismissPip() {
+ @Override
+ public void dismissPip() {
if (DEBUG) {
Log.d(TAG, "dismissPip: callers=\n" + Debug.getCallers(5, " "));
}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
index 01ee5ca..6ed1eba 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
@@ -52,7 +52,8 @@
@Suppress("DEPRECATION")
override fun onClick(dialog: DialogInterface?, which: Int) {
- Dependency.get(ActivityStarter::class.java).startActivity(intent, false)
+ Dependency.get(ActivityStarter::class.java)
+ .postStartActivityDismissingKeyguard(intent, 0)
}
})
}
@@ -118,12 +119,13 @@
appName.text = app.applicationName
if (showIcons) {
- dialogBuilder.generateIconsForApp(types).forEach {
+ dialogBuilder.generateIconsForApp(types).forEachIndexed { index, it ->
it.setBounds(0, 0, iconSize, iconSize)
val image = ImageView(context).apply {
imageTintList = ColorStateList.valueOf(iconColor)
setImageDrawable(it)
}
+ image.contentDescription = types[index].getName(context)
icons.addView(image, lp)
}
icons.visibility = View.VISIBLE
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 7224599..28285e14 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -426,6 +426,7 @@
if (mExpanded == expanded) return;
mExpanded = expanded;
mHeaderQsPanel.setExpanded(expanded);
+ updateEverything();
}
/**
@@ -697,6 +698,10 @@
.start();
}
+ public void updateEverything() {
+ post(() -> setClickable(!mExpanded));
+ }
+
public void setQSPanel(final QSPanel qsPanel) {
mQsPanel = qsPanel;
setupHost(qsPanel.getHost());
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 81757d0..c474faf 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 float mBackButtonAlpha;
private MotionEvent mStatusBarGestureDownEvent;
private float mWindowCornerRadius;
+ private boolean mSupportsRoundedCornersOnWindows;
private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() {
@@ -244,6 +245,18 @@
}
}
+ public boolean supportsRoundedCornersOnWindows() {
+ if (!verifyCaller("supportsRoundedCornersOnWindows")) {
+ return false;
+ }
+ long token = Binder.clearCallingIdentity();
+ try {
+ return mSupportsRoundedCornersOnWindows;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
private boolean verifyCaller(String reason) {
final int callerId = Binder.getCallingUserHandle().getIdentifier();
if (callerId != mCurrentBoundedUserId) {
@@ -353,6 +366,8 @@
mInteractionFlags = Prefs.getInt(mContext, Prefs.Key.QUICK_STEP_INTERACTION_FLAGS,
getDefaultInteractionFlags());
mWindowCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(mContext.getResources());
+ mSupportsRoundedCornersOnWindows = ScreenDecorationsUtils
+ .supportsRoundedCornersOnWindows(mContext.getResources());
// Listen for the package update changes.
if (mDeviceProvisionedController.getCurrentUser() == UserHandle.USER_SYSTEM) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
index bc38169..a776d0f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar;
-import static com.android.systemui.statusbar.notification.NotificationData.Entry;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Handler;
@@ -29,6 +27,7 @@
import android.view.accessibility.AccessibilityEvent;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag;
import java.util.stream.Stream;
@@ -48,7 +47,7 @@
* NotificationManagerService side, but we keep it to prevent the UI from looking weird and
* will remove when possible. See {@link NotificationLifetimeExtender}
*/
- protected final ArraySet<Entry> mExtendedLifetimeAlertEntries = new ArraySet<>();
+ protected final ArraySet<NotificationEntry> mExtendedLifetimeAlertEntries = new ArraySet<>();
protected NotificationSafeToRemoveCallback mNotificationLifetimeFinishedCallback;
protected int mMinimumDisplayTime;
@@ -61,7 +60,7 @@
* Adds the notification to be managed.
* @param entry entry to show
*/
- public void showNotification(@NonNull Entry entry) {
+ public void showNotification(@NonNull NotificationEntry entry) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "showNotification");
}
@@ -139,7 +138,7 @@
* @return the entry
*/
@Nullable
- public Entry getEntry(@NonNull String key) {
+ public NotificationEntry getEntry(@NonNull String key) {
AlertEntry entry = mAlertEntries.get(key);
return entry != null ? entry.mEntry : null;
}
@@ -149,7 +148,7 @@
* @return all entries
*/
@NonNull
- public Stream<Entry> getAllEntries() {
+ public Stream<NotificationEntry> getAllEntries() {
return mAlertEntries.values().stream().map(headsUpEntry -> headsUpEntry.mEntry);
}
@@ -180,7 +179,7 @@
* Add a new entry and begin managing it.
* @param entry the entry to add
*/
- protected final void addAlertEntry(@NonNull Entry entry) {
+ protected final void addAlertEntry(@NonNull NotificationEntry entry) {
AlertEntry alertEntry = createAlertEntry();
alertEntry.setEntry(entry);
mAlertEntries.put(entry.key, alertEntry);
@@ -203,7 +202,7 @@
if (alertEntry == null) {
return;
}
- Entry entry = alertEntry.mEntry;
+ NotificationEntry entry = alertEntry.mEntry;
mAlertEntries.remove(key);
onAlertEntryRemoved(alertEntry);
entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
@@ -250,12 +249,12 @@
}
@Override
- public boolean shouldExtendLifetime(Entry entry) {
+ public boolean shouldExtendLifetime(NotificationEntry entry) {
return !canRemoveImmediately(entry.key);
}
@Override
- public void setShouldManageLifetime(Entry entry, boolean shouldExtend) {
+ public void setShouldManageLifetime(NotificationEntry entry, boolean shouldExtend) {
if (shouldExtend) {
mExtendedLifetimeAlertEntries.add(entry);
} else {
@@ -265,17 +264,17 @@
///////////////////////////////////////////////////////////////////////////////////////////////
protected class AlertEntry implements Comparable<AlertEntry> {
- @Nullable public Entry mEntry;
+ @Nullable public NotificationEntry mEntry;
public long mPostTime;
public long mEarliestRemovaltime;
@Nullable protected Runnable mRemoveAlertRunnable;
- public void setEntry(@NonNull final Entry entry) {
+ public void setEntry(@NonNull final NotificationEntry entry) {
setEntry(entry, () -> removeAlertEntry(entry.key));
}
- public void setEntry(@NonNull final Entry entry,
+ public void setEntry(@NonNull final NotificationEntry entry,
@Nullable Runnable removeAlertRunnable) {
mEntry = entry;
mRemoveAlertRunnable = removeAlertRunnable;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AmbientPulseManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AmbientPulseManager.java
index 9bfd4ee..a3beb96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AmbientPulseManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AmbientPulseManager.java
@@ -25,7 +25,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag;
import javax.inject.Inject;
@@ -83,7 +83,7 @@
@Override
protected void onAlertEntryAdded(AlertEntry alertEntry) {
- NotificationData.Entry entry = alertEntry.mEntry;
+ NotificationEntry entry = alertEntry.mEntry;
entry.setAmbientPulsing(true);
for (OnAmbientChangedListener listener : mListeners) {
listener.onAmbientStateChanged(entry, true);
@@ -92,7 +92,7 @@
@Override
protected void onAlertEntryRemoved(AlertEntry alertEntry) {
- NotificationData.Entry entry = alertEntry.mEntry;
+ NotificationEntry entry = alertEntry.mEntry;
entry.setAmbientPulsing(false);
for (OnAmbientChangedListener listener : mListeners) {
listener.onAmbientStateChanged(entry, false);
@@ -131,7 +131,7 @@
* @param entry the entry that changed
* @param isPulsing true if the entry is now pulsing, false otherwise
*/
- void onAmbientStateChanged(NotificationData.Entry entry, boolean isPulsing);
+ void onAmbientStateChanged(NotificationEntry entry, boolean isPulsing);
}
private final class AmbientEntry extends AlertEntry {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 6a01563..904478e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -111,7 +111,6 @@
private static final int MSG_SHOW_CHARGING_ANIMATION = 44 << MSG_SHIFT;
private static final int MSG_SHOW_PINNING_TOAST_ENTER_EXIT = 45 << MSG_SHIFT;
private static final int MSG_SHOW_PINNING_TOAST_ESCAPE = 46 << MSG_SHIFT;
- private static final int MSG_BIOMETRIC_TRY_AGAIN = 47 << MSG_SHIFT;
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -271,11 +270,10 @@
default void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
int type, boolean requireConfirmation, int userId) { }
- default void onBiometricAuthenticated() { }
+ default void onBiometricAuthenticated(boolean authenticated) { }
default void onBiometricHelp(String message) { }
default void onBiometricError(String error) { }
default void hideBiometricDialog() { }
- default void showBiometricTryAgain() { }
}
@VisibleForTesting
@@ -736,9 +734,9 @@
}
@Override
- public void onBiometricAuthenticated() {
+ public void onBiometricAuthenticated(boolean authenticated) {
synchronized (mLock) {
- mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED).sendToTarget();
+ mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED, authenticated).sendToTarget();
}
}
@@ -763,13 +761,6 @@
}
}
- @Override
- public void showBiometricTryAgain() {
- synchronized (mLock) {
- mHandler.obtainMessage(MSG_BIOMETRIC_TRY_AGAIN).sendToTarget();
- }
- }
-
private final class H extends Handler {
private H(Looper l) {
super(l);
@@ -991,7 +982,7 @@
break;
case MSG_BIOMETRIC_AUTHENTICATED:
for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).onBiometricAuthenticated();
+ mCallbacks.get(i).onBiometricAuthenticated((boolean) msg.obj);
}
break;
case MSG_BIOMETRIC_HELP:
@@ -1024,11 +1015,6 @@
mCallbacks.get(i).showPinningEscapeToast();
}
break;
- case MSG_BIOMETRIC_TRY_AGAIN:
- for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).showBiometricTryAgain();
- }
- break;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java b/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java
index 758fb7a..22d1d5b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java
@@ -18,12 +18,14 @@
import android.animation.Animator;
import android.content.Context;
+import android.util.Log;
import android.view.ViewPropertyAnimator;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import com.android.systemui.Interpolators;
import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.phone.StatusBar;
/**
* Utility class to calculate general fling animation when the finger is released.
@@ -196,9 +198,16 @@
if (startGradient != mCachedStartGradient
|| velocityFactor != mCachedVelocityFactor) {
float speedup = mSpeedUpFactor * (1.0f - velocityFactor);
- mInterpolator = new PathInterpolator(speedup,
- speedup * startGradient,
- mLinearOutSlowInX2, mY2);
+ float x1 = speedup;
+ float y1 = speedup * startGradient;
+ float x2 = mLinearOutSlowInX2;
+ float y2 = mY2;
+ try {
+ mInterpolator = new PathInterpolator(x1, y1, x2, y2);
+ } catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException("Illegal path with "
+ + "x1=" + x1 + " y1=" + y1 + " x2=" + x2 + " y2=" + y2, e);
+ }
mCachedStartGradient = startGradient;
mCachedVelocityFactor = velocityFactor;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java
index e217777..3f1ff33 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java
@@ -32,7 +32,7 @@
import com.android.keyguard.AlphaOptimizedLinearLayout;
import com.android.systemui.R;
import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import java.util.List;
@@ -50,7 +50,7 @@
private int mEndMargin;
private View mIconPlaceholder;
private TextView mTextView;
- private NotificationData.Entry mShowingEntry;
+ private NotificationEntry mShowingEntry;
private Rect mLayoutedIconRect = new Rect();
private int[] mTmpPosition = new int[2];
private boolean mFirstLayout = true;
@@ -162,7 +162,7 @@
mTextView = findViewById(R.id.text);
}
- public void setEntry(NotificationData.Entry entry) {
+ public void setEntry(NotificationEntry entry) {
if (entry != null) {
mShowingEntry = entry;
CharSequence text = entry.headsUpStatusBarText;
@@ -261,7 +261,7 @@
return super.fitSystemWindows(insets);
}
- public NotificationData.Entry getShowingEntry() {
+ public NotificationEntry getShowingEntry() {
return mShowingEntry;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java
index ecd9814..0f295ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java
@@ -2,7 +2,7 @@
import androidx.annotation.NonNull;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
/**
* Interface for anything that may need to keep notifications managed even after
@@ -24,7 +24,7 @@
* @param entry the entry containing the notification to check
* @return true if the notification lifetime should be extended
*/
- boolean shouldExtendLifetime(@NonNull NotificationData.Entry entry);
+ boolean shouldExtendLifetime(@NonNull NotificationEntry entry);
/**
* Sets whether or not the lifetime should be managed by the extender. In practice, if
@@ -37,7 +37,7 @@
* @param entry the entry that needs an extended lifetime
* @param shouldManage true if the extender should manage the entry now, false otherwise
*/
- void setShouldManageLifetime(@NonNull NotificationData.Entry entry, boolean shouldManage);
+ void setShouldManageLifetime(@NonNull NotificationEntry entry, boolean shouldManage);
/**
* The callback for when the notification is now safe to remove (i.e. its lifetime has ended).
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
index bc662e3..f46ded4d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
@@ -18,7 +18,7 @@
import android.service.notification.StatusBarNotification;
import android.util.SparseArray;
-import com.android.systemui.statusbar.notification.NotificationData.Entry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
public interface NotificationLockscreenUserManager {
String PERMISSION_SELF = "com.android.systemui.permission.SELF";
@@ -55,7 +55,7 @@
void updatePublicMode();
- boolean needsRedaction(Entry entry);
+ boolean needsRedaction(NotificationEntry entry);
boolean userAllowsPrivateNotificationsInPublic(int currentUserId);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index bba4369..d5f4d04 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -46,9 +46,9 @@
import com.android.systemui.Dumpable;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
@@ -407,7 +407,7 @@
}
/** @return true if the entry needs redaction when on the lockscreen. */
- public boolean needsRedaction(NotificationData.Entry ent) {
+ public boolean needsRedaction(NotificationEntry ent) {
int userId = ent.notification.getUserId();
boolean currentUserWantsRedaction = !userAllowsPrivateNotificationsInPublic(mCurrentUserId);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 1bf101c..7412702 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -37,7 +37,6 @@
import android.os.Handler;
import android.os.Trace;
import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
@@ -47,9 +46,9 @@
import com.android.systemui.Dumpable;
import com.android.systemui.Interpolators;
import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.statusbar.notification.NotificationData.Entry;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.phone.BiometricUnlockController;
import com.android.systemui.statusbar.phone.LockscreenWallpaper;
import com.android.systemui.statusbar.phone.ScrimController;
@@ -157,15 +156,10 @@
notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
@Override
public void onEntryRemoved(
- @Nullable Entry entry,
- String key,
- StatusBarNotification old,
+ NotificationEntry entry,
NotificationVisibility visibility,
- boolean lifetimeExtended,
boolean removedByUser) {
- if (!lifetimeExtended) {
- onNotificationRemoved(key);
- }
+ onNotificationRemoved(entry.key);
}
});
}
@@ -194,7 +188,7 @@
return null;
}
synchronized (mEntryManager.getNotificationData()) {
- Entry entry = mEntryManager.getNotificationData().get(mMediaNotificationKey);
+ NotificationEntry entry = mEntryManager.getNotificationData().get(mMediaNotificationKey);
if (entry == null || entry.expandedIcon == null) {
return null;
}
@@ -216,15 +210,15 @@
boolean metaDataChanged = false;
synchronized (mEntryManager.getNotificationData()) {
- ArrayList<Entry> activeNotifications =
+ ArrayList<NotificationEntry> activeNotifications =
mEntryManager.getNotificationData().getActiveNotifications();
final int N = activeNotifications.size();
// Promote the media notification with a controller in 'playing' state, if any.
- Entry mediaNotification = null;
+ NotificationEntry mediaNotification = null;
MediaController controller = null;
for (int i = 0; i < N; i++) {
- final Entry entry = activeNotifications.get(i);
+ final NotificationEntry entry = activeNotifications.get(i);
if (entry.isMediaNotification()) {
final MediaSession.Token token =
@@ -264,7 +258,7 @@
final String pkg = aController.getPackageName();
for (int i = 0; i < N; i++) {
- final Entry entry = activeNotifications.get(i);
+ final NotificationEntry entry = activeNotifications.get(i);
if (entry.notification.getPackageName().equals(pkg)) {
if (DEBUG_MEDIA) {
Log.v(TAG, "DEBUG_MEDIA: found controller matching "
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 886d99e..7d6231f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -51,9 +51,9 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dumpable;
-import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.RemoteInputView;
@@ -103,7 +103,7 @@
* Notifications that are already removed but are kept around because the remote input is
* actively being used (i.e. user is typing in it). See {@link RemoteInputActiveExtender}.
*/
- protected final ArraySet<NotificationData.Entry> mEntriesKeptForRemoteInputActive =
+ protected final ArraySet<NotificationEntry> mEntriesKeptForRemoteInputActive =
new ArraySet<>();
// Dependencies:
@@ -253,14 +253,11 @@
notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
@Override
public void onEntryRemoved(
- @Nullable NotificationData.Entry entry,
- String key,
- StatusBarNotification old,
+ @Nullable NotificationEntry entry,
NotificationVisibility visibility,
- boolean lifetimeExtended,
boolean removedByUser) {
if (removedByUser && entry != null) {
- onPerformRemoveNotification(entry, key);
+ onPerformRemoveNotification(entry, entry.key);
}
}
});
@@ -272,7 +269,7 @@
mRemoteInputController = new RemoteInputController(delegate);
mRemoteInputController.addCallback(new RemoteInputController.Callback() {
@Override
- public void onRemoteInputSent(NotificationData.Entry entry) {
+ public void onRemoteInputSent(NotificationEntry entry) {
if (FORCE_REMOTE_INPUT_HISTORY
&& isNotificationKeptForRemoteInputHistory(entry.key)) {
mNotificationLifetimeFinishedCallback.onSafeToRemove(entry.key);
@@ -416,7 +413,7 @@
}
@VisibleForTesting
- void onPerformRemoveNotification(NotificationData.Entry entry, final String key) {
+ void onPerformRemoveNotification(NotificationEntry entry, final String key) {
if (mKeysKeptForRemoteInputHistory.contains(key)) {
mKeysKeptForRemoteInputHistory.remove(key);
}
@@ -427,7 +424,7 @@
public void onPanelCollapsed() {
for (int i = 0; i < mEntriesKeptForRemoteInputActive.size(); i++) {
- NotificationData.Entry entry = mEntriesKeptForRemoteInputActive.valueAt(i);
+ NotificationEntry entry = mEntriesKeptForRemoteInputActive.valueAt(i);
mRemoteInputController.removeRemoteInput(entry, null);
if (mNotificationLifetimeFinishedCallback != null) {
mNotificationLifetimeFinishedCallback.onSafeToRemove(entry.key);
@@ -440,7 +437,7 @@
return mKeysKeptForRemoteInputHistory.contains(key);
}
- public boolean shouldKeepForRemoteInputHistory(NotificationData.Entry entry) {
+ public boolean shouldKeepForRemoteInputHistory(NotificationEntry entry) {
if (entry.isDismissed()) {
return false;
}
@@ -450,7 +447,7 @@
return (mRemoteInputController.isSpinning(entry.key) || entry.hasJustSentRemoteInput());
}
- public boolean shouldKeepForSmartReplyHistory(NotificationData.Entry entry) {
+ public boolean shouldKeepForSmartReplyHistory(NotificationEntry entry) {
if (entry.isDismissed()) {
return false;
}
@@ -470,13 +467,13 @@
@VisibleForTesting
StatusBarNotification rebuildNotificationForCanceledSmartReplies(
- NotificationData.Entry entry) {
+ NotificationEntry entry) {
return rebuildNotificationWithRemoteInput(entry, null /* remoteInputTest */,
false /* showSpinner */);
}
@VisibleForTesting
- StatusBarNotification rebuildNotificationWithRemoteInput(NotificationData.Entry entry,
+ StatusBarNotification rebuildNotificationWithRemoteInput(NotificationEntry entry,
CharSequence remoteInputText, boolean showSpinner) {
StatusBarNotification sbn = entry.notification;
@@ -533,7 +530,7 @@
}
@VisibleForTesting
- public Set<NotificationData.Entry> getEntriesKeptForRemoteInputActive() {
+ public Set<NotificationEntry> getEntriesKeptForRemoteInputActive() {
return mEntriesKeptForRemoteInputActive;
}
@@ -556,12 +553,12 @@
*/
protected class RemoteInputHistoryExtender extends RemoteInputExtender {
@Override
- public boolean shouldExtendLifetime(@NonNull NotificationData.Entry entry) {
+ public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) {
return shouldKeepForRemoteInputHistory(entry);
}
@Override
- public void setShouldManageLifetime(NotificationData.Entry entry,
+ public void setShouldManageLifetime(NotificationEntry entry,
boolean shouldExtend) {
if (shouldExtend) {
CharSequence remoteInputText = entry.remoteInputText;
@@ -602,12 +599,12 @@
*/
protected class SmartReplyHistoryExtender extends RemoteInputExtender {
@Override
- public boolean shouldExtendLifetime(@NonNull NotificationData.Entry entry) {
+ public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) {
return shouldKeepForSmartReplyHistory(entry);
}
@Override
- public void setShouldManageLifetime(NotificationData.Entry entry,
+ public void setShouldManageLifetime(NotificationEntry entry,
boolean shouldExtend) {
if (shouldExtend) {
StatusBarNotification newSbn = rebuildNotificationForCanceledSmartReplies(entry);
@@ -640,7 +637,7 @@
*/
protected class RemoteInputActiveExtender extends RemoteInputExtender {
@Override
- public boolean shouldExtendLifetime(@NonNull NotificationData.Entry entry) {
+ public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) {
if (entry.isDismissed()) {
return false;
}
@@ -648,7 +645,7 @@
}
@Override
- public void setShouldManageLifetime(NotificationData.Entry entry,
+ public void setShouldManageLifetime(NotificationEntry entry,
boolean shouldExtend) {
if (shouldExtend) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java
index f23ae3f..f0d804d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java
@@ -24,7 +24,7 @@
import androidx.annotation.VisibleForTesting;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import java.util.ArrayList;
import java.util.Arrays;
@@ -52,7 +52,7 @@
}
public static NotificationUiAdjustment extractFromNotificationEntry(
- NotificationData.Entry entry) {
+ NotificationEntry entry) {
return new NotificationUiAdjustment(
entry.key, entry.systemGeneratedSmartActions, entry.smartReplies);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index 017a9c3..bf6caa0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -27,9 +27,9 @@
import com.android.systemui.R;
import com.android.systemui.bubbles.BubbleController;
-import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
@@ -150,13 +150,13 @@
*/
//TODO: Rewrite this to focus on Entries, or some other data object instead of views
public void updateNotificationViews() {
- ArrayList<NotificationData.Entry> activeNotifications = mEntryManager.getNotificationData()
+ ArrayList<NotificationEntry> activeNotifications = mEntryManager.getNotificationData()
.getActiveNotifications();
ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size());
- ArrayList<NotificationData.Entry> toBubble = new ArrayList<>();
+ ArrayList<NotificationEntry> toBubble = new ArrayList<>();
final int N = activeNotifications.size();
for (int i = 0; i < N; i++) {
- NotificationData.Entry ent = activeNotifications.get(i);
+ NotificationEntry ent = activeNotifications.get(i);
if (ent.isRowDismissed() || ent.isRowRemoved()) {
// we don't want to update removed notifications because they could
// temporarily become children if they were isolated before.
@@ -187,7 +187,7 @@
ent.getRow().setSensitive(sensitive, deviceSensitive);
ent.getRow().setNeedsRedaction(needsRedaction);
if (mGroupManager.isChildInGroupWithSummary(ent.notification)) {
- NotificationData.Entry summary = mGroupManager.getGroupSummary(ent.notification);
+ NotificationEntry summary = mGroupManager.getGroupSummary(ent.notification);
List<ExpandableNotificationRow> orderedChildren =
mTmpChildOrderMap.get(summary.getRow());
if (orderedChildren == null) {
@@ -271,7 +271,7 @@
for (int i = 0; i < toBubble.size(); i++) {
// TODO: might make sense to leave them in the shade and just reposition them
- NotificationData.Entry ent = toBubble.get(i);
+ NotificationEntry ent = toBubble.get(i);
mBubbleController.addBubble(ent);
}
@@ -385,7 +385,7 @@
}
while(!stack.isEmpty()) {
ExpandableNotificationRow row = stack.pop();
- NotificationData.Entry entry = row.getEntry();
+ NotificationEntry entry = row.getEntry();
boolean isChildNotification =
mGroupManager.isChildInGroupWithSummary(entry.notification);
@@ -408,7 +408,7 @@
if (!showOnKeyguard) {
// min priority notifications should show if their summary is showing
if (mGroupManager.isChildInGroupWithSummary(entry.notification)) {
- NotificationData.Entry summary = mGroupManager.getLogicalGroupSummary(
+ NotificationEntry summary = mGroupManager.getLogicalGroupSummary(
entry.notification);
if (summary != null && mLockscreenUserManager.shouldShowOnKeyguard(
summary.notification)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
index e8abcc2..998cf52 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
@@ -24,7 +24,7 @@
import android.util.Pair;
import com.android.internal.util.Preconditions;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.policy.RemoteInputView;
import java.lang.ref.WeakReference;
@@ -38,7 +38,7 @@
private static final boolean ENABLE_REMOTE_INPUT =
SystemProperties.getBoolean("debug.enable_remote_input", true);
- private final ArrayList<Pair<WeakReference<NotificationData.Entry>, Object>> mOpen
+ private final ArrayList<Pair<WeakReference<NotificationEntry>, Object>> mOpen
= new ArrayList<>();
private final ArrayMap<String, Object> mSpinning = new ArrayMap<>();
private final ArrayList<Callback> mCallbacks = new ArrayList<>(3);
@@ -101,7 +101,7 @@
* @param entry the entry for which a remote input is now active.
* @param token a token identifying the view that is managing the remote input
*/
- public void addRemoteInput(NotificationData.Entry entry, Object token) {
+ public void addRemoteInput(NotificationEntry entry, Object token) {
Preconditions.checkNotNull(entry);
Preconditions.checkNotNull(token);
@@ -122,7 +122,7 @@
* the entry is only removed if the token matches the last added token for this
* entry. If null, the entry is removed regardless.
*/
- public void removeRemoteInput(NotificationData.Entry entry, Object token) {
+ public void removeRemoteInput(NotificationEntry entry, Object token) {
Preconditions.checkNotNull(entry);
pruneWeakThenRemoveAndContains(null /* contains */, entry /* remove */, token);
@@ -173,7 +173,7 @@
return mSpinning.get(key) == token;
}
- private void apply(NotificationData.Entry entry) {
+ private void apply(NotificationEntry entry) {
mDelegate.setRemoteInputActive(entry, isRemoteInputActive(entry));
boolean remoteInputActive = isRemoteInputActive();
int N = mCallbacks.size();
@@ -185,7 +185,7 @@
/**
* @return true if {@param entry} has an active RemoteInput
*/
- public boolean isRemoteInputActive(NotificationData.Entry entry) {
+ public boolean isRemoteInputActive(NotificationEntry entry) {
return pruneWeakThenRemoveAndContains(entry /* contains */, null /* remove */,
null /* removeToken */);
}
@@ -208,10 +208,10 @@
* @return true if {@param contains} is in the set of active remote inputs
*/
private boolean pruneWeakThenRemoveAndContains(
- NotificationData.Entry contains, NotificationData.Entry remove, Object removeToken) {
+ NotificationEntry contains, NotificationEntry remove, Object removeToken) {
boolean found = false;
for (int i = mOpen.size() - 1; i >= 0; i--) {
- NotificationData.Entry item = mOpen.get(i).first.get();
+ NotificationEntry item = mOpen.get(i).first.get();
Object itemToken = mOpen.get(i).second;
boolean removeTokenMatches = (removeToken == null || itemToken == removeToken);
@@ -235,7 +235,7 @@
mCallbacks.add(callback);
}
- public void remoteInputSent(NotificationData.Entry entry) {
+ public void remoteInputSent(NotificationEntry entry) {
int N = mCallbacks.size();
for (int i = 0; i < N; i++) {
mCallbacks.get(i).onRemoteInputSent(entry);
@@ -248,16 +248,16 @@
}
// Make a copy because closing the remote inputs will modify mOpen.
- ArrayList<NotificationData.Entry> list = new ArrayList<>(mOpen.size());
+ ArrayList<NotificationEntry> list = new ArrayList<>(mOpen.size());
for (int i = mOpen.size() - 1; i >= 0; i--) {
- NotificationData.Entry entry = mOpen.get(i).first.get();
+ NotificationEntry entry = mOpen.get(i).first.get();
if (entry != null && entry.rowExists()) {
list.add(entry);
}
}
for (int i = list.size() - 1; i >= 0; i--) {
- NotificationData.Entry entry = list.get(i);
+ NotificationEntry entry = list.get(i);
if (entry.rowExists()) {
entry.closeRemoteInput();
}
@@ -268,31 +268,31 @@
mDelegate.requestDisallowLongPressAndDismiss();
}
- public void lockScrollTo(NotificationData.Entry entry) {
+ public void lockScrollTo(NotificationEntry entry) {
mDelegate.lockScrollTo(entry);
}
public interface Callback {
default void onRemoteInputActive(boolean active) {}
- default void onRemoteInputSent(NotificationData.Entry entry) {}
+ default void onRemoteInputSent(NotificationEntry entry) {}
}
public interface Delegate {
/**
* Activate remote input if necessary.
*/
- void setRemoteInputActive(NotificationData.Entry entry, boolean remoteInputActive);
+ void setRemoteInputActive(NotificationEntry entry, boolean remoteInputActive);
- /**
- * Request that the view does not dismiss nor perform long press for the current touch.
- */
- void requestDisallowLongPressAndDismiss();
+ /**
+ * Request that the view does not dismiss nor perform long press for the current touch.
+ */
+ void requestDisallowLongPressAndDismiss();
- /**
- * Request that the view is made visible by scrolling to it, and keep the scroll locked until
- * the user scrolls, or {@param v} loses focus or is detached.
- */
- void lockScrollTo(NotificationData.Entry entry);
+ /**
+ * Request that the view is made visible by scrolling to it, and keep the scroll locked until
+ * the user scrolls, or {@param entry} loses focus or is detached.
+ */
+ void lockScrollTo(NotificationEntry entry);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
deleted file mode 100644
index 6d2c001..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
+++ /dev/null
@@ -1,692 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar;
-
-import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS;
-import static android.app.StatusBarManager.DISABLE_NONE;
-
-import android.annotation.DrawableRes;
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.Resources;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.telephony.SubscriptionInfo;
-import android.util.ArraySet;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-
-import com.android.settingslib.graph.SignalDrawable;
-import com.android.systemui.Dependency;
-import com.android.systemui.R;
-import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.phone.StatusBarIconController;
-import com.android.systemui.statusbar.policy.IconLogger;
-import com.android.systemui.statusbar.policy.NetworkController;
-import com.android.systemui.statusbar.policy.NetworkController.IconState;
-import com.android.systemui.statusbar.policy.NetworkControllerImpl;
-import com.android.systemui.statusbar.policy.SecurityController;
-import com.android.systemui.tuner.TunerService;
-import com.android.systemui.tuner.TunerService.Tunable;
-import com.android.systemui.util.Utils.DisableStateTracker;
-
-import java.util.ArrayList;
-import java.util.List;
-
-// Intimately tied to the design of res/layout/signal_cluster_view.xml
-public class SignalClusterView extends LinearLayout implements NetworkControllerImpl.SignalCallback,
- SecurityController.SecurityControllerCallback, Tunable, DarkReceiver {
-
- static final String TAG = "SignalClusterView";
- static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- private static final String SLOT_AIRPLANE = "airplane";
- private static final String SLOT_MOBILE = "mobile";
- private static final String SLOT_WIFI = "wifi";
- private static final String SLOT_ETHERNET = "ethernet";
- private static final String SLOT_VPN = "vpn";
-
- private final NetworkController mNetworkController;
- private final SecurityController mSecurityController;
-
- private boolean mVpnVisible = false;
- private int mVpnIconId = 0;
- private int mLastVpnIconId = -1;
- private boolean mEthernetVisible = false;
- private int mEthernetIconId = 0;
- private int mLastEthernetIconId = -1;
- private boolean mWifiVisible = false;
- private int mWifiStrengthId = 0;
- private int mLastWifiStrengthId = -1;
- private boolean mWifiIn;
- private boolean mWifiOut;
- private boolean mIsAirplaneMode = false;
- private int mAirplaneIconId = 0;
- private int mLastAirplaneIconId = -1;
- private String mAirplaneContentDescription;
- private String mWifiDescription;
- private String mEthernetDescription;
- private ArrayList<PhoneState> mPhoneStates = new ArrayList<PhoneState>();
- private int mIconTint = Color.WHITE;
- private float mDarkIntensity;
- private final Rect mTintArea = new Rect();
-
- ViewGroup mEthernetGroup, mWifiGroup;
- ImageView mVpn, mEthernet, mWifi, mAirplane, mEthernetDark, mWifiDark;
- ImageView mWifiActivityIn;
- ImageView mWifiActivityOut;
- View mWifiAirplaneSpacer;
- View mWifiSignalSpacer;
- LinearLayout mMobileSignalGroup;
-
- private final int mMobileSignalGroupEndPadding;
- private final int mMobileDataIconStartPadding;
- private final int mSecondaryTelephonyPadding;
- private final int mEndPadding;
- private final int mEndPaddingNothingVisible;
- private final float mIconScaleFactor;
-
- private boolean mBlockAirplane;
- private boolean mBlockMobile;
- private boolean mBlockWifi;
- private boolean mBlockEthernet;
- private boolean mActivityEnabled;
- private boolean mForceBlockWifi;
-
- private final IconLogger mIconLogger = Dependency.get(IconLogger.class);
-
- public SignalClusterView(Context context) {
- this(context, null);
- }
-
- public SignalClusterView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public SignalClusterView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- Resources res = getResources();
- mMobileSignalGroupEndPadding =
- res.getDimensionPixelSize(R.dimen.mobile_signal_group_end_padding);
- mMobileDataIconStartPadding =
- res.getDimensionPixelSize(R.dimen.mobile_data_icon_start_padding);
- mSecondaryTelephonyPadding = res.getDimensionPixelSize(R.dimen.secondary_telephony_padding);
- mEndPadding = res.getDimensionPixelSize(R.dimen.signal_cluster_battery_padding);
- mEndPaddingNothingVisible = res.getDimensionPixelSize(
- R.dimen.no_signal_cluster_battery_padding);
-
- TypedValue typedValue = new TypedValue();
- res.getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true);
- mIconScaleFactor = typedValue.getFloat();
- mNetworkController = Dependency.get(NetworkController.class);
- mSecurityController = Dependency.get(SecurityController.class);
- addOnAttachStateChangeListener(
- new DisableStateTracker(DISABLE_NONE, DISABLE2_SYSTEM_ICONS));
- updateActivityEnabled();
- }
-
- public void setForceBlockWifi() {
- mForceBlockWifi = true;
- mBlockWifi = true;
- if (isAttachedToWindow()) {
- // Re-register to get new callbacks.
- mNetworkController.removeCallback(this);
- mNetworkController.addCallback(this);
- }
- }
-
- @Override
- public void onTuningChanged(String key, String newValue) {
- if (!StatusBarIconController.ICON_BLACKLIST.equals(key)) {
- return;
- }
- ArraySet<String> blockList = StatusBarIconController.getIconBlacklist(newValue);
- boolean blockAirplane = blockList.contains(SLOT_AIRPLANE);
- boolean blockMobile = blockList.contains(SLOT_MOBILE);
- boolean blockWifi = blockList.contains(SLOT_WIFI);
- boolean blockEthernet = blockList.contains(SLOT_ETHERNET);
-
- if (blockAirplane != mBlockAirplane || blockMobile != mBlockMobile
- || blockEthernet != mBlockEthernet || blockWifi != mBlockWifi) {
- mBlockAirplane = blockAirplane;
- mBlockMobile = blockMobile;
- mBlockEthernet = blockEthernet;
- mBlockWifi = blockWifi || mForceBlockWifi;
- // Re-register to get new callbacks.
- mNetworkController.removeCallback(this);
- mNetworkController.addCallback(this);
- }
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
-
- mVpn = findViewById(R.id.vpn);
- mEthernetGroup = findViewById(R.id.ethernet_combo);
- mEthernet = findViewById(R.id.ethernet);
- mEthernetDark = findViewById(R.id.ethernet_dark);
- mWifiGroup = findViewById(R.id.wifi_combo);
- mWifi = findViewById(R.id.wifi_signal);
- mWifiDark = findViewById(R.id.wifi_signal_dark);
- mWifiActivityIn = findViewById(R.id.wifi_in);
- mWifiActivityOut= findViewById(R.id.wifi_out);
- mAirplane = findViewById(R.id.airplane);
- mWifiAirplaneSpacer = findViewById(R.id.wifi_airplane_spacer);
- mWifiSignalSpacer = findViewById(R.id.wifi_signal_spacer);
- mMobileSignalGroup = findViewById(R.id.mobile_signal_group);
-
- maybeScaleVpnAndNoSimsIcons();
- }
-
- /**
- * Extracts the icon off of the VPN and no sims views and maybe scale them by
- * {@link #mIconScaleFactor}. Note that the other icons are not scaled here because they are
- * dynamic. As such, they need to be scaled each time the icon changes in {@link #apply()}.
- */
- private void maybeScaleVpnAndNoSimsIcons() {
- if (mIconScaleFactor == 1.f) {
- return;
- }
-
- mVpn.setImageDrawable(new ScalingDrawableWrapper(mVpn.getDrawable(), mIconScaleFactor));
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mVpnVisible = mSecurityController.isVpnEnabled();
- mVpnIconId = currentVpnIconId(mSecurityController.isVpnBranded());
-
- for (PhoneState state : mPhoneStates) {
- if (state.mMobileGroup.getParent() == null) {
- mMobileSignalGroup.addView(state.mMobileGroup);
- }
- }
-
- int endPadding = mMobileSignalGroup.getChildCount() > 0 ? mMobileSignalGroupEndPadding : 0;
- mMobileSignalGroup.setPaddingRelative(0, 0, endPadding, 0);
-
- Dependency.get(TunerService.class).addTunable(this, StatusBarIconController.ICON_BLACKLIST);
-
- apply();
- applyIconTint();
- mNetworkController.addCallback(this);
- mSecurityController.addCallback(this);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- mMobileSignalGroup.removeAllViews();
- Dependency.get(TunerService.class).removeTunable(this);
- mSecurityController.removeCallback(this);
- mNetworkController.removeCallback(this);
-
- super.onDetachedFromWindow();
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- super.onLayout(changed, l, t, r, b);
-
- // Re-run all checks against the tint area for all icons
- applyIconTint();
- }
-
- // From SecurityController.
- @Override
- public void onStateChanged() {
- post(new Runnable() {
- @Override
- public void run() {
- mVpnVisible = mSecurityController.isVpnEnabled();
- mVpnIconId = currentVpnIconId(mSecurityController.isVpnBranded());
- apply();
- }
- });
- }
-
- private void updateActivityEnabled() {
- mActivityEnabled = mContext.getResources().getBoolean(R.bool.config_showActivity);
- }
-
- @Override
- public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
- boolean activityIn, boolean activityOut, String description, boolean isTransient,
- String secondaryLabel) {
- mWifiVisible = statusIcon.visible && !mBlockWifi;
- mWifiStrengthId = statusIcon.icon;
- mWifiDescription = statusIcon.contentDescription;
- mWifiIn = activityIn && mActivityEnabled && mWifiVisible;
- mWifiOut = activityOut && mActivityEnabled && mWifiVisible;
-
- apply();
- }
-
- @Override
- public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
- int qsType, boolean activityIn, boolean activityOut, String typeContentDescription,
- String description, boolean isWide, int subId, boolean roaming) {
- PhoneState state = getState(subId);
- if (state == null) {
- return;
- }
- state.mMobileVisible = statusIcon.visible && !mBlockMobile;
- state.mMobileStrengthId = statusIcon.icon;
- state.mMobileTypeId = statusType;
- state.mMobileDescription = statusIcon.contentDescription;
- state.mMobileTypeDescription = typeContentDescription;
- state.mRoaming = roaming;
- state.mActivityIn = activityIn && mActivityEnabled;
- state.mActivityOut = activityOut && mActivityEnabled;
-
- apply();
- }
-
- @Override
- public void setEthernetIndicators(IconState state) {
- mEthernetVisible = state.visible && !mBlockEthernet;
- mEthernetIconId = state.icon;
- mEthernetDescription = state.contentDescription;
-
- apply();
- }
-
- @Override
- public void setNoSims(boolean show, boolean simDetected) {
- // Noop. Status bar no longer shows no sim icon.
- }
-
- @Override
- public void setSubs(List<SubscriptionInfo> subs) {
- if (hasCorrectSubs(subs)) {
- return;
- }
- mPhoneStates.clear();
- if (mMobileSignalGroup != null) {
- mMobileSignalGroup.removeAllViews();
- }
- final int n = subs.size();
- for (int i = 0; i < n; i++) {
- inflatePhoneState(subs.get(i).getSubscriptionId());
- }
- if (isAttachedToWindow()) {
- applyIconTint();
- }
- }
-
- private boolean hasCorrectSubs(List<SubscriptionInfo> subs) {
- final int N = subs.size();
- if (N != mPhoneStates.size()) {
- return false;
- }
- for (int i = 0; i < N; i++) {
- if (mPhoneStates.get(i).mSubId != subs.get(i).getSubscriptionId()) {
- return false;
- }
- }
- return true;
- }
-
- private PhoneState getState(int subId) {
- for (PhoneState state : mPhoneStates) {
- if (state.mSubId == subId) {
- return state;
- }
- }
- Log.e(TAG, "Unexpected subscription " + subId);
- return null;
- }
-
- private PhoneState inflatePhoneState(int subId) {
- PhoneState state = new PhoneState(subId, mContext);
- if (mMobileSignalGroup != null) {
- mMobileSignalGroup.addView(state.mMobileGroup);
- }
- mPhoneStates.add(state);
- return state;
- }
-
- @Override
- public void setIsAirplaneMode(IconState icon) {
- mIsAirplaneMode = icon.visible && !mBlockAirplane;
- mAirplaneIconId = icon.icon;
- mAirplaneContentDescription = icon.contentDescription;
-
- apply();
- }
-
- @Override
- public void setMobileDataEnabled(boolean enabled) {
- // Don't care.
- }
-
- @Override
- public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
- // Standard group layout onPopulateAccessibilityEvent() implementations
- // ignore content description, so populate manually
- if (mEthernetVisible && mEthernetGroup != null &&
- mEthernetGroup.getContentDescription() != null)
- event.getText().add(mEthernetGroup.getContentDescription());
- if (mWifiVisible && mWifiGroup != null && mWifiGroup.getContentDescription() != null)
- event.getText().add(mWifiGroup.getContentDescription());
- for (PhoneState state : mPhoneStates) {
- state.populateAccessibilityEvent(event);
- }
- return super.dispatchPopulateAccessibilityEventInternal(event);
- }
-
- @Override
- public void onRtlPropertiesChanged(int layoutDirection) {
- super.onRtlPropertiesChanged(layoutDirection);
-
- if (mEthernet != null) {
- mEthernet.setImageDrawable(null);
- mEthernetDark.setImageDrawable(null);
- mLastEthernetIconId = -1;
- }
-
- if (mWifi != null) {
- mWifi.setImageDrawable(null);
- mWifiDark.setImageDrawable(null);
- mLastWifiStrengthId = -1;
- }
-
- for (PhoneState state : mPhoneStates) {
- if (state.mMobileType != null) {
- state.mMobileType.setImageDrawable(null);
- state.mLastMobileTypeId = -1;
- }
- }
-
- if (mAirplane != null) {
- mAirplane.setImageDrawable(null);
- mLastAirplaneIconId = -1;
- }
-
- apply();
- }
-
- @Override
- public boolean hasOverlappingRendering() {
- return false;
- }
-
- // Run after each indicator change.
- private void apply() {
- if (mWifiGroup == null) return;
-
- if (mVpnVisible) {
- if (mLastVpnIconId != mVpnIconId) {
- setIconForView(mVpn, mVpnIconId);
- mLastVpnIconId = mVpnIconId;
- }
- mIconLogger.onIconShown(SLOT_VPN);
- mVpn.setVisibility(View.VISIBLE);
- } else {
- mIconLogger.onIconHidden(SLOT_VPN);
- mVpn.setVisibility(View.GONE);
- }
- if (DEBUG) Log.d(TAG, String.format("vpn: %s", mVpnVisible ? "VISIBLE" : "GONE"));
-
- if (mEthernetVisible) {
- if (mLastEthernetIconId != mEthernetIconId) {
- setIconForView(mEthernet, mEthernetIconId);
- setIconForView(mEthernetDark, mEthernetIconId);
- mLastEthernetIconId = mEthernetIconId;
- }
- mEthernetGroup.setContentDescription(mEthernetDescription);
- mIconLogger.onIconShown(SLOT_ETHERNET);
- mEthernetGroup.setVisibility(View.VISIBLE);
- } else {
- mIconLogger.onIconHidden(SLOT_ETHERNET);
- mEthernetGroup.setVisibility(View.GONE);
- }
-
- if (DEBUG) Log.d(TAG,
- String.format("ethernet: %s",
- (mEthernetVisible ? "VISIBLE" : "GONE")));
-
- if (mWifiVisible) {
- if (mWifiStrengthId != mLastWifiStrengthId) {
- setIconForView(mWifi, mWifiStrengthId);
- setIconForView(mWifiDark, mWifiStrengthId);
- mLastWifiStrengthId = mWifiStrengthId;
- }
- mIconLogger.onIconShown(SLOT_WIFI);
- mWifiGroup.setContentDescription(mWifiDescription);
- mWifiGroup.setVisibility(View.VISIBLE);
- } else {
- mIconLogger.onIconHidden(SLOT_WIFI);
- mWifiGroup.setVisibility(View.GONE);
- }
-
- if (DEBUG) Log.d(TAG,
- String.format("wifi: %s sig=%d",
- (mWifiVisible ? "VISIBLE" : "GONE"),
- mWifiStrengthId));
-
- mWifiActivityIn.setVisibility(mWifiIn ? View.VISIBLE : View.GONE);
- mWifiActivityOut.setVisibility(mWifiOut ? View.VISIBLE : View.GONE);
-
- boolean anyMobileVisible = false;
- int firstMobileTypeId = 0;
- for (PhoneState state : mPhoneStates) {
- if (state.apply(anyMobileVisible)) {
- if (!anyMobileVisible) {
- firstMobileTypeId = state.mMobileTypeId;
- anyMobileVisible = true;
- }
- }
- }
- if (anyMobileVisible) {
- mIconLogger.onIconShown(SLOT_MOBILE);
- } else {
- mIconLogger.onIconHidden(SLOT_MOBILE);
- }
-
- if (mIsAirplaneMode) {
- if (mLastAirplaneIconId != mAirplaneIconId) {
- setIconForView(mAirplane, mAirplaneIconId);
- mLastAirplaneIconId = mAirplaneIconId;
- }
- mAirplane.setContentDescription(mAirplaneContentDescription);
- mIconLogger.onIconShown(SLOT_AIRPLANE);
- mAirplane.setVisibility(View.VISIBLE);
- } else {
- mIconLogger.onIconHidden(SLOT_AIRPLANE);
- mAirplane.setVisibility(View.GONE);
- }
-
- if (mIsAirplaneMode && mWifiVisible) {
- mWifiAirplaneSpacer.setVisibility(View.VISIBLE);
- } else {
- mWifiAirplaneSpacer.setVisibility(View.GONE);
- }
-
- if ((anyMobileVisible && firstMobileTypeId != 0) && mWifiVisible) {
- mWifiSignalSpacer.setVisibility(View.VISIBLE);
- } else {
- mWifiSignalSpacer.setVisibility(View.GONE);
- }
-
- boolean anythingVisible = mWifiVisible || mIsAirplaneMode
- || anyMobileVisible || mVpnVisible || mEthernetVisible;
- setPaddingRelative(0, 0, anythingVisible ? mEndPadding : mEndPaddingNothingVisible, 0);
- }
-
- /**
- * Sets the given drawable id on the view. This method will also scale the icon by
- * {@link #mIconScaleFactor} if appropriate.
- */
- private void setIconForView(ImageView imageView, @DrawableRes int iconId) {
- // Using the imageView's context to retrieve the Drawable so that theme is preserved.
- Drawable icon = imageView.getContext().getDrawable(iconId);
-
- if (mIconScaleFactor == 1.f) {
- imageView.setImageDrawable(icon);
- } else {
- imageView.setImageDrawable(new ScalingDrawableWrapper(icon, mIconScaleFactor));
- }
- }
-
-
- @Override
- public void onDarkChanged(Rect tintArea, float darkIntensity, int tint) {
- boolean changed = tint != mIconTint || darkIntensity != mDarkIntensity
- || !mTintArea.equals(tintArea);
- mIconTint = tint;
- mDarkIntensity = darkIntensity;
- mTintArea.set(tintArea);
- if (changed && isAttachedToWindow()) {
- applyIconTint();
- }
- }
-
- private void applyIconTint() {
- setTint(mVpn, DarkIconDispatcher.getTint(mTintArea, mVpn, mIconTint));
- setTint(mAirplane, DarkIconDispatcher.getTint(mTintArea, mAirplane, mIconTint));
- applyDarkIntensity(
- DarkIconDispatcher.getDarkIntensity(mTintArea, mWifi, mDarkIntensity),
- mWifi, mWifiDark);
- setTint(mWifiActivityIn,
- DarkIconDispatcher.getTint(mTintArea, mWifiActivityIn, mIconTint));
- setTint(mWifiActivityOut,
- DarkIconDispatcher.getTint(mTintArea, mWifiActivityOut, mIconTint));
- applyDarkIntensity(
- DarkIconDispatcher.getDarkIntensity(mTintArea, mEthernet, mDarkIntensity),
- mEthernet, mEthernetDark);
- for (int i = 0; i < mPhoneStates.size(); i++) {
- mPhoneStates.get(i).setIconTint(mIconTint, mDarkIntensity, mTintArea);
- }
- }
-
- private void applyDarkIntensity(float darkIntensity, View lightIcon, View darkIcon) {
- lightIcon.setAlpha(1 - darkIntensity);
- darkIcon.setAlpha(darkIntensity);
- }
-
- private void setTint(ImageView v, int tint) {
- v.setImageTintList(ColorStateList.valueOf(tint));
- }
-
- private int currentVpnIconId(boolean isBranded) {
- return isBranded ? R.drawable.stat_sys_branded_vpn : R.drawable.stat_sys_vpn_ic;
- }
-
- private class PhoneState {
- private final int mSubId;
- private boolean mMobileVisible = false;
- private int mMobileStrengthId = 0, mMobileTypeId = 0;
- private int mLastMobileStrengthId = -1;
- private int mLastMobileTypeId = -1;
- private String mMobileDescription, mMobileTypeDescription;
-
- private ViewGroup mMobileGroup;
- private ImageView mMobile, mMobileType, mMobileRoaming;
- private View mMobileRoamingSpace;
- public boolean mRoaming;
- private ImageView mMobileActivityIn;
- private ImageView mMobileActivityOut;
- public boolean mActivityIn;
- public boolean mActivityOut;
- private SignalDrawable mMobileSignalDrawable;
-
- public PhoneState(int subId, Context context) {
- ViewGroup root = (ViewGroup) LayoutInflater.from(context)
- .inflate(R.layout.mobile_signal_group, null);
- setViews(root);
- mSubId = subId;
- }
-
- public void setViews(ViewGroup root) {
- mMobileGroup = root;
- mMobile = root.findViewById(R.id.mobile_signal);
- mMobileType = root.findViewById(R.id.mobile_type);
- mMobileRoaming = root.findViewById(R.id.mobile_roaming);
- mMobileRoamingSpace = root.findViewById(R.id.mobile_roaming_space);
- mMobileActivityIn = root.findViewById(R.id.mobile_in);
- mMobileActivityOut = root.findViewById(R.id.mobile_out);
- mMobileSignalDrawable = new SignalDrawable(mMobile.getContext());
- mMobile.setImageDrawable(mMobileSignalDrawable);
- }
-
- public boolean apply(boolean isSecondaryIcon) {
- if (mMobileVisible && !mIsAirplaneMode) {
- if (mLastMobileStrengthId != mMobileStrengthId) {
- mMobile.getDrawable().setLevel(mMobileStrengthId);
- mLastMobileStrengthId = mMobileStrengthId;
- }
-
- if (mLastMobileTypeId != mMobileTypeId) {
- mMobileType.setImageResource(mMobileTypeId);
- mLastMobileTypeId = mMobileTypeId;
- }
-
- mMobileGroup.setContentDescription(mMobileTypeDescription
- + " " + mMobileDescription);
- mMobileGroup.setVisibility(View.VISIBLE);
- } else {
- mMobileGroup.setVisibility(View.GONE);
- }
-
- // When this isn't next to wifi, give it some extra padding between the signals.
- mMobileGroup.setPaddingRelative(isSecondaryIcon ? mSecondaryTelephonyPadding : 0,
- 0, 0, 0);
- mMobile.setPaddingRelative(mMobileDataIconStartPadding, 0, 0, 0);
-
- if (DEBUG) Log.d(TAG, String.format("mobile: %s sig=%d typ=%d",
- (mMobileVisible ? "VISIBLE" : "GONE"), mMobileStrengthId, mMobileTypeId));
-
- mMobileType.setVisibility(mMobileTypeId != 0 ? View.VISIBLE : View.GONE);
- mMobileRoaming.setVisibility(mRoaming ? View.VISIBLE : View.GONE);
- mMobileRoamingSpace.setVisibility(mRoaming ? View.VISIBLE : View.GONE);
- mMobileActivityIn.setVisibility(mActivityIn ? View.VISIBLE : View.GONE);
- mMobileActivityOut.setVisibility(mActivityOut ? View.VISIBLE : View.GONE);
-
- return mMobileVisible;
- }
-
- public void populateAccessibilityEvent(AccessibilityEvent event) {
- if (mMobileVisible && mMobileGroup != null
- && mMobileGroup.getContentDescription() != null) {
- event.getText().add(mMobileGroup.getContentDescription());
- }
- }
-
- public void setIconTint(int tint, float darkIntensity, Rect tintArea) {
- mMobileSignalDrawable.setDarkIntensity(darkIntensity);
- setTint(mMobileType, DarkIconDispatcher.getTint(tintArea, mMobileType, tint));
- setTint(mMobileRoaming, DarkIconDispatcher.getTint(tintArea, mMobileRoaming,
- tint));
- setTint(mMobileActivityIn,
- DarkIconDispatcher.getTint(tintArea, mMobileActivityIn, tint));
- setTint(mMobileActivityOut,
- DarkIconDispatcher.getTint(tintArea, mMobileActivityOut, tint));
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
index 9e91133..573c1f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
@@ -21,8 +21,8 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import java.util.Set;
@@ -54,7 +54,7 @@
/**
* Notifies StatusBarService a smart reply is sent.
*/
- public void smartReplySent(NotificationData.Entry entry, int replyIndex, CharSequence reply,
+ public void smartReplySent(NotificationEntry entry, int replyIndex, CharSequence reply,
boolean generatedByAssistant) {
mCallback.onSmartReplySent(entry, reply);
mSendingKeys.add(entry.key);
@@ -70,7 +70,7 @@
* Notifies StatusBarService a smart action is clicked.
*/
public void smartActionClicked(
- NotificationData.Entry entry, int actionIndex, Notification.Action action,
+ NotificationEntry entry, int actionIndex, Notification.Action action,
boolean generatedByAssistant) {
final int count = mEntryManager.getNotificationData().getActiveNotifications().size();
final int rank = mEntryManager.getNotificationData().getRank(entry.key);
@@ -95,7 +95,7 @@
/**
* Smart Replies and Actions have been added to the UI.
*/
- public void smartSuggestionsAdded(final NotificationData.Entry entry, int replyCount,
+ public void smartSuggestionsAdded(final NotificationEntry entry, int replyCount,
int actionCount, boolean generatedByAssistant) {
try {
mBarService.onNotificationSmartSuggestionsAdded(
@@ -105,7 +105,7 @@
}
}
- public void stopSending(final NotificationData.Entry entry) {
+ public void stopSending(final NotificationEntry entry) {
if (entry != null) {
mSendingKeys.remove(entry.notification.getKey());
}
@@ -121,6 +121,6 @@
* @param entry the entry for the notification
* @param reply the reply that was sent
*/
- void onSmartReplySent(NotificationData.Entry entry, CharSequence reply);
+ void onSmartReplySent(NotificationEntry entry, CharSequence reply);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconContainer.java
deleted file mode 100644
index 0652227..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconContainer.java
+++ /dev/null
@@ -1,34 +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.statusbar;
-
-import com.android.internal.statusbar.StatusBarIcon;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Holds an array of {@link com.android.internal.statusbar.StatusBarIcon}s and draws them
- * in a linear layout
- */
-public class StatusBarIconContainer {
- private final List<StatusBarIcon> mIcons = new ArrayList<>();
-
- public StatusBarIconContainer(List<StatusBarIcon> icons) {
- mIcons.addAll(icons);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java
index 7b42dd9..60d8cf4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java
@@ -20,7 +20,6 @@
import static com.android.systemui.statusbar.notification.row.NotificationInflater.FLAG_CONTENT_VIEW_AMBIENT;
import static com.android.systemui.statusbar.notification.row.NotificationInflater.FLAG_CONTENT_VIEW_HEADS_UP;
-import android.annotation.Nullable;
import android.app.Notification;
import android.service.notification.StatusBarNotification;
import android.util.Log;
@@ -30,6 +29,7 @@
import com.android.systemui.statusbar.AmbientPulseManager;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.NotificationInflater;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -72,24 +72,21 @@
notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
@Override
- public void onEntryInflated(NotificationData.Entry entry, int inflatedFlags) {
+ public void onEntryInflated(NotificationEntry entry, int inflatedFlags) {
showAlertingView(entry, inflatedFlags);
}
@Override
- public void onEntryUpdated(NotificationData.Entry entry) {
+ public void onPostEntryUpdated(NotificationEntry entry) {
updateAlertState(entry);
}
@Override
public void onEntryRemoved(
- @Nullable NotificationData.Entry entry,
- String key,
- StatusBarNotification old,
+ NotificationEntry entry,
NotificationVisibility visibility,
- boolean lifetimeExtended,
boolean removedByUser) {
- stopAlerting(key);
+ stopAlerting(entry.key);
}
});
}
@@ -105,7 +102,7 @@
* @param entry entry to add
* @param inflatedFlags flags representing content views that were inflated
*/
- private void showAlertingView(NotificationData.Entry entry,
+ private void showAlertingView(NotificationEntry entry,
@NotificationInflater.InflationFlag int inflatedFlags) {
if ((inflatedFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
// Possible for shouldHeadsUp to change between the inflation starting and ending.
@@ -127,7 +124,7 @@
}
}
- private void updateAlertState(NotificationData.Entry entry) {
+ private void updateAlertState(NotificationEntry entry) {
boolean alertAgain = alertAgain(entry, entry.notification.getNotification());
AlertingNotificationManager alertManager;
boolean shouldAlert;
@@ -154,7 +151,7 @@
}
private static boolean alertAgain(
- NotificationData.Entry oldEntry, Notification newNotification) {
+ NotificationEntry oldEntry, Notification newNotification) {
return oldEntry == null || !oldEntry.hasInterrupted()
|| (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java
deleted file mode 100644
index a51896e..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java
+++ /dev/null
@@ -1,1072 +0,0 @@
-/*
- * Copyright (C) 2008 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.notification;
-
-import static android.app.Notification.CATEGORY_ALARM;
-import static android.app.Notification.CATEGORY_CALL;
-import static android.app.Notification.CATEGORY_EVENT;
-import static android.app.Notification.CATEGORY_MESSAGE;
-import static android.app.Notification.CATEGORY_REMINDER;
-import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
-import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
-import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
-import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
-import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
-
-import android.annotation.NonNull;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.Person;
-import android.content.Context;
-import android.graphics.drawable.Icon;
-import android.os.Bundle;
-import android.os.Parcelable;
-import android.os.SystemClock;
-import android.service.notification.NotificationListenerService.Ranking;
-import android.service.notification.NotificationListenerService.RankingMap;
-import android.service.notification.SnoozeCriterion;
-import android.service.notification.StatusBarNotification;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.view.View;
-import android.widget.ImageView;
-
-import androidx.annotation.Nullable;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.statusbar.StatusBarIcon;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.ContrastColorUtil;
-import com.android.systemui.Dependency;
-import com.android.systemui.statusbar.InflationTask;
-import com.android.systemui.statusbar.NotificationMediaManager;
-import com.android.systemui.statusbar.StatusBarIconView;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.NotificationGuts;
-import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * The list of currently displaying notifications.
- */
-public class NotificationData {
-
- private final NotificationFilter mNotificationFilter = Dependency.get(NotificationFilter.class);
-
- /**
- * These dependencies are late init-ed
- */
- private KeyguardEnvironment mEnvironment;
- private NotificationMediaManager mMediaManager;
-
- private HeadsUpManager mHeadsUpManager;
-
- public static final class Entry {
- private static final long LAUNCH_COOLDOWN = 2000;
- private static final long REMOTE_INPUT_COOLDOWN = 500;
- private static final long INITIALIZATION_DELAY = 400;
- private static final long NOT_LAUNCHED_YET = -LAUNCH_COOLDOWN;
- private static final int COLOR_INVALID = 1;
- public final String key;
- public StatusBarNotification notification;
- public NotificationChannel channel;
- public long lastAudiblyAlertedMs;
- public boolean noisy;
- public boolean ambient;
- public int importance;
- public StatusBarIconView icon;
- public StatusBarIconView expandedIcon;
- private boolean interruption;
- public boolean autoRedacted; // whether the redacted notification was generated by us
- public int targetSdk;
- private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET;
- public CharSequence remoteInputText;
- public List<SnoozeCriterion> snoozeCriteria;
- public int userSentiment = Ranking.USER_SENTIMENT_NEUTRAL;
- /** Smart Actions provided by the NotificationAssistantService. */
- @NonNull
- public List<Notification.Action> systemGeneratedSmartActions = Collections.emptyList();
- public CharSequence[] smartReplies = new CharSequence[0];
- @VisibleForTesting
- public int suppressedVisualEffects;
- public boolean suspended;
-
- private Entry parent; // our parent (if we're in a group)
- private ArrayList<Entry> children = new ArrayList<Entry>();
- private ExpandableNotificationRow row; // the outer expanded view
-
- private int mCachedContrastColor = COLOR_INVALID;
- private int mCachedContrastColorIsFor = COLOR_INVALID;
- private InflationTask mRunningTask = null;
- private Throwable mDebugThrowable;
- public CharSequence remoteInputTextWhenReset;
- public long lastRemoteInputSent = NOT_LAUNCHED_YET;
- public ArraySet<Integer> mActiveAppOps = new ArraySet<>(3);
- public CharSequence headsUpStatusBarText;
- public CharSequence headsUpStatusBarTextPublic;
-
- private long initializationTime = -1;
-
- /**
- * Whether or not this row represents a system notification. Note that if this is
- * {@code null}, that means we were either unable to retrieve the info or have yet to
- * retrieve the info.
- */
- public Boolean mIsSystemNotification;
-
- /**
- * Has the user sent a reply through this Notification.
- */
- private boolean hasSentReply;
-
- /**
- * Whether this notification should be displayed as a bubble.
- */
- private boolean mIsBubble;
-
- /**
- * Whether the user has dismissed this notification when it was in bubble form.
- */
- private boolean mUserDismissedBubble;
-
- public Entry(StatusBarNotification n) {
- this(n, null);
- }
-
- public Entry(StatusBarNotification n, @Nullable Ranking ranking) {
- this.key = n.getKey();
- this.notification = n;
- if (ranking != null) {
- populateFromRanking(ranking);
- }
- }
-
- public void populateFromRanking(@NonNull Ranking ranking) {
- channel = ranking.getChannel();
- lastAudiblyAlertedMs = ranking.getLastAudiblyAlertedMillis();
- importance = ranking.getImportance();
- ambient = ranking.isAmbient();
- snoozeCriteria = ranking.getSnoozeCriteria();
- userSentiment = ranking.getUserSentiment();
- systemGeneratedSmartActions = ranking.getSmartActions() == null
- ? Collections.emptyList() : ranking.getSmartActions();
- smartReplies = ranking.getSmartReplies() == null
- ? new CharSequence[0]
- : ranking.getSmartReplies().toArray(new CharSequence[0]);
- suppressedVisualEffects = ranking.getSuppressedVisualEffects();
- suspended = ranking.isSuspended();
- }
-
- public void setInterruption() {
- interruption = true;
- }
-
- public boolean hasInterrupted() {
- return interruption;
- }
-
- public void setIsBubble(boolean bubbleable) {
- mIsBubble = bubbleable;
- }
-
- public boolean isBubble() {
- return mIsBubble;
- }
-
- public void setBubbleDismissed(boolean userDismissed) {
- mUserDismissedBubble = userDismissed;
- }
-
- public boolean isBubbleDismissed() {
- return mUserDismissedBubble;
- }
-
- /**
- * Resets the notification entry to be re-used.
- */
- public void reset() {
- if (row != null) {
- row.reset();
- }
- }
-
- public ExpandableNotificationRow getRow() {
- return row;
- }
-
- //TODO: This will go away when we have a way to bind an entry to a row
- public void setRow(ExpandableNotificationRow row) {
- this.row = row;
- }
-
- @Nullable
- public List<Entry> getChildren() {
- if (children.size() <= 0) {
- return null;
- }
-
- return children;
- }
-
- public void notifyFullScreenIntentLaunched() {
- setInterruption();
- lastFullScreenIntentLaunchTime = SystemClock.elapsedRealtime();
- }
-
- public boolean hasJustLaunchedFullScreenIntent() {
- return SystemClock.elapsedRealtime() < lastFullScreenIntentLaunchTime + LAUNCH_COOLDOWN;
- }
-
- public boolean hasJustSentRemoteInput() {
- return SystemClock.elapsedRealtime() < lastRemoteInputSent + REMOTE_INPUT_COOLDOWN;
- }
-
- public boolean hasFinishedInitialization() {
- return initializationTime == -1 ||
- SystemClock.elapsedRealtime() > initializationTime + INITIALIZATION_DELAY;
- }
-
- /**
- * Create the icons for a notification
- * @param context the context to create the icons with
- * @param sbn the notification
- * @throws InflationException
- */
- public void createIcons(Context context, StatusBarNotification sbn)
- throws InflationException {
- Notification n = sbn.getNotification();
- final Icon smallIcon = n.getSmallIcon();
- if (smallIcon == null) {
- throw new InflationException("No small icon in notification from "
- + sbn.getPackageName());
- }
-
- // Construct the icon.
- icon = new StatusBarIconView(context,
- sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn);
- icon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
-
- // Construct the expanded icon.
- expandedIcon = new StatusBarIconView(context,
- sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn);
- expandedIcon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
- final StatusBarIcon ic = new StatusBarIcon(
- sbn.getUser(),
- sbn.getPackageName(),
- smallIcon,
- n.iconLevel,
- n.number,
- StatusBarIconView.contentDescForNotification(context, n));
- if (!icon.set(ic) || !expandedIcon.set(ic)) {
- icon = null;
- expandedIcon = null;
- throw new InflationException("Couldn't create icon: " + ic);
- }
- expandedIcon.setVisibility(View.INVISIBLE);
- expandedIcon.setOnVisibilityChangedListener(
- newVisibility -> {
- if (row != null) {
- row.setIconsVisible(newVisibility != View.VISIBLE);
- }
- });
- }
-
- public void setIconTag(int key, Object tag) {
- if (icon != null) {
- icon.setTag(key, tag);
- expandedIcon.setTag(key, tag);
- }
- }
-
- /**
- * Update the notification icons.
- *
- * @param context the context to create the icons with.
- * @param sbn the notification to read the icon from.
- * @throws InflationException
- */
- public void updateIcons(Context context, StatusBarNotification sbn)
- throws InflationException {
- if (icon != null) {
- // Update the icon
- Notification n = sbn.getNotification();
- final StatusBarIcon ic = new StatusBarIcon(
- notification.getUser(),
- notification.getPackageName(),
- n.getSmallIcon(),
- n.iconLevel,
- n.number,
- StatusBarIconView.contentDescForNotification(context, n));
- icon.setNotification(sbn);
- expandedIcon.setNotification(sbn);
- if (!icon.set(ic) || !expandedIcon.set(ic)) {
- throw new InflationException("Couldn't update icon: " + ic);
- }
- }
- }
-
- public int getContrastedColor(Context context, boolean isLowPriority,
- int backgroundColor) {
- int rawColor = isLowPriority ? Notification.COLOR_DEFAULT :
- notification.getNotification().color;
- if (mCachedContrastColorIsFor == rawColor && mCachedContrastColor != COLOR_INVALID) {
- return mCachedContrastColor;
- }
- final int contrasted = ContrastColorUtil.resolveContrastColor(context, rawColor,
- backgroundColor);
- mCachedContrastColorIsFor = rawColor;
- mCachedContrastColor = contrasted;
- return mCachedContrastColor;
- }
-
- /**
- * Abort all existing inflation tasks
- */
- public void abortTask() {
- if (mRunningTask != null) {
- mRunningTask.abort();
- mRunningTask = null;
- }
- }
-
- public void setInflationTask(InflationTask abortableTask) {
- // abort any existing inflation
- InflationTask existing = mRunningTask;
- abortTask();
- mRunningTask = abortableTask;
- if (existing != null && mRunningTask != null) {
- mRunningTask.supersedeTask(existing);
- }
- }
-
- public void onInflationTaskFinished() {
- mRunningTask = null;
- }
-
- @VisibleForTesting
- public InflationTask getRunningTask() {
- return mRunningTask;
- }
-
- /**
- * Set a throwable that is used for debugging
- *
- * @param debugThrowable the throwable to save
- */
- public void setDebugThrowable(Throwable debugThrowable) {
- mDebugThrowable = debugThrowable;
- }
-
- public Throwable getDebugThrowable() {
- return mDebugThrowable;
- }
-
- public void onRemoteInputInserted() {
- lastRemoteInputSent = NOT_LAUNCHED_YET;
- remoteInputTextWhenReset = null;
- }
-
- public void setHasSentReply() {
- hasSentReply = true;
- }
-
- public boolean isLastMessageFromReply() {
- if (!hasSentReply) {
- return false;
- }
- Bundle extras = notification.getNotification().extras;
- CharSequence[] replyTexts = extras.getCharSequenceArray(
- Notification.EXTRA_REMOTE_INPUT_HISTORY);
- if (!ArrayUtils.isEmpty(replyTexts)) {
- return true;
- }
- Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
- if (messages != null && messages.length > 0) {
- Parcelable message = messages[messages.length - 1];
- if (message instanceof Bundle) {
- Notification.MessagingStyle.Message lastMessage =
- Notification.MessagingStyle.Message.getMessageFromBundle(
- (Bundle) message);
- if (lastMessage != null) {
- Person senderPerson = lastMessage.getSenderPerson();
- if (senderPerson == null) {
- return true;
- }
- Person user = extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON);
- return Objects.equals(user, senderPerson);
- }
- }
- }
- return false;
- }
-
- public void setInitializationTime(long time) {
- if (initializationTime == -1) {
- initializationTime = time;
- }
- }
-
- public void sendAccessibilityEvent(int eventType) {
- if (row != null) {
- row.sendAccessibilityEvent(eventType);
- }
- }
-
- /**
- * Used by NotificationMediaManager to determine... things
- * @return {@code true} if we are a media notification
- */
- public boolean isMediaNotification() {
- if (row == null) return false;
-
- return row.isMediaRow();
- }
-
- /**
- * We are a top level child if our parent is the list of notifications duh
- * @return {@code true} if we're a top level notification
- */
- public boolean isTopLevelChild() {
- return row != null && row.isTopLevelChild();
- }
-
- public void resetUserExpansion() {
- if (row != null) row.resetUserExpansion();
- }
-
- public void freeContentViewWhenSafe(@InflationFlag int inflationFlag) {
- if (row != null) row.freeContentViewWhenSafe(inflationFlag);
- }
-
- public void setAmbientPulsing(boolean pulsing) {
- if (row != null) row.setAmbientPulsing(pulsing);
- }
-
- public boolean rowExists() {
- return row != null;
- }
-
- public boolean isRowDismissed() {
- return row != null && row.isDismissed();
- }
-
- public boolean isRowRemoved() {
- return row != null && row.isRemoved();
- }
-
- /**
- * @return {@code true} if the row is null or removed
- */
- public boolean isRemoved() {
- //TODO: recycling invalidates this
- return row == null || row.isRemoved();
- }
-
- /**
- * @return {@code true} if the row is null or dismissed
- */
- public boolean isDismissed() {
- //TODO: recycling
- return row == null || row.isDismissed();
- }
-
- public boolean isRowPinned() {
- return row != null && row.isPinned();
- }
-
- public void setRowPinned(boolean pinned) {
- if (row != null) row.setPinned(pinned);
- }
-
- public boolean isRowAnimatingAway() {
- return row != null && row.isHeadsUpAnimatingAway();
- }
-
- public boolean isRowHeadsUp() {
- return row != null && row.isHeadsUp();
- }
-
- public void setHeadsUp(boolean shouldHeadsUp) {
- if (row != null) row.setHeadsUp(shouldHeadsUp);
- }
-
- public boolean mustStayOnScreen() {
- return row != null && row.mustStayOnScreen();
- }
-
- public void setHeadsUpIsVisible() {
- if (row != null) row.setHeadsUpIsVisible();
- }
-
- //TODO: i'm imagining a world where this isn't just the row, but I could be rwong
- public ExpandableNotificationRow getHeadsUpAnimationView() {
- return row;
- }
-
- public void setUserLocked(boolean userLocked) {
- if (row != null) row.setUserLocked(userLocked);
- }
-
- public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) {
- if (row != null) row.setUserExpanded(userExpanded, allowChildExpansion);
- }
-
- public void setGroupExpansionChanging(boolean changing) {
- if (row != null) row.setGroupExpansionChanging(changing);
- }
-
- public void notifyHeightChanged(boolean needsAnimation) {
- if (row != null) row.notifyHeightChanged(needsAnimation);
- }
-
- public void closeRemoteInput() {
- if (row != null) row.closeRemoteInput();
- }
-
- public boolean areChildrenExpanded() {
- return row != null && row.areChildrenExpanded();
- }
-
- public boolean keepInParent() {
- return row != null && row.keepInParent();
- }
-
- //TODO: probably less confusing to say "is group fully visible"
- public boolean isGroupNotFullyVisible() {
- return row == null || row.isGroupNotFullyVisible();
- }
-
- public NotificationGuts getGuts() {
- if (row != null) return row.getGuts();
- return null;
- }
-
- public boolean hasLowPriorityStateUpdated() {
- return row != null && row.hasLowPriorityStateUpdated();
- }
-
- public void removeRow() {
- if (row != null) row.setRemoved();
- }
-
- public boolean isSummaryWithChildren() {
- return row != null && row.isSummaryWithChildren();
- }
-
- public void setKeepInParent(boolean keep) {
- if (row != null) row.setKeepInParent(keep);
- }
-
- public void onDensityOrFontScaleChanged() {
- if (row != null) row.onDensityOrFontScaleChanged();
- }
-
- public boolean areGutsExposed() {
- return row != null && row.getGuts() != null && row.getGuts().isExposed();
- }
-
- public boolean isChildInGroup() {
- return parent == null;
- }
-
- public void setLowPriorityStateUpdated(boolean updated) {
- if (row != null) row.setLowPriorityStateUpdated(updated);
- }
-
- /**
- * @return Can the underlying notification be cleared? This can be different from whether the
- * notification can be dismissed in case notifications are sensitive on the lockscreen.
- * @see #canViewBeDismissed()
- */
- public boolean isClearable() {
- if (notification == null || !notification.isClearable()) {
- return false;
- }
- if (children.size() > 0) {
- for (int i = 0; i < children.size(); i++) {
- Entry child = children.get(i);
- if (!child.isClearable()) {
- return false;
- }
- }
- }
- return true;
- }
-
- public boolean canViewBeDismissed() {
- if (row == null) return true;
- return row.canViewBeDismissed();
- }
-
- boolean isExemptFromDndVisualSuppression() {
- if (isNotificationBlockedByPolicy(notification.getNotification())) {
- return false;
- }
-
- if ((notification.getNotification().flags
- & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
- return true;
- }
- if (notification.getNotification().isMediaNotification()) {
- return true;
- }
- if (mIsSystemNotification != null && mIsSystemNotification) {
- return true;
- }
- return false;
- }
-
- private boolean shouldSuppressVisualEffect(int effect) {
- if (isExemptFromDndVisualSuppression()) {
- return false;
- }
- return (suppressedVisualEffects & effect) != 0;
- }
-
- /**
- * Returns whether {@link NotificationManager.Policy#SUPPRESSED_EFFECT_FULL_SCREEN_INTENT}
- * is set for this entry.
- */
- public boolean shouldSuppressFullScreenIntent() {
- return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_FULL_SCREEN_INTENT);
- }
-
- /**
- * Returns whether {@link NotificationManager.Policy#SUPPRESSED_EFFECT_PEEK}
- * is set for this entry.
- */
- public boolean shouldSuppressPeek() {
- return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_PEEK);
- }
-
- /**
- * Returns whether {@link NotificationManager.Policy#SUPPRESSED_EFFECT_STATUS_BAR}
- * is set for this entry.
- */
- public boolean shouldSuppressStatusBar() {
- return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_STATUS_BAR);
- }
-
- /**
- * Returns whether {@link NotificationManager.Policy#SUPPRESSED_EFFECT_AMBIENT}
- * is set for this entry.
- */
- public boolean shouldSuppressAmbient() {
- return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_AMBIENT);
- }
-
- /**
- * Returns whether {@link NotificationManager.Policy#SUPPRESSED_EFFECT_NOTIFICATION_LIST}
- * is set for this entry.
- */
- public boolean shouldSuppressNotificationList() {
- return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_NOTIFICATION_LIST);
- }
- }
-
- private final ArrayMap<String, Entry> mEntries = new ArrayMap<>();
- private final ArrayList<Entry> mSortedAndFiltered = new ArrayList<>();
- private final ArrayList<Entry> mFilteredForUser = new ArrayList<>();
-
- private final NotificationGroupManager mGroupManager
- = Dependency.get(NotificationGroupManager.class);
-
- private RankingMap mRankingMap;
- private final Ranking mTmpRanking = new Ranking();
-
- public void setHeadsUpManager(HeadsUpManager headsUpManager) {
- mHeadsUpManager = headsUpManager;
- }
-
- private final Comparator<Entry> mRankingComparator = new Comparator<Entry>() {
- private final Ranking mRankingA = new Ranking();
- private final Ranking mRankingB = new Ranking();
-
- @Override
- public int compare(Entry a, Entry b) {
- final StatusBarNotification na = a.notification;
- final StatusBarNotification nb = b.notification;
- int aImportance = NotificationManager.IMPORTANCE_DEFAULT;
- int bImportance = NotificationManager.IMPORTANCE_DEFAULT;
- int aRank = 0;
- int bRank = 0;
-
- if (mRankingMap != null) {
- // RankingMap as received from NoMan
- getRanking(a.key, mRankingA);
- getRanking(b.key, mRankingB);
- aImportance = mRankingA.getImportance();
- bImportance = mRankingB.getImportance();
- aRank = mRankingA.getRank();
- bRank = mRankingB.getRank();
- }
-
- String mediaNotification = getMediaManager().getMediaNotificationKey();
-
- // IMPORTANCE_MIN media streams are allowed to drift to the bottom
- final boolean aMedia = a.key.equals(mediaNotification)
- && aImportance > NotificationManager.IMPORTANCE_MIN;
- final boolean bMedia = b.key.equals(mediaNotification)
- && bImportance > NotificationManager.IMPORTANCE_MIN;
-
- boolean aSystemMax = aImportance >= NotificationManager.IMPORTANCE_HIGH &&
- isSystemNotification(na);
- boolean bSystemMax = bImportance >= NotificationManager.IMPORTANCE_HIGH &&
- isSystemNotification(nb);
-
- boolean isHeadsUp = a.row.isHeadsUp();
- if (isHeadsUp != b.row.isHeadsUp()) {
- return isHeadsUp ? -1 : 1;
- } else if (isHeadsUp) {
- // Provide consistent ranking with headsUpManager
- return mHeadsUpManager.compare(a, b);
- } else if (a.row.isAmbientPulsing() != b.row.isAmbientPulsing()) {
- return a.row.isAmbientPulsing() ? -1 : 1;
- } else if (aMedia != bMedia) {
- // Upsort current media notification.
- return aMedia ? -1 : 1;
- } else if (aSystemMax != bSystemMax) {
- // Upsort PRIORITY_MAX system notifications
- return aSystemMax ? -1 : 1;
- } else if (aRank != bRank) {
- return aRank - bRank;
- } else {
- return Long.compare(nb.getNotification().when, na.getNotification().when);
- }
- }
- };
-
- private KeyguardEnvironment getEnvironment() {
- if (mEnvironment == null) {
- mEnvironment = Dependency.get(KeyguardEnvironment.class);
- }
- return mEnvironment;
- }
-
- private NotificationMediaManager getMediaManager() {
- if (mMediaManager == null) {
- mMediaManager = Dependency.get(NotificationMediaManager.class);
- }
- return mMediaManager;
- }
-
- /**
- * Returns the sorted list of active notifications (depending on {@link KeyguardEnvironment}
- *
- * <p>
- * This call doesn't update the list of active notifications. Call {@link #filterAndSort()}
- * when the environment changes.
- * <p>
- * Don't hold on to or modify the returned list.
- */
- public ArrayList<Entry> getActiveNotifications() {
- return mSortedAndFiltered;
- }
-
- public ArrayList<Entry> getNotificationsForCurrentUser() {
- mFilteredForUser.clear();
-
- synchronized (mEntries) {
- final int N = mEntries.size();
- for (int i = 0; i < N; i++) {
- Entry entry = mEntries.valueAt(i);
- final StatusBarNotification sbn = entry.notification;
- if (!getEnvironment().isNotificationForCurrentProfiles(sbn)) {
- continue;
- }
- mFilteredForUser.add(entry);
- }
- }
- return mFilteredForUser;
- }
-
- public Entry get(String key) {
- return mEntries.get(key);
- }
-
- public void add(Entry entry) {
- synchronized (mEntries) {
- mEntries.put(entry.notification.getKey(), entry);
- }
- mGroupManager.onEntryAdded(entry);
-
- updateRankingAndSort(mRankingMap);
- }
-
- public Entry remove(String key, RankingMap ranking) {
- Entry removed;
- synchronized (mEntries) {
- removed = mEntries.remove(key);
- }
- if (removed == null) return null;
- mGroupManager.onEntryRemoved(removed);
- updateRankingAndSort(ranking);
- return removed;
- }
-
- /** Updates the given notification entry with the provided ranking. */
- public void update(Entry entry, RankingMap ranking, StatusBarNotification notification) {
- updateRanking(ranking);
- final StatusBarNotification oldNotification = entry.notification;
- entry.notification = notification;
- mGroupManager.onEntryUpdated(entry, oldNotification);
- }
-
- public void updateRanking(RankingMap ranking) {
- updateRankingAndSort(ranking);
- }
-
- public void updateAppOp(int appOp, int uid, String pkg, String key, boolean showIcon) {
- synchronized (mEntries) {
- final int N = mEntries.size();
- for (int i = 0; i < N; i++) {
- Entry entry = mEntries.valueAt(i);
- if (uid == entry.notification.getUid()
- && pkg.equals(entry.notification.getPackageName())
- && key.equals(entry.key)) {
- if (showIcon) {
- entry.mActiveAppOps.add(appOp);
- } else {
- entry.mActiveAppOps.remove(appOp);
- }
- }
- }
- }
- }
-
- /**
- * Returns true if this notification should be displayed in the high-priority notifications
- * section (and on the lockscreen and status bar).
- */
- public boolean isHighPriority(StatusBarNotification statusBarNotification) {
- if (mRankingMap != null) {
- getRanking(statusBarNotification.getKey(), mTmpRanking);
- if (mTmpRanking.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT
- || statusBarNotification.getNotification().isForegroundService()
- || statusBarNotification.getNotification().hasMediaSession()) {
- return true;
- }
- if (mGroupManager.isSummaryOfGroup(statusBarNotification)) {
- for (Entry child : mGroupManager.getLogicalChildren(statusBarNotification)) {
- if (isHighPriority(child.notification)) {
- return true;
- }
- }
- }
- }
- return false;
- }
-
- public boolean isAmbient(String key) {
- if (mRankingMap != null) {
- getRanking(key, mTmpRanking);
- return mTmpRanking.isAmbient();
- }
- return false;
- }
-
- public int getVisibilityOverride(String key) {
- if (mRankingMap != null) {
- getRanking(key, mTmpRanking);
- return mTmpRanking.getVisibilityOverride();
- }
- return Ranking.VISIBILITY_NO_OVERRIDE;
- }
-
- /**
- * Categories that are explicitly called out on DND settings screens are always blocked, if
- * DND has flagged them, even if they are foreground or system notifications that might
- * otherwise visually bypass DND.
- */
- private static boolean isNotificationBlockedByPolicy(Notification n) {
- if (isCategory(CATEGORY_CALL, n)
- || isCategory(CATEGORY_MESSAGE, n)
- || isCategory(CATEGORY_ALARM, n)
- || isCategory(CATEGORY_EVENT, n)
- || isCategory(CATEGORY_REMINDER, n)) {
- return true;
- }
- return false;
- }
-
- private static boolean isCategory(String category, Notification n) {
- return Objects.equals(n.category, category);
- }
-
- public int getImportance(String key) {
- if (mRankingMap != null) {
- getRanking(key, mTmpRanking);
- return mTmpRanking.getImportance();
- }
- return NotificationManager.IMPORTANCE_UNSPECIFIED;
- }
-
- public String getOverrideGroupKey(String key) {
- if (mRankingMap != null) {
- getRanking(key, mTmpRanking);
- return mTmpRanking.getOverrideGroupKey();
- }
- return null;
- }
-
- public List<SnoozeCriterion> getSnoozeCriteria(String key) {
- if (mRankingMap != null) {
- getRanking(key, mTmpRanking);
- return mTmpRanking.getSnoozeCriteria();
- }
- return null;
- }
-
- public NotificationChannel getChannel(String key) {
- if (mRankingMap != null) {
- getRanking(key, mTmpRanking);
- return mTmpRanking.getChannel();
- }
- return null;
- }
-
- public int getRank(String key) {
- if (mRankingMap != null) {
- getRanking(key, mTmpRanking);
- return mTmpRanking.getRank();
- }
- return 0;
- }
-
- public boolean shouldHide(String key) {
- if (mRankingMap != null) {
- getRanking(key, mTmpRanking);
- return mTmpRanking.isSuspended();
- }
- return false;
- }
-
- private void updateRankingAndSort(RankingMap ranking) {
- if (ranking != null) {
- mRankingMap = ranking;
- synchronized (mEntries) {
- final int N = mEntries.size();
- for (int i = 0; i < N; i++) {
- Entry entry = mEntries.valueAt(i);
- if (!getRanking(entry.key, mTmpRanking)) {
- continue;
- }
- final StatusBarNotification oldSbn = entry.notification.cloneLight();
- final String overrideGroupKey = getOverrideGroupKey(entry.key);
- if (!Objects.equals(oldSbn.getOverrideGroupKey(), overrideGroupKey)) {
- entry.notification.setOverrideGroupKey(overrideGroupKey);
- mGroupManager.onEntryUpdated(entry, oldSbn);
- }
- entry.populateFromRanking(mTmpRanking);
- }
- }
- }
- filterAndSort();
- }
-
- /**
- * Get the ranking from the current ranking map.
- *
- * @param key the key to look up
- * @param outRanking the ranking to populate
- *
- * @return {@code true} if the ranking was properly obtained.
- */
- @VisibleForTesting
- protected boolean getRanking(String key, Ranking outRanking) {
- return mRankingMap.getRanking(key, outRanking);
- }
-
- // TODO: This should not be public. Instead the Environment should notify this class when
- // anything changed, and this class should call back the UI so it updates itself.
- public void filterAndSort() {
- mSortedAndFiltered.clear();
-
- synchronized (mEntries) {
- final int N = mEntries.size();
- for (int i = 0; i < N; i++) {
- Entry entry = mEntries.valueAt(i);
-
- if (mNotificationFilter.shouldFilterOut(entry)) {
- continue;
- }
-
- mSortedAndFiltered.add(entry);
- }
- }
-
- Collections.sort(mSortedAndFiltered, mRankingComparator);
- }
-
- public void dump(PrintWriter pw, String indent) {
- int N = mSortedAndFiltered.size();
- pw.print(indent);
- pw.println("active notifications: " + N);
- int active;
- for (active = 0; active < N; active++) {
- NotificationData.Entry e = mSortedAndFiltered.get(active);
- dumpEntry(pw, indent, active, e);
- }
- synchronized (mEntries) {
- int M = mEntries.size();
- pw.print(indent);
- pw.println("inactive notifications: " + (M - active));
- int inactiveCount = 0;
- for (int i = 0; i < M; i++) {
- Entry entry = mEntries.valueAt(i);
- if (!mSortedAndFiltered.contains(entry)) {
- dumpEntry(pw, indent, inactiveCount, entry);
- inactiveCount++;
- }
- }
- }
- }
-
- private void dumpEntry(PrintWriter pw, String indent, int i, Entry e) {
- getRanking(e.key, mTmpRanking);
- pw.print(indent);
- pw.println(" [" + i + "] key=" + e.key + " icon=" + e.icon);
- StatusBarNotification n = e.notification;
- pw.print(indent);
- pw.println(" pkg=" + n.getPackageName() + " id=" + n.getId() + " importance=" +
- mTmpRanking.getImportance());
- pw.print(indent);
- pw.println(" notification=" + n.getNotification());
- }
-
- private static boolean isSystemNotification(StatusBarNotification sbn) {
- String sbnPackage = sbn.getPackageName();
- return "android".equals(sbnPackage) || "com.android.systemui".equals(sbnPackage);
- }
-
- /**
- * Provides access to keyguard state and user settings dependent data.
- */
- public interface KeyguardEnvironment {
- boolean isDeviceProvisioned();
- boolean isNotificationForCurrentProfiles(StatusBarNotification sbn);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java
index 1d06ce0..eea4490 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java
@@ -19,6 +19,7 @@
import android.service.notification.StatusBarNotification;
import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.NotificationInflater;
/**
@@ -29,25 +30,31 @@
* Called when a new notification is posted. At this point, the notification is "pending": its
* views haven't been inflated yet and most of the system pretends like it doesn't exist yet.
*/
- default void onPendingEntryAdded(NotificationData.Entry entry) {
+ default void onPendingEntryAdded(NotificationEntry entry) {
}
/**
* Called when a new entry is created.
*/
- default void onNotificationAdded(NotificationData.Entry entry) {
+ default void onNotificationAdded(NotificationEntry entry) {
}
/**
- * Called when a notification was updated.
+ * Called when a notification is updated, before any filtering of notifications have occurred.
*/
- default void onEntryUpdated(NotificationData.Entry entry) {
+ default void onPreEntryUpdated(NotificationEntry entry) {
+ }
+
+ /**
+ * Called when a notification was updated, after any filtering of notifications have occurred.
+ */
+ default void onPostEntryUpdated(NotificationEntry entry) {
}
/**
* Called when a notification's views are inflated for the first time.
*/
- default void onEntryInflated(NotificationData.Entry entry,
+ default void onEntryInflated(NotificationEntry entry,
@NotificationInflater.InflationFlag int inflatedFlags) {
}
@@ -57,7 +64,7 @@
*
* @param entry notification data entry that was reinflated.
*/
- default void onEntryReinflated(NotificationData.Entry entry) {
+ default void onEntryReinflated(NotificationEntry entry) {
}
/**
@@ -71,20 +78,14 @@
* because the developer retracted it).
* @param entry notification data entry that was removed. Null if no entry existed for the
* removed key at the time of removal.
- * @param key key of notification that was removed
- * @param old StatusBarNotification of the notification before it was removed
* @param visibility logging data related to the visibility of the notification at the time of
* removal, if it was removed by a user action. Null if it was not removed by
* a user action.
- * @param lifetimeExtended true if something is artificially extending how long the notification
* @param removedByUser true if the notification was removed by a user action
*/
default void onEntryRemoved(
- @Nullable NotificationData.Entry entry,
- String key,
- StatusBarNotification old,
+ NotificationEntry entry,
@Nullable NotificationVisibility visibility,
- boolean lifetimeExtended,
boolean removedByUser) {
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index e7344f8..45db002 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -15,13 +15,9 @@
*/
package com.android.systemui.statusbar.notification;
-import static com.android.systemui.bubbles.BubbleController.DEBUG_DEMOTE_TO_NOTIF;
-
import android.annotation.Nullable;
import android.app.Notification;
import android.content.Context;
-import android.os.Handler;
-import android.os.PowerManager;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
@@ -34,14 +30,14 @@
import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.ForegroundServiceController;
-import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.statusbar.NotificationLifetimeExtender;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationUiAdjustment;
import com.android.systemui.statusbar.NotificationUpdateHandler;
-import com.android.systemui.statusbar.notification.NotificationData.KeyguardEnvironment;
-import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
+import com.android.systemui.statusbar.notification.collection.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationData.KeyguardEnvironment;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.NotificationInflater;
import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -54,7 +50,6 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
-import java.util.concurrent.TimeUnit;
/**
* NotificationEntryManager is responsible for the adding, removing, and updating of notifications.
@@ -65,37 +60,28 @@
Dumpable,
NotificationInflater.InflationCallback,
NotificationUpdateHandler,
- VisualStabilityManager.Callback,
- BubbleController.BubbleDismissListener {
+ VisualStabilityManager.Callback {
private static final String TAG = "NotificationEntryMgr";
- protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- public static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30);
+ private final Context mContext;
+ @VisibleForTesting
+ protected final HashMap<String, NotificationEntry> mPendingNotifications = new HashMap<>();
- protected final Context mContext;
- protected final HashMap<String, NotificationData.Entry> mPendingNotifications = new HashMap<>();
-
- private final NotificationGutsManager mGutsManager =
- Dependency.get(NotificationGutsManager.class);
private final DeviceProvisionedController mDeviceProvisionedController =
Dependency.get(DeviceProvisionedController.class);
private final ForegroundServiceController mForegroundServiceController =
Dependency.get(ForegroundServiceController.class);
- private final BubbleController mBubbleController = Dependency.get(BubbleController.class);
// Lazily retrieved dependencies
private NotificationRemoteInputManager mRemoteInputManager;
private NotificationRowBinder mNotificationRowBinder;
- private final Handler mDeferredNotificationViewUpdateHandler;
- private Runnable mUpdateNotificationViewsCallback;
-
private NotificationPresenter mPresenter;
- protected PowerManager mPowerManager;
private NotificationListenerService.RankingMap mLatestRankingMap;
- protected HeadsUpManager mHeadsUpManager;
+ @VisibleForTesting
protected NotificationData mNotificationData;
- protected NotificationListContainer mListContainer;
+ private NotificationListContainer mListContainer;
@VisibleForTesting
final ArrayList<NotificationLifetimeExtender> mNotificationLifetimeExtenders
= new ArrayList<>();
@@ -121,7 +107,7 @@
if (mPendingNotifications.size() == 0) {
pw.println("null");
} else {
- for (NotificationData.Entry entry : mPendingNotifications.values()) {
+ for (NotificationEntry entry : mPendingNotifications.values()) {
pw.println(entry.notification);
}
}
@@ -129,10 +115,7 @@
public NotificationEntryManager(Context context) {
mContext = context;
- mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
- mBubbleController.setDismissListener(this /* bubbleEventListener */);
mNotificationData = new NotificationData();
- mDeferredNotificationViewUpdateHandler = new Handler();
}
/** Adds a {@link NotificationEntryListener}. */
@@ -157,13 +140,17 @@
return mNotificationRowBinder;
}
+ // TODO: Remove this once we can always use a mocked row binder in our tests
+ @VisibleForTesting
+ void setRowBinder(NotificationRowBinder notificationRowBinder) {
+ mNotificationRowBinder = notificationRowBinder;
+ }
+
public void setUpWithPresenter(NotificationPresenter presenter,
NotificationListContainer listContainer,
HeadsUpManager headsUpManager) {
mPresenter = presenter;
- mUpdateNotificationViewsCallback = mPresenter::updateNotificationViews;
- mHeadsUpManager = headsUpManager;
- mNotificationData.setHeadsUpManager(mHeadsUpManager);
+ mNotificationData.setHeadsUpManager(headsUpManager);
mListContainer = listContainer;
mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
@@ -186,14 +173,6 @@
return mNotificationData;
}
- protected Context getContext() {
- return mContext;
- }
-
- protected NotificationPresenter getPresenter() {
- return mPresenter;
- }
-
@Override
public void onReorderingAllowed() {
updateNotifications();
@@ -208,30 +187,13 @@
n.getKey(), null, nv, false /* forceRemove */, true /* removedByUser */);
}
- @Override
- public void onStackDismissed() {
- updateNotifications();
- }
-
- @Override
- public void onBubbleDismissed(String key) {
- NotificationData.Entry entry = mNotificationData.get(key);
- if (entry != null) {
- entry.setBubbleDismissed(true);
- if (!DEBUG_DEMOTE_TO_NOTIF) {
- performRemoveNotification(entry.notification);
- }
- }
- updateNotifications();
- }
-
private void abortExistingInflation(String key) {
if (mPendingNotifications.containsKey(key)) {
- NotificationData.Entry entry = mPendingNotifications.get(key);
+ NotificationEntry entry = mPendingNotifications.get(key);
entry.abortTask();
mPendingNotifications.remove(key);
}
- NotificationData.Entry addedEntry = mNotificationData.get(key);
+ NotificationEntry addedEntry = mNotificationData.get(key);
if (addedEntry != null) {
addedEntry.abortTask();
}
@@ -252,32 +214,8 @@
}
}
- private void addEntry(NotificationData.Entry shadeEntry) {
- if (shadeEntry == null) {
- return;
- }
- // Add the expanded view and icon.
- mNotificationData.add(shadeEntry);
- tagForeground(shadeEntry.notification);
- updateNotifications();
- for (NotificationEntryListener listener : mNotificationEntryListeners) {
- listener.onNotificationAdded(shadeEntry);
- }
-
- maybeScheduleUpdateNotificationViews(shadeEntry);
- }
-
- private void maybeScheduleUpdateNotificationViews(NotificationData.Entry entry) {
- long audibleAlertTimeout = RECENTLY_ALERTED_THRESHOLD_MS
- - (System.currentTimeMillis() - entry.lastAudiblyAlertedMs);
- if (audibleAlertTimeout > 0) {
- mDeferredNotificationViewUpdateHandler.postDelayed(
- mUpdateNotificationViewsCallback, audibleAlertTimeout);
- }
- }
-
@Override
- public void onAsyncInflationFinished(NotificationData.Entry entry,
+ public void onAsyncInflationFinished(NotificationEntry entry,
@InflationFlag int inflatedFlags) {
mPendingNotifications.remove(entry.key);
// If there was an async task started after the removal, we don't want to add it back to
@@ -288,7 +226,12 @@
for (NotificationEntryListener listener : mNotificationEntryListeners) {
listener.onEntryInflated(entry, inflatedFlags);
}
- addEntry(entry);
+ mNotificationData.add(entry);
+ tagForeground(entry.notification);
+ updateNotifications();
+ for (NotificationEntryListener listener : mNotificationEntryListeners) {
+ listener.onNotificationAdded(entry);
+ }
} else {
for (NotificationEntryListener listener : mNotificationEntryListeners) {
listener.onEntryReinflated(entry);
@@ -310,11 +253,10 @@
@Nullable NotificationVisibility visibility,
boolean forceRemove,
boolean removedByUser) {
- final NotificationData.Entry entry = mNotificationData.get(key);
+ final NotificationEntry entry = mNotificationData.get(key);
abortExistingInflation(key);
- StatusBarNotification old = null;
boolean lifetimeExtended = false;
if (entry != null) {
@@ -347,25 +289,15 @@
// Let's remove the children if this was a summary
handleGroupSummaryRemoved(key);
- old = removeNotificationViews(key, ranking);
+ mNotificationData.remove(key, ranking);
+ updateNotifications();
+ Dependency.get(LeakDetector.class).trackGarbage(entry);
+
+ for (NotificationEntryListener listener : mNotificationEntryListeners) {
+ listener.onEntryRemoved(entry, visibility, removedByUser);
+ }
}
}
-
- for (NotificationEntryListener listener : mNotificationEntryListeners) {
- listener.onEntryRemoved(entry, key, old, visibility, lifetimeExtended, removedByUser);
- }
- }
-
- private StatusBarNotification removeNotificationViews(String key,
- NotificationListenerService.RankingMap ranking) {
- NotificationData.Entry entry = mNotificationData.remove(key, ranking);
- if (entry == null) {
- Log.w(TAG, "removeNotification for unknown key: " + key);
- return null;
- }
- updateNotifications();
- Dependency.get(LeakDetector.class).trackGarbage(entry);
- return entry.notification;
}
/**
@@ -379,19 +311,19 @@
*
*/
private void handleGroupSummaryRemoved(String key) {
- NotificationData.Entry entry = mNotificationData.get(key);
+ NotificationEntry entry = mNotificationData.get(key);
if (entry != null && entry.rowExists() && entry.isSummaryWithChildren()) {
if (entry.notification.getOverrideGroupKey() != null && !entry.isRowDismissed()) {
// We don't want to remove children for autobundled notifications as they are not
// always cancelled. We only remove them if they were dismissed by the user.
return;
}
- List<NotificationData.Entry> childEntries = entry.getChildren();
+ List<NotificationEntry> childEntries = entry.getChildren();
if (childEntries == null) {
return;
}
for (int i = 0; i < childEntries.size(); i++) {
- NotificationData.Entry childEntry = childEntries.get(i);
+ NotificationEntry childEntry = childEntries.get(i);
boolean isForeground = (entry.notification.getNotification().flags
& Notification.FLAG_FOREGROUND_SERVICE) != 0;
boolean keepForReply =
@@ -410,39 +342,6 @@
}
}
- public void updateNotificationsOnDensityOrFontScaleChanged() {
- ArrayList<NotificationData.Entry> userNotifications =
- mNotificationData.getNotificationsForCurrentUser();
- for (int i = 0; i < userNotifications.size(); i++) {
- NotificationData.Entry entry = userNotifications.get(i);
- entry.onDensityOrFontScaleChanged();
- boolean exposedGuts = entry.areGutsExposed();
- if (exposedGuts) {
- mGutsManager.onDensityOrFontScaleChanged(entry);
- }
- }
- }
-
- private NotificationData.Entry createNotificationEntry(
- StatusBarNotification sbn, NotificationListenerService.Ranking ranking)
- throws InflationException {
- if (DEBUG) {
- Log.d(TAG, "createNotificationEntry(notification=" + sbn + " " + ranking);
- }
-
- NotificationData.Entry entry = new NotificationData.Entry(sbn, ranking);
- if (BubbleController.shouldAutoBubble(getContext(), entry)) {
- entry.setIsBubble(true);
- }
-
- Dependency.get(LeakDetector.class).trackInstance(entry);
- entry.createIcons(mContext, sbn);
- // Construct the expanded view.
- getRowBinder().inflateViews(entry, () -> performRemoveNotification(sbn),
- mNotificationData.get(entry.key) != null);
- return entry;
- }
-
private void addNotificationInternal(StatusBarNotification notification,
NotificationListenerService.RankingMap rankingMap) throws InflationException {
String key = notification.getKey();
@@ -453,7 +352,14 @@
mNotificationData.updateRanking(rankingMap);
NotificationListenerService.Ranking ranking = new NotificationListenerService.Ranking();
rankingMap.getRanking(key, ranking);
- NotificationData.Entry entry = createNotificationEntry(notification, ranking);
+
+ NotificationEntry entry = new NotificationEntry(notification, ranking);
+
+ Dependency.get(LeakDetector.class).trackInstance(entry);
+ // Construct the expanded view.
+ getRowBinder().inflateViews(entry, () -> performRemoveNotification(notification),
+ mNotificationData.get(entry.key) != null);
+
abortExistingInflation(key);
mPendingNotifications.put(key, entry);
@@ -500,7 +406,7 @@
final String key = notification.getKey();
abortExistingInflation(key);
- NotificationData.Entry entry = mNotificationData.get(key);
+ NotificationEntry entry = mNotificationData.get(key);
if (entry == null) {
return;
}
@@ -513,18 +419,15 @@
mNotificationData.update(entry, ranking, notification);
- entry.updateIcons(mContext, notification);
getRowBinder().inflateViews(entry, () -> performRemoveNotification(notification),
mNotificationData.get(entry.key) != null);
- updateNotifications();
-
- if (!notification.isClearable()) {
- // The user may have performed a dismiss action on the notification, since it's
- // not clearable we should snap it back.
- mListContainer.snapViewIfNeeded(entry);
+ for (NotificationEntryListener listener : mNotificationEntryListeners) {
+ listener.onPreEntryUpdated(entry);
}
+ updateNotifications();
+
if (DEBUG) {
// Is this for you?
boolean isForCurrentUser = Dependency.get(KeyguardEnvironment.class)
@@ -533,10 +436,8 @@
}
for (NotificationEntryListener listener : mNotificationEntryListeners) {
- listener.onEntryUpdated(entry);
+ listener.onPostEntryUpdated(entry);
}
-
- maybeScheduleUpdateNotificationViews(entry);
}
@Override
@@ -556,14 +457,14 @@
}
public void updateNotificationRanking(NotificationListenerService.RankingMap rankingMap) {
- List<NotificationData.Entry> entries = new ArrayList<>();
+ List<NotificationEntry> entries = new ArrayList<>();
entries.addAll(mNotificationData.getActiveNotifications());
entries.addAll(mPendingNotifications.values());
// Has a copy of the current UI adjustments.
ArrayMap<String, NotificationUiAdjustment> oldAdjustments = new ArrayMap<>();
ArrayMap<String, Integer> oldImportances = new ArrayMap<>();
- for (NotificationData.Entry entry : entries) {
+ for (NotificationEntry entry : entries) {
NotificationUiAdjustment adjustment =
NotificationUiAdjustment.extractFromNotificationEntry(entry);
oldAdjustments.put(entry.key, adjustment);
@@ -575,7 +476,7 @@
updateRankingOfPendingNotifications(rankingMap);
// By comparing the old and new UI adjustments, reinflate the view accordingly.
- for (NotificationData.Entry entry : entries) {
+ for (NotificationEntry entry : entries) {
getRowBinder().onNotificationRankingUpdated(
entry,
oldImportances.get(entry.key),
@@ -593,7 +494,7 @@
return;
}
NotificationListenerService.Ranking tmpRanking = new NotificationListenerService.Ranking();
- for (NotificationData.Entry pendingNotification : mPendingNotifications.values()) {
+ for (NotificationEntry pendingNotification : mPendingNotifications.values()) {
rankingMap.getRanking(pendingNotification.key, tmpRanking);
pendingNotification.populateFromRanking(tmpRanking);
}
@@ -604,7 +505,7 @@
* notifications whose views have not yet been inflated. In general, the system pretends like
* these don't exist, although there are a couple exceptions.
*/
- public Iterable<NotificationData.Entry> getPendingNotificationsIterator() {
+ public Iterable<NotificationEntry> getPendingNotificationsIterator() {
return mPendingNotifications.values();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
index 700382a..e199ead 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
@@ -28,6 +28,8 @@
import com.android.systemui.Dependency;
import com.android.systemui.ForegroundServiceController;
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.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -82,7 +84,7 @@
/**
* @return true if the provided notification should NOT be shown right now.
*/
- public boolean shouldFilterOut(NotificationData.Entry entry) {
+ public boolean shouldFilterOut(NotificationEntry entry) {
final StatusBarNotification sbn = entry.notification;
if (!(getEnvironment().isDeviceProvisioned()
|| showNotificationEvenIfUnprovisioned(sbn))) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
index 8bd0e9a..fc7a2b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
@@ -37,6 +37,7 @@
import com.android.systemui.Dependency;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -139,7 +140,7 @@
* @param entry the entry to check
* @return true if the entry should heads up, false otherwise
*/
- public boolean shouldHeadsUp(NotificationData.Entry entry) {
+ public boolean shouldHeadsUp(NotificationEntry entry) {
StatusBarNotification sbn = entry.notification;
if (getShadeController().isDozing()) {
@@ -227,7 +228,7 @@
* @param entry the entry to check
* @return true if the entry should ambient pulse, false otherwise
*/
- public boolean shouldPulse(NotificationData.Entry entry) {
+ public boolean shouldPulse(NotificationEntry entry) {
StatusBarNotification sbn = entry.notification;
if (!getShadeController().isDozing()) {
@@ -273,14 +274,14 @@
/**
* Common checks between heads up alerting and ambient pulse alerting. See
- * {@link #shouldHeadsUp(NotificationData.Entry)} and
- * {@link #shouldPulse(NotificationData.Entry)}. Notifications that fail any of these checks
+ * {@link #shouldHeadsUp(NotificationEntry)} and
+ * {@link #shouldPulse(NotificationEntry)}. Notifications that fail any of these checks
* should not alert at all.
*
* @param entry the entry to check
* @return true if these checks pass, false if the notification should not alert
*/
- protected boolean canAlertCommon(NotificationData.Entry entry) {
+ protected boolean canAlertCommon(NotificationEntry entry) {
StatusBarNotification sbn = entry.notification;
if (mNotificationFilter.shouldFilterOut(entry)) {
@@ -325,7 +326,7 @@
* @param sbn notification that might be heads upped
* @return false if the notification can not be heads upped
*/
- boolean canHeadsUp(NotificationData.Entry entry, StatusBarNotification sbn);
+ boolean canHeadsUp(NotificationEntry entry, StatusBarNotification sbn);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationRowBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationRowBinder.java
index 0bde99b..cc302b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationRowBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationRowBinder.java
@@ -42,6 +42,7 @@
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationUiAdjustment;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.row.NotificationInflater;
@@ -125,17 +126,22 @@
/**
* Inflates the views for the given entry (possibly asynchronously).
*/
- public void inflateViews(NotificationData.Entry entry, Runnable onDismissRunnable,
- boolean isUpdate) {
+ public void inflateViews(
+ NotificationEntry entry,
+ Runnable onDismissRunnable,
+ boolean isUpdate)
+ throws InflationException {
ViewGroup parent = mListContainer.getViewParentForNotification(entry);
PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext,
entry.notification.getUser().getIdentifier());
final StatusBarNotification sbn = entry.notification;
if (entry.rowExists()) {
+ entry.updateIcons(mContext, sbn);
entry.reset();
updateNotification(entry, pmUser, sbn, entry.getRow(), isUpdate);
} else {
+ entry.createIcons(mContext, sbn);
new RowInflaterTask().inflate(mContext, parent, entry,
row -> {
bindRow(entry, pmUser, sbn, row, onDismissRunnable);
@@ -144,7 +150,7 @@
}
}
- private void bindRow(NotificationData.Entry entry, PackageManager pmUser,
+ private void bindRow(NotificationEntry entry, PackageManager pmUser,
StatusBarNotification sbn, ExpandableNotificationRow row,
Runnable onDismissRunnable) {
row.setExpansionLogger(mExpansionLogger, entry.notification.getKey());
@@ -192,7 +198,7 @@
* reinflating them.
*/
public void onNotificationRankingUpdated(
- NotificationData.Entry entry,
+ NotificationEntry entry,
@Nullable Integer oldImportance,
NotificationUiAdjustment oldAdjustment,
NotificationUiAdjustment newAdjustment,
@@ -219,7 +225,7 @@
//TODO: This method associates a row with an entry, but eventually needs to not do that
private void updateNotification(
- NotificationData.Entry entry,
+ NotificationEntry entry,
PackageManager pmUser,
StatusBarNotification sbn,
ExpandableNotificationRow row,
@@ -287,7 +293,7 @@
* @param sbn notification
* @param row row for the notification
*/
- void onBindRow(NotificationData.Entry entry, PackageManager pmUser,
+ void onBindRow(NotificationEntry entry, PackageManager pmUser,
StatusBarNotification sbn, ExpandableNotificationRow row);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisibilityLocationProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisibilityLocationProvider.java
index a194eef..f09c57d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisibilityLocationProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisibilityLocationProvider.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
/**
* An object that can determine the visibility of a Notification.
*/
@@ -27,5 +29,5 @@
* @param entry
* @return true if row is in a visible location
*/
- boolean isInVisibleLocation(NotificationData.Entry entry);
+ boolean isInVisibleLocation(NotificationEntry entry);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
index 8e6a93d..c886685 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
@@ -21,6 +21,7 @@
import androidx.collection.ArraySet;
import com.android.systemui.statusbar.NotificationPresenter;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
@@ -52,7 +53,7 @@
public VisualStabilityManager(NotificationEntryManager notificationEntryManager) {
notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
@Override
- public void onEntryReinflated(NotificationData.Entry entry) {
+ public void onEntryReinflated(NotificationEntry entry) {
if (entry.hasLowPriorityStateUpdated()) {
onLowPriorityUpdated(entry);
if (mPresenter != null) {
@@ -163,7 +164,7 @@
}
@Override
- public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {
+ public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
if (isHeadsUp) {
// Heads up notifications should in general be allowed to reorder if they are out of
// view and stay at the current location if they aren't.
@@ -171,7 +172,7 @@
}
}
- private void onLowPriorityUpdated(NotificationData.Entry entry) {
+ private void onLowPriorityUpdated(NotificationEntry entry) {
mLowPriorityReorderingViews.add(entry.getRow());
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java
new file mode 100644
index 0000000..8c29fb5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java
@@ -0,0 +1,424 @@
+/*
+ * 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.notification.collection;
+
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.service.notification.NotificationListenerService.Ranking;
+import android.service.notification.NotificationListenerService.RankingMap;
+import android.service.notification.SnoozeCriterion;
+import android.service.notification.StatusBarNotification;
+import android.util.ArrayMap;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Dependency;
+import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.notification.NotificationFilter;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * The list of currently displaying notifications.
+ */
+public class NotificationData {
+
+ private final NotificationFilter mNotificationFilter = Dependency.get(NotificationFilter.class);
+
+ /**
+ * These dependencies are late init-ed
+ */
+ private KeyguardEnvironment mEnvironment;
+ private NotificationMediaManager mMediaManager;
+
+ private HeadsUpManager mHeadsUpManager;
+
+ private final ArrayMap<String, NotificationEntry> mEntries = new ArrayMap<>();
+ private final ArrayList<NotificationEntry> mSortedAndFiltered = new ArrayList<>();
+ private final ArrayList<NotificationEntry> mFilteredForUser = new ArrayList<>();
+
+ private final NotificationGroupManager mGroupManager =
+ Dependency.get(NotificationGroupManager.class);
+
+ private RankingMap mRankingMap;
+ private final Ranking mTmpRanking = new Ranking();
+
+ public void setHeadsUpManager(HeadsUpManager headsUpManager) {
+ mHeadsUpManager = headsUpManager;
+ }
+
+ private final Comparator<NotificationEntry> mRankingComparator =
+ new Comparator<NotificationEntry>() {
+ private final Ranking mRankingA = new Ranking();
+ private final Ranking mRankingB = new Ranking();
+
+ @Override
+ public int compare(NotificationEntry a, NotificationEntry b) {
+ final StatusBarNotification na = a.notification;
+ final StatusBarNotification nb = b.notification;
+ int aImportance = NotificationManager.IMPORTANCE_DEFAULT;
+ int bImportance = NotificationManager.IMPORTANCE_DEFAULT;
+ int aRank = 0;
+ int bRank = 0;
+
+ if (mRankingMap != null) {
+ // RankingMap as received from NoMan
+ getRanking(a.key, mRankingA);
+ getRanking(b.key, mRankingB);
+ aImportance = mRankingA.getImportance();
+ bImportance = mRankingB.getImportance();
+ aRank = mRankingA.getRank();
+ bRank = mRankingB.getRank();
+ }
+
+ String mediaNotification = getMediaManager().getMediaNotificationKey();
+
+ // IMPORTANCE_MIN media streams are allowed to drift to the bottom
+ final boolean aMedia = a.key.equals(mediaNotification)
+ && aImportance > NotificationManager.IMPORTANCE_MIN;
+ final boolean bMedia = b.key.equals(mediaNotification)
+ && bImportance > NotificationManager.IMPORTANCE_MIN;
+
+ boolean aSystemMax = aImportance >= NotificationManager.IMPORTANCE_HIGH
+ && isSystemNotification(na);
+ boolean bSystemMax = bImportance >= NotificationManager.IMPORTANCE_HIGH
+ && isSystemNotification(nb);
+
+ boolean isHeadsUp = a.getRow().isHeadsUp();
+ if (isHeadsUp != b.getRow().isHeadsUp()) {
+ return isHeadsUp ? -1 : 1;
+ } else if (isHeadsUp) {
+ // Provide consistent ranking with headsUpManager
+ return mHeadsUpManager.compare(a, b);
+ } else if (a.getRow().isAmbientPulsing() != b.getRow().isAmbientPulsing()) {
+ return a.getRow().isAmbientPulsing() ? -1 : 1;
+ } else if (aMedia != bMedia) {
+ // Upsort current media notification.
+ return aMedia ? -1 : 1;
+ } else if (aSystemMax != bSystemMax) {
+ // Upsort PRIORITY_MAX system notifications
+ return aSystemMax ? -1 : 1;
+ } else if (aRank != bRank) {
+ return aRank - bRank;
+ } else {
+ return Long.compare(nb.getNotification().when, na.getNotification().when);
+ }
+ }
+ };
+
+ private KeyguardEnvironment getEnvironment() {
+ if (mEnvironment == null) {
+ mEnvironment = Dependency.get(KeyguardEnvironment.class);
+ }
+ return mEnvironment;
+ }
+
+ private NotificationMediaManager getMediaManager() {
+ if (mMediaManager == null) {
+ mMediaManager = Dependency.get(NotificationMediaManager.class);
+ }
+ return mMediaManager;
+ }
+
+ /**
+ * Returns the sorted list of active notifications (depending on {@link KeyguardEnvironment}
+ *
+ * <p>
+ * This call doesn't update the list of active notifications. Call {@link #filterAndSort()}
+ * when the environment changes.
+ * <p>
+ * Don't hold on to or modify the returned list.
+ */
+ public ArrayList<NotificationEntry> getActiveNotifications() {
+ return mSortedAndFiltered;
+ }
+
+ public ArrayList<NotificationEntry> getNotificationsForCurrentUser() {
+ mFilteredForUser.clear();
+
+ synchronized (mEntries) {
+ final int len = mEntries.size();
+ for (int i = 0; i < len; i++) {
+ NotificationEntry entry = mEntries.valueAt(i);
+ final StatusBarNotification sbn = entry.notification;
+ if (!getEnvironment().isNotificationForCurrentProfiles(sbn)) {
+ continue;
+ }
+ mFilteredForUser.add(entry);
+ }
+ }
+ return mFilteredForUser;
+ }
+
+ public NotificationEntry get(String key) {
+ return mEntries.get(key);
+ }
+
+ public void add(NotificationEntry entry) {
+ synchronized (mEntries) {
+ mEntries.put(entry.notification.getKey(), entry);
+ }
+ mGroupManager.onEntryAdded(entry);
+
+ updateRankingAndSort(mRankingMap);
+ }
+
+ public NotificationEntry remove(String key, RankingMap ranking) {
+ NotificationEntry removed;
+ synchronized (mEntries) {
+ removed = mEntries.remove(key);
+ }
+ if (removed == null) return null;
+ mGroupManager.onEntryRemoved(removed);
+ updateRankingAndSort(ranking);
+ return removed;
+ }
+
+ /** Updates the given notification entry with the provided ranking. */
+ public void update(
+ NotificationEntry entry,
+ RankingMap ranking,
+ StatusBarNotification notification) {
+ updateRanking(ranking);
+ final StatusBarNotification oldNotification = entry.notification;
+ entry.notification = notification;
+ mGroupManager.onEntryUpdated(entry, oldNotification);
+ }
+
+ public void updateRanking(RankingMap ranking) {
+ updateRankingAndSort(ranking);
+ }
+
+ public void updateAppOp(int appOp, int uid, String pkg, String key, boolean showIcon) {
+ synchronized (mEntries) {
+ final int len = mEntries.size();
+ for (int i = 0; i < len; i++) {
+ NotificationEntry entry = mEntries.valueAt(i);
+ if (uid == entry.notification.getUid()
+ && pkg.equals(entry.notification.getPackageName())
+ && key.equals(entry.key)) {
+ if (showIcon) {
+ entry.mActiveAppOps.add(appOp);
+ } else {
+ entry.mActiveAppOps.remove(appOp);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns true if this notification should be displayed in the high-priority notifications
+ * section (and on the lockscreen and status bar).
+ */
+ public boolean isHighPriority(StatusBarNotification statusBarNotification) {
+ if (mRankingMap != null) {
+ getRanking(statusBarNotification.getKey(), mTmpRanking);
+ if (mTmpRanking.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT
+ || statusBarNotification.getNotification().isForegroundService()
+ || statusBarNotification.getNotification().hasMediaSession()) {
+ return true;
+ }
+ if (mGroupManager.isSummaryOfGroup(statusBarNotification)) {
+ final ArrayList<NotificationEntry> logicalChildren =
+ mGroupManager.getLogicalChildren(statusBarNotification);
+ for (NotificationEntry child : logicalChildren) {
+ if (isHighPriority(child.notification)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ public boolean isAmbient(String key) {
+ if (mRankingMap != null) {
+ getRanking(key, mTmpRanking);
+ return mTmpRanking.isAmbient();
+ }
+ return false;
+ }
+
+ public int getVisibilityOverride(String key) {
+ if (mRankingMap != null) {
+ getRanking(key, mTmpRanking);
+ return mTmpRanking.getVisibilityOverride();
+ }
+ return Ranking.VISIBILITY_NO_OVERRIDE;
+ }
+
+ public int getImportance(String key) {
+ if (mRankingMap != null) {
+ getRanking(key, mTmpRanking);
+ return mTmpRanking.getImportance();
+ }
+ return NotificationManager.IMPORTANCE_UNSPECIFIED;
+ }
+
+ public String getOverrideGroupKey(String key) {
+ if (mRankingMap != null) {
+ getRanking(key, mTmpRanking);
+ return mTmpRanking.getOverrideGroupKey();
+ }
+ return null;
+ }
+
+ public List<SnoozeCriterion> getSnoozeCriteria(String key) {
+ if (mRankingMap != null) {
+ getRanking(key, mTmpRanking);
+ return mTmpRanking.getSnoozeCriteria();
+ }
+ return null;
+ }
+
+ public NotificationChannel getChannel(String key) {
+ if (mRankingMap != null) {
+ getRanking(key, mTmpRanking);
+ return mTmpRanking.getChannel();
+ }
+ return null;
+ }
+
+ public int getRank(String key) {
+ if (mRankingMap != null) {
+ getRanking(key, mTmpRanking);
+ return mTmpRanking.getRank();
+ }
+ return 0;
+ }
+
+ public boolean shouldHide(String key) {
+ if (mRankingMap != null) {
+ getRanking(key, mTmpRanking);
+ return mTmpRanking.isSuspended();
+ }
+ return false;
+ }
+
+ private void updateRankingAndSort(RankingMap ranking) {
+ if (ranking != null) {
+ mRankingMap = ranking;
+ synchronized (mEntries) {
+ final int len = mEntries.size();
+ for (int i = 0; i < len; i++) {
+ NotificationEntry entry = mEntries.valueAt(i);
+ if (!getRanking(entry.key, mTmpRanking)) {
+ continue;
+ }
+ final StatusBarNotification oldSbn = entry.notification.cloneLight();
+ final String overrideGroupKey = getOverrideGroupKey(entry.key);
+ if (!Objects.equals(oldSbn.getOverrideGroupKey(), overrideGroupKey)) {
+ entry.notification.setOverrideGroupKey(overrideGroupKey);
+ mGroupManager.onEntryUpdated(entry, oldSbn);
+ }
+ entry.populateFromRanking(mTmpRanking);
+ }
+ }
+ }
+ filterAndSort();
+ }
+
+ /**
+ * Get the ranking from the current ranking map.
+ *
+ * @param key the key to look up
+ * @param outRanking the ranking to populate
+ *
+ * @return {@code true} if the ranking was properly obtained.
+ */
+ @VisibleForTesting
+ protected boolean getRanking(String key, Ranking outRanking) {
+ return mRankingMap.getRanking(key, outRanking);
+ }
+
+ // TODO: This should not be public. Instead the Environment should notify this class when
+ // anything changed, and this class should call back the UI so it updates itself.
+ public void filterAndSort() {
+ mSortedAndFiltered.clear();
+
+ synchronized (mEntries) {
+ final int len = mEntries.size();
+ for (int i = 0; i < len; i++) {
+ NotificationEntry entry = mEntries.valueAt(i);
+
+ if (mNotificationFilter.shouldFilterOut(entry)) {
+ continue;
+ }
+
+ mSortedAndFiltered.add(entry);
+ }
+ }
+
+ Collections.sort(mSortedAndFiltered, mRankingComparator);
+ }
+
+ public void dump(PrintWriter pw, String indent) {
+ int filteredLen = mSortedAndFiltered.size();
+ pw.print(indent);
+ pw.println("active notifications: " + filteredLen);
+ int active;
+ for (active = 0; active < filteredLen; active++) {
+ NotificationEntry e = mSortedAndFiltered.get(active);
+ dumpEntry(pw, indent, active, e);
+ }
+ synchronized (mEntries) {
+ int totalLen = mEntries.size();
+ pw.print(indent);
+ pw.println("inactive notifications: " + (totalLen - active));
+ int inactiveCount = 0;
+ for (int i = 0; i < totalLen; i++) {
+ NotificationEntry entry = mEntries.valueAt(i);
+ if (!mSortedAndFiltered.contains(entry)) {
+ dumpEntry(pw, indent, inactiveCount, entry);
+ inactiveCount++;
+ }
+ }
+ }
+ }
+
+ private void dumpEntry(PrintWriter pw, String indent, int i, NotificationEntry e) {
+ getRanking(e.key, mTmpRanking);
+ pw.print(indent);
+ pw.println(" [" + i + "] key=" + e.key + " icon=" + e.icon);
+ StatusBarNotification n = e.notification;
+ pw.print(indent);
+ pw.println(" pkg=" + n.getPackageName() + " id=" + n.getId() + " importance="
+ + mTmpRanking.getImportance());
+ pw.print(indent);
+ pw.println(" notification=" + n.getNotification());
+ }
+
+ private static boolean isSystemNotification(StatusBarNotification sbn) {
+ String sbnPackage = sbn.getPackageName();
+ return "android".equals(sbnPackage) || "com.android.systemui".equals(sbnPackage);
+ }
+
+ /**
+ * Provides access to keyguard state and user settings dependent data.
+ */
+ public interface KeyguardEnvironment {
+ boolean isDeviceProvisioned();
+ boolean isNotificationForCurrentProfiles(StatusBarNotification sbn);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
new file mode 100644
index 0000000..58aa02c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -0,0 +1,701 @@
+/*
+ * 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.notification.collection;
+
+import static android.app.Notification.CATEGORY_ALARM;
+import static android.app.Notification.CATEGORY_CALL;
+import static android.app.Notification.CATEGORY_EVENT;
+import static android.app.Notification.CATEGORY_MESSAGE;
+import static android.app.Notification.CATEGORY_REMINDER;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
+
+import android.annotation.NonNull;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager.Policy;
+import android.app.Person;
+import android.content.Context;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.SnoozeCriterion;
+import android.service.notification.StatusBarNotification;
+import android.util.ArraySet;
+import android.view.View;
+import android.widget.ImageView;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.statusbar.StatusBarIcon;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.ContrastColorUtil;
+import com.android.systemui.statusbar.InflationTask;
+import com.android.systemui.statusbar.StatusBarIconView;
+import com.android.systemui.statusbar.notification.InflationException;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotificationGuts;
+import com.android.systemui.statusbar.notification.row.NotificationInflater;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Represents a notification that the system UI knows about
+ *
+ * Whenever the NotificationManager tells us about the existence of a new notification, we wrap it
+ * in a NotificationEntry. Thus, every notification has an associated NotificationEntry, even if
+ * that notification is never displayed to the user (for example, if it's filtered out for some
+ * reason).
+ *
+ * Entries store information about the current state of the notification. Essentially:
+ * anything that needs to persist or be modifiable even when the notification's views don't
+ * exist. Any other state should be stored on the views/view controllers themselves.
+ *
+ * At the moment, there are many things here that shouldn't be and vice-versa. Hopefully we can
+ * clean this up in the future.
+ */
+public final class NotificationEntry {
+ private static final long LAUNCH_COOLDOWN = 2000;
+ private static final long REMOTE_INPUT_COOLDOWN = 500;
+ private static final long INITIALIZATION_DELAY = 400;
+ private static final long NOT_LAUNCHED_YET = -LAUNCH_COOLDOWN;
+ private static final int COLOR_INVALID = 1;
+ public final String key;
+ public StatusBarNotification notification;
+ public NotificationChannel channel;
+ public long lastAudiblyAlertedMs;
+ public boolean noisy;
+ public boolean ambient;
+ public int importance;
+ public StatusBarIconView icon;
+ public StatusBarIconView expandedIcon;
+ private boolean interruption;
+ public boolean autoRedacted; // whether the redacted notification was generated by us
+ public int targetSdk;
+ private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET;
+ public CharSequence remoteInputText;
+ public List<SnoozeCriterion> snoozeCriteria;
+ public int userSentiment = NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
+ /** Smart Actions provided by the NotificationAssistantService. */
+ @NonNull
+ public List<Notification.Action> systemGeneratedSmartActions = Collections.emptyList();
+ public CharSequence[] smartReplies = new CharSequence[0];
+ @VisibleForTesting
+ public int suppressedVisualEffects;
+ public boolean suspended;
+
+ private NotificationEntry parent; // our parent (if we're in a group)
+ private ArrayList<NotificationEntry> children = new ArrayList<NotificationEntry>();
+ private ExpandableNotificationRow row; // the outer expanded view
+
+ private int mCachedContrastColor = COLOR_INVALID;
+ private int mCachedContrastColorIsFor = COLOR_INVALID;
+ private InflationTask mRunningTask = null;
+ private Throwable mDebugThrowable;
+ public CharSequence remoteInputTextWhenReset;
+ public long lastRemoteInputSent = NOT_LAUNCHED_YET;
+ public ArraySet<Integer> mActiveAppOps = new ArraySet<>(3);
+ public CharSequence headsUpStatusBarText;
+ public CharSequence headsUpStatusBarTextPublic;
+
+ private long initializationTime = -1;
+
+ /**
+ * Whether or not this row represents a system notification. Note that if this is
+ * {@code null}, that means we were either unable to retrieve the info or have yet to
+ * retrieve the info.
+ */
+ public Boolean mIsSystemNotification;
+
+ /**
+ * Has the user sent a reply through this Notification.
+ */
+ private boolean hasSentReply;
+
+ /**
+ * Whether this notification should be displayed as a bubble.
+ */
+ private boolean mIsBubble;
+
+ /**
+ * Whether the user has dismissed this notification when it was in bubble form.
+ */
+ private boolean mUserDismissedBubble;
+
+ public NotificationEntry(StatusBarNotification n) {
+ this(n, null);
+ }
+
+ public NotificationEntry(
+ StatusBarNotification n,
+ @Nullable NotificationListenerService.Ranking ranking) {
+ this.key = n.getKey();
+ this.notification = n;
+ if (ranking != null) {
+ populateFromRanking(ranking);
+ }
+ }
+
+ public void populateFromRanking(@NonNull NotificationListenerService.Ranking ranking) {
+ channel = ranking.getChannel();
+ lastAudiblyAlertedMs = ranking.getLastAudiblyAlertedMillis();
+ importance = ranking.getImportance();
+ ambient = ranking.isAmbient();
+ snoozeCriteria = ranking.getSnoozeCriteria();
+ userSentiment = ranking.getUserSentiment();
+ systemGeneratedSmartActions = ranking.getSmartActions() == null
+ ? Collections.emptyList() : ranking.getSmartActions();
+ smartReplies = ranking.getSmartReplies() == null
+ ? new CharSequence[0]
+ : ranking.getSmartReplies().toArray(new CharSequence[0]);
+ suppressedVisualEffects = ranking.getSuppressedVisualEffects();
+ suspended = ranking.isSuspended();
+ }
+
+ public void setInterruption() {
+ interruption = true;
+ }
+
+ public boolean hasInterrupted() {
+ return interruption;
+ }
+
+ public void setIsBubble(boolean bubbleable) {
+ mIsBubble = bubbleable;
+ }
+
+ public boolean isBubble() {
+ return mIsBubble;
+ }
+
+ public void setBubbleDismissed(boolean userDismissed) {
+ mUserDismissedBubble = userDismissed;
+ }
+
+ public boolean isBubbleDismissed() {
+ return mUserDismissedBubble;
+ }
+
+ /**
+ * Resets the notification entry to be re-used.
+ */
+ public void reset() {
+ if (row != null) {
+ row.reset();
+ }
+ }
+
+ public ExpandableNotificationRow getRow() {
+ return row;
+ }
+
+ //TODO: This will go away when we have a way to bind an entry to a row
+ public void setRow(ExpandableNotificationRow row) {
+ this.row = row;
+ }
+
+ @Nullable
+ public List<NotificationEntry> getChildren() {
+ if (children.size() <= 0) {
+ return null;
+ }
+
+ return children;
+ }
+
+ public void notifyFullScreenIntentLaunched() {
+ setInterruption();
+ lastFullScreenIntentLaunchTime = SystemClock.elapsedRealtime();
+ }
+
+ public boolean hasJustLaunchedFullScreenIntent() {
+ return SystemClock.elapsedRealtime() < lastFullScreenIntentLaunchTime + LAUNCH_COOLDOWN;
+ }
+
+ public boolean hasJustSentRemoteInput() {
+ return SystemClock.elapsedRealtime() < lastRemoteInputSent + REMOTE_INPUT_COOLDOWN;
+ }
+
+ public boolean hasFinishedInitialization() {
+ return initializationTime == -1
+ || SystemClock.elapsedRealtime() > initializationTime + INITIALIZATION_DELAY;
+ }
+
+ /**
+ * Create the icons for a notification
+ * @param context the context to create the icons with
+ * @param sbn the notification
+ * @throws InflationException Exception if required icons are not valid or specified
+ */
+ public void createIcons(Context context, StatusBarNotification sbn)
+ throws InflationException {
+ Notification n = sbn.getNotification();
+ final Icon smallIcon = n.getSmallIcon();
+ if (smallIcon == null) {
+ throw new InflationException("No small icon in notification from "
+ + sbn.getPackageName());
+ }
+
+ // Construct the icon.
+ icon = new StatusBarIconView(context,
+ sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn);
+ icon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
+
+ // Construct the expanded icon.
+ expandedIcon = new StatusBarIconView(context,
+ sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn);
+ expandedIcon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
+ final StatusBarIcon ic = new StatusBarIcon(
+ sbn.getUser(),
+ sbn.getPackageName(),
+ smallIcon,
+ n.iconLevel,
+ n.number,
+ StatusBarIconView.contentDescForNotification(context, n));
+ if (!icon.set(ic) || !expandedIcon.set(ic)) {
+ icon = null;
+ expandedIcon = null;
+ throw new InflationException("Couldn't create icon: " + ic);
+ }
+ expandedIcon.setVisibility(View.INVISIBLE);
+ expandedIcon.setOnVisibilityChangedListener(
+ newVisibility -> {
+ if (row != null) {
+ row.setIconsVisible(newVisibility != View.VISIBLE);
+ }
+ });
+ }
+
+ public void setIconTag(int key, Object tag) {
+ if (icon != null) {
+ icon.setTag(key, tag);
+ expandedIcon.setTag(key, tag);
+ }
+ }
+
+ /**
+ * Update the notification icons.
+ *
+ * @param context the context to create the icons with.
+ * @param sbn the notification to read the icon from.
+ * @throws InflationException Exception if required icons are not valid or specified
+ */
+ public void updateIcons(Context context, StatusBarNotification sbn)
+ throws InflationException {
+ if (icon != null) {
+ // Update the icon
+ Notification n = sbn.getNotification();
+ final StatusBarIcon ic = new StatusBarIcon(
+ notification.getUser(),
+ notification.getPackageName(),
+ n.getSmallIcon(),
+ n.iconLevel,
+ n.number,
+ StatusBarIconView.contentDescForNotification(context, n));
+ icon.setNotification(sbn);
+ expandedIcon.setNotification(sbn);
+ if (!icon.set(ic) || !expandedIcon.set(ic)) {
+ throw new InflationException("Couldn't update icon: " + ic);
+ }
+ }
+ }
+
+ public int getContrastedColor(Context context, boolean isLowPriority,
+ int backgroundColor) {
+ int rawColor = isLowPriority ? Notification.COLOR_DEFAULT :
+ notification.getNotification().color;
+ if (mCachedContrastColorIsFor == rawColor && mCachedContrastColor != COLOR_INVALID) {
+ return mCachedContrastColor;
+ }
+ final int contrasted = ContrastColorUtil.resolveContrastColor(context, rawColor,
+ backgroundColor);
+ mCachedContrastColorIsFor = rawColor;
+ mCachedContrastColor = contrasted;
+ return mCachedContrastColor;
+ }
+
+ /**
+ * Abort all existing inflation tasks
+ */
+ public void abortTask() {
+ if (mRunningTask != null) {
+ mRunningTask.abort();
+ mRunningTask = null;
+ }
+ }
+
+ public void setInflationTask(InflationTask abortableTask) {
+ // abort any existing inflation
+ InflationTask existing = mRunningTask;
+ abortTask();
+ mRunningTask = abortableTask;
+ if (existing != null && mRunningTask != null) {
+ mRunningTask.supersedeTask(existing);
+ }
+ }
+
+ public void onInflationTaskFinished() {
+ mRunningTask = null;
+ }
+
+ @VisibleForTesting
+ public InflationTask getRunningTask() {
+ return mRunningTask;
+ }
+
+ /**
+ * Set a throwable that is used for debugging
+ *
+ * @param debugThrowable the throwable to save
+ */
+ public void setDebugThrowable(Throwable debugThrowable) {
+ mDebugThrowable = debugThrowable;
+ }
+
+ public Throwable getDebugThrowable() {
+ return mDebugThrowable;
+ }
+
+ public void onRemoteInputInserted() {
+ lastRemoteInputSent = NOT_LAUNCHED_YET;
+ remoteInputTextWhenReset = null;
+ }
+
+ public void setHasSentReply() {
+ hasSentReply = true;
+ }
+
+ public boolean isLastMessageFromReply() {
+ if (!hasSentReply) {
+ return false;
+ }
+ Bundle extras = notification.getNotification().extras;
+ CharSequence[] replyTexts = extras.getCharSequenceArray(
+ Notification.EXTRA_REMOTE_INPUT_HISTORY);
+ if (!ArrayUtils.isEmpty(replyTexts)) {
+ return true;
+ }
+ Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
+ if (messages != null && messages.length > 0) {
+ Parcelable message = messages[messages.length - 1];
+ if (message instanceof Bundle) {
+ Notification.MessagingStyle.Message lastMessage =
+ Notification.MessagingStyle.Message.getMessageFromBundle(
+ (Bundle) message);
+ if (lastMessage != null) {
+ Person senderPerson = lastMessage.getSenderPerson();
+ if (senderPerson == null) {
+ return true;
+ }
+ Person user = extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON);
+ return Objects.equals(user, senderPerson);
+ }
+ }
+ }
+ return false;
+ }
+
+ public void setInitializationTime(long time) {
+ if (initializationTime == -1) {
+ initializationTime = time;
+ }
+ }
+
+ public void sendAccessibilityEvent(int eventType) {
+ if (row != null) {
+ row.sendAccessibilityEvent(eventType);
+ }
+ }
+
+ /**
+ * Used by NotificationMediaManager to determine... things
+ * @return {@code true} if we are a media notification
+ */
+ public boolean isMediaNotification() {
+ if (row == null) return false;
+
+ return row.isMediaRow();
+ }
+
+ /**
+ * We are a top level child if our parent is the list of notifications duh
+ * @return {@code true} if we're a top level notification
+ */
+ public boolean isTopLevelChild() {
+ return row != null && row.isTopLevelChild();
+ }
+
+ public void resetUserExpansion() {
+ if (row != null) row.resetUserExpansion();
+ }
+
+ public void freeContentViewWhenSafe(@NotificationInflater.InflationFlag int inflationFlag) {
+ if (row != null) row.freeContentViewWhenSafe(inflationFlag);
+ }
+
+ public void setAmbientPulsing(boolean pulsing) {
+ if (row != null) row.setAmbientPulsing(pulsing);
+ }
+
+ public boolean rowExists() {
+ return row != null;
+ }
+
+ public boolean isRowDismissed() {
+ return row != null && row.isDismissed();
+ }
+
+ public boolean isRowRemoved() {
+ return row != null && row.isRemoved();
+ }
+
+ /**
+ * @return {@code true} if the row is null or removed
+ */
+ public boolean isRemoved() {
+ //TODO: recycling invalidates this
+ return row == null || row.isRemoved();
+ }
+
+ /**
+ * @return {@code true} if the row is null or dismissed
+ */
+ public boolean isDismissed() {
+ //TODO: recycling
+ return row == null || row.isDismissed();
+ }
+
+ public boolean isRowPinned() {
+ return row != null && row.isPinned();
+ }
+
+ public void setRowPinned(boolean pinned) {
+ if (row != null) row.setPinned(pinned);
+ }
+
+ public boolean isRowAnimatingAway() {
+ return row != null && row.isHeadsUpAnimatingAway();
+ }
+
+ public boolean isRowHeadsUp() {
+ return row != null && row.isHeadsUp();
+ }
+
+ public void setHeadsUp(boolean shouldHeadsUp) {
+ if (row != null) row.setHeadsUp(shouldHeadsUp);
+ }
+
+ public boolean mustStayOnScreen() {
+ return row != null && row.mustStayOnScreen();
+ }
+
+ public void setHeadsUpIsVisible() {
+ if (row != null) row.setHeadsUpIsVisible();
+ }
+
+ //TODO: i'm imagining a world where this isn't just the row, but I could be rwong
+ public ExpandableNotificationRow getHeadsUpAnimationView() {
+ return row;
+ }
+
+ public void setUserLocked(boolean userLocked) {
+ if (row != null) row.setUserLocked(userLocked);
+ }
+
+ public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) {
+ if (row != null) row.setUserExpanded(userExpanded, allowChildExpansion);
+ }
+
+ public void setGroupExpansionChanging(boolean changing) {
+ if (row != null) row.setGroupExpansionChanging(changing);
+ }
+
+ public void notifyHeightChanged(boolean needsAnimation) {
+ if (row != null) row.notifyHeightChanged(needsAnimation);
+ }
+
+ public void closeRemoteInput() {
+ if (row != null) row.closeRemoteInput();
+ }
+
+ public boolean areChildrenExpanded() {
+ return row != null && row.areChildrenExpanded();
+ }
+
+ public boolean keepInParent() {
+ return row != null && row.keepInParent();
+ }
+
+ //TODO: probably less confusing to say "is group fully visible"
+ public boolean isGroupNotFullyVisible() {
+ return row == null || row.isGroupNotFullyVisible();
+ }
+
+ public NotificationGuts getGuts() {
+ if (row != null) return row.getGuts();
+ return null;
+ }
+
+ public boolean hasLowPriorityStateUpdated() {
+ return row != null && row.hasLowPriorityStateUpdated();
+ }
+
+ public void removeRow() {
+ if (row != null) row.setRemoved();
+ }
+
+ public boolean isSummaryWithChildren() {
+ return row != null && row.isSummaryWithChildren();
+ }
+
+ public void setKeepInParent(boolean keep) {
+ if (row != null) row.setKeepInParent(keep);
+ }
+
+ public void onDensityOrFontScaleChanged() {
+ if (row != null) row.onDensityOrFontScaleChanged();
+ }
+
+ public boolean areGutsExposed() {
+ return row != null && row.getGuts() != null && row.getGuts().isExposed();
+ }
+
+ public boolean isChildInGroup() {
+ return parent == null;
+ }
+
+ public void setLowPriorityStateUpdated(boolean updated) {
+ if (row != null) row.setLowPriorityStateUpdated(updated);
+ }
+
+ /**
+ * @return Can the underlying notification be cleared? This can be different from whether the
+ * notification can be dismissed in case notifications are sensitive on the lockscreen.
+ * @see #canViewBeDismissed()
+ */
+ public boolean isClearable() {
+ if (notification == null || !notification.isClearable()) {
+ return false;
+ }
+ if (children.size() > 0) {
+ for (int i = 0; i < children.size(); i++) {
+ NotificationEntry child = children.get(i);
+ if (!child.isClearable()) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ public boolean canViewBeDismissed() {
+ if (row == null) return true;
+ return row.canViewBeDismissed();
+ }
+
+ @VisibleForTesting
+ boolean isExemptFromDndVisualSuppression() {
+ if (isNotificationBlockedByPolicy(notification.getNotification())) {
+ return false;
+ }
+
+ if ((notification.getNotification().flags
+ & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
+ return true;
+ }
+ if (notification.getNotification().isMediaNotification()) {
+ return true;
+ }
+ if (mIsSystemNotification != null && mIsSystemNotification) {
+ return true;
+ }
+ return false;
+ }
+
+ private boolean shouldSuppressVisualEffect(int effect) {
+ if (isExemptFromDndVisualSuppression()) {
+ return false;
+ }
+ return (suppressedVisualEffects & effect) != 0;
+ }
+
+ /**
+ * Returns whether {@link Policy#SUPPRESSED_EFFECT_FULL_SCREEN_INTENT}
+ * is set for this entry.
+ */
+ public boolean shouldSuppressFullScreenIntent() {
+ return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_FULL_SCREEN_INTENT);
+ }
+
+ /**
+ * Returns whether {@link Policy#SUPPRESSED_EFFECT_PEEK}
+ * is set for this entry.
+ */
+ public boolean shouldSuppressPeek() {
+ return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_PEEK);
+ }
+
+ /**
+ * Returns whether {@link Policy#SUPPRESSED_EFFECT_STATUS_BAR}
+ * is set for this entry.
+ */
+ public boolean shouldSuppressStatusBar() {
+ return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_STATUS_BAR);
+ }
+
+ /**
+ * Returns whether {@link Policy#SUPPRESSED_EFFECT_AMBIENT}
+ * is set for this entry.
+ */
+ public boolean shouldSuppressAmbient() {
+ return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_AMBIENT);
+ }
+
+ /**
+ * Returns whether {@link Policy#SUPPRESSED_EFFECT_NOTIFICATION_LIST}
+ * is set for this entry.
+ */
+ public boolean shouldSuppressNotificationList() {
+ return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_NOTIFICATION_LIST);
+ }
+
+ /**
+ * Categories that are explicitly called out on DND settings screens are always blocked, if
+ * DND has flagged them, even if they are foreground or system notifications that might
+ * otherwise visually bypass DND.
+ */
+ private static boolean isNotificationBlockedByPolicy(Notification n) {
+ return isCategory(CATEGORY_CALL, n)
+ || isCategory(CATEGORY_MESSAGE, n)
+ || isCategory(CATEGORY_ALARM, n)
+ || isCategory(CATEGORY_EVENT, n)
+ || isCategory(CATEGORY_REMINDER, n);
+ }
+
+ private static boolean isCategory(String category, Notification n) {
+ return Objects.equals(n.category, category);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index 610d300..3eec38e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -15,7 +15,6 @@
*/
package com.android.systemui.statusbar.notification.logging;
-import android.annotation.Nullable;
import android.content.Context;
import android.os.Handler;
import android.os.RemoteException;
@@ -34,9 +33,9 @@
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -116,11 +115,11 @@
// notifications.
// 3. Report newly visible and no-longer visible notifications.
// 4. Keep currently visible notifications for next report.
- ArrayList<NotificationData.Entry> activeNotifications = mEntryManager
+ ArrayList<NotificationEntry> activeNotifications = mEntryManager
.getNotificationData().getActiveNotifications();
int N = activeNotifications.size();
for (int i = 0; i < N; i++) {
- NotificationData.Entry entry = activeNotifications.get(i);
+ NotificationEntry entry = activeNotifications.get(i);
String key = entry.notification.getKey();
boolean isVisible = mListContainer.isInVisibleLocation(entry);
NotificationVisibility visObj = NotificationVisibility.obtain(key, i, N, isVisible);
@@ -168,14 +167,11 @@
entryManager.addNotificationEntryListener(new NotificationEntryListener() {
@Override
public void onEntryRemoved(
- @Nullable NotificationData.Entry entry,
- String key,
- StatusBarNotification old,
+ NotificationEntry entry,
NotificationVisibility visibility,
- boolean lifetimeExtended,
boolean removedByUser) {
- if (removedByUser && visibility != null && entry != null) {
- logNotificationClear(key, entry.notification, visibility);
+ if (removedByUser && visibility != null) {
+ logNotificationClear(entry.key, entry.notification, visibility);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index a58c7cd..95bd1ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -86,10 +86,9 @@
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
-import com.android.systemui.statusbar.notification.NotificationData;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.logging.NotificationCounters;
import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
@@ -106,6 +105,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.TimeUnit;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
@@ -122,6 +122,7 @@
private static final int MENU_VIEW_INDEX = 0;
private static final String TAG = "ExpandableNotifRow";
public static final float DEFAULT_HEADER_VISIBLE_AMOUNT = 1.0f;
+ private static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30);
private boolean mUpdateBackgroundOnUpdate;
/**
@@ -199,7 +200,7 @@
private ExpansionLogger mLogger;
private String mLoggingKey;
private NotificationGuts mGuts;
- private NotificationData.Entry mEntry;
+ private NotificationEntry mEntry;
private StatusBarNotification mStatusBarNotification;
private String mAppName;
@@ -451,7 +452,7 @@
*
* @param entry the entry this row is tied to
*/
- public void setEntry(@NonNull NotificationData.Entry entry) {
+ public void setEntry(@NonNull NotificationEntry entry) {
mEntry = entry;
mStatusBarNotification = entry.notification;
cacheIsSystemNotification();
@@ -685,7 +686,7 @@
return mStatusBarNotification;
}
- public NotificationData.Entry getEntry() {
+ public NotificationEntry getEntry() {
return mEntry;
}
@@ -1422,7 +1423,7 @@
public void performDismiss(boolean fromAccessibility) {
if (isOnlyChildInGroup()) {
- NotificationData.Entry groupSummary =
+ NotificationEntry groupSummary =
mGroupManager.getLogicalGroupSummary(getStatusBarNotification());
if (groupSummary.isClearable()) {
// If this is the only child in the group, dismiss the group, but don't try to show
@@ -1693,17 +1694,31 @@
/** Sets the last time the notification being displayed audibly alerted the user. */
public void setLastAudiblyAlertedMs(long lastAudiblyAlertedMs) {
if (NotificationUtils.useNewInterruptionModel(mContext)) {
- boolean recentlyAudiblyAlerted = System.currentTimeMillis() - lastAudiblyAlertedMs
- < NotificationEntryManager.RECENTLY_ALERTED_THRESHOLD_MS;
- if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() != null) {
- mChildrenContainer.getHeaderView().setRecentlyAudiblyAlerted(
- recentlyAudiblyAlerted);
+ long timeSinceAlertedAudibly = System.currentTimeMillis() - lastAudiblyAlertedMs;
+ boolean alertedRecently =
+ timeSinceAlertedAudibly < RECENTLY_ALERTED_THRESHOLD_MS;
+
+ applyAudiblyAlertedRecently(alertedRecently);
+
+ removeCallbacks(mExpireRecentlyAlertedFlag);
+ if (alertedRecently) {
+ long timeUntilNoLongerRecent =
+ RECENTLY_ALERTED_THRESHOLD_MS - timeSinceAlertedAudibly;
+ postDelayed(mExpireRecentlyAlertedFlag, timeUntilNoLongerRecent);
}
- mPrivateLayout.setRecentlyAudiblyAlerted(recentlyAudiblyAlerted);
- mPublicLayout.setRecentlyAudiblyAlerted(recentlyAudiblyAlerted);
}
}
+ private final Runnable mExpireRecentlyAlertedFlag = () -> applyAudiblyAlertedRecently(false);
+
+ private void applyAudiblyAlertedRecently(boolean audiblyAlertedRecently) {
+ if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() != null) {
+ mChildrenContainer.getHeaderView().setRecentlyAudiblyAlerted(audiblyAlertedRecently);
+ }
+ mPrivateLayout.setRecentlyAudiblyAlerted(audiblyAlertedRecently);
+ mPublicLayout.setRecentlyAudiblyAlerted(audiblyAlertedRecently);
+ }
+
public View.OnClickListener getAppOpsOnClickListener() {
return mOnAppOpsClickListener;
}
@@ -2538,7 +2553,7 @@
/**
* @return Whether this view is allowed to be dismissed. Only valid for visible notifications as
* otherwise some state might not be updated. To request about the general clearability
- * see {@link NotificationData.Entry#isClearable()}.
+ * see {@link NotificationEntry#isClearable()}.
*/
public boolean canViewBeDismissed() {
return mEntry.isClearable() && (!shouldShowPublic() || !mSensitiveHiddenInGeneral);
@@ -2969,7 +2984,7 @@
}
public interface OnExpandClickListener {
- void onExpandClicked(NotificationData.Entry clickedEntry, boolean nowExpanded);
+ void onExpandClicked(NotificationEntry clickedEntry, boolean nowExpanded);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 02a310c..bf30cf9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -46,8 +46,8 @@
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.TransformableView;
-import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationCustomViewWrapper;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
@@ -1231,7 +1231,7 @@
updateAllSingleLineViews();
}
- public void onNotificationUpdated(NotificationData.Entry entry) {
+ public void onNotificationUpdated(NotificationEntry entry) {
mStatusBarNotification = entry.notification;
mOnContentViewInactiveListeners.clear();
mBeforeN = entry.targetSdk < Build.VERSION_CODES.N;
@@ -1292,7 +1292,7 @@
}
}
- private void applyRemoteInputAndSmartReply(final NotificationData.Entry entry) {
+ private void applyRemoteInputAndSmartReply(final NotificationEntry entry) {
if (mRemoteInputController == null) {
return;
}
@@ -1313,7 +1313,7 @@
@VisibleForTesting
static SmartRepliesAndActions chooseSmartRepliesAndActions(
SmartReplyConstants smartReplyConstants,
- final NotificationData.Entry entry) {
+ final NotificationEntry entry) {
boolean enableAppGeneratedSmartReplies = (smartReplyConstants.isEnabled()
&& (!smartReplyConstants.requiresTargetingP()
|| entry.targetSdk >= Build.VERSION_CODES.P));
@@ -1370,7 +1370,7 @@
smartReplies, smartActions, freeformRemoteInputActionPair != null);
}
- private void applyRemoteInput(NotificationData.Entry entry, boolean hasFreeformRemoteInput) {
+ private void applyRemoteInput(NotificationEntry entry, boolean hasFreeformRemoteInput) {
View bigContentView = mExpandedChild;
if (bigContentView != null) {
mExpandedRemoteInput = applyRemoteInput(bigContentView, entry, hasFreeformRemoteInput,
@@ -1402,7 +1402,7 @@
mCachedHeadsUpRemoteInput = null;
}
- private RemoteInputView applyRemoteInput(View view, NotificationData.Entry entry,
+ private RemoteInputView applyRemoteInput(View view, NotificationEntry entry,
boolean hasRemoteInput, PendingIntent existingPendingIntent,
RemoteInputView cachedView, NotificationViewWrapper wrapper) {
View actionContainerCandidate = view.findViewById(
@@ -1470,7 +1470,7 @@
}
private void applySmartReplyView(SmartRepliesAndActions smartRepliesAndActions,
- NotificationData.Entry entry) {
+ NotificationEntry entry) {
if (mExpandedChild != null) {
mExpandedSmartReplyView =
applySmartReplyView(mExpandedChild, smartRepliesAndActions, entry);
@@ -1496,7 +1496,7 @@
}
private SmartReplyView applySmartReplyView(View view,
- SmartRepliesAndActions smartRepliesAndActions, NotificationData.Entry entry) {
+ SmartRepliesAndActions smartRepliesAndActions, NotificationEntry entry) {
View smartReplyContainerCandidate = view.findViewById(
com.android.internal.R.id.smart_reply_container);
if (!(smartReplyContainerCandidate instanceof LinearLayout)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index ac4e583..bd1dfb1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -48,7 +48,7 @@
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -116,7 +116,7 @@
mNotificationActivityStarter = notificationActivityStarter;
}
- public void onDensityOrFontScaleChanged(NotificationData.Entry entry) {
+ public void onDensityOrFontScaleChanged(NotificationEntry entry) {
setExposedGuts(entry.getGuts());
bindGuts(entry.getRow());
}
@@ -429,7 +429,7 @@
}
@Override
- public boolean shouldExtendLifetime(NotificationData.Entry entry) {
+ public boolean shouldExtendLifetime(NotificationEntry entry) {
return entry != null
&&(mNotificationGutsExposed != null
&& entry.getGuts() != null
@@ -438,7 +438,7 @@
}
@Override
- public void setShouldManageLifetime(NotificationData.Entry entry, boolean shouldExtend) {
+ public void setShouldManageLifetime(NotificationEntry entry, boolean shouldExtend) {
if (shouldExtend) {
mKeyToRemoveOnGutsClosed = entry.key;
if (Log.isLoggable(TAG, Log.DEBUG)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInflater.java
index 9908049..42ebfce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInflater.java
@@ -37,7 +37,7 @@
import com.android.systemui.statusbar.InflationTask;
import com.android.systemui.statusbar.notification.InflationException;
import com.android.systemui.statusbar.notification.MediaNotificationProcessor;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.util.Assert;
@@ -614,7 +614,7 @@
@Nullable InflationCallback endListener, ExpandableNotificationRow row,
boolean redactAmbient) {
Assert.isMainThread();
- NotificationData.Entry entry = row.getEntry();
+ NotificationEntry entry = row.getEntry();
NotificationContentView privateLayout = row.getPrivateLayout();
NotificationContentView publicLayout = row.getPublicLayout();
if (runningInflations.isEmpty()) {
@@ -724,7 +724,7 @@
* @param entry the entry with the content views set
* @param inflatedFlags the flags associated with the content views that were inflated
*/
- void onAsyncInflationFinished(NotificationData.Entry entry,
+ void onAsyncInflationFinished(NotificationEntry entry,
@InflationFlag int inflatedFlags);
/**
@@ -782,7 +782,7 @@
mRedactAmbient = redactAmbient;
mRemoteViewClickHandler = remoteViewClickHandler;
mCallback = callback;
- NotificationData.Entry entry = row.getEntry();
+ NotificationEntry entry = row.getEntry();
entry.setInflationTask(this);
}
@@ -857,7 +857,7 @@
}
@Override
- public void onAsyncInflationFinished(NotificationData.Entry entry,
+ public void onAsyncInflationFinished(NotificationEntry entry,
@InflationFlag int inflatedFlags) {
mRow.getEntry().onInflationTaskFinished();
mRow.onNotificationUpdated();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
index 1741a0b..0160c547 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
@@ -25,7 +25,7 @@
import com.android.systemui.R;
import com.android.systemui.statusbar.InflationTask;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
/**
* An inflater task that asynchronously inflates a ExpandableNotificationRow
@@ -36,14 +36,14 @@
private static final boolean TRACE_ORIGIN = true;
private RowInflationFinishedListener mListener;
- private NotificationData.Entry mEntry;
+ private NotificationEntry mEntry;
private boolean mCancelled;
private Throwable mInflateOrigin;
/**
* Inflates a new notificationView. This should not be called twice on this object
*/
- public void inflate(Context context, ViewGroup parent, NotificationData.Entry entry,
+ public void inflate(Context context, ViewGroup parent, NotificationEntry entry,
RowInflationFinishedListener listener) {
if (TRACE_ORIGIN) {
mInflateOrigin = new Throwable("inflate requested here");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 670908f..cbec37e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -25,7 +25,7 @@
import com.android.systemui.statusbar.AmbientPulseManager;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -358,7 +358,7 @@
mPulsing = hasPulsing;
}
- public boolean isPulsing(NotificationData.Entry entry) {
+ public boolean isPulsing(NotificationEntry entry) {
if (!mPulsing || mAmbientPulseManager == null) {
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java
index f0a2653..f771be0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java
@@ -16,15 +16,14 @@
package com.android.systemui.statusbar.notification.stack;
-import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator
- .ExpandAnimationParameters;
+import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
import android.view.View;
import android.view.ViewGroup;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
-import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -113,19 +112,12 @@
void setMaxDisplayedNotifications(int maxNotifications);
/**
- * Handle snapping a non-dismissable row back if the user tried to dismiss it.
- *
- * @param entry the entry whose row needs to snap back
- */
- void snapViewIfNeeded(NotificationData.Entry entry);
-
- /**
* Get the view parent for a notification entry. For example, NotificationStackScrollLayout.
*
* @param entry entry to get the view parent for
* @return the view parent for entry
*/
- ViewGroup getViewParentForNotification(NotificationData.Entry entry);
+ ViewGroup getViewParentForNotification(NotificationEntry entry);
/**
* Resets the currently exposed menu view.
@@ -148,7 +140,7 @@
*
* @param entry the entry whose view's view state needs to be cleaned up (say that 5 times fast)
*/
- void cleanUpViewStateForEntry(NotificationData.Entry entry);
+ void cleanUpViewStateForEntry(NotificationEntry entry);
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
index 4f0831f1..9418601 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
@@ -18,7 +18,7 @@
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.NUM_SECTIONS;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -49,12 +49,12 @@
}
@Override
- public void onHeadsUpPinned(NotificationData.Entry headsUp) {
+ public void onHeadsUpPinned(NotificationEntry headsUp) {
updateView(headsUp.getRow(), false /* animate */);
}
@Override
- public void onHeadsUpUnPinned(NotificationData.Entry headsUp) {
+ public void onHeadsUpUnPinned(NotificationEntry headsUp) {
updateView(headsUp.getRow(), true /* animate */);
}
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 8deb7d5..80db6c1 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
@@ -96,12 +96,13 @@
import com.android.systemui.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.statusbar.notification.FakeShadowView;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.ShadeViewRefactor;
import com.android.systemui.statusbar.notification.ShadeViewRefactor.RefactorComponent;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -517,6 +518,17 @@
mLowPriorityBeforeSpeedBump = "1".equals(newValue);
}
}, LOW_PRIORITY);
+
+ mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
+ @Override
+ public void onPostEntryUpdated(NotificationEntry entry) {
+ if (!entry.notification.isClearable()) {
+ // The user may have performed a dismiss action on the notification, since it's
+ // not clearable we should snap it back.
+ snapViewIfNeeded(entry);
+ }
+ }
+ });
}
@Override
@@ -590,14 +602,14 @@
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public RemoteInputController.Delegate createDelegate() {
return new RemoteInputController.Delegate() {
- public void setRemoteInputActive(NotificationData.Entry entry,
+ public void setRemoteInputActive(NotificationEntry entry,
boolean remoteInputActive) {
mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
entry.notifyHeightChanged(true /* needsAnimation */);
updateFooter();
}
- public void lockScrollTo(NotificationData.Entry entry) {
+ public void lockScrollTo(NotificationEntry entry) {
NotificationStackScrollLayout.this.lockScrollTo(entry.getRow());
}
@@ -910,7 +922,7 @@
@Override
@ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
- public boolean isInVisibleLocation(NotificationData.Entry entry) {
+ public boolean isInVisibleLocation(NotificationEntry entry) {
ExpandableNotificationRow row = entry.getRow();
ExpandableViewState childViewState = row.getViewState();
@@ -1224,13 +1236,13 @@
*/
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
private int getTopHeadsUpPinnedHeight() {
- NotificationData.Entry topEntry = mHeadsUpManager.getTopEntry();
+ NotificationEntry topEntry = mHeadsUpManager.getTopEntry();
if (topEntry == null) {
return 0;
}
ExpandableNotificationRow row = topEntry.getRow();
if (row.isChildInGroup()) {
- final NotificationData.Entry groupSummary
+ final NotificationEntry groupSummary
= mGroupManager.getGroupSummary(row.getStatusBarNotification());
if (groupSummary != null) {
row = groupSummary.getRow();
@@ -1405,7 +1417,7 @@
&& touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) {
if (slidingChild instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild;
- NotificationData.Entry entry = row.getEntry();
+ NotificationEntry entry = row.getEntry();
if (!mIsExpanded && row.isHeadsUp() && row.isPinned()
&& mHeadsUpManager.getTopEntry().getRow() != row
&& mGroupManager.getGroupSummary(
@@ -1538,9 +1550,8 @@
true /* isDismissAll */);
}
- @Override
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
- public void snapViewIfNeeded(NotificationData.Entry entry) {
+ private void snapViewIfNeeded(NotificationEntry entry) {
ExpandableNotificationRow child = entry.getRow();
boolean animate = mIsExpanded || isPinnedHeadsUp(child);
// If the child is showing the notification menu snap to that
@@ -1550,7 +1561,7 @@
@Override
@ShadeViewRefactor(RefactorComponent.ADAPTER)
- public ViewGroup getViewParentForNotification(NotificationData.Entry entry) {
+ public ViewGroup getViewParentForNotification(NotificationEntry entry) {
return this;
}
@@ -2053,7 +2064,7 @@
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
- private boolean isPulsing(NotificationData.Entry entry) {
+ private boolean isPulsing(NotificationEntry entry) {
return mAmbientState.isPulsing(entry);
}
@@ -2558,7 +2569,7 @@
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
@Override
- public void cleanUpViewStateForEntry(NotificationData.Entry entry) {
+ public void cleanUpViewStateForEntry(NotificationEntry entry) {
View child = entry.getRow();
if (child == mSwipeHelper.getTranslatingParentView()) {
mSwipeHelper.clearTranslatingParentView();
@@ -2686,7 +2697,7 @@
private boolean isChildInInvisibleGroup(View child) {
if (child instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) child;
- NotificationData.Entry groupSummary =
+ NotificationEntry groupSummary =
mGroupManager.getGroupSummary(row.getStatusBarNotification());
if (groupSummary != null && groupSummary.getRow() != row) {
return row.getVisibility() == View.INVISIBLE;
@@ -4705,7 +4716,7 @@
mHeadsUpManager.setAnimationStateHandler(this::setHeadsUpGoingAwayAnimationsAllowed);
}
- public void generateHeadsUpAnimation(NotificationData.Entry entry, boolean isHeadsUp) {
+ public void generateHeadsUpAnimation(NotificationEntry entry, boolean isHeadsUp) {
ExpandableNotificationRow row = entry.getHeadsUpAnimationView();
generateHeadsUpAnimation(row, isHeadsUp);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index f1d9549..975aee5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -189,10 +189,13 @@
boolean isFastNonDismissGesture =
gestureFastEnough && !gestureTowardsMenu && !isDismissGesture;
boolean isMenuRevealingGestureAwayFromMenu = slowSwipedFarEnough || isFastNonDismissGesture;
- if (isNonDismissGestureTowardsMenu
- || (!isFalseGesture(ev) && isMenuRevealingGestureAwayFromMenu)) {
+ int menuSnapTarget = menuRow.getMenuSnapTarget();
+ boolean isNonFalseMenuRevealingGesture =
+ !isFalseGesture(ev) && isMenuRevealingGestureAwayFromMenu;
+ if ((isNonDismissGestureTowardsMenu || isNonFalseMenuRevealingGesture)
+ && menuSnapTarget != 0) {
// Menu has not been snapped to previously and this is menu revealing gesture
- snapOpen(animView, menuRow.getMenuSnapTarget(), velocity);
+ snapOpen(animView, menuSnapTarget, velocity);
menuRow.onSnapOpen();
} else if (isDismissGesture(ev) && !gestureTowardsMenu) {
dismiss(animView, velocity);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
index 280dda0..577e8d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -84,6 +84,14 @@
public void onCancelled() {
pulseFinished();
}
+
+ /**
+ * Whether to fade out wallpaper.
+ */
+ @Override
+ public boolean isFadeOutWallpaper() {
+ return mPulseReason == DozeLog.PULSE_REASON_DOCKING;
+ }
};
public DozeScrimController(DozeParameters dozeParameters) {
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 d1e488a..876b902 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -28,7 +28,7 @@
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.HeadsUpStatusBarView;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
@@ -143,7 +143,7 @@
}
@Override
- public void onHeadsUpPinned(NotificationData.Entry entry) {
+ public void onHeadsUpPinned(NotificationEntry entry) {
updateTopEntry();
updateHeader(entry);
}
@@ -206,11 +206,11 @@
}
private void updateTopEntry() {
- NotificationData.Entry newEntry = null;
+ NotificationEntry newEntry = null;
if (!mIsExpanded && mHeadsUpManager.hasPinnedHeadsUp()) {
newEntry = mHeadsUpManager.getTopEntry();
}
- NotificationData.Entry previousEntry = mHeadsUpStatusBarView.getShowingEntry();
+ NotificationEntry previousEntry = mHeadsUpStatusBarView.getShowingEntry();
mHeadsUpStatusBarView.setEntry(newEntry);
if (newEntry != previousEntry) {
boolean animateIsolation = false;
@@ -298,7 +298,7 @@
}
@Override
- public void onHeadsUpUnPinned(NotificationData.Entry entry) {
+ public void onHeadsUpUnPinned(NotificationEntry entry) {
updateTopEntry();
updateHeader(entry);
}
@@ -338,7 +338,7 @@
});
}
- public void updateHeader(NotificationData.Entry entry) {
+ public void updateHeader(NotificationEntry entry) {
ExpandableNotificationRow row = entry.getRow();
float headerVisibleAmount = 1.0f;
if (row.isPinned() || row.isHeadsUpAnimatingAway() || row == mTrackedChild) {
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 f4cfd41..0fada66 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -41,8 +41,8 @@
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -72,8 +72,8 @@
private int mDisplayCutoutTouchableRegionSize;
private boolean mTrackingHeadsUp;
private HashSet<String> mSwipedOutKeys = new HashSet<>();
- private HashSet<NotificationData.Entry> mEntriesToRemoveAfterExpand = new HashSet<>();
- private ArraySet<NotificationData.Entry> mEntriesToRemoveWhenReorderingAllowed
+ private HashSet<NotificationEntry> mEntriesToRemoveAfterExpand = new HashSet<>();
+ private ArraySet<NotificationEntry> mEntriesToRemoveWhenReorderingAllowed
= new ArraySet<>();
private boolean mIsExpanded;
private int[] mTmpTwoArray = new int[2];
@@ -187,7 +187,7 @@
releaseAllImmediately();
mReleaseOnExpandFinish = false;
} else {
- for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) {
+ for (NotificationEntry entry : mEntriesToRemoveAfterExpand) {
if (isAlerting(entry.key)) {
// Maybe the heads-up was removed already
removeAlertEntry(entry.key);
@@ -252,7 +252,7 @@
* @param remoteInputActive True to notify active, False to notify inactive.
*/
public void setRemoteInputActive(
- @NonNull NotificationData.Entry entry, boolean remoteInputActive) {
+ @NonNull NotificationEntry entry, boolean remoteInputActive) {
HeadsUpEntryPhone headsUpEntry = getHeadsUpEntryPhone(entry.key);
if (headsUpEntry != null && headsUpEntry.remoteInputActive != remoteInputActive) {
headsUpEntry.remoteInputActive = remoteInputActive;
@@ -268,7 +268,7 @@
* Sets whether an entry's menu row is exposed and therefore it should stick in the heads up
* area if it's pinned until it's hidden again.
*/
- public void setMenuShown(@NonNull NotificationData.Entry entry, boolean menuShown) {
+ public void setMenuShown(@NonNull NotificationEntry entry, boolean menuShown) {
HeadsUpEntry headsUpEntry = getHeadsUpEntry(entry.key);
if (headsUpEntry instanceof HeadsUpEntryPhone && entry.isRowPinned()) {
((HeadsUpEntryPhone) headsUpEntry).setMenuShownPinned(menuShown);
@@ -315,9 +315,9 @@
return;
}
if (hasPinnedHeadsUp()) {
- NotificationData.Entry topEntry = getTopEntry();
+ NotificationEntry topEntry = getTopEntry();
if (topEntry.isChildInGroup()) {
- final NotificationData.Entry groupSummary
+ final NotificationEntry groupSummary
= mGroupManager.getGroupSummary(topEntry.notification);
if (groupSummary != null) {
topEntry = groupSummary;
@@ -374,7 +374,7 @@
@Override
public void onReorderingAllowed() {
mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
- for (NotificationData.Entry entry : mEntriesToRemoveWhenReorderingAllowed) {
+ for (NotificationEntry entry : mEntriesToRemoveWhenReorderingAllowed) {
if (isAlerting(entry.key)) {
// Maybe the heads-up was removed already
removeAlertEntry(entry.key);
@@ -399,7 +399,7 @@
}
@Override
- protected boolean shouldHeadsUpBecomePinned(NotificationData.Entry entry) {
+ protected boolean shouldHeadsUpBecomePinned(NotificationEntry entry) {
return mStatusBarState != StatusBarState.KEYGUARD && !mIsExpanded
|| super.shouldHeadsUpBecomePinned(entry);
}
@@ -488,7 +488,7 @@
return super.isSticky() || mMenuShownPinned;
}
- public void setEntry(@NonNull final NotificationData.Entry entry) {
+ public void setEntry(@NonNull final NotificationEntry entry) {
Runnable removeHeadsUpRunnable = () -> {
if (!mVisualStabilityManager.isReorderingAllowed()) {
mEntriesToRemoveWhenReorderingAllowed.add(entry);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
index 9c1c71a..dd200da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
@@ -21,7 +21,7 @@
import android.view.ViewConfiguration;
import com.android.systemui.Gefingerpoken;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -83,7 +83,7 @@
} else if (child == null && !mCallback.isExpanded()) {
// We might touch above the visible heads up child, but then we still would
// like to capture it.
- NotificationData.Entry topEntry = mHeadsUpManager.getTopEntry();
+ NotificationEntry topEntry = mHeadsUpManager.getTopEntry();
if (topEntry != null && topEntry.isRowPinned()) {
mPickedChild = topEntry.getRow();
mTouchingHeadsUpView = true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java
index 927228e..925a19d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java
@@ -22,7 +22,7 @@
import com.android.systemui.Dependency;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.notification.NotificationData.KeyguardEnvironment;
+import com.android.systemui.statusbar.notification.collection.NotificationData.KeyguardEnvironment;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
public class KeyguardEnvironmentImpl implements KeyguardEnvironment {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
index 3839ed5..d364356 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
@@ -31,9 +31,9 @@
import com.android.systemui.statusbar.InflationTask;
import com.android.systemui.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.statusbar.notification.NotificationData.Entry;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.NotificationInflater.AsyncInflationTask;
import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag;
import com.android.systemui.statusbar.phone.NotificationGroupManager.NotificationGroup;
@@ -108,7 +108,7 @@
* @param entry notification to check
* @return true if the entry was transferred to and should inflate + alert
*/
- public boolean isAlertTransferPending(@NonNull Entry entry) {
+ public boolean isAlertTransferPending(@NonNull NotificationEntry entry) {
PendingAlertInfo alertInfo = mPendingAlerts.get(entry.key);
return alertInfo != null && alertInfo.isStillValid();
}
@@ -172,16 +172,16 @@
};
@Override
- public void onAmbientStateChanged(Entry entry, boolean isAmbient) {
+ public void onAmbientStateChanged(NotificationEntry entry, boolean isAmbient) {
onAlertStateChanged(entry, isAmbient, mAmbientPulseManager);
}
@Override
- public void onHeadsUpStateChanged(Entry entry, boolean isHeadsUp) {
+ public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
onAlertStateChanged(entry, isHeadsUp, mHeadsUpManager);
}
- private void onAlertStateChanged(Entry entry, boolean isAlerting,
+ private void onAlertStateChanged(NotificationEntry entry, boolean isAlerting,
AlertingNotificationManager alertManager) {
if (isAlerting && mGroupManager.isSummaryOfSuppressedGroup(entry.notification)) {
handleSuppressedSummaryAlerted(entry, alertManager);
@@ -193,7 +193,7 @@
// Called when a new notification has been posted but is not inflated yet. We use this to
// see as early as we can if we need to abort a transfer.
@Override
- public void onPendingEntryAdded(Entry entry) {
+ public void onPendingEntryAdded(NotificationEntry entry) {
String groupKey = mGroupManager.getGroupKey(entry.notification);
GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(groupKey);
if (groupAlertEntry != null) {
@@ -204,7 +204,7 @@
// Called when the entry's reinflation has finished. If there is an alert pending, we
// then show the alert.
@Override
- public void onEntryReinflated(Entry entry) {
+ public void onEntryReinflated(NotificationEntry entry) {
PendingAlertInfo alertInfo = mPendingAlerts.remove(entry.key);
if (alertInfo != null) {
if (alertInfo.isStillValid()) {
@@ -219,16 +219,13 @@
@Override
public void onEntryRemoved(
- @Nullable Entry entry,
- String key,
- StatusBarNotification old,
+ @Nullable NotificationEntry entry,
NotificationVisibility visibility,
- boolean lifetimeExtended,
boolean removedByUser) {
// Removes any alerts pending on this entry. Note that this will not stop any inflation
// tasks started by a transfer, so this should only be used as clean-up for when
// inflation is stopped and the pending alert no longer needs to happen.
- mPendingAlerts.remove(key);
+ mPendingAlerts.remove(entry.key);
}
};
@@ -244,8 +241,8 @@
return 0;
}
int number = 0;
- Iterable<Entry> values = mEntryManager.getPendingNotificationsIterator();
- for (Entry entry : values) {
+ Iterable<NotificationEntry> values = mEntryManager.getPendingNotificationsIterator();
+ for (NotificationEntry entry : values) {
if (isPendingNotificationInGroup(entry, group) && onlySummaryAlerts(entry)) {
number++;
}
@@ -263,8 +260,8 @@
if (mEntryManager == null) {
return false;
}
- Iterable<Entry> values = mEntryManager.getPendingNotificationsIterator();
- for (Entry entry : values) {
+ Iterable<NotificationEntry> values = mEntryManager.getPendingNotificationsIterator();
+ for (NotificationEntry entry : values) {
if (isPendingNotificationInGroup(entry, group)) {
return true;
}
@@ -279,7 +276,7 @@
* @param group group to check
* @return true if the notification will add to the group, false o/w
*/
- private boolean isPendingNotificationInGroup(@NonNull Entry entry,
+ private boolean isPendingNotificationInGroup(@NonNull NotificationEntry entry,
@NonNull NotificationGroup group) {
String groupKey = mGroupManager.getGroupKey(group.summary.notification);
return mGroupManager.isGroupChild(entry.notification)
@@ -296,7 +293,7 @@
* @param summary the summary that is suppressed and alerting
* @param alertManager the alert manager that manages the alerting summary
*/
- private void handleSuppressedSummaryAlerted(@NonNull Entry summary,
+ private void handleSuppressedSummaryAlerted(@NonNull NotificationEntry summary,
@NonNull AlertingNotificationManager alertManager) {
StatusBarNotification sbn = summary.notification;
GroupAlertEntry groupAlertEntry =
@@ -312,7 +309,7 @@
return;
}
- Entry child = mGroupManager.getLogicalChildren(summary.notification).iterator().next();
+ NotificationEntry child = mGroupManager.getLogicalChildren(summary.notification).iterator().next();
if (child != null) {
if (child.getRow().keepInParent()
|| child.isRowRemoved()
@@ -336,7 +333,7 @@
* @param toEntry entry to transfer to
* @param alertManager alert manager for the alert type
*/
- private void transferAlertState(@NonNull Entry fromEntry, @NonNull Entry toEntry,
+ private void transferAlertState(@NonNull NotificationEntry fromEntry, @NonNull NotificationEntry toEntry,
@NonNull AlertingNotificationManager alertManager) {
alertManager.removeNotification(fromEntry.key, true /* releaseImmediately */);
alertNotificationWhenPossible(toEntry, alertManager);
@@ -356,13 +353,13 @@
private void checkShouldTransferBack(@NonNull GroupAlertEntry groupAlertEntry) {
if (SystemClock.elapsedRealtime() - groupAlertEntry.mLastAlertTransferTime
< ALERT_TRANSFER_TIMEOUT) {
- Entry summary = groupAlertEntry.mGroup.summary;
+ NotificationEntry summary = groupAlertEntry.mGroup.summary;
AlertingNotificationManager alertManager = getActiveAlertManager();
if (!onlySummaryAlerts(summary)) {
return;
}
- ArrayList<Entry> children = mGroupManager.getLogicalChildren(summary.notification);
+ ArrayList<NotificationEntry> children = mGroupManager.getLogicalChildren(summary.notification);
int numChildren = children.size();
int numPendingChildren = getPendingChildrenNotAlerting(groupAlertEntry.mGroup);
numChildren += numPendingChildren;
@@ -371,7 +368,7 @@
}
boolean releasedChild = false;
for (int i = 0; i < children.size(); i++) {
- Entry entry = children.get(i);
+ NotificationEntry entry = children.get(i);
if (onlySummaryAlerts(entry) && alertManager.isAlerting(entry.key)) {
releasedChild = true;
alertManager.removeNotification(entry.key, true /* releaseImmediately */);
@@ -402,7 +399,7 @@
* @param entry entry to show
* @param alertManager alert manager for the alert type
*/
- private void alertNotificationWhenPossible(@NonNull Entry entry,
+ private void alertNotificationWhenPossible(@NonNull NotificationEntry entry,
@NonNull AlertingNotificationManager alertManager) {
@InflationFlag int contentFlag = alertManager.getContentFlag();
if (!entry.getRow().isInflationFlagSet(contentFlag)) {
@@ -422,7 +419,7 @@
return mIsDozing ? mAmbientPulseManager : mHeadsUpManager;
}
- private boolean onlySummaryAlerts(Entry entry) {
+ private boolean onlySummaryAlerts(NotificationEntry entry) {
return entry.notification.getNotification().getGroupAlertBehavior()
== Notification.GROUP_ALERT_SUMMARY;
}
@@ -442,7 +439,7 @@
* the transfer is still valid if the notification is updated.
*/
final StatusBarNotification mOriginalNotification;
- final Entry mEntry;
+ final NotificationEntry mEntry;
/**
* The notification is still pending inflation but we've decided that we no longer need
@@ -453,7 +450,7 @@
*/
boolean mAbortOnInflation;
- PendingAlertInfo(Entry entry, AlertingNotificationManager alertManager) {
+ PendingAlertInfo(NotificationEntry entry, AlertingNotificationManager alertManager) {
mOriginalNotification = entry.notification;
mEntry = entry;
mAlertManager = alertManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
index 3c1c076..bb9e418 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -27,7 +27,7 @@
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
@@ -97,7 +97,7 @@
}
}
- public void onEntryRemoved(NotificationData.Entry removed) {
+ public void onEntryRemoved(NotificationEntry removed) {
onEntryRemovedInternal(removed, removed.notification);
mIsolatedEntries.remove(removed.key);
}
@@ -109,7 +109,7 @@
* @param sbn the notification the entry has, which doesn't need to be the same as it's internal
* notification
*/
- private void onEntryRemovedInternal(NotificationData.Entry removed,
+ private void onEntryRemovedInternal(NotificationEntry removed,
final StatusBarNotification sbn) {
String groupKey = getGroupKey(sbn);
final NotificationGroup group = mGroupMap.get(groupKey);
@@ -136,7 +136,7 @@
}
}
- public void onEntryAdded(final NotificationData.Entry added) {
+ public void onEntryAdded(final NotificationEntry added) {
if (added.isRowRemoved()) {
added.setDebugThrowable(new Throwable());
}
@@ -152,7 +152,7 @@
}
}
if (isGroupChild) {
- NotificationData.Entry existing = group.children.get(added.key);
+ NotificationEntry existing = group.children.get(added.key);
if (existing != null && existing != added) {
Throwable existingThrowable = existing.getDebugThrowable();
Log.wtf(TAG, "Inconsistent entries found with the same key " + added.key
@@ -169,9 +169,9 @@
group.expanded = added.areChildrenExpanded();
updateSuppression(group);
if (!group.children.isEmpty()) {
- ArrayList<NotificationData.Entry> childrenCopy
+ ArrayList<NotificationEntry> childrenCopy
= new ArrayList<>(group.children.values());
- for (NotificationData.Entry child : childrenCopy) {
+ for (NotificationEntry child : childrenCopy) {
onEntryBecomingChild(child);
}
for (OnGroupChangeListener listener : mListeners) {
@@ -181,7 +181,7 @@
}
}
- private void onEntryBecomingChild(NotificationData.Entry entry) {
+ private void onEntryBecomingChild(NotificationEntry entry) {
if (shouldIsolate(entry)) {
isolateNotification(entry);
}
@@ -221,7 +221,7 @@
return count;
}
- private NotificationData.Entry getIsolatedChild(String groupKey) {
+ private NotificationEntry getIsolatedChild(String groupKey) {
for (StatusBarNotification sbn : mIsolatedEntries.values()) {
if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn)) {
return mGroupMap.get(sbn.getKey()).summary;
@@ -230,7 +230,7 @@
return null;
}
- public void onEntryUpdated(NotificationData.Entry entry,
+ public void onEntryUpdated(NotificationEntry entry,
StatusBarNotification oldNotification) {
String oldKey = oldNotification.getGroupKey();
String newKey = entry.notification.getGroupKey();
@@ -267,7 +267,7 @@
if (!isOnlyChild(sbn)) {
return false;
}
- NotificationData.Entry logicalGroupSummary = getLogicalGroupSummary(sbn);
+ NotificationEntry logicalGroupSummary = getLogicalGroupSummary(sbn);
return logicalGroupSummary != null
&& !logicalGroupSummary.notification.equals(sbn);
}
@@ -343,7 +343,7 @@
* Get the summary of a specified status bar notification. For isolated notification this return
* itself.
*/
- public NotificationData.Entry getGroupSummary(StatusBarNotification sbn) {
+ public NotificationEntry getGroupSummary(StatusBarNotification sbn) {
return getGroupSummary(getGroupKey(sbn));
}
@@ -352,12 +352,12 @@
* but the logical summary, i.e when a child is isolated, it still returns the summary as if
* it wasn't isolated.
*/
- public NotificationData.Entry getLogicalGroupSummary(StatusBarNotification sbn) {
+ public NotificationEntry getLogicalGroupSummary(StatusBarNotification sbn) {
return getGroupSummary(sbn.getGroupKey());
}
@Nullable
- private NotificationData.Entry getGroupSummary(String groupKey) {
+ private NotificationEntry getGroupSummary(String groupKey) {
NotificationGroup group = mGroupMap.get(groupKey);
//TODO: see if this can become an Entry
return group == null ? null
@@ -371,13 +371,13 @@
* @param summary summary of a group
* @return list of the children
*/
- public ArrayList<NotificationData.Entry> getLogicalChildren(StatusBarNotification summary) {
+ public ArrayList<NotificationEntry> getLogicalChildren(StatusBarNotification summary) {
NotificationGroup group = mGroupMap.get(summary.getGroupKey());
if (group == null) {
return null;
}
- ArrayList<NotificationData.Entry> children = new ArrayList<>(group.children.values());
- NotificationData.Entry isolatedChild = getIsolatedChild(summary.getGroupKey());
+ ArrayList<NotificationEntry> children = new ArrayList<>(group.children.values());
+ NotificationEntry isolatedChild = getIsolatedChild(summary.getGroupKey());
if (isolatedChild != null) {
children.add(isolatedChild);
}
@@ -443,24 +443,24 @@
}
@Override
- public void onHeadsUpPinned(NotificationData.Entry entry) {
+ public void onHeadsUpPinned(NotificationEntry entry) {
}
@Override
- public void onHeadsUpUnPinned(NotificationData.Entry entry) {
+ public void onHeadsUpUnPinned(NotificationEntry entry) {
}
@Override
- public void onAmbientStateChanged(NotificationData.Entry entry, boolean isAmbient) {
+ public void onAmbientStateChanged(NotificationEntry entry, boolean isAmbient) {
onAlertStateChanged(entry, isAmbient);
}
@Override
- public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {
+ public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
onAlertStateChanged(entry, isHeadsUp);
}
- private void onAlertStateChanged(NotificationData.Entry entry, boolean isAlerting) {
+ private void onAlertStateChanged(NotificationEntry entry, boolean isAlerting) {
if (isAlerting) {
if (shouldIsolate(entry)) {
isolateNotification(entry);
@@ -479,7 +479,7 @@
* @return true if the entry should be isolated
*/
- private boolean shouldIsolate(NotificationData.Entry entry) {
+ private boolean shouldIsolate(NotificationEntry entry) {
StatusBarNotification sbn = entry.notification;
NotificationGroup notificationGroup = mGroupMap.get(sbn.getGroupKey());
if (!sbn.isGroup() || sbn.getNotification().isGroupSummary()) {
@@ -499,7 +499,7 @@
*
* @param entry the notification to isolate
*/
- private void isolateNotification(NotificationData.Entry entry) {
+ private void isolateNotification(NotificationEntry entry) {
StatusBarNotification sbn = entry.notification;
// We will be isolated now, so lets update the groups
@@ -523,7 +523,7 @@
*
* @param entry the notification to un-isolate
*/
- private void stopIsolatingNotification(NotificationData.Entry entry) {
+ private void stopIsolatingNotification(NotificationEntry entry) {
StatusBarNotification sbn = entry.notification;
if (mIsolatedEntries.containsKey(sbn.getKey())) {
// not isolated anymore, we need to update the groups
@@ -564,8 +564,8 @@
}
public static class NotificationGroup {
- public final HashMap<String, NotificationData.Entry> children = new HashMap<>();
- public NotificationData.Entry summary;
+ public final HashMap<String, NotificationEntry> children = new HashMap<>();
+ public NotificationEntry summary;
public boolean expanded;
/**
* Is this notification group suppressed, i.e its summary is hidden
@@ -580,7 +580,7 @@
? Log.getStackTraceString(summary.getDebugThrowable())
: "");
result += "\n children size: " + children.size();
- for (NotificationData.Entry child : children.values()) {
+ for (NotificationEntry child : children.values()) {
result += "\n " + child.notification
+ (child.getDebugThrowable() != null
? Log.getStackTraceString(child.getDebugThrowable())
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 056c8a7..1fb5484 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -23,9 +23,9 @@
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarIconView;
-import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.tuner.TunerService;
@@ -182,7 +182,7 @@
return mStatusBar.getStatusBarHeight();
}
- protected boolean shouldShowNotificationIcon(NotificationData.Entry entry,
+ protected boolean shouldShowNotificationIcon(NotificationEntry entry,
boolean showAmbient, boolean showLowPriority, boolean hideDismissed,
boolean hideRepliedMessages) {
if (mEntryManager.getNotificationData().isAmbient(entry.key) && !showAmbient) {
@@ -247,7 +247,7 @@
* @param hideDismissed should dismissed icons be hidden
* @param hideRepliedMessages should messages that have been replied to be hidden
*/
- private void updateIconsForLayout(Function<NotificationData.Entry, StatusBarIconView> function,
+ private void updateIconsForLayout(Function<NotificationEntry, StatusBarIconView> function,
NotificationIconContainer hostLayout, boolean showAmbient, boolean showLowPriority,
boolean hideDismissed, boolean hideRepliedMessages) {
ArrayList<StatusBarIconView> toShow = new ArrayList<>(
@@ -257,7 +257,7 @@
for (int i = 0; i < mNotificationScrollLayout.getChildCount(); i++) {
View view = mNotificationScrollLayout.getChildAt(i);
if (view instanceof ExpandableNotificationRow) {
- NotificationData.Entry ent = ((ExpandableNotificationRow) view).getEntry();
+ NotificationEntry ent = ((ExpandableNotificationRow) view).getEntry();
if (shouldShowNotificationIcon(ent, showAmbient, showLowPriority, hideDismissed,
hideRepliedMessages)) {
toShow.add(function.apply(ent));
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 16576ad..75adf50 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -76,9 +76,9 @@
import com.android.systemui.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.AnimatableProperty;
-import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.PropertyAnimator;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -956,7 +956,7 @@
handled = true;
}
handled |= super.onTouchEvent(event);
- return mDozing ? handled : true;
+ return !mDozing || mPulsing || handled;
}
private boolean handleQsTouch(MotionEvent event) {
@@ -2489,12 +2489,12 @@
}
@Override
- public void onHeadsUpPinned(NotificationData.Entry entry) {
+ public void onHeadsUpPinned(NotificationEntry entry) {
mNotificationStackScroller.generateHeadsUpAnimation(entry.getHeadsUpAnimationView(), true);
}
@Override
- public void onHeadsUpUnPinned(NotificationData.Entry entry) {
+ public void onHeadsUpUnPinned(NotificationEntry entry) {
// When we're unpinning the notification via active edge they remain heads-upped,
// we need to make sure that an animation happens in this case, otherwise the notification
@@ -2507,7 +2507,7 @@
}
@Override
- public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {
+ public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
mNotificationStackScroller.generateHeadsUpAnimation(entry, isHeadsUp);
}
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 e25c829..853d7ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -275,9 +275,12 @@
holdWakeLock();
}
- // AOD wallpapers should fade away after a while
- if (mWallpaperSupportsAmbientMode && mDozeParameters.getAlwaysOn()
- && mState == ScrimState.AOD) {
+ // AOD wallpapers should fade away after a while.
+ // Docking pulses may take a long time, wallpapers should also fade away after a while.
+ if (mWallpaperSupportsAmbientMode && (
+ mDozeParameters.getAlwaysOn() && mState == ScrimState.AOD
+ || mState == ScrimState.PULSING && mCallback != null
+ && mCallback.isFadeOutWallpaper())) {
if (!mWallpaperVisibilityTimedOut) {
mTimeTicker.schedule(mDozeParameters.getWallpaperAodDuration(),
AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
@@ -329,7 +332,7 @@
@VisibleForTesting
protected void onHideWallpaperTimeout() {
- if (mState != ScrimState.AOD) {
+ if (mState != ScrimState.AOD && mState != ScrimState.PULSING) {
return;
}
@@ -364,7 +367,7 @@
mExpansionFraction = fraction;
final boolean keyguardOrUnlocked = mState == ScrimState.UNLOCKED
- || mState == ScrimState.KEYGUARD;
+ || mState == ScrimState.KEYGUARD || mState == ScrimState.PULSING;
if (!keyguardOrUnlocked || !mExpansionAffectsAlpha) {
return;
}
@@ -409,7 +412,7 @@
behindFraction = (float) Math.pow(behindFraction, 0.8f);
mCurrentBehindAlpha = behindFraction * GRADIENT_SCRIM_ALPHA_BUSY;
mCurrentInFrontAlpha = 0;
- } else if (mState == ScrimState.KEYGUARD) {
+ } else if (mState == ScrimState.KEYGUARD || mState == ScrimState.PULSING) {
// Either darken of make the scrim transparent when you
// pull down the shade
float interpolatedFract = getInterpolatedFraction();
@@ -504,7 +507,8 @@
// We want to override the back scrim opacity for the AOD state
// when it's time to fade the wallpaper away.
- boolean aodWallpaperTimeout = mState == ScrimState.AOD && mWallpaperVisibilityTimedOut;
+ boolean aodWallpaperTimeout = (mState == ScrimState.AOD || mState == ScrimState.PULSING)
+ && mWallpaperVisibilityTimedOut;
// We also want to hide FLAG_SHOW_WHEN_LOCKED activities under the scrim.
boolean occludedKeyguard = (mState == ScrimState.PULSING || mState == ScrimState.AOD)
&& mKeyguardOccluded;
@@ -562,8 +566,8 @@
if (alpha == 0f) {
scrim.setClickable(false);
} else {
- // Eat touch events (unless dozing or pulsing).
- scrim.setClickable(mState != ScrimState.AOD && mState != ScrimState.PULSING);
+ // Eat touch events (unless dozing).
+ scrim.setClickable(mState != ScrimState.AOD);
}
updateScrim(scrim, alpha);
}
@@ -917,6 +921,9 @@
}
default void onCancelled() {
}
+ default boolean isFadeOutWallpaper() {
+ return false;
+ }
}
/**
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 fb3c4aa..72519ba3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -219,6 +219,14 @@
public void prepare(ScrimState previousState) {
}
+ /**
+ * Check if lockscreen wallpaper or music album art exists.
+ * @return true if lockscreen wallpaper or music album art exists.
+ */
+ public boolean hasBackdrop() {
+ return mHasBackdrop;
+ }
+
public int getIndex() {
return mIndex;
}
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 977e336..9abd86d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -70,6 +70,7 @@
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.database.ContentObserver;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
@@ -98,6 +99,7 @@
import android.service.notification.StatusBarNotification;
import android.util.DisplayMetrics;
import android.util.EventLog;
+import android.util.FeatureFlagUtils;
import android.util.Log;
import android.util.Slog;
import android.view.Display;
@@ -192,12 +194,11 @@
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationAlertingManager;
import com.android.systemui.statusbar.notification.NotificationClicker;
-import com.android.systemui.statusbar.notification.NotificationData;
-import com.android.systemui.statusbar.notification.NotificationData.Entry;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
import com.android.systemui.statusbar.notification.NotificationRowBinder;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -479,8 +480,13 @@
WallpaperInfo info = wallpaperManager.getWallpaperInfo(UserHandle.USER_CURRENT);
final boolean deviceSupportsAodWallpaper = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_dozeSupportsAodWallpaper);
+ final boolean aodImageWallpaperEnabled = FeatureFlagUtils.isEnabled(mContext,
+ FeatureFlagUtils.AOD_IMAGEWALLPAPER_ENABLED);
+ updateAodMaskVisibility(deviceSupportsAodWallpaper && aodImageWallpaperEnabled);
+ // If WallpaperInfo is null, it must be ImageWallpaper.
final boolean supportsAmbientMode = deviceSupportsAodWallpaper
- && info != null && info.supportsAmbientMode();
+ && (info == null && aodImageWallpaperEnabled
+ || info != null && info.supportsAmbientMode());
mStatusBarWindowController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
mScrimController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
@@ -489,7 +495,7 @@
private Runnable mLaunchTransitionEndRunnable;
protected boolean mLaunchTransitionFadingAway;
- private NotificationData.Entry mDraggedDownEntry;
+ private NotificationEntry mDraggedDownEntry;
private boolean mLaunchCameraOnScreenTurningOn;
private boolean mLaunchCameraOnFinishedGoingToSleep;
private int mLastCameraLaunchSource;
@@ -582,6 +588,7 @@
protected NotificationPresenter mPresenter;
private NotificationActivityStarter mNotificationActivityStarter;
private boolean mPulsing;
+ private ContentObserver mFeatureFlagObserver;
@Override
public void onActiveStateChanged(int code, int uid, String packageName, boolean active) {
@@ -633,7 +640,7 @@
mBubbleController.setExpandListener(mBubbleExpandListener);
KeyguardSliceProvider sliceProvider = KeyguardSliceProvider.getAttachedInstance();
if (sliceProvider != null) {
- sliceProvider.initDependencies();
+ sliceProvider.initDependencies(mMediaManager, mStatusBarStateController);
} else {
Log.w(TAG, "Cannot init KeyguardSliceProvider dependencies");
}
@@ -698,6 +705,9 @@
mContext.registerReceiverAsUser(mWallpaperChangedReceiver, UserHandle.ALL,
wallpaperChangedFilter, null /* broadcastPermission */, null /* scheduler */);
mWallpaperChangedReceiver.onReceive(mContext, null);
+ mFeatureFlagObserver = new FeatureFlagObserver(
+ FeatureFlagUtils.AOD_IMAGEWALLPAPER_ENABLED /* feature */,
+ () -> mWallpaperChangedReceiver.onReceive(mContext, null) /* callback */);
// Set up the initial notification state. This needs to happen before CommandQueue.disable()
setUpPresenter();
@@ -1510,21 +1520,21 @@
}
@Override
- public void onHeadsUpPinned(NotificationData.Entry entry) {
+ public void onHeadsUpPinned(NotificationEntry entry) {
dismissVolumeDialog();
}
@Override
- public void onHeadsUpUnPinned(NotificationData.Entry entry) {
+ public void onHeadsUpUnPinned(NotificationEntry entry) {
}
@Override
- public void onHeadsUpStateChanged(Entry entry, boolean isHeadsUp) {
+ public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
mEntryManager.updateNotificationRanking(null /* rankingMap */);
}
@Override
- public void onAmbientStateChanged(Entry entry, boolean isAmbient) {
+ public void onAmbientStateChanged(NotificationEntry entry, boolean isAmbient) {
mEntryManager.updateNotificationRanking(null);
if (isAmbient) {
mDozeServiceHost.fireNotificationPulse();
@@ -2551,11 +2561,11 @@
};
public void resetUserExpandedStates() {
- ArrayList<Entry> activeNotifications = mEntryManager.getNotificationData()
+ ArrayList<NotificationEntry> activeNotifications = mEntryManager.getNotificationData()
.getActiveNotifications();
final int notificationCount = activeNotifications.size();
for (int i = 0; i < notificationCount; i++) {
- NotificationData.Entry entry = activeNotifications.get(i);
+ NotificationEntry entry = activeNotifications.get(i);
entry.resetUserExpansion();
}
}
@@ -3506,7 +3516,7 @@
int userId = mLockscreenUserManager.getCurrentUserId();
ExpandableNotificationRow row = null;
- NotificationData.Entry entry = null;
+ NotificationEntry entry = null;
if (expandView instanceof ExpandableNotificationRow) {
entry = ((ExpandableNotificationRow) expandView).getEntry();
entry.setUserExpanded(true /* userExpanded */, true /* allowChildExpansion */);
@@ -3574,10 +3584,7 @@
mVisualStabilityManager.setScreenOn(false);
updateVisibleToUser();
- // We need to disable touch events because these might
- // collapse the panel after we expanded it, and thus we would end up with a blank
- // Keyguard.
- mNotificationPanel.setTouchAndAnimationDisabled(true);
+ updateNotificationPanelTouchState();
mStatusBarWindow.cancelCurrentTouch();
if (mLaunchCameraOnFinishedGoingToSleep) {
mLaunchCameraOnFinishedGoingToSleep = false;
@@ -3600,13 +3607,22 @@
mDeviceInteractive = true;
mAmbientPulseManager.releaseAllImmediately();
mVisualStabilityManager.setScreenOn(true);
- mNotificationPanel.setTouchAndAnimationDisabled(false);
+ updateNotificationPanelTouchState();
updateVisibleToUser();
updateIsKeyguard();
mDozeServiceHost.stopDozing();
}
};
+ /**
+ * We need to disable touch events because these might
+ * collapse the panel after we expanded it, and thus we would end up with a blank
+ * Keyguard.
+ */
+ private void updateNotificationPanelTouchState() {
+ mNotificationPanel.setTouchAndAnimationDisabled(!mDeviceInteractive && !mPulsing);
+ }
+
final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
@Override
public void onScreenTurningOn() {
@@ -3872,17 +3888,15 @@
@Override
public void onPulseStarted() {
callback.onPulseStarted();
- if (mAmbientPulseManager.hasNotifications()) {
- // Only pulse the stack scroller if there's actually something to show.
- // Otherwise just show the always-on screen.
- setPulsing(true);
- }
+ updateNotificationPanelTouchState();
+ setPulsing(true);
}
@Override
public void onPulseFinished() {
mPulsing = false;
callback.onPulseFinished();
+ updateNotificationPanelTouchState();
setPulsing(false);
}
@@ -4412,4 +4426,33 @@
public @TransitionMode int getStatusBarMode() {
return mStatusBarMode;
}
+
+ private void updateAodMaskVisibility(boolean supportsAodWallpaper) {
+ View mask = mStatusBarWindow.findViewById(R.id.aod_mask);
+ if (mask != null) {
+ mask.setVisibility(supportsAodWallpaper ? View.VISIBLE : View.INVISIBLE);
+ }
+ }
+
+ private final class FeatureFlagObserver extends ContentObserver {
+ private final Runnable mCallback;
+
+ FeatureFlagObserver(String feature, Runnable callback) {
+ this(null, feature, callback);
+ }
+
+ private FeatureFlagObserver(Handler handler, String feature, Runnable callback) {
+ super(handler);
+ mCallback = callback;
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(feature), false, this);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ if (mCallback != null) {
+ mStatusBarWindow.post(mCallback);
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index db7589d..f846036 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -36,7 +36,6 @@
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
-import com.android.systemui.statusbar.policy.IconLogger;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
@@ -61,7 +60,6 @@
private final ArrayList<IconManager> mIconGroups = new ArrayList<>();
private final ArraySet<String> mIconBlacklist = new ArraySet<>();
- private final IconLogger mIconLogger = Dependency.get(IconLogger.class);
// Points to light or dark context depending on the... context?
private Context mContext;
@@ -147,7 +145,6 @@
int viewIndex = getViewIndex(index, holder.getTag());
boolean blocked = mIconBlacklist.contains(slot);
- mIconLogger.onIconVisibility(getSlotName(index), holder.isVisible());
mIconGroups.forEach(l -> l.onIconAdded(viewIndex, slot, blocked, holder));
}
@@ -281,8 +278,6 @@
return;
}
- mIconLogger.onIconHidden(slotName);
-
int slotIndex = getSlotIndex(slotName);
List<StatusBarIconHolder> iconsToRemove = slot.getHolderListInViewOrder();
for (StatusBarIconHolder holder : iconsToRemove) {
@@ -297,7 +292,6 @@
if (getIcon(index, tag) == null) {
return;
}
- mIconLogger.onIconHidden(getSlotName(index));
super.removeIcon(index, tag);
int viewIndex = getViewIndex(index, 0);
mIconGroups.forEach(l -> l.onRemoveIcon(viewIndex));
@@ -305,7 +299,6 @@
private void handleSet(int index, StatusBarIconHolder holder) {
int viewIndex = getViewIndex(index, holder.getTag());
- mIconLogger.onIconVisibility(getSlotName(index), holder.isVisible());
mIconGroups.forEach(l -> l.onSetIconHolder(viewIndex, holder));
}
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 0f8970f..bb23608 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -187,7 +187,7 @@
mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
} else if (bouncerNeedsScrimming()) {
mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
- } else if (mShowing && !mDozing) {
+ } else if (mShowing) {
if (!isWakeAndUnlocking() && !mStatusBar.isInLaunchTransition()) {
mBouncer.setExpansion(expansion);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 8d1b911..4f61009 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -60,10 +60,10 @@
import com.android.systemui.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
-import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.policy.HeadsUpUtil;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
@@ -131,7 +131,7 @@
mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
@Override
- public void onPendingEntryAdded(NotificationData.Entry entry) {
+ public void onPendingEntryAdded(NotificationEntry entry) {
handleFullScreenIntent(entry);
}
});
@@ -267,7 +267,7 @@
}
}
Intent fillInIntent = null;
- NotificationData.Entry entry = row.getEntry();
+ NotificationEntry entry = row.getEntry();
CharSequence remoteInputText = null;
if (!TextUtils.isEmpty(entry.remoteInputText)) {
remoteInputText = entry.remoteInputText;
@@ -345,7 +345,7 @@
}, null, false /* afterKeyguardGone */);
}
- private void handleFullScreenIntent(NotificationData.Entry entry) {
+ private void handleFullScreenIntent(NotificationEntry entry) {
boolean isHeadsUped = mNotificationInterruptionStateProvider.shouldHeadsUp(entry);
if (!isHeadsUped && entry.notification.getNotification().fullScreenIntent != null) {
if (shouldSuppressFullScreenIntent(entry)) {
@@ -413,7 +413,7 @@
|| !mActivityLaunchAnimator.isAnimationPending();
}
- private boolean shouldSuppressFullScreenIntent(NotificationData.Entry entry) {
+ private boolean shouldSuppressFullScreenIntent(NotificationEntry entry) {
if (mPresenter.isDeviceInVrMode()) {
return true;
}
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 b9372e8..df7f53b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -60,12 +60,12 @@
import com.android.systemui.statusbar.notification.AboveShelfObserver;
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.NotificationAlertingManager;
-import com.android.systemui.statusbar.notification.NotificationData.Entry;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
import com.android.systemui.statusbar.notification.NotificationRowBinder;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -104,6 +104,8 @@
Dependency.get(NotificationMediaManager.class);
private final VisualStabilityManager mVisualStabilityManager =
Dependency.get(VisualStabilityManager.class);
+ private final NotificationGutsManager mGutsManager =
+ Dependency.get(NotificationGutsManager.class);
protected AmbientPulseManager mAmbientPulseManager = Dependency.get(AmbientPulseManager.class);
private final NotificationPanelView mNotificationPanel;
@@ -183,41 +185,35 @@
Dependency.get(InitController.class).addPostInitTask(() -> {
NotificationEntryListener notificationEntryListener = new NotificationEntryListener() {
@Override
- public void onNotificationAdded(Entry entry) {
+ public void onNotificationAdded(NotificationEntry entry) {
// Recalculate the position of the sliding windows and the titles.
mShadeController.updateAreThereNotifications();
}
@Override
- public void onEntryUpdated(Entry entry) {
+ public void onPostEntryUpdated(NotificationEntry entry) {
mShadeController.updateAreThereNotifications();
}
@Override
public void onEntryRemoved(
- @Nullable Entry entry,
- String key,
- StatusBarNotification old,
+ @Nullable NotificationEntry entry,
NotificationVisibility visibility,
- boolean lifetimeExtended,
boolean removedByUser) {
- if (!lifetimeExtended) {
- StatusBarNotificationPresenter.this.onNotificationRemoved(key, old);
- }
+ StatusBarNotificationPresenter.this.onNotificationRemoved(
+ entry.key, entry.notification);
if (removedByUser) {
maybeEndAmbientPulse();
}
}
};
- NotificationGutsManager gutsManager = Dependency.get(NotificationGutsManager.class);
-
mViewHierarchyManager.setUpWithPresenter(this, notifListContainer);
mEntryManager.setUpWithPresenter(this, notifListContainer, mHeadsUpManager);
mEntryManager.addNotificationEntryListener(notificationEntryListener);
mEntryManager.addNotificationLifetimeExtender(mHeadsUpManager);
mEntryManager.addNotificationLifetimeExtender(mAmbientPulseManager);
- mEntryManager.addNotificationLifetimeExtender(gutsManager);
+ mEntryManager.addNotificationLifetimeExtender(mGutsManager);
mEntryManager.addNotificationLifetimeExtenders(
remoteInputManager.getLifetimeExtenders());
mNotificationRowBinder.setUpWithPresenter(this, notifListContainer, mHeadsUpManager,
@@ -227,7 +223,7 @@
mLockscreenUserManager.setUpWithPresenter(this);
mMediaManager.setUpWithPresenter(this);
mVisualStabilityManager.setUpWithPresenter(this);
- gutsManager.setUpWithPresenter(this,
+ mGutsManager.setUpWithPresenter(this,
notifListContainer, mCheckSaveListener, mOnSettingsClickListener);
// ForegroundServiceControllerListener adds its listener in its constructor
// but we need to request it here in order for it to be instantiated.
@@ -246,7 +242,7 @@
MessagingMessage.dropCache();
MessagingGroup.dropCache();
if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) {
- mEntryManager.updateNotificationsOnDensityOrFontScaleChanged();
+ updateNotificationsOnDensityOrFontScaleChanged();
} else {
mReinflateNotificationsOnUserSwitched = true;
}
@@ -262,10 +258,10 @@
}
private void updateNotificationOnUiModeChanged() {
- ArrayList<Entry> userNotifications
+ ArrayList<NotificationEntry> userNotifications
= mEntryManager.getNotificationData().getNotificationsForCurrentUser();
for (int i = 0; i < userNotifications.size(); i++) {
- Entry entry = userNotifications.get(i);
+ NotificationEntry entry = userNotifications.get(i);
ExpandableNotificationRow row = entry.getRow();
if (row != null) {
row.onUiModeChanged();
@@ -273,6 +269,19 @@
}
}
+ private void updateNotificationsOnDensityOrFontScaleChanged() {
+ ArrayList<NotificationEntry> userNotifications =
+ mEntryManager.getNotificationData().getNotificationsForCurrentUser();
+ for (int i = 0; i < userNotifications.size(); i++) {
+ NotificationEntry entry = userNotifications.get(i);
+ entry.onDensityOrFontScaleChanged();
+ boolean exposedGuts = entry.areGutsExposed();
+ if (exposedGuts) {
+ mGutsManager.onDensityOrFontScaleChanged(entry);
+ }
+ }
+ }
+
@Override
public boolean isCollapsing() {
return mNotificationPanel.isCollapsing()
@@ -327,7 +336,7 @@
return !mEntryManager.getNotificationData().getActiveNotifications().isEmpty();
}
- public boolean canHeadsUp(Entry entry, StatusBarNotification sbn) {
+ public boolean canHeadsUp(NotificationEntry entry, StatusBarNotification sbn) {
if (mShadeController.isDozing()) {
return false;
}
@@ -371,7 +380,7 @@
if (MULTIUSER_DEBUG) mNotificationPanelDebugText.setText("USER " + newUserId);
mCommandQueue.animateCollapsePanels();
if (mReinflateNotificationsOnUserSwitched) {
- mEntryManager.updateNotificationsOnDensityOrFontScaleChanged();
+ updateNotificationsOnDensityOrFontScaleChanged();
mReinflateNotificationsOnUserSwitched = false;
}
if (mDispatchUiModeChangeOnUserSwitched) {
@@ -385,7 +394,7 @@
}
@Override
- public void onBindRow(Entry entry, PackageManager pmUser,
+ public void onBindRow(NotificationEntry entry, PackageManager pmUser,
StatusBarNotification sbn, ExpandableNotificationRow row) {
row.setAboveShelfChangedListener(mAboveShelfObserver);
row.setSecureStateProvider(mUnlockMethodCache::canSkipBouncer);
@@ -443,7 +452,7 @@
}
@Override
- public void onExpandClicked(Entry clickedEntry, boolean nowExpanded) {
+ public void onExpandClicked(NotificationEntry clickedEntry, boolean nowExpanded) {
mHeadsUpManager.setExpanded(clickedEntry, nowExpanded);
if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD && nowExpanded) {
mShadeController.goToLockedShade(clickedEntry.getRow());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index 53e461d..8b25c34 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -339,7 +339,7 @@
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
NotificationStackScrollLayout stackScrollLayout = getStackScrollLayout();
- if (mService.isDozing() && !stackScrollLayout.hasPulsingNotifications()) {
+ if (mService.isDozing() && !mService.isPulsing()) {
// Capture all touch events in always-on.
return true;
}
@@ -347,8 +347,7 @@
if (mNotificationPanel.isFullyExpanded()
&& stackScrollLayout.getVisibility() == View.VISIBLE
&& mStatusBarStateController.getState() == StatusBarState.KEYGUARD
- && !mService.isBouncerShowing()
- && !mService.isDozing()) {
+ && !mService.isBouncerShowing()) {
intercept = mDragDownHelper.onInterceptTouchEvent(ev);
}
if (!intercept) {
@@ -369,7 +368,7 @@
boolean handled = false;
if (mService.isDozing()) {
mDoubleTapHelper.onTouchEvent(ev);
- handled = true;
+ handled = !mService.isPulsing();
}
if ((mStatusBarStateController.getState() == StatusBarState.KEYGUARD && !handled)
|| mDragDownHelper.isDraggingDown()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index de7ef3b..4299af7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -274,7 +274,6 @@
private void updateClockVisibility() {
boolean visible = shouldBeVisible();
- Dependency.get(IconLogger.class).onIconVisibility("clock", visible);
int visibility = visible ? View.VISIBLE : View.GONE;
super.setVisibility(visibility);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index a02c9d5..fd3f680 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -30,7 +30,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
import com.android.systemui.statusbar.AlertingNotificationManager;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag;
import java.io.FileDescriptor;
@@ -108,11 +108,11 @@
}
}
- protected boolean shouldHeadsUpBecomePinned(@NonNull NotificationData.Entry entry) {
+ protected boolean shouldHeadsUpBecomePinned(@NonNull NotificationEntry entry) {
return hasFullScreenIntent(entry);
}
- protected boolean hasFullScreenIntent(@NonNull NotificationData.Entry entry) {
+ protected boolean hasFullScreenIntent(@NonNull NotificationEntry entry) {
return entry.notification.getNotification().fullScreenIntent != null;
}
@@ -121,7 +121,7 @@
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "setEntryPinned: " + isPinned);
}
- NotificationData.Entry entry = headsUpEntry.mEntry;
+ NotificationEntry entry = headsUpEntry.mEntry;
if (entry.isRowPinned() != isPinned) {
entry.setRowPinned(isPinned);
updatePinnedMode();
@@ -141,7 +141,7 @@
@Override
protected void onAlertEntryAdded(AlertEntry alertEntry) {
- NotificationData.Entry entry = alertEntry.mEntry;
+ NotificationEntry entry = alertEntry.mEntry;
entry.setHeadsUp(true);
setEntryPinned((HeadsUpEntry) alertEntry, shouldHeadsUpBecomePinned(entry));
for (OnHeadsUpChangedListener listener : mListeners) {
@@ -151,7 +151,7 @@
@Override
protected void onAlertEntryRemoved(AlertEntry alertEntry) {
- NotificationData.Entry entry = alertEntry.mEntry;
+ NotificationEntry entry = alertEntry.mEntry;
entry.setHeadsUp(false);
setEntryPinned((HeadsUpEntry) alertEntry, false /* isPinned */);
for (OnHeadsUpChangedListener listener : mListeners) {
@@ -222,7 +222,7 @@
* Returns the top Heads Up Notification, which appears to show at first.
*/
@Nullable
- public NotificationData.Entry getTopEntry() {
+ public NotificationEntry getTopEntry() {
HeadsUpEntry topEntry = getTopHeadsUpEntry();
return (topEntry != null) ? topEntry.mEntry : null;
}
@@ -323,7 +323,7 @@
* @return -1 if the first argument should be ranked higher than the second, 1 if the second
* one should be ranked higher and 0 if they are equal.
*/
- public int compare(@NonNull NotificationData.Entry a, @NonNull NotificationData.Entry b) {
+ public int compare(@NonNull NotificationEntry a, @NonNull NotificationEntry b) {
AlertEntry aEntry = getHeadsUpEntry(a.key);
AlertEntry bEntry = getHeadsUpEntry(b.key);
if (aEntry == null || bEntry == null) {
@@ -336,7 +336,7 @@
* Set an entry to be expanded and therefore stick in the heads up area if it's pinned
* until it's collapsed again.
*/
- public void setExpanded(@NonNull NotificationData.Entry entry, boolean expanded) {
+ public void setExpanded(@NonNull NotificationEntry entry, boolean expanded) {
HeadsUpEntry headsUpEntry = getHeadsUpEntry(entry.key);
if (headsUpEntry != null && entry.isRowPinned()) {
headsUpEntry.setExpanded(expanded);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLogger.java
deleted file mode 100644
index 710e1df..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLogger.java
+++ /dev/null
@@ -1,29 +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.statusbar.policy;
-
-public interface IconLogger {
-
- void onIconShown(String tag);
- void onIconHidden(String tag);
-
- default void onIconVisibility(String tag, boolean visible) {
- if (visible) {
- onIconShown(tag);
- } else {
- onIconHidden(tag);
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLoggerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLoggerImpl.java
deleted file mode 100644
index ba6369e..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLoggerImpl.java
+++ /dev/null
@@ -1,115 +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.statusbar.policy;
-
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_NUM_STATUS_ICONS;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_STATUS_ICONS;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.STATUS_BAR_ICONS_CHANGED;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_ACTION;
-import static com.android.systemui.Dependency.BG_LOOPER_NAME;
-
-import android.content.Context;
-import android.metrics.LogMaker;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.ArraySet;
-
-import androidx.annotation.VisibleForTesting;
-
-import com.android.internal.logging.MetricsLogger;
-
-import java.util.Arrays;
-import java.util.List;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-public class IconLoggerImpl implements IconLogger {
-
- // Minimum ms between log statements.
- // NonFinalForTesting
- @VisibleForTesting
- protected static long MIN_LOG_INTERVAL = 1000;
-
- private final Context mContext;
- private final Handler mHandler;
- private final MetricsLogger mLogger;
- private final ArraySet<String> mIcons = new ArraySet<>();
- private final List<String> mIconIndex;
- private long mLastLog = System.currentTimeMillis();
-
- @Inject
- public IconLoggerImpl(Context context, @Named(BG_LOOPER_NAME) Looper bgLooper,
- MetricsLogger logger) {
- mContext = context;
- mHandler = new Handler(bgLooper);
- mLogger = logger;
- String[] icons = mContext.getResources().getStringArray(
- com.android.internal.R.array.config_statusBarIcons);
- mIconIndex = Arrays.asList(icons);
- doLog();
- }
-
- @Override
- public void onIconShown(String tag) {
- synchronized (mIcons) {
- if (mIcons.contains(tag)) return;
- mIcons.add(tag);
- }
- if (!mHandler.hasCallbacks(mLog)) {
- mHandler.postDelayed(mLog, MIN_LOG_INTERVAL);
- }
- }
-
- @Override
- public void onIconHidden(String tag) {
- synchronized (mIcons) {
- if (!mIcons.contains(tag)) return;
- mIcons.remove(tag);
- }
- if (!mHandler.hasCallbacks(mLog)) {
- mHandler.postDelayed(mLog, MIN_LOG_INTERVAL);
- }
- }
-
- private void doLog() {
- long time = System.currentTimeMillis();
- long timeSinceLastLog = time - mLastLog;
- mLastLog = time;
-
- ArraySet<String> icons;
- synchronized (mIcons) {
- icons = new ArraySet<>(mIcons);
- }
- mLogger.write(new LogMaker(STATUS_BAR_ICONS_CHANGED)
- .setType(TYPE_ACTION)
- .setLatency(timeSinceLastLog)
- .addTaggedData(FIELD_NUM_STATUS_ICONS, icons.size())
- .addTaggedData(FIELD_STATUS_ICONS, getBitField(icons)));
- }
-
- private int getBitField(ArraySet<String> icons) {
- int iconsVisible = 0;
- for (String icon : icons) {
- int index = mIconIndex.indexOf(icon);
- if (index >= 0) {
- iconsVisible |= (1 << index);
- }
- }
- return iconsVisible;
- }
-
- private final Runnable mLog = this::doLog;
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/OnHeadsUpChangedListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/OnHeadsUpChangedListener.java
index 7ad547a..438226a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/OnHeadsUpChangedListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/OnHeadsUpChangedListener.java
@@ -16,8 +16,7 @@
package com.android.systemui.statusbar.policy;
-import com.android.systemui.statusbar.notification.NotificationData;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
/**
* A listener to heads up changes
@@ -33,12 +32,12 @@
/**
* A notification was just pinned to the top.
*/
- default void onHeadsUpPinned(NotificationData.Entry entry) {}
+ default void onHeadsUpPinned(NotificationEntry entry) {}
/**
* A notification was just unpinned from the top.
*/
- default void onHeadsUpUnPinned(NotificationData.Entry entry) {}
+ default void onHeadsUpUnPinned(NotificationEntry entry) {}
/**
* A notification just became a heads up or turned back to its normal state.
@@ -46,5 +45,5 @@
* @param entry the entry of the changed notification
* @param isHeadsUp whether the notification is now a headsUp notification
*/
- default void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {}
+ default void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {}
}
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 866015e..dfb02cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -56,7 +56,7 @@
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.RemoteInputController;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
@@ -83,7 +83,7 @@
private RemoteInputController mController;
private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
- private NotificationData.Entry mEntry;
+ private NotificationEntry mEntry;
private boolean mRemoved;
@@ -180,7 +180,7 @@
}
public static RemoteInputView inflate(Context context, ViewGroup root,
- NotificationData.Entry entry,
+ NotificationEntry entry,
RemoteInputController controller) {
RemoteInputView v = (RemoteInputView)
LayoutInflater.from(context).inflate(R.layout.remote_input, root, false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
index 6193159..0c63e29 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
@@ -18,6 +18,7 @@
import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
+import android.app.RemoteInput;
import android.content.Context;
import android.content.res.Resources;
import android.database.ContentObserver;
@@ -42,14 +43,18 @@
private static final String KEY_REQUIRES_TARGETING_P = "requires_targeting_p";
private static final String KEY_MAX_SQUEEZE_REMEASURE_ATTEMPTS =
"max_squeeze_remeasure_attempts";
+ private static final String KEY_EDIT_CHOICES_BEFORE_SENDING =
+ "edit_choices_before_sending";
private final boolean mDefaultEnabled;
private final boolean mDefaultRequiresP;
private final int mDefaultMaxSqueezeRemeasureAttempts;
+ private final boolean mDefaultEditChoicesBeforeSending;
private boolean mEnabled;
private boolean mRequiresTargetingP;
private int mMaxSqueezeRemeasureAttempts;
+ private boolean mEditChoicesBeforeSending;
private final Context mContext;
private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -66,6 +71,8 @@
R.bool.config_smart_replies_in_notifications_requires_targeting_p);
mDefaultMaxSqueezeRemeasureAttempts = resources.getInteger(
R.integer.config_smart_replies_in_notifications_max_squeeze_remeasure_attempts);
+ mDefaultEditChoicesBeforeSending = resources.getBoolean(
+ R.bool.config_smart_replies_in_notifications_edit_choices_before_sending);
mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS),
@@ -90,6 +97,8 @@
mRequiresTargetingP = mParser.getBoolean(KEY_REQUIRES_TARGETING_P, mDefaultRequiresP);
mMaxSqueezeRemeasureAttempts = mParser.getInt(
KEY_MAX_SQUEEZE_REMEASURE_ATTEMPTS, mDefaultMaxSqueezeRemeasureAttempts);
+ mEditChoicesBeforeSending = mParser.getBoolean(
+ KEY_EDIT_CHOICES_BEFORE_SENDING, mDefaultEditChoicesBeforeSending);
}
}
@@ -113,4 +122,24 @@
public int getMaxSqueezeRemeasureAttempts() {
return mMaxSqueezeRemeasureAttempts;
}
+
+ /**
+ * Returns whether by tapping on a choice should let the user edit the input before it
+ * is sent to the app.
+ *
+ * @param remoteInputEditChoicesBeforeSending The value from
+ * {@link RemoteInput#getEditChoicesBeforeSending()}
+ */
+ public boolean getEffectiveEditChoicesBeforeSending(
+ @RemoteInput.EditChoicesBeforeSending int remoteInputEditChoicesBeforeSending) {
+ switch (remoteInputEditChoicesBeforeSending) {
+ case RemoteInput.EDIT_CHOICES_BEFORE_SENDING_DISABLED:
+ return false;
+ case RemoteInput.EDIT_CHOICES_BEFORE_SENDING_ENABLED:
+ return true;
+ case RemoteInput.EDIT_CHOICES_BEFORE_SENDING_AUTO:
+ default:
+ return mEditChoicesBeforeSending;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
index f85d803..a76cf16 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
@@ -36,8 +36,8 @@
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.SmartReplyController;
-import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
import java.text.BreakIterator;
@@ -194,7 +194,7 @@
*/
public void addRepliesFromRemoteInput(
SmartReplies smartReplies,
- SmartReplyController smartReplyController, NotificationData.Entry entry) {
+ SmartReplyController smartReplyController, NotificationEntry entry) {
if (smartReplies.remoteInput != null && smartReplies.pendingIntent != null) {
if (smartReplies.choices != null) {
for (int i = 0; i < smartReplies.choices.length; ++i) {
@@ -212,7 +212,7 @@
* notification are shown.
*/
public void addSmartActions(SmartActions smartActions,
- SmartReplyController smartReplyController, NotificationData.Entry entry,
+ SmartReplyController smartReplyController, NotificationEntry entry,
HeadsUpManager headsUpManager) {
int numSmartActions = smartActions.actions.size();
for (int n = 0; n < numSmartActions; n++) {
@@ -235,16 +235,15 @@
@VisibleForTesting
Button inflateReplyButton(Context context, ViewGroup root, int replyIndex,
SmartReplies smartReplies, SmartReplyController smartReplyController,
- NotificationData.Entry entry) {
+ NotificationEntry entry) {
Button b = (Button) LayoutInflater.from(context).inflate(
R.layout.smart_reply_button, root, false);
CharSequence choice = smartReplies.choices[replyIndex];
b.setText(choice);
OnDismissAction action = () -> {
- // TODO(b/111437455): Also for EDIT_CHOICES_BEFORE_SENDING_AUTO, depending on flags.
- if (smartReplies.remoteInput.getEditChoicesBeforeSending()
- == RemoteInput.EDIT_CHOICES_BEFORE_SENDING_ENABLED) {
+ if (mConstants.getEffectiveEditChoicesBeforeSending(
+ smartReplies.remoteInput.getEditChoicesBeforeSending())) {
entry.remoteInputText = choice;
mRemoteInputManager.activateRemoteInput(b,
new RemoteInput[] { smartReplies.remoteInput }, smartReplies.remoteInput,
@@ -289,7 +288,7 @@
@VisibleForTesting
Button inflateActionButton(Context context, ViewGroup root, int actionIndex,
SmartActions smartActions, SmartReplyController smartReplyController,
- NotificationData.Entry entry, HeadsUpManager headsUpManager) {
+ NotificationEntry entry, HeadsUpManager headsUpManager) {
Notification.Action action = smartActions.actions.get(actionIndex);
Button button = (Button) LayoutInflater.from(context).inflate(
R.layout.smart_action_button, root, false);
@@ -428,9 +427,9 @@
markButtonsWithPendingSqueezeStatusAs(
LayoutParams.SQUEEZE_STATUS_FAILED, coveredSuggestions);
- // The current button doesn't fit, so there's no point in measuring further
- // buttons.
- break;
+ // The current button doesn't fit, keep on adding lower-priority buttons in case
+ // any of those fit.
+ continue;
}
// The current button fits, so mark all squeezed buttons as "successfully squeezed"
diff --git a/packages/SystemUI/src/com/android/systemui/wallpaper/AodMaskView.java b/packages/SystemUI/src/com/android/systemui/wallpaper/AodMaskView.java
new file mode 100644
index 0000000..52cabe2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpaper/AodMaskView.java
@@ -0,0 +1,222 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.wallpaper;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.RectF;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
+import android.util.AttributeSet;
+import android.util.FeatureFlagUtils;
+import android.util.Log;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.widget.ImageView;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
+import com.android.systemui.statusbar.notification.PropertyAnimator;
+import com.android.systemui.statusbar.notification.stack.AnimationProperties;
+import com.android.systemui.statusbar.phone.ScrimState;
+
+/**
+ * A view that draws mask upon either image wallpaper or music album art in AOD.
+ */
+public class AodMaskView extends ImageView implements StatusBarStateController.StateListener,
+ ImageWallpaperTransformer.TransformationListener {
+ private static final String TAG = AodMaskView.class.getSimpleName();
+ private static final int TRANSITION_DURATION = 1000;
+
+ private static final AnimatableProperty TRANSITION_PROGRESS = AnimatableProperty.from(
+ "transition_progress",
+ AodMaskView::setTransitionAmount,
+ AodMaskView::getTransitionAmount,
+ R.id.aod_mask_transition_progress_tag,
+ R.id.aod_mask_transition_progress_start_tag,
+ R.id.aod_mask_transition_progress_end_tag
+ );
+
+ private final AnimationProperties mTransitionProperties = new AnimationProperties();
+ private final ImageWallpaperTransformer mTransformer;
+ private final RectF mBounds = new RectF();
+ private boolean mChangingStates;
+ private boolean mNeedMask;
+ private float mTransitionAmount;
+ private final WallpaperManager mWallpaperManager;
+ private final DisplayManager mDisplayManager;
+ private DisplayListener mDisplayListener = new DisplayListener() {
+ @Override
+ public void onDisplayAdded(int displayId) {
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ }
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+ // We just support DEFAULT_DISPLAY currently.
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ mTransformer.updateDisplayInfo(getDisplayInfo(displayId));
+ }
+ }
+ };
+
+ public AodMaskView(Context context) {
+ this(context, null);
+ }
+
+ public AodMaskView(Context context, AttributeSet attrs) {
+ this(context, attrs, null);
+ }
+
+ @VisibleForTesting
+ public AodMaskView(Context context, AttributeSet attrs, ImageWallpaperTransformer transformer) {
+ super(context, attrs);
+ setClickable(false);
+
+ StatusBarStateController controller = Dependency.get(StatusBarStateController.class);
+ if (controller != null) {
+ controller.addCallback(this);
+ } else {
+ Log.d(TAG, "Can not get StatusBarStateController!");
+ }
+
+ mDisplayManager = (DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE);
+ mDisplayManager.registerDisplayListener(mDisplayListener, null);
+ mWallpaperManager =
+ (WallpaperManager) getContext().getSystemService(Context.WALLPAPER_SERVICE);
+
+ if (transformer == null) {
+ mTransformer = new ImageWallpaperTransformer(this);
+ mTransformer.addFilter(new ScrimFilter());
+ mTransformer.addFilter(new VignetteFilter());
+ mTransformer.updateOffsets();
+ mTransformer.updateDisplayInfo(getDisplayInfo(Display.DEFAULT_DISPLAY));
+
+ mTransitionProperties.setDuration(TRANSITION_DURATION);
+ mTransitionProperties.setAnimationFinishListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mTransformer.setIsTransiting(false);
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mTransformer.setIsTransiting(true);
+ }
+ });
+ } else {
+ // This part should only be hit by test cases.
+ mTransformer = transformer;
+ }
+ }
+
+ private DisplayInfo getDisplayInfo(int displayId) {
+ DisplayInfo displayInfo = new DisplayInfo();
+ mDisplayManager.getDisplay(displayId).getDisplayInfo(displayInfo);
+ return displayInfo;
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ mBounds.set(0, 0, w, h);
+ mTransformer.updateOffsets();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ if (mNeedMask) {
+ mTransformer.drawTransformedImage(canvas, null /* target */, null /* src */, mBounds);
+ }
+ }
+
+ private boolean checkIfNeedMask() {
+ // We need mask for ImageWallpaper / LockScreen Wallpaper (Music album art).
+ return mWallpaperManager.getWallpaperInfo() == null || ScrimState.AOD.hasBackdrop();
+ }
+
+ @Override
+ public void onStatePreChange(int oldState, int newState) {
+ mChangingStates = oldState != newState;
+ mNeedMask = checkIfNeedMask();
+ }
+
+ @Override
+ public void onStatePostChange() {
+ mChangingStates = false;
+ }
+
+ @Override
+ public void onStateChanged(int newState) {
+ }
+
+ @Override
+ public void onDozingChanged(boolean isDozing) {
+ if (!mNeedMask) {
+ return;
+ }
+
+ boolean enabled = checkFeatureIsEnabled();
+ mTransformer.updateAmbientModeState(enabled && isDozing);
+
+ if (enabled && !mChangingStates) {
+ setAnimatorProperty(isDozing);
+ } else {
+ invalidate();
+ }
+ }
+
+ private boolean checkFeatureIsEnabled() {
+ return FeatureFlagUtils.isEnabled(
+ getContext(), FeatureFlagUtils.AOD_IMAGEWALLPAPER_ENABLED);
+ }
+
+ @VisibleForTesting
+ void setAnimatorProperty(boolean isDozing) {
+ PropertyAnimator.setProperty(
+ this,
+ TRANSITION_PROGRESS,
+ isDozing ? 1f : 0f /* newEndValue */,
+ mTransitionProperties,
+ true /* animated */);
+ }
+
+ @Override
+ public void onTransformationUpdated() {
+ invalidate();
+ }
+
+ private void setTransitionAmount(float amount) {
+ mTransitionAmount = amount;
+ mTransformer.updateTransitionAmount(amount);
+ }
+
+ private float getTransitionAmount() {
+ return mTransitionAmount;
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperFilter.java b/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperFilter.java
new file mode 100644
index 0000000..d457dac
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperFilter.java
@@ -0,0 +1,81 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.wallpaper;
+
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.RectF;
+
+/**
+ * Abstract filter used by static image wallpaper.
+ */
+abstract class ImageWallpaperFilter {
+ protected static final boolean DEBUG = false;
+
+ private ImageWallpaperTransformer mTransformer;
+
+ /**
+ * Apply this filter to the bitmap before drawing on canvas.
+ * @param c The canvas that will draw to.
+ * @param bitmap The bitmap to apply this filter.
+ * @param src The subset of the bitmap to be drawn.
+ * @param dest The rectangle that the bitmap will be scaled/translated to fit into.
+ */
+ public abstract void apply(@NonNull Canvas c, @Nullable Bitmap bitmap,
+ @Nullable Rect src, @NonNull RectF dest);
+
+ /**
+ * Notifies the occurrence of built-in transition of the animation.
+ * @param animator The animator which was animated.
+ */
+ public abstract void onAnimatorUpdate(ValueAnimator animator);
+
+ /**
+ * Notifies the occurrence of another transition of the animation.
+ * @param amount The transition amount.
+ */
+ public abstract void onTransitionAmountUpdate(float amount);
+
+ /**
+ * To set the associated transformer.
+ * @param transformer The transformer that is associated with this filter.
+ */
+ public void setTransformer(ImageWallpaperTransformer transformer) {
+ if (transformer != null) {
+ mTransformer = transformer;
+ }
+ }
+
+ protected ImageWallpaperTransformer getTransformer() {
+ return mTransformer;
+ }
+
+ /**
+ * Notifies the changing of the offset value of the ImageWallpaper.
+ * @param force True to force re-evaluate offsets.
+ * @param xOffset X offset of the ImageWallpaper in percentage.
+ * @param yOffset Y offset of the ImageWallpaper in percentage.
+ */
+ public void onOffsetsUpdate(boolean force, float xOffset, float yOffset) {
+ // No-op
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperTransformer.java b/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperTransformer.java
new file mode 100644
index 0000000..25b0b0a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperTransformer.java
@@ -0,0 +1,173 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.wallpaper;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.view.DisplayInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class is used to manage the filters that will be applied.
+ */
+public class ImageWallpaperTransformer {
+ private static final String TAG = ImageWallpaperTransformer.class.getSimpleName();
+
+ private DisplayInfo mDisplayInfo;
+ private final List<ImageWallpaperFilter> mFilters;
+ private final TransformationListener mListener;
+ private boolean mIsInAmbientMode;
+ private boolean mIsTransiting;
+
+ /**
+ * Constructor.
+ * @param listener A listener to inform you the transformation has updated.
+ */
+ public ImageWallpaperTransformer(TransformationListener listener) {
+ mFilters = new ArrayList<>();
+ mListener = listener;
+ }
+
+ /**
+ * Claim that we want to use the specified filter.
+ * @param filter The filter will be used.
+ */
+ public void addFilter(ImageWallpaperFilter filter) {
+ if (filter != null) {
+ filter.setTransformer(this);
+ mFilters.add(filter);
+ }
+ }
+
+ /**
+ * Check if any transition is running.
+ * @return True if the transition is running, false otherwise.
+ */
+ boolean isTransiting() {
+ return mIsTransiting;
+ }
+
+ /**
+ * Indicate if any transition is running. <br/>
+ * @param isTransiting True if the transition is running.
+ */
+ void setIsTransiting(boolean isTransiting) {
+ mIsTransiting = isTransiting;
+ }
+
+ /**
+ * Check if the device is in ambient mode.
+ * @return True if the device is in ambient mode, false otherwise.
+ */
+ public boolean isInAmbientMode() {
+ return mIsInAmbientMode;
+ }
+
+ /**
+ * Update current state of ambient mode.
+ * @param isInAmbientMode Current ambient mode state.
+ */
+ public void updateAmbientModeState(boolean isInAmbientMode) {
+ mIsInAmbientMode = isInAmbientMode;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ int idx = 0;
+ for (ImageWallpaperFilter filter : mFilters) {
+ sb.append(idx++).append(": ").append(filter.getClass().getSimpleName()).append("\n");
+ }
+ if (sb.length() == 0) {
+ sb.append("No filters applied");
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Set a new display info.
+ * @param displayInfo New display info.
+ */
+ public void updateDisplayInfo(DisplayInfo displayInfo) {
+ mDisplayInfo = displayInfo;
+ }
+
+ /**
+ * To get current display info.
+ * @return Current display info.
+ */
+ public DisplayInfo getDisplayInfo() {
+ return mDisplayInfo;
+ }
+
+ /**
+ * Update the offsets with default value.
+ */
+ public void updateOffsets() {
+ this.updateOffsets(true, 0f, .5f);
+ }
+
+ /**
+ * To notify the filters that the offset of the ImageWallpaper changes.
+ * @param force True to force re-evaluate offsets.
+ * @param offsetX X offset of the ImageWallpaper in percentage.
+ * @param offsetY Y offset of the ImageWallpaper in percentage.
+ */
+ public void updateOffsets(boolean force, float offsetX, float offsetY) {
+ mFilters.forEach(filter -> filter.onOffsetsUpdate(force, offsetX, offsetY));
+ }
+
+ /**
+ * Apply all specified filters to the bitmap then draw to the canvas.
+ * @param c The canvas that will draw to.
+ * @param target The bitmap to apply filters.
+ * @param src The subset of the bitmap to be drawn
+ * @param dest The rectangle that the bitmap will be scaled/translated to fit into.
+ */
+ void drawTransformedImage(@NonNull Canvas c, @Nullable Bitmap target,
+ @Nullable Rect src, @NonNull RectF dest) {
+ mFilters.forEach(filter -> filter.apply(c, target, src, dest));
+ }
+
+ /**
+ * Update the transition amount. <br/>
+ * Must invoke this to update transition amount if not running built-in transition.
+ * @param amount The transition amount.
+ */
+ void updateTransitionAmount(float amount) {
+ mFilters.forEach(filter -> filter.onTransitionAmountUpdate(amount));
+ if (mListener != null) {
+ mListener.onTransformationUpdated();
+ }
+ }
+
+ /**
+ * An interface that informs the transformation status.
+ */
+ public interface TransformationListener {
+ /**
+ * Notifies the update of the transformation.
+ */
+ void onTransformationUpdated();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpaper/ScrimFilter.java b/packages/SystemUI/src/com/android/systemui/wallpaper/ScrimFilter.java
new file mode 100644
index 0000000..637e48e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpaper/ScrimFilter.java
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.wallpaper;
+
+import android.animation.ValueAnimator;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+
+/**
+ * A filter that implements 70% black scrim effect.
+ */
+public class ScrimFilter extends ImageWallpaperFilter {
+ private static final int MAX_ALPHA = (int) (255 * .7f);
+ private static final int MIN_ALPHA = 0;
+
+ private final Paint mPaint;
+
+ public ScrimFilter() {
+ mPaint = new Paint();
+ mPaint.setColor(Color.BLACK);
+ mPaint.setAlpha(MAX_ALPHA);
+ }
+
+ @Override
+ public void apply(Canvas c, Bitmap bitmap, Rect src, RectF dest) {
+ ImageWallpaperTransformer transformer = getTransformer();
+
+ // If it is not in the transition, we need to set the property according to aod state.
+ if (!transformer.isTransiting()) {
+ mPaint.setAlpha(transformer.isInAmbientMode() ? MAX_ALPHA : MIN_ALPHA);
+ }
+
+ c.drawRect(dest, mPaint);
+ }
+
+ @Override
+ public void onAnimatorUpdate(ValueAnimator animator) {
+ ImageWallpaperTransformer transformer = getTransformer();
+ float fraction = animator.getAnimatedFraction();
+ float factor = transformer.isInAmbientMode() ? fraction : 1f - fraction;
+ mPaint.setAlpha((int) (factor * MAX_ALPHA));
+ }
+
+ @Override
+ public void onTransitionAmountUpdate(float amount) {
+ mPaint.setAlpha((int) (amount * MAX_ALPHA));
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpaper/VignetteFilter.java b/packages/SystemUI/src/com/android/systemui/wallpaper/VignetteFilter.java
new file mode 100644
index 0000000..ad0b98b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpaper/VignetteFilter.java
@@ -0,0 +1,124 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.wallpaper;
+
+import android.animation.ValueAnimator;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PointF;
+import android.graphics.RadialGradient;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import android.util.Log;
+import android.view.DisplayInfo;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * A filter that implements vignette effect.
+ */
+public class VignetteFilter extends ImageWallpaperFilter {
+ private static final String TAG = VignetteFilter.class.getSimpleName();
+ private static final int MAX_ALPHA = 255;
+ private static final int MIN_ALPHA = 0;
+
+ private final Paint mPaint;
+ private final Matrix mMatrix;
+ private final Shader mShader;
+
+ private float mXOffset;
+ private float mYOffset;
+ private float mCenterX;
+ private float mCenterY;
+ private float mStretchX;
+ private float mStretchY;
+ private boolean mCalculateOffsetNeeded;
+
+ public VignetteFilter() {
+ mPaint = new Paint();
+ mMatrix = new Matrix();
+ mShader = new RadialGradient(0, 0, 1,
+ Color.TRANSPARENT, Color.BLACK, Shader.TileMode.CLAMP);
+ }
+
+ @Override
+ public void apply(Canvas c, Bitmap bitmap, Rect src, RectF dest) {
+ DisplayInfo info = getTransformer().getDisplayInfo();
+
+ if (mCalculateOffsetNeeded) {
+ int lw = info.logicalWidth;
+ int lh = info.logicalHeight;
+ mCenterX = lw / 2 + (dest.width() - lw) * mXOffset;
+ mCenterY = lh / 2 + (dest.height() - lh) * mYOffset;
+ mStretchX = info.logicalWidth / 2;
+ mStretchY = info.logicalHeight / 2;
+ mCalculateOffsetNeeded = false;
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "apply: lw=" + info.logicalWidth + ", lh=" + info.logicalHeight
+ + ", center=(" + mCenterX + "," + mCenterY + ")"
+ + ", stretch=(" + mStretchX + "," + mStretchY + ")");
+ }
+
+ mMatrix.reset();
+ mMatrix.postTranslate(mCenterX, mCenterY);
+ mMatrix.postScale(mStretchX, mStretchY, mCenterX, mCenterY);
+ mShader.setLocalMatrix(mMatrix);
+ mPaint.setShader(mShader);
+
+ ImageWallpaperTransformer transformer = getTransformer();
+
+ // If it is not in the transition, we need to set the property according to aod state.
+ if (!transformer.isTransiting()) {
+ mPaint.setAlpha(transformer.isInAmbientMode() ? MAX_ALPHA : MIN_ALPHA);
+ }
+
+ c.drawRect(dest, mPaint);
+ }
+
+ @Override
+ public void onAnimatorUpdate(ValueAnimator animator) {
+ ImageWallpaperTransformer transformer = getTransformer();
+ float fraction = animator.getAnimatedFraction();
+ float factor = transformer.isInAmbientMode() ? fraction : 1f - fraction;
+ mPaint.setAlpha((int) (factor * MAX_ALPHA));
+ }
+
+ @Override
+ public void onTransitionAmountUpdate(float amount) {
+ mPaint.setAlpha((int) (amount * MAX_ALPHA));
+ }
+
+ @Override
+ public void onOffsetsUpdate(boolean force, float xOffset, float yOffset) {
+ if (force || mXOffset != xOffset || mYOffset != yOffset) {
+ mXOffset = xOffset;
+ mYOffset = yOffset;
+ mCalculateOffsetNeeded = true;
+ }
+ }
+
+ @VisibleForTesting
+ public PointF getCenterPoint() {
+ return new PointF(mCenterX, mCenterY);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index 4150602..fbc1c20 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -21,7 +21,6 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -35,22 +34,25 @@
import android.testing.TestableLooper.RunWithLooper;
import android.text.TextPaint;
import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TextClock;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.ClockPlugin;
-import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.StatusBarStateController;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.function.Consumer;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
// Need to run on the main thread because KeyguardSliceView$Row init checks for
@@ -58,8 +60,8 @@
// the keyguard_clcok_switch layout is inflated.
@RunWithLooper(setAsMainLooper = true)
public class KeyguardClockSwitchTest extends SysuiTestCase {
- private PluginManager mPluginManager;
private FrameLayout mClockContainer;
+ private StatusBarStateController.StateListener mStateListener;
@Mock
TextClock mClockView;
@@ -68,29 +70,13 @@
@Before
public void setUp() {
- mPluginManager = mDependency.injectMockDependency(PluginManager.class);
LayoutInflater layoutInflater = LayoutInflater.from(getContext());
mKeyguardClockSwitch =
(KeyguardClockSwitch) layoutInflater.inflate(R.layout.keyguard_clock_switch, null);
mClockContainer = mKeyguardClockSwitch.findViewById(R.id.clock_view);
MockitoAnnotations.initMocks(this);
when(mClockView.getPaint()).thenReturn(mock(TextPaint.class));
- }
-
- @Test
- public void onAttachToWindow_addPluginListener() {
- mKeyguardClockSwitch.onAttachedToWindow();
-
- ArgumentCaptor<PluginListener> listener = ArgumentCaptor.forClass(PluginListener.class);
- verify(mPluginManager).addPluginListener(listener.capture(), eq(ClockPlugin.class));
- }
-
- @Test
- public void onDetachToWindow_removePluginListener() {
- mKeyguardClockSwitch.onDetachedFromWindow();
-
- ArgumentCaptor<PluginListener> listener = ArgumentCaptor.forClass(PluginListener.class);
- verify(mPluginManager).removePluginListener(listener.capture());
+ mStateListener = mKeyguardClockSwitch.getStateListener();
}
@Test
@@ -98,9 +84,8 @@
ClockPlugin plugin = mock(ClockPlugin.class);
TextClock pluginView = new TextClock(getContext());
when(plugin.getView()).thenReturn(pluginView);
- PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
- listener.onPluginConnected(plugin, null);
+ mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
verify(mClockView).setVisibility(GONE);
assertThat(plugin.getView().getParent()).isEqualTo(mClockContainer);
@@ -116,9 +101,8 @@
ClockPlugin plugin = mock(ClockPlugin.class);
TextClock pluginView = new TextClock(getContext());
when(plugin.getBigClockView()).thenReturn(pluginView);
- PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
// WHEN the plugin is connected
- listener.onPluginConnected(plugin, null);
+ mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
// THEN the big clock container is visible and it is the parent of the
// big clock view.
assertThat(bigClockContainer.getVisibility()).isEqualTo(VISIBLE);
@@ -128,8 +112,7 @@
@Test
public void onPluginConnected_nullView() {
ClockPlugin plugin = mock(ClockPlugin.class);
- PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
- listener.onPluginConnected(plugin, null);
+ mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
verify(mClockView, never()).setVisibility(GONE);
}
@@ -138,27 +121,36 @@
// GIVEN a plugin has already connected
ClockPlugin plugin1 = mock(ClockPlugin.class);
when(plugin1.getView()).thenReturn(new TextClock(getContext()));
- PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
- listener.onPluginConnected(plugin1, null);
+ mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin1);
// WHEN a second plugin is connected
ClockPlugin plugin2 = mock(ClockPlugin.class);
when(plugin2.getView()).thenReturn(new TextClock(getContext()));
- listener.onPluginConnected(plugin2, null);
+ mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin2);
// THEN only the view from the second plugin should be a child of KeyguardClockSwitch.
assertThat(plugin2.getView().getParent()).isEqualTo(mClockContainer);
assertThat(plugin1.getView().getParent()).isNull();
}
@Test
+ public void onPluginConnected_darkAmountInitialized() {
+ // GIVEN that the dark amount has already been set
+ mKeyguardClockSwitch.setDarkAmount(0.5f);
+ // WHEN a plugin is connected
+ ClockPlugin plugin = mock(ClockPlugin.class);
+ mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
+ // THEN dark amount should be initalized on the plugin.
+ verify(plugin).setDarkAmount(0.5f);
+ }
+
+ @Test
public void onPluginDisconnected_showDefaultClock() {
ClockPlugin plugin = mock(ClockPlugin.class);
TextClock pluginView = new TextClock(getContext());
when(plugin.getView()).thenReturn(pluginView);
mClockView.setVisibility(GONE);
- PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
- listener.onPluginConnected(plugin, null);
- listener.onPluginDisconnected(plugin);
+ mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
+ mKeyguardClockSwitch.getClockPluginConsumer().accept(null);
verify(mClockView).setVisibility(VISIBLE);
assertThat(plugin.getView().getParent()).isNull();
@@ -174,10 +166,9 @@
ClockPlugin plugin = mock(ClockPlugin.class);
TextClock pluginView = new TextClock(getContext());
when(plugin.getBigClockView()).thenReturn(pluginView);
- PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
- listener.onPluginConnected(plugin, null);
- // WHEN the plugin is disconnected
- listener.onPluginDisconnected(plugin);
+ // WHEN the plugin is connected and then disconnected
+ mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
+ mKeyguardClockSwitch.getClockPluginConsumer().accept(null);
// THEN the big lock container is GONE and the big clock view doesn't have
// a parent.
assertThat(bigClockContainer.getVisibility()).isEqualTo(GONE);
@@ -187,41 +178,23 @@
@Test
public void onPluginDisconnected_nullView() {
ClockPlugin plugin = mock(ClockPlugin.class);
- PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
- listener.onPluginConnected(plugin, null);
- listener.onPluginDisconnected(plugin);
+ mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
+ mKeyguardClockSwitch.getClockPluginConsumer().accept(null);
verify(mClockView, never()).setVisibility(GONE);
}
@Test
- public void onPluginDisconnected_firstOfTwoDisconnected() {
- // GIVEN two plugins are connected
- ClockPlugin plugin1 = mock(ClockPlugin.class);
- when(plugin1.getView()).thenReturn(new TextClock(getContext()));
- PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
- listener.onPluginConnected(plugin1, null);
- ClockPlugin plugin2 = mock(ClockPlugin.class);
- when(plugin2.getView()).thenReturn(new TextClock(getContext()));
- listener.onPluginConnected(plugin2, null);
- // WHEN the first plugin is disconnected
- listener.onPluginDisconnected(plugin1);
- // THEN the view from the second plugin is still a child of KeyguardClockSwitch.
- assertThat(plugin2.getView().getParent()).isEqualTo(mClockContainer);
- assertThat(plugin1.getView().getParent()).isNull();
- }
-
- @Test
public void onPluginDisconnected_secondOfTwoDisconnected() {
// GIVEN two plugins are connected
ClockPlugin plugin1 = mock(ClockPlugin.class);
when(plugin1.getView()).thenReturn(new TextClock(getContext()));
- PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
- listener.onPluginConnected(plugin1, null);
+ Consumer<ClockPlugin> consumer = mKeyguardClockSwitch.getClockPluginConsumer();
+ consumer.accept(plugin1);
ClockPlugin plugin2 = mock(ClockPlugin.class);
when(plugin2.getView()).thenReturn(new TextClock(getContext()));
- listener.onPluginConnected(plugin2, null);
+ consumer.accept(plugin2);
// WHEN the second plugin is disconnected
- listener.onPluginDisconnected(plugin2);
+ consumer.accept(null);
// THEN the default clock should be shown.
verify(mClockView).setVisibility(VISIBLE);
assertThat(plugin1.getView().getParent()).isNull();
@@ -240,8 +213,7 @@
ClockPlugin plugin = mock(ClockPlugin.class);
TextClock pluginView = new TextClock(getContext());
when(plugin.getView()).thenReturn(pluginView);
- PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
- listener.onPluginConnected(plugin, null);
+ mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
mKeyguardClockSwitch.setTextColor(Color.WHITE);
@@ -265,11 +237,34 @@
TextClock pluginView = new TextClock(getContext());
when(plugin.getView()).thenReturn(pluginView);
Style style = mock(Style.class);
- PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
- listener.onPluginConnected(plugin, null);
+ mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
mKeyguardClockSwitch.setStyle(style);
verify(plugin).setStyle(style);
}
+
+ @Test
+ public void onStateChanged_InvisibleInShade() {
+ // GIVEN that the big clock container is visible
+ ViewGroup container = mock(ViewGroup.class);
+ when(container.getVisibility()).thenReturn(View.VISIBLE);
+ mKeyguardClockSwitch.setBigClockContainer(container);
+ // WHEN transitioned to SHADE state
+ mStateListener.onStateChanged(StatusBarState.SHADE);
+ // THEN the container is invisible.
+ verify(container).setVisibility(View.INVISIBLE);
+ }
+
+ @Test
+ public void onStateChanged_VisibleInKeyguard() {
+ // GIVEN that the big clock container is invisible
+ ViewGroup container = mock(ViewGroup.class);
+ when(container.getVisibility()).thenReturn(View.INVISIBLE);
+ mKeyguardClockSwitch.setBigClockContainer(container);
+ // WHEN transitioned to KEYGUARD state
+ mStateListener.onStateChanged(StatusBarState.KEYGUARD);
+ // THEN the container is visible.
+ verify(container).setVisibility(View.VISIBLE);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
index 199a3a6..31e8071 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
@@ -36,9 +36,9 @@
import android.widget.RemoteViews;
import com.android.internal.messages.nano.SystemMessageProto;
-import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import org.junit.Before;
import org.junit.Test;
@@ -391,19 +391,19 @@
}
private void entryRemoved(StatusBarNotification notification) {
- mEntryListener.onEntryRemoved(new NotificationData.Entry(notification),
- null, null, null, false, false);
+ mEntryListener.onEntryRemoved(new NotificationEntry(notification),
+ null, false);
}
private void entryAdded(StatusBarNotification notification, int importance) {
- NotificationData.Entry entry = new NotificationData.Entry(notification);
+ NotificationEntry entry = new NotificationEntry(notification);
entry.importance = importance;
mEntryListener.onPendingEntryAdded(entry);
}
private void entryUpdated(StatusBarNotification notification, int importance) {
- NotificationData.Entry entry = new NotificationData.Entry(notification);
+ NotificationEntry entry = new NotificationEntry(notification);
entry.importance = importance;
- mEntryListener.onEntryUpdated(entry);
+ mEntryListener.onPostEntryUpdated(entry);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
index 521d5d1..53ad0b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
@@ -16,17 +16,12 @@
package com.android.systemui;
-
-import static org.junit.Assert.assertNotNull;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.graphics.Bitmap;
@@ -58,13 +53,15 @@
@Mock private SurfaceHolder mSurfaceHolder;
@Mock private DisplayInfo mDisplayInfo;
- CountDownLatch mEventCountdown;
+ private CountDownLatch mEventCountdown;
+ private CountDownLatch mAmbientEventCountdown;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mEventCountdown = new CountDownLatch(1);
+ mAmbientEventCountdown = new CountDownLatch(2);
mImageWallpaper = new ImageWallpaper() {
@Override
@@ -86,6 +83,11 @@
assertTrue("mFixedSizeAllowed should be true", allowed);
mEventCountdown.countDown();
}
+
+ @Override
+ public void onAmbientModeChanged(boolean inAmbientMode, long duration) {
+ mAmbientEventCountdown.countDown();
+ }
};
}
};
@@ -132,4 +134,23 @@
verify(mSurfaceHolder, times(1)).setFixedSize(ImageWallpaper.DrawableEngine.MIN_BACKGROUND_WIDTH, ImageWallpaper.DrawableEngine.MIN_BACKGROUND_HEIGHT);
}
+ @Test
+ public void testDeliversAmbientModeChanged() {
+ ImageWallpaper.DrawableEngine wallpaperEngine =
+ (ImageWallpaper.DrawableEngine) mImageWallpaper.onCreateEngine();
+
+ assertEquals("setFixedSizeAllowed should have been called.",
+ 0, mEventCountdown.getCount());
+
+ wallpaperEngine.setCreated(true);
+ wallpaperEngine.doAmbientModeChanged(false, 1000);
+ assertFalse("ambient mode should be false", wallpaperEngine.isInAmbientMode());
+ assertEquals("onAmbientModeChanged should have been called.",
+ 1, mAmbientEventCountdown.getCount());
+
+ wallpaperEngine.doAmbientModeChanged(true, 1000);
+ assertTrue("ambient mode should be true", wallpaperEngine.isInAmbientMode());
+ assertEquals("onAmbientModeChanged should have been called.",
+ 0, mAmbientEventCountdown.getCount());
+ }
}
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 8f2b2d0..21d3652 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -18,6 +18,10 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.IActivityManager;
import android.content.Context;
@@ -29,6 +33,9 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.NotificationTestHelper;
+import com.android.systemui.statusbar.notification.NotificationEntryListener;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationData;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.StatusBarWindowController;
@@ -36,6 +43,8 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -45,6 +54,8 @@
public class BubbleControllerTest extends SysuiTestCase {
@Mock
+ private NotificationEntryManager mNotificationEntryManager;
+ @Mock
private WindowManager mWindowManager;
@Mock
private IActivityManager mActivityManager;
@@ -52,17 +63,23 @@
private DozeParameters mDozeParameters;
@Mock
private FrameLayout mStatusBarView;
+ @Captor
+ private ArgumentCaptor<NotificationEntryListener> mEntryListenerCaptor;
private TestableBubbleController mBubbleController;
private StatusBarWindowController mStatusBarWindowController;
+ private NotificationEntryListener mEntryListener;
private NotificationTestHelper mNotificationTestHelper;
private ExpandableNotificationRow mRow;
private ExpandableNotificationRow mRow2;
+ private final NotificationData mNotificationData = new NotificationData();
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ mDependency.injectTestDependency(NotificationEntryManager.class, mNotificationEntryManager);
// Bubbles get added to status bar window view
mStatusBarWindowController = new StatusBarWindowController(mContext, mWindowManager,
@@ -74,7 +91,15 @@
mRow = mNotificationTestHelper.createBubble();
mRow2 = mNotificationTestHelper.createBubble();
+ // Return non-null notification data from the NEM
+ when(mNotificationEntryManager.getNotificationData()).thenReturn(mNotificationData);
+
mBubbleController = new TestableBubbleController(mContext, mStatusBarWindowController);
+
+ // Get a reference to the BubbleController's entry listener
+ verify(mNotificationEntryManager, atLeastOnce())
+ .addNotificationEntryListener(mEntryListenerCaptor.capture());
+ mEntryListener = mEntryListenerCaptor.getValue();
}
@Test
@@ -102,6 +127,8 @@
mBubbleController.removeBubble(mRow.getEntry().key);
assertFalse(mStatusBarWindowController.getBubblesShowing());
+ assertTrue(mRow.getEntry().isBubbleDismissed());
+ verify(mNotificationEntryManager).updateNotifications();
}
@Test
@@ -112,6 +139,7 @@
mBubbleController.dismissStack();
assertFalse(mStatusBarWindowController.getBubblesShowing());
+ verify(mNotificationEntryManager, times(3)).updateNotifications();
}
@Test
@@ -140,6 +168,12 @@
assertFalse(mBubbleController.isStackExpanded());
}
+ @Test
+ public void testMarkNewNotificationAsBubble() {
+ mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ assertTrue(mRow.getEntry().isBubble());
+ }
+
static class TestableBubbleController extends BubbleController {
TestableBubbleController(Context context,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
index a8ff0b2..cfc19ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
@@ -21,6 +21,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -44,6 +45,7 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.StatusBarStateController;
import org.junit.Assert;
import org.junit.Before;
@@ -67,6 +69,8 @@
private AlarmManager mAlarmManager;
@Mock
private NotificationMediaManager mNotificationMediaManager;
+ @Mock
+ private StatusBarStateController mStatusBarStateController;
private TestableKeyguardSliceProvider mProvider;
private boolean mIsZenMode;
@@ -76,7 +80,7 @@
mIsZenMode = false;
mProvider = new TestableKeyguardSliceProvider();
mProvider.attachInfo(getContext(), null);
- mProvider.initDependencies();
+ mProvider.initDependencies(mNotificationMediaManager, mStatusBarStateController);
SliceProvider.setSpecs(new HashSet<>(Arrays.asList(SliceSpecs.LIST)));
}
@@ -98,6 +102,7 @@
@Test
public void onBindSlice_readsMedia() {
MediaMetadata metadata = mock(MediaMetadata.class);
+ mProvider.onDozingChanged(true);
mProvider.onMetadataChanged(metadata);
mProvider.onBindSlice(mProvider.getUri());
verify(metadata).getText(eq(MediaMetadata.METADATA_KEY_TITLE));
@@ -162,7 +167,31 @@
@Test
public void onMetadataChanged_updatesSlice() {
mProvider.onMetadataChanged(mock(MediaMetadata.class));
+ mProvider.onDozingChanged(true);
verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null));
+
+ // Hides after waking up
+ reset(mContentResolver);
+ mProvider.onDozingChanged(false);
+ verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null));
+
+ // And won't update slice if device is awake
+ reset(mContentResolver);
+ mProvider.onMetadataChanged(mock(MediaMetadata.class));
+ verify(mContentResolver, never()).notifyChange(eq(mProvider.getUri()), eq(null));
+ }
+
+ @Test
+ public void onDozingChanged_updatesSliceIfMedia() {
+ // Show media when dozing
+ mProvider.onMetadataChanged(mock(MediaMetadata.class));
+ mProvider.onDozingChanged(true);
+ verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null));
+
+ // Do not notify again if nothing changed
+ reset(mContentResolver);
+ mProvider.onDozingChanged(true);
+ verify(mContentResolver, never()).notifyChange(eq(mProvider.getUri()), eq(null));
}
private class TestableKeyguardSliceProvider extends KeyguardSliceProvider {
@@ -201,11 +230,6 @@
protected String getFormattedDateLocked() {
return super.getFormattedDateLocked() + mCounter++;
}
-
- @Override
- public void initDependencies() {
- mMediaManager = mNotificationMediaManager;
- }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
index 9d0556f..f8957b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
@@ -36,7 +36,7 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -67,7 +67,7 @@
private AlertingNotificationManager mAlertingNotificationManager;
- protected NotificationData.Entry mEntry;
+ protected NotificationEntry mEntry;
protected Handler mTestHandler;
private StatusBarNotification mSbn;
protected boolean mTimedOut = false;
@@ -119,7 +119,7 @@
public void setUp() {
mTestHandler = Handler.createAsync(Looper.myLooper());
mSbn = createNewNotification(0 /* id */);
- mEntry = new NotificationData.Entry(mSbn);
+ mEntry = new NotificationEntry(mSbn);
mEntry.setRow(mRow);
mAlertingNotificationManager = createAlertingNotificationManager();
@@ -170,7 +170,7 @@
public void testReleaseAllImmediately() {
for (int i = 0; i < TEST_NUM_NOTIFICATIONS; i++) {
StatusBarNotification sbn = createNewNotification(i);
- NotificationData.Entry entry = new NotificationData.Entry(sbn);
+ NotificationEntry entry = new NotificationEntry(sbn);
entry.setRow(mRow);
mAlertingNotificationManager.showNotification(entry);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
index 65c04fe..c880172 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
@@ -31,8 +31,9 @@
import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import org.junit.Before;
import org.junit.Test;
@@ -85,7 +86,7 @@
@Test
public void testNotificationUpdateCallsUpdateNotification() {
- when(mNotificationData.get(mSbn.getKey())).thenReturn(new NotificationData.Entry(mSbn));
+ when(mNotificationData.get(mSbn.getKey())).thenReturn(new NotificationEntry(mSbn));
mListener.onNotificationPosted(mSbn, mRanking);
TestableLooper.get(this).processAllMessages();
verify(mEntryManager).updateNotification(mSbn, mRanking);
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 f168a49..d7ca5b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar;
-import static android.content.Intent.ACTION_DEVICE_LOCKED_CHANGED;
import static android.content.Intent.ACTION_USER_SWITCHED;
import static junit.framework.Assert.assertFalse;
@@ -42,8 +41,8 @@
import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationData;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
index 150dcb9..c159516 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
@@ -24,8 +24,8 @@
import com.android.systemui.statusbar.NotificationRemoteInputManager.RemoteInputActiveExtender;
import com.android.systemui.statusbar.NotificationRemoteInputManager.RemoteInputHistoryExtender;
import com.android.systemui.statusbar.NotificationRemoteInputManager.SmartReplyHistoryExtender;
-import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.phone.ShadeController;
@@ -60,7 +60,7 @@
private TestableNotificationRemoteInputManager mRemoteInputManager;
private StatusBarNotification mSbn;
- private NotificationData.Entry mEntry;
+ private NotificationEntry mEntry;
private RemoteInputHistoryExtender mRemoteInputHistoryExtender;
private SmartReplyHistoryExtender mSmartReplyHistoryExtender;
private RemoteInputActiveExtender mRemoteInputActiveExtender;
@@ -75,7 +75,7 @@
Handler.createAsync(Looper.myLooper()));
mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID,
0, new Notification(), UserHandle.CURRENT, null, 0);
- mEntry = new NotificationData.Entry(mSbn);
+ mEntry = new NotificationEntry(mSbn);
mEntry.setRow(mRow);
mRemoteInputManager.setUpWithPresenterForTest(mCallback,
@@ -170,7 +170,7 @@
// Setup a notification entry with 1 remote input.
StatusBarNotification newSbn =
mRemoteInputManager.rebuildNotificationWithRemoteInput(mEntry, "A Reply", false);
- NotificationData.Entry entry = new NotificationData.Entry(newSbn);
+ NotificationEntry entry = new NotificationEntry(newSbn);
// Try rebuilding to add another reply.
newSbn = mRemoteInputManager.rebuildNotificationWithRemoteInput(entry, "Reply 2", true);
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 fb5e875..529da82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
@@ -32,7 +32,7 @@
import android.widget.RemoteViews;
import com.android.systemui.R;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag;
import com.android.systemui.statusbar.notification.row.NotificationInflaterTest;
@@ -238,7 +238,7 @@
mUser,
null /* overrideGroupKey */,
System.currentTimeMillis());
- NotificationData.Entry entry = new NotificationData.Entry(sbn);
+ NotificationEntry entry = new NotificationEntry(sbn);
entry.setRow(row);
entry.createIcons(mContext, sbn);
entry.channel = new NotificationChannel(
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 8cf4b05..bf91305 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -38,10 +38,10 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
-import com.android.systemui.statusbar.notification.NotificationData;
-import com.android.systemui.statusbar.notification.NotificationData.Entry;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
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.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -77,7 +77,7 @@
@Mock private ShadeController mShadeController;
private NotificationViewHierarchyManager mViewHierarchyManager;
- private NotificationTestHelper mHelper = new NotificationTestHelper(mContext);
+ private NotificationTestHelper mHelper;
@Before
public void setUp() {
@@ -90,6 +90,8 @@
mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
mDependency.injectTestDependency(ShadeController.class, mShadeController);
+ mHelper = new NotificationTestHelper(mContext);
+
when(mEntryManager.getNotificationData()).thenReturn(mNotificationData);
mViewHierarchyManager = new NotificationViewHierarchyManager(mContext,
@@ -100,9 +102,9 @@
mViewHierarchyManager.setUpWithPresenter(mPresenter, mListContainer);
}
- private NotificationData.Entry createEntry() throws Exception {
+ private NotificationEntry createEntry() throws Exception {
ExpandableNotificationRow row = mHelper.createRow();
- NotificationData.Entry entry = new NotificationData.Entry(row.getStatusBarNotification());
+ NotificationEntry entry = new NotificationEntry(row.getStatusBarNotification());
entry.setRow(row);
return entry;
}
@@ -111,9 +113,9 @@
public void testNotificationsBecomingBundled() throws Exception {
// Tests 3 top level notifications becoming a single bundled notification with |entry0| as
// the summary.
- NotificationData.Entry entry0 = createEntry();
- NotificationData.Entry entry1 = createEntry();
- NotificationData.Entry entry2 = createEntry();
+ NotificationEntry entry0 = createEntry();
+ NotificationEntry entry1 = createEntry();
+ NotificationEntry entry2 = createEntry();
// Set up the prior state to look like three top level notifications.
mListContainer.addContainerView(entry0.getRow());
@@ -140,9 +142,9 @@
@Test
public void testNotificationsBecomingUnbundled() throws Exception {
// Tests a bundled notification becoming three top level notifications.
- NotificationData.Entry entry0 = createEntry();
- NotificationData.Entry entry1 = createEntry();
- NotificationData.Entry entry2 = createEntry();
+ NotificationEntry entry0 = createEntry();
+ NotificationEntry entry1 = createEntry();
+ NotificationEntry entry2 = createEntry();
entry0.getRow().addChildNotification(entry1.getRow());
entry0.getRow().addChildNotification(entry2.getRow());
@@ -171,8 +173,8 @@
@Test
public void testNotificationsBecomingSuppressed() throws Exception {
// Tests two top level notifications becoming a suppressed summary and a child.
- NotificationData.Entry entry0 = createEntry();
- NotificationData.Entry entry1 = createEntry();
+ NotificationEntry entry0 = createEntry();
+ NotificationEntry entry1 = createEntry();
entry0.getRow().addChildNotification(entry1.getRow());
// Set up the prior state to look like a top level notification.
@@ -197,7 +199,7 @@
@Test
public void testUpdateNotificationViews_appOps() throws Exception {
- NotificationData.Entry entry0 = createEntry();
+ NotificationEntry entry0 = createEntry();
entry0.setRow(spy(entry0.getRow()));
when(mNotificationData.getActiveNotifications()).thenReturn(
Lists.newArrayList(entry0));
@@ -262,10 +264,7 @@
public void setMaxDisplayedNotifications(int maxNotifications) {}
@Override
- public void snapViewIfNeeded(Entry entry) {}
-
- @Override
- public ViewGroup getViewParentForNotification(NotificationData.Entry entry) {
+ public ViewGroup getViewParentForNotification(NotificationEntry entry) {
return null;
}
@@ -281,10 +280,10 @@
}
@Override
- public void cleanUpViewStateForEntry(Entry entry) { }
+ public void cleanUpViewStateForEntry(NotificationEntry entry) { }
@Override
- public boolean isInVisibleLocation(Entry entry) {
+ public boolean isInVisibleLocation(NotificationEntry entry) {
return true;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
index e7a1f05..6cca434 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
@@ -36,8 +36,8 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.phone.ShadeController;
import org.junit.Before;
@@ -58,7 +58,7 @@
private static final int TEST_ACTION_COUNT = 3;
private Notification mNotification;
- private NotificationData.Entry mEntry;
+ private NotificationEntry mEntry;
private SmartReplyController mSmartReplyController;
private NotificationRemoteInputManager mRemoteInputManager;
@@ -92,7 +92,7 @@
mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID,
0, mNotification, new UserHandle(ActivityManager.getCurrentUser()), null, 0);
- mEntry = new NotificationData.Entry(mSbn);
+ mEntry = new NotificationEntry(mSbn);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index 57c9d29..d937f93 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -26,6 +26,7 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -65,7 +66,9 @@
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.StatusBarIconView;
-import com.android.systemui.statusbar.notification.NotificationData.KeyguardEnvironment;
+import com.android.systemui.statusbar.notification.collection.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationData.KeyguardEnvironment;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.row.NotificationInflater;
@@ -82,6 +85,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -121,8 +125,9 @@
@Mock private MetricsLogger mMetricsLogger;
@Mock private SmartReplyController mSmartReplyController;
@Mock private RowInflaterTask mAsyncInflationTask;
+ @Mock private NotificationRowBinder mMockedRowBinder;
- private NotificationData.Entry mEntry;
+ private NotificationEntry mEntry;
private StatusBarNotification mSbn;
private TestableNotificationEntryManager mEntryManager;
private CountDownLatch mCountDownLatch;
@@ -135,8 +140,12 @@
mCountDownLatch = new CountDownLatch(1);
}
+ public void setNotificationData(NotificationData data) {
+ mNotificationData = data;
+ }
+
@Override
- public void onAsyncInflationFinished(NotificationData.Entry entry,
+ public void onAsyncInflationFinished(NotificationEntry entry,
@NotificationInflater.InflationFlag int inflatedFlags) {
super.onAsyncInflationFinished(entry, inflatedFlags);
@@ -221,7 +230,7 @@
.setContentText("Text");
mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID,
0, n.build(), new UserHandle(ActivityManager.getCurrentUser()), null, 0);
- mEntry = new NotificationData.Entry(mSbn);
+ mEntry = new NotificationEntry(mSbn);
mEntry.expandedIcon = mock(StatusBarIconView.class);
mEntryManager = new TestableNotificationEntryManager(mContext);
@@ -258,10 +267,10 @@
verify(mEntryListener, never()).onInflationError(any(), any());
// Row inflation:
- ArgumentCaptor<NotificationData.Entry> entryCaptor = ArgumentCaptor.forClass(
- NotificationData.Entry.class);
+ ArgumentCaptor<NotificationEntry> entryCaptor = ArgumentCaptor.forClass(
+ NotificationEntry.class);
verify(mBindCallback).onBindRow(entryCaptor.capture(), any(), eq(mSbn), any());
- NotificationData.Entry entry = entryCaptor.getValue();
+ NotificationEntry entry = entryCaptor.getValue();
verify(mRemoteInputManager).bindRow(entry.getRow());
// Row content inflation:
@@ -290,14 +299,43 @@
verify(mEntryListener, never()).onInflationError(any(), any());
+ verify(mEntryListener).onPreEntryUpdated(mEntry);
verify(mPresenter).updateNotificationViews();
- verify(mEntryListener).onEntryUpdated(mEntry);
+ verify(mEntryListener).onPostEntryUpdated(mEntry);
+
assertNotNull(mEntry.getRow());
assertEquals(mEntry.userSentiment,
NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE);
}
@Test
+ public void testUpdateNotification_prePostEntryOrder() throws Exception {
+ com.android.systemui.util.Assert.isNotMainThread();
+ TestableLooper.get(this).processAllMessages();
+
+ NotificationData notifData = mock(NotificationData.class);
+ when(notifData.get(mEntry.key)).thenReturn(mEntry);
+
+ mEntryManager.setNotificationData(notifData);
+
+ mEntryManager.updateNotification(mSbn, mRankingMap);
+ TestableLooper.get(this).processMessages(1);
+ // Wait for content update.
+ assertTrue(mEntryManager.getCountDownLatch().await(10, TimeUnit.SECONDS));
+
+ verify(mEntryListener, never()).onInflationError(any(), any());
+
+ // Ensure that update callbacks happen in correct order
+ InOrder order = inOrder(mEntryListener, notifData, mPresenter, mEntryListener);
+ order.verify(mEntryListener).onPreEntryUpdated(mEntry);
+ order.verify(notifData).filterAndSort();
+ order.verify(mPresenter).updateNotificationViews();
+ order.verify(mEntryListener).onPostEntryUpdated(mEntry);
+
+ assertNotNull(mEntry.getRow());
+ }
+
+ @Test
public void testRemoveNotification() throws Exception {
com.android.systemui.util.Assert.isNotMainThread();
@@ -310,8 +348,8 @@
verify(mListContainer).cleanUpViewStateForEntry(mEntry);
verify(mPresenter).updateNotificationViews();
- verify(mEntryListener).onEntryRemoved(mEntry, mSbn.getKey(), mSbn,
- null, false /* lifetimeExtended */, false /* removedByUser */);
+ verify(mEntryListener).onEntryRemoved(
+ mEntry, null, false /* removedByUser */);
verify(mRow).setRemoved();
assertNull(mEntryManager.getNotificationData().get(mSbn.getKey()));
@@ -335,8 +373,31 @@
assertNotNull(mEntryManager.getNotificationData().get(mSbn.getKey()));
verify(extender).setShouldManageLifetime(mEntry, true /* shouldManage */);
- verify(mEntryListener).onEntryRemoved(mEntry, mSbn.getKey(), null,
- null, true /* lifetimeExtended */, false /* removedByUser */);
+ verify(mEntryListener, never()).onEntryRemoved(
+ mEntry, null, false /* removedByUser */);
+ }
+
+ @Test
+ public void testRemoveNotification_onEntryRemoveNotFiredIfEntryDoesntExist() {
+ com.android.systemui.util.Assert.isNotMainThread();
+
+ mEntryManager.removeNotification("not_a_real_key", mRankingMap);
+
+ verify(mEntryListener, never()).onEntryRemoved(
+ mEntry, null, false /* removedByUser */);
+ }
+
+ @Test
+ public void testRemoveNotification_whilePending() throws InterruptedException {
+ com.android.systemui.util.Assert.isNotMainThread();
+
+ mEntryManager.setRowBinder(mMockedRowBinder);
+
+ mEntryManager.addNotification(mSbn, mRankingMap);
+ mEntryManager.removeNotification(mSbn.getKey(), mRankingMap);
+
+ verify(mEntryListener, never()).onEntryRemoved(
+ mEntry, null, false /* removedByUser */);
}
@Test
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 da8bc01d..387475f 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
@@ -40,6 +40,8 @@
import com.android.systemui.ForegroundServiceController;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.NotificationTestHelper;
+import com.android.systemui.statusbar.notification.collection.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.ShadeController;
@@ -191,12 +193,12 @@
// test should filter out hidden notifications:
// hidden
- NotificationData.Entry entry = new NotificationData.Entry(mMockStatusBarNotification);
+ NotificationEntry entry = new NotificationEntry(mMockStatusBarNotification);
entry.suspended = true;
assertTrue(mNotificationFilter.shouldFilterOut(entry));
// not hidden
- entry = new NotificationData.Entry(mMockStatusBarNotification);
+ entry = new NotificationEntry(mMockStatusBarNotification);
entry.suspended = false;
assertFalse(mNotificationFilter.shouldFilterOut(entry));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java
index 6fbd8c7..813fc9d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java
@@ -29,6 +29,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.NotificationPresenter;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import org.junit.Before;
@@ -44,13 +45,13 @@
private VisualStabilityManager.Callback mCallback = mock(VisualStabilityManager.Callback.class);
private VisibilityLocationProvider mLocationProvider = mock(VisibilityLocationProvider.class);
private ExpandableNotificationRow mRow = mock(ExpandableNotificationRow.class);
- private NotificationData.Entry mEntry;
+ private NotificationEntry mEntry;
@Before
public void setUp() {
mVisualStabilityManager.setUpWithPresenter(mock(NotificationPresenter.class));
mVisualStabilityManager.setVisibilityLocationProvider(mLocationProvider);
- mEntry = new NotificationData.Entry(mock(StatusBarNotification.class));
+ mEntry = new NotificationEntry(mock(StatusBarNotification.class));
mEntry.setRow(mRow);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java
similarity index 94%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationDataTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java
index 871ff89..193f483 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.systemui.statusbar.notification;
+package com.android.systemui.statusbar.notification.collection;
import static android.app.AppOpsManager.OP_ACCEPT_HANDOVER;
import static android.app.AppOpsManager.OP_CAMERA;
@@ -59,7 +59,9 @@
import com.android.systemui.InitController;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.NotificationTestHelper;
-import com.android.systemui.statusbar.notification.NotificationData.KeyguardEnvironment;
+import com.android.systemui.statusbar.notification.collection.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationData.KeyguardEnvironment;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.ShadeController;
@@ -203,7 +205,7 @@
mRow.getEntry().notification)).thenReturn(false);
when(mEnvironment.isNotificationForCurrentProfiles(
row2.getEntry().notification)).thenReturn(true);
- ArrayList<NotificationData.Entry> result =
+ ArrayList<NotificationEntry> result =
mNotificationData.getNotificationsForCurrentUser();
assertEquals(result.size(), 1);
@@ -217,7 +219,7 @@
TEST_EXEMPT_DND_VISUAL_SUPPRESSION_KEY);
Notification n = mMockStatusBarNotification.getNotification();
n.flags = Notification.FLAG_FOREGROUND_SERVICE;
- NotificationData.Entry entry = new NotificationData.Entry(mMockStatusBarNotification);
+ NotificationEntry entry = new NotificationEntry(mMockStatusBarNotification);
mNotificationData.add(entry);
assertTrue(entry.isExemptFromDndVisualSuppression());
@@ -234,7 +236,7 @@
nb.setStyle(new Notification.MediaStyle().setMediaSession(mock(MediaSession.Token.class)));
n = nb.build();
when(mMockStatusBarNotification.getNotification()).thenReturn(n);
- NotificationData.Entry entry = new NotificationData.Entry(mMockStatusBarNotification);
+ NotificationEntry entry = new NotificationEntry(mMockStatusBarNotification);
mNotificationData.add(entry);
assertTrue(entry.isExemptFromDndVisualSuppression());
@@ -246,7 +248,7 @@
initStatusBarNotification(false);
when(mMockStatusBarNotification.getKey()).thenReturn(
TEST_EXEMPT_DND_VISUAL_SUPPRESSION_KEY);
- NotificationData.Entry entry = new NotificationData.Entry(mMockStatusBarNotification);
+ NotificationEntry entry = new NotificationEntry(mMockStatusBarNotification);
entry.mIsSystemNotification = true;
mNotificationData.add(entry);
@@ -259,7 +261,7 @@
initStatusBarNotification(false);
when(mMockStatusBarNotification.getKey()).thenReturn(
TEST_EXEMPT_DND_VISUAL_SUPPRESSION_KEY);
- NotificationData.Entry entry = new NotificationData.Entry(mMockStatusBarNotification);
+ NotificationEntry entry = new NotificationEntry(mMockStatusBarNotification);
entry.mIsSystemNotification = true;
mNotificationData.add(entry);
@@ -313,8 +315,8 @@
snoozeCriterions.add(snoozeCriterion);
when(ranking.getSnoozeCriteria()).thenReturn(snoozeCriterions);
- NotificationData.Entry entry =
- new NotificationData.Entry(mMockStatusBarNotification, ranking);
+ NotificationEntry entry =
+ new NotificationEntry(mMockStatusBarNotification, ranking);
assertEquals(systemGeneratedSmartActions, entry.systemGeneratedSmartActions);
assertEquals(NOTIFICATION_CHANNEL, entry.channel);
@@ -343,7 +345,7 @@
StatusBarNotification sbn = new StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0,
notification, mContext.getUser(), "", 0);
- NotificationData.Entry entry = new NotificationData.Entry(sbn);
+ NotificationEntry entry = new NotificationEntry(sbn);
entry.setHasSentReply();
assertTrue(entry.isLastMessageFromReply());
@@ -400,8 +402,7 @@
Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon),
title,
PendingIntent.getBroadcast(getContext(), 0, new Intent("Action"), 0))
- .setSemanticAction(
- Notification.Action.SEMANTIC_ACTION_CONTEXTUAL_SUGGESTION)
+ .setContextual(true)
.build();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
index 983ca83..6472589 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
@@ -41,9 +41,10 @@
import com.android.systemui.UiOffloadThread;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -77,7 +78,7 @@
@Mock private NotificationListener mListener;
@Captor private ArgumentCaptor<NotificationEntryListener> mEntryListenerCaptor;
- private NotificationData.Entry mEntry;
+ private NotificationEntry mEntry;
private TestableNotificationLogger mLogger;
private NotificationEntryListener mNotificationEntryListener;
private ConcurrentLinkedQueue<AssertionError> mErrorQueue = new ConcurrentLinkedQueue<>();
@@ -93,7 +94,7 @@
StatusBarNotification sbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME,
0, null, TEST_UID,
0, new Notification(), UserHandle.CURRENT, null, 0);
- mEntry = new NotificationData.Entry(sbn);
+ mEntry = new NotificationEntry(sbn);
mEntry.setRow(mRow);
mLogger = new TestableNotificationLogger(mListener, Dependency.get(UiOffloadThread.class),
@@ -159,11 +160,6 @@
verify(mBarService, times(1)).onNotificationVisibilityChanged(any(), any());
}
- @Test
- public void testHandleNullEntryOnEntryRemoved() {
- mNotificationEntryListener.onEntryRemoved(null, "foobar", null, null, false, false);
- }
-
private class TestableNotificationLogger extends NotificationLogger {
TestableNotificationLogger(NotificationListener notificationListener,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
index 82b42c5..d94bf84 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
@@ -47,7 +47,7 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.policy.SmartReplyConstants;
import org.junit.Before;
@@ -73,7 +73,7 @@
StatusBarNotification mStatusBarNotification;
@Mock
Notification mNotification;
- NotificationData.Entry mEntry;
+ NotificationEntry mEntry;
@Mock
RemoteInput mRemoteInput;
@Mock
@@ -107,7 +107,7 @@
// Smart replies and actions
when(mNotification.getAllowSystemGeneratedContextualActions()).thenReturn(true);
when(mStatusBarNotification.getNotification()).thenReturn(mNotification);
- mEntry = new NotificationData.Entry(mStatusBarNotification);
+ mEntry = new NotificationEntry(mStatusBarNotification);
when(mSmartReplyConstants.isEnabled()).thenReturn(true);
mActionIcon = Icon.createWithResource(mContext, R.drawable.ic_person);
}
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 ad43bea..0899c73 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
@@ -60,7 +60,7 @@
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationTestHelper;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -183,8 +183,8 @@
when(row.getGuts()).thenReturn(guts);
doNothing().when(row).inflateGuts();
- NotificationData.Entry realEntry = realRow.getEntry();
- NotificationData.Entry entry = spy(realEntry);
+ NotificationEntry realEntry = realRow.getEntry();
+ NotificationEntry entry = spy(realEntry);
when(entry.getRow()).thenReturn(row);
when(entry.getGuts()).thenReturn(guts);
@@ -443,7 +443,7 @@
NotificationGuts guts = new NotificationGuts(mContext);
ExpandableNotificationRow row = spy(createTestNotificationRow());
doReturn(guts).when(row).getGuts();
- NotificationData.Entry entry = row.getEntry();
+ NotificationEntry entry = row.getEntry();
entry.setRow(row);
mGutsManager.setExposedGuts(guts);
@@ -452,7 +452,7 @@
@Test
public void testSetShouldManageLifetime_setShouldManage() {
- NotificationData.Entry entry = createTestNotificationRow().getEntry();
+ NotificationEntry entry = createTestNotificationRow().getEntry();
mGutsManager.setShouldManageLifetime(entry, true /* shouldManage */);
assertTrue(entry.key.equals(mGutsManager.mKeyToRemoveOnGutsClosed));
@@ -460,7 +460,7 @@
@Test
public void testSetShouldManageLifetime_setShouldNotManage() {
- NotificationData.Entry entry = createTestNotificationRow().getEntry();
+ NotificationEntry entry = createTestNotificationRow().getEntry();
mGutsManager.mKeyToRemoveOnGutsClosed = entry.key;
mGutsManager.setShouldManageLifetime(entry, false /* shouldManage */);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInflaterTest.java
index d6b706d..648df3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInflaterTest.java
@@ -48,7 +48,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.InflationTask;
import com.android.systemui.statusbar.NotificationTestHelper;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import org.junit.Assert;
import org.junit.Before;
@@ -88,7 +88,7 @@
}
@Override
- public void onAsyncInflationFinished(NotificationData.Entry entry,
+ public void onAsyncInflationFinished(NotificationEntry entry,
@NotificationInflater.InflationFlag int inflatedFlags) {
}
});
@@ -174,7 +174,7 @@
}
@Override
- public void onAsyncInflationFinished(NotificationData.Entry entry,
+ public void onAsyncInflationFinished(NotificationEntry entry,
@NotificationInflater.InflationFlag int inflatedFlags) {
countDownLatch.countDown();
}
@@ -256,7 +256,7 @@
}
@Override
- public void onAsyncInflationFinished(NotificationData.Entry entry,
+ public void onAsyncInflationFinished(NotificationEntry entry,
@NotificationInflater.InflationFlag int inflatedFlags) {
if (expectingException) {
exceptionHolder.setException(new RuntimeException(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
index e4d0196..04d7509 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
@@ -35,7 +35,7 @@
import android.view.ViewGroup;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.utils.leaks.LeakCheckedTest;
import org.junit.Before;
@@ -54,7 +54,7 @@
public void setup() {
injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
mRow = mock(ExpandableNotificationRow.class);
- NotificationData.Entry entry = new NotificationData.Entry(
+ NotificationEntry entry = new NotificationEntry(
mock(StatusBarNotification.class));
entry.channel = mock(NotificationChannel.class);
when(mRow.getEntry()).thenReturn(entry);
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 06be5f6..214404c 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
@@ -35,12 +35,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.os.Handler;
-import android.os.IPowerManager;
-import android.os.Looper;
-import android.os.PowerManager;
import android.provider.Settings;
-import android.service.dreams.IDreamManager;
import android.support.test.annotation.UiThreadTest;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -58,9 +53,9 @@
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.notification.NotificationData;
-import com.android.systemui.statusbar.notification.NotificationData.Entry;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.FooterView;
import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
@@ -104,8 +99,6 @@
@Mock private NotificationData mNotificationData;
@Mock private NotificationRemoteInputManager mRemoteInputManager;
@Mock private RemoteInputController mRemoteInputController;
- @Mock private IDreamManager mDreamManager;
- private PowerManager mPowerManager;
private TestableNotificationEntryManager mEntryManager;
private int mOriginalInterruptionModelSetting;
@@ -122,12 +115,7 @@
mDependency.injectMockDependency(ShadeController.class);
when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController);
- IPowerManager powerManagerService = mock(IPowerManager.class);
- mPowerManager = new PowerManager(mContext, powerManagerService,
- Handler.createAsync(Looper.myLooper()));
-
- mEntryManager = new TestableNotificationEntryManager(mPowerManager,
- mContext);
+ mEntryManager = new TestableNotificationEntryManager(mContext);
mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
Dependency.get(InitController.class).executePostInitTasks();
mEntryManager.setUpForTest(mock(NotificationPresenter.class), null, mHeadsUpManager,
@@ -277,8 +265,8 @@
@Test
public void testUpdateFooter_remoteInput() {
setBarStateForTest(StatusBarState.SHADE);
- ArrayList<Entry> entries = new ArrayList<>();
- entries.add(mock(Entry.class));
+ ArrayList<NotificationEntry> entries = new ArrayList<>();
+ entries.add(mock(NotificationEntry.class));
when(mNotificationData.getActiveNotifications()).thenReturn(entries);
ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
@@ -294,8 +282,8 @@
@Test
public void testUpdateFooter_oneClearableNotification() {
setBarStateForTest(StatusBarState.SHADE);
- ArrayList<Entry> entries = new ArrayList<>();
- entries.add(mock(Entry.class));
+ ArrayList<NotificationEntry> entries = new ArrayList<>();
+ entries.add(mock(NotificationEntry.class));
when(mNotificationData.getActiveNotifications()).thenReturn(entries);
ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
@@ -310,8 +298,8 @@
@Test
public void testUpdateFooter_oneNonClearableNotification() {
setBarStateForTest(StatusBarState.SHADE);
- ArrayList<Entry> entries = new ArrayList<>();
- entries.add(mock(Entry.class));
+ ArrayList<NotificationEntry> entries = new ArrayList<>();
+ entries.add(mock(NotificationEntry.class));
when(mEntryManager.getNotificationData().getActiveNotifications()).thenReturn(entries);
assertTrue(mEntryManager.getNotificationData().getActiveNotifications().size() != 0);
@@ -326,7 +314,7 @@
// add notification
ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
- NotificationData.Entry entry = mock(NotificationData.Entry.class);
+ NotificationEntry entry = mock(NotificationEntry.class);
when(row.getEntry()).thenReturn(entry);
when(entry.isClearable()).thenReturn(true);
mStackScroller.addContainerView(row);
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 44deb10..9ceb325 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
@@ -16,15 +16,20 @@
package com.android.systemui.statusbar.phone;
-import android.view.View;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.when;
+
import android.support.test.filters.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.view.View;
import com.android.systemui.statusbar.AlertingNotificationManager;
import com.android.systemui.statusbar.AlertingNotificationManagerTest;
-import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import org.junit.Before;
import org.junit.Rule;
@@ -34,11 +39,6 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
-import static junit.framework.Assert.assertTrue;
-import static junit.framework.Assert.assertFalse;
-
-import static org.mockito.Mockito.when;
-
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@@ -96,7 +96,7 @@
@Test
public void testCanRemoveImmediately_notTopEntry() {
- NotificationData.Entry laterEntry = new NotificationData.Entry(createNewNotification(1));
+ NotificationEntry laterEntry = new NotificationEntry(createNewNotification(1));
laterEntry.setRow(mRow);
mHeadsUpManager.showNotification(mEntry);
mHeadsUpManager.showNotification(laterEntry);
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 56af400..cefd4ac 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
@@ -32,10 +32,9 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.AmbientPulseManager;
-import com.android.systemui.statusbar.notification.NotificationData;
-import com.android.systemui.statusbar.notification.NotificationData.Entry;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import org.junit.Before;
@@ -64,7 +63,7 @@
@Captor
private ArgumentCaptor<NotificationEntryListener> mListenerCaptor;
private NotificationEntryListener mNotificationEntryListener;
- private final HashMap<String, Entry> mPendingEntries = new HashMap<>();
+ private final HashMap<String, NotificationEntry> mPendingEntries = new HashMap<>();
private final NotificationGroupTestHelper mGroupTestHelper =
new NotificationGroupTestHelper(mContext);
@@ -94,9 +93,9 @@
@Test
public void testSuppressedSummaryHeadsUpTransfersToChild() {
- Entry summaryEntry = mGroupTestHelper.createSummaryNotification();
+ NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
mHeadsUpManager.showNotification(summaryEntry);
- Entry childEntry = mGroupTestHelper.createChildNotification();
+ NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
// Summary will be suppressed because there is only one child.
mGroupManager.onEntryAdded(summaryEntry);
@@ -109,11 +108,11 @@
@Test
public void testSuppressedSummaryHeadsUpTransfersToChildButBackAgain() {
- NotificationData.Entry summaryEntry =
+ NotificationEntry summaryEntry =
mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
- NotificationData.Entry childEntry =
+ NotificationEntry childEntry =
mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
- NotificationData.Entry childEntry2 =
+ NotificationEntry childEntry2 =
mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
mHeadsUpManager.showNotification(summaryEntry);
// Trigger a transfer of alert state from summary to child.
@@ -133,11 +132,11 @@
@Test
public void testSuppressedSummaryHeadsUpDoesntTransferBackOnDozingChanged() {
- NotificationData.Entry summaryEntry =
+ NotificationEntry summaryEntry =
mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
- NotificationData.Entry childEntry =
+ NotificationEntry childEntry =
mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
- NotificationData.Entry childEntry2 =
+ NotificationEntry childEntry2 =
mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
mHeadsUpManager.showNotification(summaryEntry);
// Trigger a transfer of alert state from summary to child.
@@ -158,9 +157,9 @@
@Test
public void testSuppressedSummaryHeadsUpTransferDoesNotAlertChildIfUninflated() {
- Entry summaryEntry = mGroupTestHelper.createSummaryNotification();
+ NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
mHeadsUpManager.showNotification(summaryEntry);
- Entry childEntry = mGroupTestHelper.createChildNotification();
+ NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag()))
.thenReturn(false);
@@ -176,9 +175,9 @@
@Test
public void testSuppressedSummaryHeadsUpTransferAlertsChildOnInflation() {
- Entry summaryEntry = mGroupTestHelper.createSummaryNotification();
+ NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
mHeadsUpManager.showNotification(summaryEntry);
- Entry childEntry = mGroupTestHelper.createChildNotification();
+ NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag()))
.thenReturn(false);
@@ -196,13 +195,13 @@
@Test
public void testSuppressedSummaryHeadsUpTransferBackAbortsChildInflation() {
- NotificationData.Entry summaryEntry =
+ NotificationEntry summaryEntry =
mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
- NotificationData.Entry childEntry =
+ NotificationEntry childEntry =
mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag()))
.thenReturn(false);
- NotificationData.Entry childEntry2 =
+ NotificationEntry childEntry2 =
mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
mHeadsUpManager.showNotification(summaryEntry);
// Trigger a transfer of alert state from summary to child.
@@ -226,9 +225,9 @@
@Test
public void testCleanUpPendingAlertInfo() {
- NotificationData.Entry summaryEntry =
+ NotificationEntry summaryEntry =
mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
- NotificationData.Entry childEntry =
+ NotificationEntry childEntry =
mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag()))
.thenReturn(false);
@@ -237,17 +236,16 @@
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
- mNotificationEntryListener.onEntryRemoved(childEntry, childEntry.key, null, null,
- false, false);
+ mNotificationEntryListener.onEntryRemoved(childEntry, null, false);
assertFalse(mGroupAlertTransferHelper.isAlertTransferPending(childEntry));
}
@Test
public void testUpdateGroupChangeDoesNotTransfer() {
- NotificationData.Entry summaryEntry =
+ NotificationEntry summaryEntry =
mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
- NotificationData.Entry childEntry =
+ NotificationEntry childEntry =
mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag()))
.thenReturn(false);
@@ -268,9 +266,9 @@
@Test
public void testUpdateChildToSummaryDoesNotTransfer() {
- NotificationData.Entry summaryEntry =
+ NotificationEntry summaryEntry =
mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
- NotificationData.Entry childEntry =
+ NotificationEntry childEntry =
mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag()))
.thenReturn(false);
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 b0bd1fb..ecb3e4d 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
@@ -29,7 +29,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.AmbientPulseManager;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import org.junit.Before;
@@ -69,8 +69,8 @@
@Test
public void testIsOnlyChildInGroup() {
- NotificationData.Entry childEntry = mGroupTestHelper.createChildNotification();
- NotificationData.Entry summaryEntry = mGroupTestHelper.createSummaryNotification();
+ NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
+ NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
@@ -80,8 +80,8 @@
@Test
public void testIsChildInGroupWithSummary() {
- NotificationData.Entry childEntry = mGroupTestHelper.createChildNotification();
- NotificationData.Entry summaryEntry = mGroupTestHelper.createSummaryNotification();
+ NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
+ NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
@@ -92,8 +92,8 @@
@Test
public void testIsSummaryOfGroupWithChildren() {
- NotificationData.Entry childEntry = mGroupTestHelper.createChildNotification();
- NotificationData.Entry summaryEntry = mGroupTestHelper.createSummaryNotification();
+ NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
+ NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
@@ -105,8 +105,8 @@
@Test
public void testRemoveChildFromGroupWithSummary() {
- NotificationData.Entry childEntry = mGroupTestHelper.createChildNotification();
- NotificationData.Entry summaryEntry = mGroupTestHelper.createSummaryNotification();
+ NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
+ NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
mGroupManager.onEntryAdded(mGroupTestHelper.createChildNotification());
@@ -118,8 +118,8 @@
@Test
public void testRemoveSummaryFromGroupWithSummary() {
- NotificationData.Entry childEntry = mGroupTestHelper.createChildNotification();
- NotificationData.Entry summaryEntry = mGroupTestHelper.createSummaryNotification();
+ NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
+ NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
mGroupManager.onEntryAdded(mGroupTestHelper.createChildNotification());
@@ -132,8 +132,8 @@
@Test
public void testHeadsUpEntryIsIsolated() {
- NotificationData.Entry childEntry = mGroupTestHelper.createChildNotification();
- NotificationData.Entry summaryEntry = mGroupTestHelper.createSummaryNotification();
+ NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
+ NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
mGroupManager.onEntryAdded(mGroupTestHelper.createChildNotification());
@@ -149,8 +149,8 @@
@Test
public void testAmbientPulseEntryIsIsolated() {
- NotificationData.Entry childEntry = mGroupTestHelper.createChildNotification();
- NotificationData.Entry summaryEntry = mGroupTestHelper.createSummaryNotification();
+ NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
+ NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
mGroupManager.onEntryAdded(mGroupTestHelper.createChildNotification());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java
index 7ad68eb..e6b9778 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java
@@ -27,7 +27,7 @@
import android.service.notification.StatusBarNotification;
import com.android.systemui.R;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
/**
@@ -44,23 +44,23 @@
mContext = context;
}
- public NotificationData.Entry createSummaryNotification() {
+ public NotificationEntry createSummaryNotification() {
return createSummaryNotification(Notification.GROUP_ALERT_ALL);
}
- public NotificationData.Entry createSummaryNotification(int groupAlertBehavior) {
+ public NotificationEntry createSummaryNotification(int groupAlertBehavior) {
return createEntry(true, groupAlertBehavior);
}
- public NotificationData.Entry createChildNotification() {
+ public NotificationEntry createChildNotification() {
return createChildNotification(Notification.GROUP_ALERT_ALL);
}
- public NotificationData.Entry createChildNotification(int groupAlertBehavior) {
+ public NotificationEntry createChildNotification(int groupAlertBehavior) {
return createEntry(false, groupAlertBehavior);
}
- public NotificationData.Entry createEntry(boolean isSummary, int groupAlertBehavior) {
+ public NotificationEntry createEntry(boolean isSummary, int groupAlertBehavior) {
Notification notif = new Notification.Builder(mContext, TEST_CHANNEL_ID)
.setContentTitle("Title")
.setSmallIcon(R.drawable.ic_person)
@@ -79,7 +79,7 @@
new UserHandle(ActivityManager.getCurrentUser()),
null /* overrideGroupKey */,
0 /* postTime */);
- NotificationData.Entry entry = new NotificationData.Entry(sbn);
+ NotificationEntry entry = new NotificationEntry(sbn);
ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
entry.setRow(row);
when(row.getEntry()).thenReturn(entry);
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 146c5d6..8eb42c4 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
@@ -54,7 +54,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
import java.util.function.Consumer;
@@ -94,6 +94,7 @@
},
visible -> mScrimVisibility = visible, mDozeParamenters, mAlarmManager);
mScrimController.setHasBackdrop(false);
+ mScrimController.setWallpaperSupportsAmbientMode(false);
}
@Test
@@ -474,6 +475,26 @@
}
@Test
+ public void testHoldsPulsingWallpaperAnimationLock() {
+ // Pre-conditions
+ mScrimController.transitionTo(ScrimState.PULSING, new ScrimController.Callback() {
+ @Override
+ public boolean isFadeOutWallpaper() {
+ return true;
+ }
+ });
+ mScrimController.finishAnimationsImmediately();
+ reset(mWakeLock);
+
+ mScrimController.onHideWallpaperTimeout();
+ verify(mWakeLock).acquire();
+ verify(mWakeLock, never()).release();
+ mScrimController.finishAnimationsImmediately();
+ verify(mWakeLock).release();
+ assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
+ }
+
+ @Test
public void testWillHideAodWallpaper() {
mScrimController.setWallpaperSupportsAmbientMode(true);
mScrimController.transitionTo(ScrimState.AOD);
@@ -483,6 +504,34 @@
}
@Test
+ public void testWillHidePulsingWallpaper_withRequestFadeOut() {
+ mScrimController.setWallpaperSupportsAmbientMode(true);
+ mScrimController.transitionTo(ScrimState.PULSING, new ScrimController.Callback() {
+ @Override
+ public boolean isFadeOutWallpaper() {
+ return true;
+ }
+ });
+ verify(mAlarmManager).setExact(anyInt(), anyLong(), any(), any(), any());
+ mScrimController.transitionTo(ScrimState.KEYGUARD);
+ verify(mAlarmManager).cancel(any(AlarmManager.OnAlarmListener.class));
+ }
+
+ @Test
+ public void testDoesNotHidePulsingWallpaper_withoutRequestFadeOut() {
+ mScrimController.setWallpaperSupportsAmbientMode(true);
+ mScrimController.transitionTo(ScrimState.PULSING, new ScrimController.Callback() {});
+ verify(mAlarmManager, never()).setExact(anyInt(), anyLong(), any(), any(), any());
+ }
+
+ @Test
+ public void testDoesNotHidePulsingWallpaper_withoutCallback() {
+ mScrimController.setWallpaperSupportsAmbientMode(true);
+ mScrimController.transitionTo(ScrimState.PULSING);
+ verify(mAlarmManager, never()).setExact(anyInt(), anyLong(), any(), any(), any());
+ }
+
+ @Test
public void testConservesExpansionOpacityAfterTransition() {
mScrimController.transitionTo(ScrimState.UNLOCKED);
mScrimController.setPanelExpansion(0.5f);
@@ -578,7 +627,7 @@
@Test
public void testEatsTouchEvent() {
HashSet<ScrimState> eatsTouches =
- new HashSet<>(Arrays.asList(ScrimState.AOD, ScrimState.PULSING));
+ new HashSet<>(Collections.singletonList(ScrimState.AOD));
for (ScrimState state : ScrimState.values()) {
if (state == ScrimState.UNINITIALIZED) {
continue;
@@ -738,5 +787,4 @@
callback.run();
}
}
-
}
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 2bbfd7d..12cb9957 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
@@ -40,7 +40,7 @@
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.NotificationAlertingManager;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -82,7 +82,7 @@
Notification n = new Notification.Builder(getContext(), "a").build();
StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
UserHandle.of(0), null, 0);
- NotificationData.Entry entry = new NotificationData.Entry(sbn);
+ NotificationEntry entry = new NotificationEntry(sbn);
mCommandQueue.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_EXPAND, 0,
false /* animate */);
TestableLooper.get(this).processAllMessages();
@@ -96,7 +96,7 @@
Notification n = new Notification.Builder(getContext(), "a").build();
StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
UserHandle.of(0), null, 0);
- NotificationData.Entry entry = new NotificationData.Entry(sbn);
+ NotificationEntry entry = new NotificationEntry(sbn);
mCommandQueue.disable(DEFAULT_DISPLAY, 0, StatusBarManager.DISABLE2_NOTIFICATION_SHADE,
false /* animate */);
TestableLooper.get(this).processAllMessages();
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 d57d565..0d4046b 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
@@ -95,13 +95,13 @@
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.NotificationAlertingManager;
-import com.android.systemui.statusbar.notification.NotificationData;
-import com.android.systemui.statusbar.notification.NotificationData.Entry;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationFilter;
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.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -135,7 +135,7 @@
@Mock private IDreamManager mDreamManager;
@Mock private ScrimController mScrimController;
@Mock private DozeScrimController mDozeScrimController;
- @Mock private ArrayList<Entry> mNotificationList;
+ @Mock private ArrayList<NotificationEntry> mNotificationList;
@Mock private BiometricUnlockController mBiometricUnlockController;
@Mock private NotificationData mNotificationData;
@Mock
@@ -205,7 +205,7 @@
mMetricsLogger = new FakeMetricsLogger();
mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
- mEntryManager = new TestableNotificationEntryManager(mPowerManager, mContext);
+ mEntryManager = new TestableNotificationEntryManager(mContext);
mNotificationLogger = new NotificationLogger(mNotificationListener,
Dependency.get(UiOffloadThread.class), mEntryManager, mStatusBarStateController);
mDependency.injectTestDependency(NotificationLogger.class, mNotificationLogger);
@@ -409,7 +409,7 @@
.build();
StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
UserHandle.of(0), null, 0);
- NotificationData.Entry entry = new NotificationData.Entry(sbn);
+ NotificationEntry entry = new NotificationEntry(sbn);
entry.importance = IMPORTANCE_HIGH;
assertTrue(mNotificationInterruptionStateProvider.shouldHeadsUp(entry));
@@ -430,7 +430,7 @@
.build();
StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
UserHandle.of(0), null, 0);
- NotificationData.Entry entry = new NotificationData.Entry(sbn);
+ NotificationEntry entry = new NotificationEntry(sbn);
entry.importance = IMPORTANCE_HIGH;
assertFalse(mNotificationInterruptionStateProvider.shouldHeadsUp(entry));
@@ -447,7 +447,7 @@
Notification n = new Notification.Builder(getContext(), "a").build();
StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
UserHandle.of(0), null, 0);
- NotificationData.Entry entry = new NotificationData.Entry(sbn);
+ NotificationEntry entry = new NotificationEntry(sbn);
entry.suppressedVisualEffects = SUPPRESSED_EFFECT_PEEK;
entry.importance = IMPORTANCE_HIGH;
@@ -465,7 +465,7 @@
Notification n = new Notification.Builder(getContext(), "a").build();
StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
UserHandle.of(0), null, 0);
- NotificationData.Entry entry = new NotificationData.Entry(sbn);
+ NotificationEntry entry = new NotificationEntry(sbn);
entry.importance = IMPORTANCE_HIGH;
assertTrue(mNotificationInterruptionStateProvider.shouldHeadsUp(entry));
@@ -761,9 +761,8 @@
public static class TestableNotificationEntryManager extends NotificationEntryManager {
- public TestableNotificationEntryManager(PowerManager powerManager, Context context) {
+ public TestableNotificationEntryManager(Context context) {
super(context);
- mPowerManager = powerManager;
}
public void setUpForTest(NotificationPresenter presenter,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/IconLoggerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/IconLoggerImplTest.java
deleted file mode 100644
index 5c34730..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/IconLoggerImplTest.java
+++ /dev/null
@@ -1,177 +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.statusbar.policy;
-
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_NUM_STATUS_ICONS;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_STATUS_ICONS;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent
- .RESERVED_FOR_LOGBUILDER_LATENCY_MILLIS;
-
-import static org.junit.Assert.*;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import static java.lang.Thread.sleep;
-
-import android.metrics.LogMaker;
-import android.support.test.filters.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.testing.TestableLooper.MessageHandler;
-import android.testing.TestableLooper.RunWithLooper;
-import android.util.Log;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentMatcher;
-
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-@SmallTest
-public class IconLoggerImplTest extends SysuiTestCase {
-
- private MetricsLogger mMetricsLogger;
- private IconLoggerImpl mIconLogger;
- private TestableLooper mTestableLooper;
- private MessageHandler mMessageHandler;
-
- @Before
- public void setup() {
- IconLoggerImpl.MIN_LOG_INTERVAL = 5; // Low interval for testing
- mMetricsLogger = mock(MetricsLogger.class);
- mTestableLooper = TestableLooper.get(this);
- mMessageHandler = mock(MessageHandler.class);
- mTestableLooper.setMessageHandler(mMessageHandler);
- String[] iconArray = new String[] {
- "test_icon_1",
- "test_icon_2",
- };
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.array.config_statusBarIcons, iconArray);
- mIconLogger = new IconLoggerImpl(mContext, mTestableLooper.getLooper(), mMetricsLogger);
- when(mMessageHandler.onMessageHandled(any())).thenReturn(true);
- clearInvocations(mMetricsLogger);
- }
-
- @Test
- public void testIconShown() throws InterruptedException {
- // Should only get one message, for the same icon shown twice.
- mIconLogger.onIconShown("test_icon_2");
- mIconLogger.onIconShown("test_icon_2");
-
- // There should be some delay before execute.
- mTestableLooper.processAllMessages();
- verify(mMessageHandler, never()).onMessageHandled(any());
-
- sleep(10);
- mTestableLooper.processAllMessages();
- verify(mMessageHandler, times(1)).onMessageHandled(any());
- }
-
- @Test
- public void testIconHidden() throws InterruptedException {
- // Add the icon so that it can be removed.
- mIconLogger.onIconShown("test_icon_2");
- sleep(10);
- mTestableLooper.processAllMessages();
- clearInvocations(mMessageHandler);
-
- // Should only get one message, for the same icon shown twice.
- mIconLogger.onIconHidden("test_icon_2");
- mIconLogger.onIconHidden("test_icon_2");
-
- // There should be some delay before execute.
- mTestableLooper.processAllMessages();
- verify(mMessageHandler, never()).onMessageHandled(any());
-
- sleep(10);
- mTestableLooper.processAllMessages();
- verify(mMessageHandler, times(1)).onMessageHandled(any());
- }
-
- @Test
- public void testLog() throws InterruptedException {
- mIconLogger.onIconShown("test_icon_2");
- sleep(10);
- mTestableLooper.processAllMessages();
-
- verify(mMetricsLogger).write(argThat(maker -> {
- if (IconLoggerImpl.MIN_LOG_INTERVAL >
- (long) maker.getTaggedData(RESERVED_FOR_LOGBUILDER_LATENCY_MILLIS)) {
- Log.e("IconLoggerImplTest", "Invalid latency "
- + maker.getTaggedData(RESERVED_FOR_LOGBUILDER_LATENCY_MILLIS));
- return false;
- }
- if (1 != (int) maker.getTaggedData(FIELD_NUM_STATUS_ICONS)) {
- Log.e("IconLoggerImplTest", "Invalid icon count "
- + maker.getTaggedData(FIELD_NUM_STATUS_ICONS));
- return false;
- }
- return true;
- }));
- }
-
- @Test
- public void testBitField() throws InterruptedException {
- mIconLogger.onIconShown("test_icon_2");
- sleep(10);
- mTestableLooper.processAllMessages();
-
- verify(mMetricsLogger).write(argThat(maker -> {
- if ((1 << 1) != (int) maker.getTaggedData(FIELD_STATUS_ICONS)) {
- Log.e("IconLoggerImplTest", "Invalid bitfield " + Integer.toHexString(
- (Integer) maker.getTaggedData(FIELD_NUM_STATUS_ICONS)));
- return false;
- }
- return true;
- }));
-
- mIconLogger.onIconShown("test_icon_1");
- sleep(10);
- mTestableLooper.processAllMessages();
-
- verify(mMetricsLogger).write(argThat(maker -> {
- if ((1 << 1 | 1 << 0) != (int) maker.getTaggedData(FIELD_STATUS_ICONS)) {
- Log.e("IconLoggerImplTest", "Invalid bitfield " + Integer.toHexString(
- (Integer) maker.getTaggedData(FIELD_NUM_STATUS_ICONS)));
- return false;
- }
- return true;
- }));
-
- mIconLogger.onIconHidden("test_icon_2");
- sleep(10);
- mTestableLooper.processAllMessages();
-
- verify(mMetricsLogger).write(argThat(maker -> {
- if ((1 << 0) != (int) maker.getTaggedData(FIELD_STATUS_ICONS)) {
- Log.e("IconLoggerImplTest", "Invalid bitfield " + Integer.toHexString(
- (Integer) maker.getTaggedData(FIELD_STATUS_ICONS)));
- return false;
- }
- return true;
- }));
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
index 2266b47..37a56a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
@@ -20,6 +20,7 @@
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import android.app.RemoteInput;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
@@ -51,6 +52,8 @@
resources.addOverride(R.bool.config_smart_replies_in_notifications_enabled, true);
resources.addOverride(
R.integer.config_smart_replies_in_notifications_max_squeeze_remeasure_attempts, 7);
+ resources.addOverride(
+ R.bool.config_smart_replies_in_notifications_edit_choices_before_sending, false);
mConstants = new SmartReplyConstants(Handler.createAsync(Looper.myLooper()), mContext);
}
@@ -104,6 +107,51 @@
assertEquals(5, mConstants.getMaxSqueezeRemeasureAttempts());
}
+ @Test
+ public void testGetEffectiveEditChoicesBeforeSendingWithNoConfig() {
+ overrideSetting("enabled=true");
+ triggerConstantsOnChange();
+ assertFalse(
+ mConstants.getEffectiveEditChoicesBeforeSending(
+ RemoteInput.EDIT_CHOICES_BEFORE_SENDING_AUTO));
+ assertTrue(
+ mConstants.getEffectiveEditChoicesBeforeSending(
+ RemoteInput.EDIT_CHOICES_BEFORE_SENDING_ENABLED));
+ assertFalse(
+ mConstants.getEffectiveEditChoicesBeforeSending(
+ RemoteInput.EDIT_CHOICES_BEFORE_SENDING_DISABLED));
+ }
+
+ @Test
+ public void testGetEffectiveEditChoicesBeforeSendingWithEnabledConfig() {
+ overrideSetting("enabled=true,edit_choices_before_sending=true");
+ triggerConstantsOnChange();
+ assertTrue(
+ mConstants.getEffectiveEditChoicesBeforeSending(
+ RemoteInput.EDIT_CHOICES_BEFORE_SENDING_AUTO));
+ assertTrue(
+ mConstants.getEffectiveEditChoicesBeforeSending(
+ RemoteInput.EDIT_CHOICES_BEFORE_SENDING_ENABLED));
+ assertFalse(
+ mConstants.getEffectiveEditChoicesBeforeSending(
+ RemoteInput.EDIT_CHOICES_BEFORE_SENDING_DISABLED));
+ }
+
+ @Test
+ public void testGetEffectiveEditChoicesBeforeSendingWithDisabledConfig() {
+ overrideSetting("enabled=true,edit_choices_before_sending=false");
+ triggerConstantsOnChange();
+ assertFalse(
+ mConstants.getEffectiveEditChoicesBeforeSending(
+ RemoteInput.EDIT_CHOICES_BEFORE_SENDING_AUTO));
+ assertTrue(
+ mConstants.getEffectiveEditChoicesBeforeSending(
+ RemoteInput.EDIT_CHOICES_BEFORE_SENDING_ENABLED));
+ assertFalse(
+ mConstants.getEffectiveEditChoicesBeforeSending(
+ RemoteInput.EDIT_CHOICES_BEFORE_SENDING_DISABLED));
+ }
+
private void overrideSetting(String flags) {
Settings.Global.putString(mContext.getContentResolver(),
Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS, flags);
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 c5bac92..1066bc1 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
@@ -50,7 +50,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.statusbar.SmartReplyController;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
import com.android.systemui.statusbar.phone.ShadeController;
@@ -94,7 +94,7 @@
private int mSpacing;
@Mock private SmartReplyController mLogger;
- private NotificationData.Entry mEntry;
+ private NotificationEntry mEntry;
private Notification mNotification;
@Mock ActivityStarter mActivityStarter;
@@ -127,7 +127,7 @@
StatusBarNotification sbn = mock(StatusBarNotification.class);
when(sbn.getNotification()).thenReturn(mNotification);
when(sbn.getKey()).thenReturn(TEST_NOTIFICATION_KEY);
- mEntry = new NotificationData.Entry(sbn);
+ mEntry = new NotificationEntry(sbn);
mActionIcon = Icon.createWithResource(mContext, R.drawable.ic_person);
}
@@ -855,4 +855,92 @@
assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(2));
assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(2), mView.getChildAt(3));
}
+
+ /**
+ * Test to ensure that we try to add all possible actions - if we find one action that's too
+ * long we just skip that one rather than quitting altogether.
+ */
+ @Test
+ public void testMeasure_skipTooLongActions() {
+ String[] choices = new String[] {};
+ String[] actions = new String[] {
+ "a1", "a2", "this action is soooooooo long it's ridiculous", "a4"};
+
+ // All actions should be displayed as DOUBLE-line smart action buttons.
+ ViewGroup expectedView = buildExpectedView(new String[] {}, 1 /* lineCount */,
+ createActions(new String[] {"a1", "a2", "a4"}));
+ expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+
+ setSmartRepliesAndActions(choices, actions);
+ mView.measure(
+ MeasureSpec.makeMeasureSpec(expectedView.getMeasuredWidth(), MeasureSpec.AT_MOST),
+ MeasureSpec.UNSPECIFIED);
+
+ assertEqualMeasures(expectedView, mView);
+ assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0));
+ assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(1));
+ assertReplyButtonHidden(mView.getChildAt(2));
+ assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(2), mView.getChildAt(3));
+ }
+
+ /**
+ * Test to ensure that we try to add all possible replies - if we find one reply that's too
+ * long we just skip that one rather than quitting altogether.
+ */
+ @Test
+ public void testMeasure_skipTooLongReplies() {
+ String[] choices = new String[] {
+ "r1", "r2", "this reply is soooooooo long it's ridiculous", "r4"};
+ String[] actions = new String[] {};
+
+ // All replies should be displayed as single-line smart reply buttons.
+ ViewGroup expectedView = buildExpectedView(new String[] {"r1", "r2", "r4"},
+ 1 /* lineCount */, Collections.emptyList());
+ expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+
+ setSmartRepliesAndActions(choices, actions);
+ mView.measure(
+ MeasureSpec.makeMeasureSpec(expectedView.getMeasuredWidth(), MeasureSpec.AT_MOST),
+ MeasureSpec.UNSPECIFIED);
+
+ assertEqualMeasures(expectedView, mView);
+ assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0));
+ assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(1));
+ assertReplyButtonHidden(mView.getChildAt(2));
+ assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(2), mView.getChildAt(3));
+ }
+
+ /**
+ * Test to ensure that we try to add all possible replies and actions - if we find a reply or
+ * action that's too long we just skip that one rather than quitting altogether.
+ */
+ @Test
+ public void testMeasure_skipTooLongRepliesAndActions() {
+ String[] choices = new String[] {
+ "r1", "r2", "this reply is soooooooo long it's ridiculous", "r4"};
+ String[] actions = new String[] {
+ "a1", "ThisActionIsSooooooooLongItsRidiculousIPromise"};
+
+ // All replies should be displayed as single-line smart reply buttons.
+ ViewGroup expectedView = buildExpectedView(new String[] {"r1", "r2", "r4"},
+ 1 /* lineCount */, createActions(new String[] {"a1"}));
+ expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+
+ setSmartRepliesAndActions(choices, actions);
+ mView.measure(
+ MeasureSpec.makeMeasureSpec(expectedView.getMeasuredWidth(), MeasureSpec.AT_MOST),
+ MeasureSpec.UNSPECIFIED);
+
+ assertEqualMeasures(expectedView, mView);
+ assertReplyButtonShownWithEqualMeasures(
+ expectedView.getChildAt(0), mView.getChildAt(0)); // r1
+ assertReplyButtonShownWithEqualMeasures(
+ expectedView.getChildAt(1), mView.getChildAt(1)); // r2
+ assertReplyButtonHidden(mView.getChildAt(2)); // long reply
+ assertReplyButtonShownWithEqualMeasures(
+ expectedView.getChildAt(2), mView.getChildAt(3)); // r4
+ assertReplyButtonShownWithEqualMeasures(
+ expectedView.getChildAt(3), mView.getChildAt(4)); // a1
+ assertReplyButtonHidden(mView.getChildAt(5)); // long action
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpaper/AodMaskViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpaper/AodMaskViewTest.java
new file mode 100644
index 0000000..c44a366
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpaper/AodMaskViewTest.java
@@ -0,0 +1,128 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.wallpaper;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.RectF;
+import android.hardware.display.DisplayManager;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.FeatureFlagUtils;
+import android.view.DisplayInfo;
+import android.view.WindowManager;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AodMaskViewTest extends SysuiTestCase {
+ private AodMaskView mMaskView;
+ private DisplayInfo mDisplayInfo;
+ private ImageWallpaperTransformer mTransformer;
+
+ @Before
+ public void setUp() throws Exception {
+ DisplayManager displayManager =
+ spy((DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE));
+ doNothing().when(displayManager).registerDisplayListener(any(), any());
+ mContext.addMockSystemService(DisplayManager.class, displayManager);
+
+ WallpaperManager wallpaperManager =
+ spy((WallpaperManager) mContext.getSystemService(Context.WALLPAPER_SERVICE));
+ doReturn(null).when(wallpaperManager).getWallpaperInfo();
+ mContext.addMockSystemService(WallpaperManager.class, wallpaperManager);
+
+ mTransformer = spy(new ImageWallpaperTransformer(null /* listener */));
+ mMaskView = spy(new AodMaskView(getContext(), null /* attrs */, mTransformer));
+ mDisplayInfo = new DisplayInfo();
+
+ ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE))
+ .getDefaultDisplay().getDisplayInfo(mDisplayInfo);
+
+ FeatureFlagUtils.setEnabled(
+ mContext, FeatureFlagUtils.AOD_IMAGEWALLPAPER_ENABLED, true);
+ }
+
+ @After
+ public void tearDown() {
+ FeatureFlagUtils.setEnabled(
+ mContext, FeatureFlagUtils.AOD_IMAGEWALLPAPER_ENABLED, false);
+ }
+
+ @Test
+ public void testCreateMaskView_TransformerIsNotNull() {
+ assertNotNull("mTransformer should not be null", mTransformer);
+ }
+
+ @Test
+ public void testAodMaskView_ShouldNotClickable() {
+ assertFalse("MaskView should not be clickable", mMaskView.isClickable());
+ }
+
+ @Test
+ public void testAodMaskView_OnSizeChange_ShouldUpdateTransformerOffsets() {
+ mMaskView.onSizeChanged(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight, 0, 0);
+ verify(mTransformer, times(1)).updateOffsets();
+ }
+
+ @Test
+ public void testAodMaskView_OnDraw_ShouldDrawTransformedImage() {
+ Canvas c = new Canvas();
+ RectF bounds = new RectF(0, 0, mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
+ mMaskView.onSizeChanged(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight, 0, 0);
+ mMaskView.onStatePreChange(0, 1);
+ mMaskView.onDraw(c);
+ verify(mTransformer, times(1)).drawTransformedImage(c, null, null, bounds);
+ }
+
+ @Test
+ public void testAodMaskView_IsDozing_ShouldUpdateAmbientModeState() {
+ doNothing().when(mMaskView).setAnimatorProperty(anyBoolean());
+ mMaskView.onStatePreChange(0, 1);
+ mMaskView.onDozingChanged(true);
+ verify(mTransformer, times(1)).updateAmbientModeState(true);
+ }
+
+ @Test
+ public void testAodMaskView_IsDozing_ShouldDoTransitionOrDrawFinalFrame() {
+ doNothing().when(mMaskView).setAnimatorProperty(anyBoolean());
+ mMaskView.onStatePreChange(0, 1);
+ mMaskView.onDozingChanged(true);
+ mMaskView.onStatePostChange();
+ mMaskView.onDozingChanged(false);
+ verify(mMaskView, times(1)).invalidate();
+ verify(mMaskView, times(1)).setAnimatorProperty(false);
+ }
+
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpaper/ImageWallpaperTransformerTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpaper/ImageWallpaperTransformerTest.java
new file mode 100644
index 0000000..55b0aae
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpaper/ImageWallpaperTransformerTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2009 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.wallpaper;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.DisplayInfo;
+import android.view.WindowManager;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ImageWallpaperTransformerTest extends SysuiTestCase {
+ private DisplayInfo mDisplayInfo;
+ private Bitmap mBitmap;
+ private Canvas mCanvas;
+ private RectF mDestination;
+
+ @Before
+ public void setUp() throws Exception {
+ mDisplayInfo = new DisplayInfo();
+ ((WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE))
+ .getDefaultDisplay().getDisplayInfo(mDisplayInfo);
+ int dimension = Math.max(mDisplayInfo.logicalHeight, mDisplayInfo.logicalWidth);
+ mBitmap = Bitmap.createBitmap(dimension, dimension, Bitmap.Config.ARGB_8888);
+ mCanvas = new Canvas(mBitmap);
+ mCanvas.drawColor(Color.RED);
+ mDestination = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
+ }
+
+ @Test
+ public void testVignetteFilter() {
+ VignetteFilter vignette = new VignetteFilter();
+
+ ImageWallpaperTransformer transformer = getTransformer(vignette);
+ transformer.drawTransformedImage(mCanvas, mBitmap, null, mDestination);
+
+ PointF center = vignette.getCenterPoint();
+ int p1 = mBitmap.getPixel((int) center.x, (int) center.y);
+ int p2 = mBitmap.getPixel(0, 0);
+ int p3 = mBitmap.getPixel(mBitmap.getWidth() - 1, mBitmap.getHeight() - 1);
+
+ assertThat(p1).isEqualTo(Color.RED);
+ assertThat(p2 | p3).isEqualTo(Color.BLACK);
+ }
+
+ @Test
+ public void testScrimFilter() {
+ getTransformer(new ScrimFilter())
+ .drawTransformedImage(mCanvas, mBitmap, null, mDestination);
+
+ int pixel = mBitmap.getPixel(0, 0);
+
+ // 0xff4d0000 is the result of 70% alpha pre-multiplied which is 0.7*(0,0,0)+0.3*(255,0,0).
+ assertThat(pixel).isEqualTo(0xff4d0000);
+ }
+
+ private ImageWallpaperTransformer getTransformer(ImageWallpaperFilter filter) {
+ ImageWallpaperTransformer transformer = new ImageWallpaperTransformer(null);
+ transformer.addFilter(filter);
+ transformer.updateDisplayInfo(mDisplayInfo);
+ transformer.updateOffsets();
+ transformer.updateAmbientModeState(true);
+ return transformer;
+ }
+}
diff --git a/packages/overlays/AccentColorBlackOverlay/res/values/strings.xml b/packages/overlays/AccentColorBlackOverlay/res/values/strings.xml
index baf09b1..da10361 100644
--- a/packages/overlays/AccentColorBlackOverlay/res/values/strings.xml
+++ b/packages/overlays/AccentColorBlackOverlay/res/values/strings.xml
@@ -18,6 +18,6 @@
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- Black accent color name application label. [CHAR LIMIT=50] -->
- <string name="accent_color_black_overlay" translatable="false">Black Accent Color</string>
+ <string name="accent_color_black_overlay" translatable="false">Black</string>
</resources>
diff --git a/packages/overlays/AccentColorGreenOverlay/res/values/strings.xml b/packages/overlays/AccentColorGreenOverlay/res/values/strings.xml
index 4de344c..623a1da 100644
--- a/packages/overlays/AccentColorGreenOverlay/res/values/strings.xml
+++ b/packages/overlays/AccentColorGreenOverlay/res/values/strings.xml
@@ -18,6 +18,6 @@
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- Green accent color name application label. [CHAR LIMIT=50] -->
- <string name="accent_color_green_overlay" translatable="false">Green Accent Color</string>
+ <string name="accent_color_green_overlay" translatable="false">Green</string>
</resources>
diff --git a/packages/overlays/AccentColorPurpleOverlay/res/values/strings.xml b/packages/overlays/AccentColorPurpleOverlay/res/values/strings.xml
index d1eb95a..d1c7168 100644
--- a/packages/overlays/AccentColorPurpleOverlay/res/values/strings.xml
+++ b/packages/overlays/AccentColorPurpleOverlay/res/values/strings.xml
@@ -18,6 +18,6 @@
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- Purple accent color name application label. [CHAR LIMIT=50] -->
- <string name="accent_color_purple_overlay" translatable="false">Purple Accent Color</string>
+ <string name="accent_color_purple_overlay" translatable="false">Purple</string>
</resources>
diff --git a/packages/overlays/FontArbutusSourceOverlay/res/values/config.xml b/packages/overlays/FontArbutusSourceOverlay/res/values/config.xml
index 165aab2..a6aa64c 100644
--- a/packages/overlays/FontArbutusSourceOverlay/res/values/config.xml
+++ b/packages/overlays/FontArbutusSourceOverlay/res/values/config.xml
@@ -19,7 +19,7 @@
<!-- Name of a font family to use for body text. -->
<string name="config_bodyFontFamily" translatable="false">source-sans-pro</string>
<!-- Name of a font family to use for medium body text. -->
- <string name="config_bodyFontFamilyMedium" translatable="false">source-sans-pro-medium</string>
+ <string name="config_bodyFontFamilyMedium" translatable="false">source-sans-pro-semi-bold</string>
<!-- Name of a font family to use for headlines. If empty, falls back to platform default -->
<string name="config_headlineFontFamily" translatable="false">arbutus-slab</string>
<!-- Name of the font family used for system surfaces where the font should use medium weight -->
diff --git a/packages/overlays/FontArvoLatoOverlay/res/values/config.xml b/packages/overlays/FontArvoLatoOverlay/res/values/config.xml
index 229c578..4e70d72 100644
--- a/packages/overlays/FontArvoLatoOverlay/res/values/config.xml
+++ b/packages/overlays/FontArvoLatoOverlay/res/values/config.xml
@@ -19,10 +19,10 @@
<!-- Name of a font family to use for body text. -->
<string name="config_bodyFontFamily" translatable="false">lato</string>
<!-- Name of a font family to use for medium body text. -->
- <string name="config_bodyFontFamilyMedium" translatable="false">lato-medium</string>
+ <string name="config_bodyFontFamilyMedium" translatable="false">lato-bold</string>
<!-- Name of a font family to use for headlines. If empty, falls back to platform default -->
<string name="config_headlineFontFamily" translatable="false">arvo</string>
<!-- Name of the font family used for system surfaces where the font should use medium weight -->
- <string name="config_headlineFontFamilyMedium" translatable="false">arvo-medium</string>
+ <string name="config_headlineFontFamilyMedium" translatable="false">arvo-bold</string>
</resources>
diff --git a/packages/overlays/IconShapeRoundedRectOverlay/res/values/strings.xml b/packages/overlays/IconShapeRoundedRectOverlay/res/values/strings.xml
index dc5c196..3c4c24d 100644
--- a/packages/overlays/IconShapeRoundedRectOverlay/res/values/strings.xml
+++ b/packages/overlays/IconShapeRoundedRectOverlay/res/values/strings.xml
@@ -18,6 +18,6 @@
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- Rounded corner rectangle overlay -->
- <string name="icon_shape_roundedrect_overlay" translatable="false">RoundedRectangle Icons</string>
+ <string name="icon_shape_roundedrect_overlay" translatable="false">Rounded Rectangle</string>
</resources>
diff --git a/packages/overlays/IconShapeSquareOverlay/res/values/strings.xml b/packages/overlays/IconShapeSquareOverlay/res/values/strings.xml
index 4fd39ff26..5772165 100644
--- a/packages/overlays/IconShapeSquareOverlay/res/values/strings.xml
+++ b/packages/overlays/IconShapeSquareOverlay/res/values/strings.xml
@@ -18,6 +18,6 @@
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- Square icon overlay -->
- <string name="icon_shape_square_overlay" translatable="false">Square Icons</string>
+ <string name="icon_shape_square_overlay" translatable="false">Square</string>
</resources>
diff --git a/packages/overlays/IconShapeSquircleOverlay/res/values/strings.xml b/packages/overlays/IconShapeSquircleOverlay/res/values/strings.xml
index b7c001c..028eccb 100644
--- a/packages/overlays/IconShapeSquircleOverlay/res/values/strings.xml
+++ b/packages/overlays/IconShapeSquircleOverlay/res/values/strings.xml
@@ -18,6 +18,6 @@
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- Squircle icon shape overlay -->
- <string name="icon_shape_squircle_overlay" translatable="false">Square Icons</string>
+ <string name="icon_shape_squircle_overlay" translatable="false">Squircle</string>
</resources>
diff --git a/packages/overlays/IconShapeTeardropOverlay/res/values/strings.xml b/packages/overlays/IconShapeTeardropOverlay/res/values/strings.xml
index d946ee8..db9fa98 100644
--- a/packages/overlays/IconShapeTeardropOverlay/res/values/strings.xml
+++ b/packages/overlays/IconShapeTeardropOverlay/res/values/strings.xml
@@ -18,6 +18,6 @@
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- Teardrop icon overlay -->
- <string name="icon_shape_teardrop_overlay" translatable="false">Teardrop Icons</string>
+ <string name="icon_shape_teardrop_overlay" translatable="false">Teardrop</string>
</resources>
diff --git a/packages/services/PacProcessor/jni/com_android_pacprocessor_PacNative.cpp b/packages/services/PacProcessor/jni/com_android_pacprocessor_PacNative.cpp
index 846ff25..d969c69 100644
--- a/packages/services/PacProcessor/jni/com_android_pacprocessor_PacNative.cpp
+++ b/packages/services/PacProcessor/jni/com_android_pacprocessor_PacNative.cpp
@@ -16,6 +16,9 @@
#define LOG_TAG "PacProcessor"
+#include <stdlib.h>
+#include <string>
+
#include <utils/Log.h>
#include <utils/Mutex.h>
#include "android_runtime/AndroidRuntime.h"
@@ -23,40 +26,24 @@
#include "jni.h"
#include <nativehelper/JNIHelp.h>
-#include "proxy_resolver_v8.h"
+#include "proxy_resolver_v8_wrapper.h"
namespace android {
-class ProxyErrorLogger : public net::ProxyErrorListener {
-public:
- ~ProxyErrorLogger() {
-
- }
- void AlertMessage(String16 message) {
- String8 str(message);
- ALOGD("Alert: %s", str.string());
- }
- void ErrorMessage(String16 message) {
- String8 str(message);
- ALOGE("Error: %s", str.string());
- }
-};
-
-net::ProxyResolverV8* proxyResolver = NULL;
-ProxyErrorLogger* logger = NULL;
+ProxyResolverV8Handle* proxyResolver = NULL;
bool pacSet = false;
-String16 jstringToString16(JNIEnv* env, jstring jstr) {
+std::u16string jstringToString16(JNIEnv* env, jstring jstr) {
const jchar* str = env->GetStringCritical(jstr, 0);
- String16 str16(reinterpret_cast<const char16_t*>(str),
+ std::u16string str16(reinterpret_cast<const char16_t*>(str),
env->GetStringLength(jstr));
env->ReleaseStringCritical(jstr, str);
return str16;
}
-jstring string16ToJstring(JNIEnv* env, String16 string) {
- const char16_t* str = string.string();
- size_t len = string.size();
+jstring string16ToJstring(JNIEnv* env, std::u16string string) {
+ const char16_t* str = string.data();
+ size_t len = string.length();
return env->NewString(reinterpret_cast<const jchar*>(str), len);
}
@@ -64,9 +51,7 @@
static jboolean com_android_pacprocessor_PacNative_createV8ParserNativeLocked(JNIEnv* /* env */,
jobject) {
if (proxyResolver == NULL) {
- logger = new ProxyErrorLogger();
- proxyResolver = new net::ProxyResolverV8(net::ProxyResolverJSBindings::CreateDefault(),
- logger);
+ proxyResolver = ProxyResolverV8Handle_new();
pacSet = false;
return JNI_FALSE;
}
@@ -76,9 +61,7 @@
static jboolean com_android_pacprocessor_PacNative_destroyV8ParserNativeLocked(JNIEnv* /* env */,
jobject) {
if (proxyResolver != NULL) {
- delete logger;
- delete proxyResolver;
- logger = NULL;
+ ProxyResolverV8Handle_delete(proxyResolver);
proxyResolver = NULL;
return JNI_FALSE;
}
@@ -87,14 +70,14 @@
static jboolean com_android_pacprocessor_PacNative_setProxyScriptNativeLocked(JNIEnv* env, jobject,
jstring script) {
- String16 script16 = jstringToString16(env, script);
+ std::u16string script16 = jstringToString16(env, script);
if (proxyResolver == NULL) {
ALOGE("V8 Parser not started when setting PAC script");
return JNI_TRUE;
}
- if (proxyResolver->SetPacScript(script16) != OK) {
+ if (ProxyResolverV8Handle_SetPacScript(proxyResolver, script16.data()) != OK) {
ALOGE("Unable to set PAC script");
return JNI_TRUE;
}
@@ -105,9 +88,8 @@
static jstring com_android_pacprocessor_PacNative_makeProxyRequestNativeLocked(JNIEnv* env, jobject,
jstring url, jstring host) {
- String16 url16 = jstringToString16(env, url);
- String16 host16 = jstringToString16(env, host);
- String16 ret;
+ std::u16string url16 = jstringToString16(env, url);
+ std::u16string host16 = jstringToString16(env, host);
if (proxyResolver == NULL) {
ALOGE("V8 Parser not initialized when running PAC script");
@@ -119,12 +101,14 @@
return NULL;
}
- if (proxyResolver->GetProxyForURL(url16, host16, &ret) != OK) {
- String8 ret8(ret);
- ALOGE("Error Running PAC: %s", ret8.string());
+ std::unique_ptr<char16_t, decltype(&free)> result = std::unique_ptr<char16_t, decltype(&free)>(
+ ProxyResolverV8Handle_GetProxyForURL(proxyResolver, url16.data(), host16.data()), &free);
+ if (result.get() == NULL) {
+ ALOGE("Error Running PAC");
return NULL;
}
+ std::u16string ret(result.get());
jstring jret = string16ToJstring(env, ret);
return jret;
diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index 3180b47..aff5e13 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -3684,7 +3684,7 @@
ACTION_SETTINGS_TILE_CLICK = 830;
// OPEN: Notification unsnoozed. CLOSE: Notification snoozed. UPDATE: snoozed notification
- // updated
+ // updated. TYPE_DISMISS: snoozing canceled due to data being cleared on package
// CATEGORY: NOTIFICATION
// OS: O
NOTIFICATION_SNOOZED = 831;
@@ -5325,7 +5325,7 @@
// OS: P
ACCESSIBILITY_VIBRATION = 1292;
- // OPEN: Settings > Accessibility > Vibration > Ring & notification vibration
+ // OPEN: Settings > Accessibility > Vibration > Notification vibration
// CATEGORY: SETTINGS
// OS: P
ACCESSIBILITY_VIBRATION_NOTIFICATION = 1293;
@@ -6730,6 +6730,44 @@
// OS: Q
ZEN_CUSTOM_SETTINGS_DIALOG = 1612;
+ // OPEN: Settings > Developer Options > Game Update Packages
+ // CATEGORY: SETTINGS
+ // OS: Q
+ SETTINGS_GUP_DASHBOARD = 1613;
+
+ // CATEGORY: The category for all actions relating to language detection logging.
+ // OS: Q
+ LANGUAGE_DETECTION = 1614;
+
+ // CATEGORY: The category for all actions relating to conversation actions logging.
+ // OS: Q
+ CONVERSATION_ACTIONS = 1615;
+
+ // ACTION: Actions from a text classifier are shown to user.
+ // CATEGORY: CONVERSATION_ACTIONS
+ // OS: Q
+ ACTION_TEXT_CLASSIFIER_ACTIONS_SHOWN = 1616;
+
+ // ACTION: Event time of a text classifier event in unix timestamp.
+ // CATEGORY: CONVERSATION_ACTIONS, LANGUAGE_DETECTION
+ // OS: Q
+ FIELD_TEXT_CLASSIFIER_EVENT_TIME = 1617;
+
+ // ACTION: Users compose their own replies instead of using suggested ones.
+ // CATEGORY: CONVERSATION_ACTIONS
+ // OS: Q
+ ACTION_TEXT_CLASSIFIER_MANUAL_REPLY = 1618;
+
+ // ACTION: Text classifier generates an action.
+ // CATEGORY: CONVERSATION_ACTIONS
+ // OS: Q
+ ACTION_TEXT_CLASSIFIER_ACTIONS_GENERATED = 1619;
+
+ // OPEN: Settings > Accessibility > Vibration > Ring vibration
+ // CATEGORY: SETTINGS
+ // OS: Q
+ ACCESSIBILITY_VIBRATION_RING = 1620;
+
// ---- End Q Constants, all Q constants go above this line ----
// Add new aosp constants above this line.
diff --git a/services/Android.bp b/services/Android.bp
index 58a0997..31385ed 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -17,17 +17,21 @@
static_libs: [
"services.core",
"services.accessibility",
+ "services.appprediction",
"services.appwidget",
"services.autofill",
"services.backup",
"services.companion",
"services.contentcapture",
+ "services.contentsuggestions",
"services.coverage",
"services.devicepolicy",
+ "services.ipmemorystore",
"services.midi",
"services.net",
"services.print",
"services.restrictions",
+ "services.startop",
"services.usage",
"services.usb",
"services.voiceinteraction",
diff --git a/services/appprediction/Android.bp b/services/appprediction/Android.bp
new file mode 100644
index 0000000..a7be587
--- /dev/null
+++ b/services/appprediction/Android.bp
@@ -0,0 +1,5 @@
+java_library_static {
+ name: "services.appprediction",
+ srcs: ["java/**/*.java"],
+ libs: ["services.core"],
+}
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
new file mode 100644
index 0000000..833eaa0
--- /dev/null
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
@@ -0,0 +1,156 @@
+/*
+ * 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.
+ */
+
+package com.android.server.appprediction;
+
+import static android.Manifest.permission.MANAGE_APP_PREDICTIONS;
+import static android.content.Context.APP_PREDICTION_SERVICE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionSessionId;
+import android.app.prediction.AppTargetEvent;
+import android.app.prediction.IPredictionCallback;
+import android.app.prediction.IPredictionManager;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.os.UserHandle;
+
+import com.android.server.infra.AbstractMasterSystemService;
+import com.android.server.infra.FrameworkResourcesServiceNameResolver;
+
+import java.io.FileDescriptor;
+import java.util.function.Consumer;
+
+/**
+ * A service used to predict app and shortcut usage.
+ *
+ * <p>The data collected by this service can be analyzed and combined with other sources to provide
+ * predictions in different areas of the system such as Launcher and Share sheet.
+ */
+public class AppPredictionManagerService extends
+ AbstractMasterSystemService<AppPredictionManagerService, AppPredictionPerUserService> {
+
+ private static final String TAG = AppPredictionManagerService.class.getSimpleName();
+
+ private static final int MAX_TEMP_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes
+
+ public AppPredictionManagerService(Context context) {
+ super(context, new FrameworkResourcesServiceNameResolver(context,
+ com.android.internal.R.string.config_defaultAppPredictionService), null);
+ }
+
+ @Override
+ protected AppPredictionPerUserService newServiceLocked(int resolvedUserId, boolean disabled) {
+ return new AppPredictionPerUserService(this, mLock, resolvedUserId);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(APP_PREDICTION_SERVICE, new PredictionManagerServiceStub());
+ }
+
+ @Override
+ protected void enforceCallingPermissionForManagement() {
+ getContext().enforceCallingPermission(MANAGE_APP_PREDICTIONS, TAG);
+ }
+
+ @Override
+ protected int getMaximumTemporaryServiceDurationMs() {
+ return MAX_TEMP_SERVICE_DURATION_MS;
+ }
+
+ private class PredictionManagerServiceStub extends IPredictionManager.Stub {
+
+ @Override
+ public void createPredictionSession(@NonNull AppPredictionContext context,
+ @NonNull AppPredictionSessionId sessionId) {
+ runForUserLocked((service) ->
+ service.onCreatePredictionSessionLocked(context, sessionId));
+ }
+
+ @Override
+ public void notifyAppTargetEvent(@NonNull AppPredictionSessionId sessionId,
+ @NonNull AppTargetEvent event) {
+ runForUserLocked((service) -> service.notifyAppTargetEventLocked(sessionId, event));
+ }
+
+ @Override
+ public void notifyLocationShown(@NonNull AppPredictionSessionId sessionId,
+ @NonNull String launchLocation, @NonNull ParceledListSlice targetIds) {
+ runForUserLocked((service) ->
+ service.notifyLocationShownLocked(sessionId, launchLocation, targetIds));
+ }
+
+ @Override
+ public void sortAppTargets(@NonNull AppPredictionSessionId sessionId,
+ @NonNull ParceledListSlice targets,
+ IPredictionCallback callback) {
+ runForUserLocked((service) ->
+ service.sortAppTargetsLocked(sessionId, targets, callback));
+ }
+
+ @Override
+ public void registerPredictionUpdates(@NonNull AppPredictionSessionId sessionId,
+ @NonNull IPredictionCallback callback) {
+ runForUserLocked((service) ->
+ service.registerPredictionUpdatesLocked(sessionId, callback));
+ }
+
+ public void unregisterPredictionUpdates(@NonNull AppPredictionSessionId sessionId,
+ @NonNull IPredictionCallback callback) {
+ runForUserLocked((service) ->
+ service.unregisterPredictionUpdatesLocked(sessionId, callback));
+ }
+
+ @Override
+ public void requestPredictionUpdate(@NonNull AppPredictionSessionId sessionId) {
+ runForUserLocked((service) -> service.requestPredictionUpdateLocked(sessionId));
+ }
+
+ @Override
+ public void onDestroyPredictionSession(@NonNull AppPredictionSessionId sessionId) {
+ runForUserLocked((service) -> service.onDestroyPredictionSessionLocked(sessionId));
+ }
+
+ public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+ @Nullable FileDescriptor err,
+ @NonNull String[] args, @Nullable ShellCallback callback,
+ @NonNull ResultReceiver resultReceiver) throws RemoteException {
+ new AppPredictionManagerServiceShellCommand(AppPredictionManagerService.this)
+ .exec(this, in, out, err, args, callback, resultReceiver);
+ }
+
+ private void runForUserLocked(@NonNull Consumer<AppPredictionPerUserService> c) {
+ final int userId = UserHandle.getCallingUserId();
+ // TODO(b/111701043): Determine what permission model we want for this
+ long origId = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ final AppPredictionPerUserService service = getServiceForUserLocked(userId);
+ c.accept(service);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+ }
+}
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerServiceShellCommand.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerServiceShellCommand.java
new file mode 100644
index 0000000..ed7cc67
--- /dev/null
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerServiceShellCommand.java
@@ -0,0 +1,83 @@
+/*
+ * 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.
+ */
+
+package com.android.server.appprediction;
+
+import android.annotation.NonNull;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+/**
+ * The shell command implementation for the AppPredictionManagerService.
+ */
+public class AppPredictionManagerServiceShellCommand extends ShellCommand {
+
+ private static final String TAG =
+ AppPredictionManagerServiceShellCommand.class.getSimpleName();
+
+ private final AppPredictionManagerService mService;
+
+ public AppPredictionManagerServiceShellCommand(@NonNull AppPredictionManagerService service) {
+ mService = service;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+ final PrintWriter pw = getOutPrintWriter();
+ switch (cmd) {
+ case "set": {
+ final String what = getNextArgRequired();
+ switch (what) {
+ case "temporary-service": {
+ final int userId = Integer.parseInt(getNextArgRequired());
+ String serviceName = getNextArg();
+ if (serviceName == null) {
+ mService.resetTemporaryService(userId);
+ return 0;
+ }
+ final int duration = Integer.parseInt(getNextArgRequired());
+ mService.setTemporaryService(userId, serviceName, duration);
+ pw.println("AppPredictionService temporarily set to " + serviceName
+ + " for " + duration + "ms");
+ break;
+ }
+ }
+ }
+ break;
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ return 0;
+ }
+
+ @Override
+ public void onHelp() {
+ try (PrintWriter pw = getOutPrintWriter()) {
+ pw.println("AppPredictionManagerService commands:");
+ pw.println(" help");
+ pw.println(" Prints this help text.");
+ pw.println("");
+ pw.println(" set temporary-service USER_ID [COMPONENT_NAME DURATION]");
+ pw.println(" Temporarily (for DURATION ms) changes the service implemtation.");
+ pw.println(" To reset, call with just the USER_ID argument.");
+ pw.println("");
+ }
+ }
+}
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
new file mode 100644
index 0000000..24d592c
--- /dev/null
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
@@ -0,0 +1,218 @@
+/*
+ * 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.
+ */
+
+package com.android.server.appprediction;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppGlobals;
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionSessionId;
+import android.app.prediction.AppTargetEvent;
+import android.app.prediction.IPredictionCallback;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.ServiceInfo;
+import android.os.RemoteException;
+import android.service.appprediction.AppPredictionService;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.infra.AbstractPerUserSystemService;
+
+/**
+ * Per-user instance of {@link AppPredictionManagerService}.
+ */
+public class AppPredictionPerUserService extends
+ AbstractPerUserSystemService<AppPredictionPerUserService, AppPredictionManagerService>
+ implements RemoteAppPredictionService.RemoteAppPredictionServiceCallbacks {
+
+ private static final String TAG = AppPredictionPerUserService.class.getSimpleName();
+
+ @Nullable
+ @GuardedBy("mLock")
+ private RemoteAppPredictionService mRemoteService;
+
+ protected AppPredictionPerUserService(AppPredictionManagerService master,
+ Object lock, int userId) {
+ super(master, lock, userId);
+ }
+
+ @Override // from PerUserSystemService
+ protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
+ throws NameNotFoundException {
+
+ ServiceInfo si;
+ try {
+ si = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
+ PackageManager.GET_META_DATA, mUserId);
+ } catch (RemoteException e) {
+ throw new NameNotFoundException("Could not get service for " + serviceComponent);
+ }
+ // TODO(b/111701043): must check that either the service is from a system component,
+ // or it matches a service set by shell cmd (so it can be used on CTS tests and when
+ // OEMs are implementing the real service and also verify the proper permissions
+ return si;
+ }
+
+ @GuardedBy("mLock")
+ @Override // from PerUserSystemService
+ protected boolean updateLocked(boolean disabled) {
+ final boolean enabledChanged = super.updateLocked(disabled);
+ if (enabledChanged) {
+ if (!isEnabledLocked()) {
+ // Clear the remote service for the next call
+ mRemoteService = null;
+ }
+ }
+ return enabledChanged;
+ }
+
+ /**
+ * Notifies the service of a new prediction session.
+ */
+ @GuardedBy("mLock")
+ public void onCreatePredictionSessionLocked(@NonNull AppPredictionContext context,
+ @NonNull AppPredictionSessionId sessionId) {
+ final RemoteAppPredictionService service = getRemoteServiceLocked();
+ if (service != null) {
+ service.onCreatePredictionSession(context, sessionId);
+ }
+ }
+
+ /**
+ * Records an app target event to the service.
+ */
+ @GuardedBy("mLock")
+ public void notifyAppTargetEventLocked(@NonNull AppPredictionSessionId sessionId,
+ @NonNull AppTargetEvent event) {
+ final RemoteAppPredictionService service = getRemoteServiceLocked();
+ if (service != null) {
+ service.notifyAppTargetEvent(sessionId, event);
+ }
+ }
+
+ /**
+ * Records when a launch location is shown.
+ */
+ @GuardedBy("mLock")
+ public void notifyLocationShownLocked(@NonNull AppPredictionSessionId sessionId,
+ @NonNull String launchLocation, @NonNull ParceledListSlice targetIds) {
+ final RemoteAppPredictionService service = getRemoteServiceLocked();
+ if (service != null) {
+ service.notifyLocationShown(sessionId, launchLocation, targetIds);
+ }
+ }
+
+ /**
+ * Requests the service to sort a list of apps or shortcuts.
+ */
+ @GuardedBy("mLock")
+ public void sortAppTargetsLocked(@NonNull AppPredictionSessionId sessionId,
+ @NonNull ParceledListSlice targets, @NonNull IPredictionCallback callback) {
+ final RemoteAppPredictionService service = getRemoteServiceLocked();
+ if (service != null) {
+ service.sortAppTargets(sessionId, targets, callback);
+ }
+ }
+
+ /**
+ * Registers a callback for continuous updates of predicted apps or shortcuts.
+ */
+ @GuardedBy("mLock")
+ public void registerPredictionUpdatesLocked(@NonNull AppPredictionSessionId sessionId,
+ @NonNull IPredictionCallback callback) {
+ final RemoteAppPredictionService service = getRemoteServiceLocked();
+ if (service != null) {
+ service.registerPredictionUpdates(sessionId, callback);
+ }
+ }
+
+ /**
+ * Unregisters a callback for continuous updates of predicted apps or shortcuts.
+ */
+ @GuardedBy("mLock")
+ public void unregisterPredictionUpdatesLocked(@NonNull AppPredictionSessionId sessionId,
+ @NonNull IPredictionCallback callback) {
+ final RemoteAppPredictionService service = getRemoteServiceLocked();
+ if (service != null) {
+ service.unregisterPredictionUpdates(sessionId, callback);
+ }
+ }
+
+ /**
+ * Requests a new set of predicted apps or shortcuts.
+ */
+ @GuardedBy("mLock")
+ public void requestPredictionUpdateLocked(@NonNull AppPredictionSessionId sessionId) {
+ final RemoteAppPredictionService service = getRemoteServiceLocked();
+ if (service != null) {
+ service.requestPredictionUpdate(sessionId);
+ }
+ }
+
+ /**
+ * Notifies the service of the end of an existing prediction session.
+ */
+ @GuardedBy("mLock")
+ public void onDestroyPredictionSessionLocked(@NonNull AppPredictionSessionId sessionId) {
+ final RemoteAppPredictionService service = getRemoteServiceLocked();
+ if (service != null) {
+ service.onDestroyPredictionSession(sessionId);
+ }
+ }
+
+ @Override
+ public void onFailureOrTimeout(boolean timedOut) {
+ if (isDebug()) {
+ Slog.d(TAG, "onFailureOrTimeout(): timed out=" + timedOut);
+ }
+
+ // Do nothing, we are just proxying to the prediction service
+ }
+
+ @Override
+ public void onServiceDied(RemoteAppPredictionService service) {
+ if (isDebug()) {
+ Slog.d(TAG, "onServiceDied():");
+ }
+
+ // Do nothing, we are just proxying to the prediction service
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ private RemoteAppPredictionService getRemoteServiceLocked() {
+ if (mRemoteService == null) {
+ final String serviceName = getComponentNameLocked();
+ if (serviceName == null) {
+ if (mMaster.verbose) {
+ Slog.v(TAG, "getRemoteServiceLocked(): not set");
+ }
+ return null;
+ }
+ ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
+
+ mRemoteService = new RemoteAppPredictionService(getContext(),
+ AppPredictionService.SERVICE_INTERFACE, serviceComponent, mUserId, this,
+ mMaster.isBindInstantServiceAllowed(), mMaster.verbose);
+ }
+
+ return mRemoteService;
+ }
+}
diff --git a/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java b/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java
new file mode 100644
index 0000000..45ea86f
--- /dev/null
+++ b/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java
@@ -0,0 +1,142 @@
+/*
+ * 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.
+ */
+package com.android.server.appprediction;
+
+import android.annotation.NonNull;
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionSessionId;
+import android.app.prediction.AppTargetEvent;
+import android.app.prediction.IPredictionCallback;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.os.IBinder;
+import android.service.appprediction.IPredictionService;
+import android.text.format.DateUtils;
+
+import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
+
+
+/**
+ * Proxy to the {@link android.service.appprediction.AppPredictionService} implemention in another
+ * process.
+ */
+public class RemoteAppPredictionService extends
+ AbstractMultiplePendingRequestsRemoteService<RemoteAppPredictionService,
+ IPredictionService> {
+
+ private static final String TAG = "RemoteAppPredictionService";
+
+ private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS;
+
+ public RemoteAppPredictionService(Context context, String serviceInterface,
+ ComponentName componentName, int userId,
+ RemoteAppPredictionServiceCallbacks callback, boolean bindInstantServiceAllowed,
+ boolean verbose) {
+ super(context, serviceInterface, componentName, userId, callback, bindInstantServiceAllowed,
+ verbose, /* initialCapacity= */ 1);
+ }
+
+ @Override
+ protected IPredictionService getServiceInterface(IBinder service) {
+ return IPredictionService.Stub.asInterface(service);
+ }
+
+ @Override
+ protected long getTimeoutIdleBindMillis() {
+ return PERMANENT_BOUND_TIMEOUT_MS;
+ }
+
+ @Override
+ protected long getRemoteRequestMillis() {
+ return TIMEOUT_REMOTE_REQUEST_MILLIS;
+ }
+
+ /**
+ * Notifies the service of a new prediction session.
+ */
+ public void onCreatePredictionSession(@NonNull AppPredictionContext context,
+ @NonNull AppPredictionSessionId sessionId) {
+ scheduleAsyncRequest((s) -> s.onCreatePredictionSession(context, sessionId));
+ }
+
+ /**
+ * Records an app target event to the service.
+ */
+ public void notifyAppTargetEvent(@NonNull AppPredictionSessionId sessionId,
+ @NonNull AppTargetEvent event) {
+ scheduleAsyncRequest((s) -> s.notifyAppTargetEvent(sessionId, event));
+ }
+
+ /**
+ * Records when a launch location is shown.
+ */
+ public void notifyLocationShown(@NonNull AppPredictionSessionId sessionId,
+ @NonNull String launchLocation, @NonNull ParceledListSlice targetIds) {
+ scheduleAsyncRequest((s) -> s.notifyLocationShown(sessionId, launchLocation, targetIds));
+ }
+
+ /**
+ * Requests the service to sort a list of apps or shortcuts.
+ */
+ public void sortAppTargets(@NonNull AppPredictionSessionId sessionId,
+ @NonNull ParceledListSlice targets, @NonNull IPredictionCallback callback) {
+ scheduleAsyncRequest((s) -> s.sortAppTargets(sessionId, targets, callback));
+ }
+
+
+ /**
+ * Registers a callback for continuous updates of predicted apps or shortcuts.
+ */
+ public void registerPredictionUpdates(@NonNull AppPredictionSessionId sessionId,
+ @NonNull IPredictionCallback callback) {
+ scheduleAsyncRequest((s) -> s.registerPredictionUpdates(sessionId, callback));
+ }
+
+ /**
+ * Unregisters a callback for continuous updates of predicted apps or shortcuts.
+ */
+ public void unregisterPredictionUpdates(@NonNull AppPredictionSessionId sessionId,
+ @NonNull IPredictionCallback callback) {
+ scheduleAsyncRequest((s) -> s.unregisterPredictionUpdates(sessionId, callback));
+ }
+
+ /**
+ * Requests a new set of predicted apps or shortcuts.
+ */
+ public void requestPredictionUpdate(@NonNull AppPredictionSessionId sessionId) {
+ scheduleAsyncRequest((s) -> s.requestPredictionUpdate(sessionId));
+ }
+
+ /**
+ * Notifies the service of the end of an existing prediction session.
+ */
+ public void onDestroyPredictionSession(@NonNull AppPredictionSessionId sessionId) {
+ scheduleAsyncRequest((s) -> s.onDestroyPredictionSession(sessionId));
+ }
+
+ /**
+ * Failure callback
+ */
+ public interface RemoteAppPredictionServiceCallbacks
+ extends VultureCallback<RemoteAppPredictionService> {
+
+ /**
+ * Notifies a the failure or timeout of a remote call.
+ */
+ void onFailureOrTimeout(boolean timedOut);
+ }
+}
diff --git a/services/art-profile b/services/art-profile
index af9d7a9..7e4884b2 100644
--- a/services/art-profile
+++ b/services/art-profile
@@ -6970,6 +6970,7 @@
PLcom/android/server/am/ActivityManagerService$LocalService;->getMemoryStateForProcesses()Ljava/util/List;
PLcom/android/server/am/ActivityManagerService$LocalService;->getTopVisibleActivities()Ljava/util/List;
PLcom/android/server/am/ActivityManagerService$LocalService;->getUidProcessState(I)I
+PLcom/android/server/am/ActivityManagerService$LocalService;->isAppForeground(I)Z
PLcom/android/server/am/ActivityManagerService$LocalService;->isCallerRecents(I)Z
PLcom/android/server/am/ActivityManagerService$LocalService;->isRuntimeRestarted()Z
PLcom/android/server/am/ActivityManagerService$LocalService;->isSystemReady()Z
@@ -7125,7 +7126,6 @@
PLcom/android/server/am/ActivityManagerService;->installEncryptionUnawareProviders(I)V
PLcom/android/server/am/ActivityManagerService;->installSystemProviders()V
PLcom/android/server/am/ActivityManagerService;->isAllowedWhileBooting(Landroid/content/pm/ApplicationInfo;)Z
-PLcom/android/server/am/ActivityManagerService;->isAppForeground(I)Z
PLcom/android/server/am/ActivityManagerService;->isAssistDataAllowedOnCurrentActivity()Z
PLcom/android/server/am/ActivityManagerService;->isGetTasksAllowed(Ljava/lang/String;II)Z
PLcom/android/server/am/ActivityManagerService;->isInLockTaskMode()Z
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 1a6bee9..6cccd62 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -70,6 +70,7 @@
import com.android.internal.os.IResultReceiver;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
+import com.android.internal.util.SyncResultReceiver;
import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.autofill.ui.AutoFillUI;
@@ -606,15 +607,15 @@
}
private void send(@NonNull IResultReceiver receiver, @Nullable String value) {
- send(receiver, AutofillManager.SyncResultReceiver.bundleFor(value));
+ send(receiver, SyncResultReceiver.bundleFor(value));
}
private void send(@NonNull IResultReceiver receiver, @Nullable String[] value) {
- send(receiver, AutofillManager.SyncResultReceiver.bundleFor(value));
+ send(receiver, SyncResultReceiver.bundleFor(value));
}
private void send(@NonNull IResultReceiver receiver, @Nullable Parcelable value) {
- send(receiver, AutofillManager.SyncResultReceiver.bundleFor(value));
+ send(receiver, SyncResultReceiver.bundleFor(value));
}
private void send(@NonNull IResultReceiver receiver, boolean value) {
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 4b7efe1..a6bb049 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -870,8 +870,11 @@
}
pw.print(prefix); pw.print("Default component: "); pw.println(getContext()
.getString(R.string.config_defaultAutofillService));
- pw.print(prefix); pw.print("mAugmentedAutofillNamer: ");
- mAugmentedAutofillResolver.dumpShort(pw); pw.println();
+
+ pw.print(prefix); pw.println("mAugmentedAutofillNamer: ");
+ pw.print(prefix2); mAugmentedAutofillResolver.dumpShort(pw); pw.println();
+ pw.print(prefix2); mAugmentedAutofillResolver.dumpShort(pw, mUserId); pw.println();
+
if (mRemoteAugmentedAutofillService != null) {
pw.print(prefix); pw.println("RemoteAugmentedAutofillService: ");
mRemoteAugmentedAutofillService.dump(prefix2, pw);
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 8b07db7..12db4f3 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -39,7 +39,6 @@
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
import android.os.Trace;
import android.os.UserHandle;
import android.util.Slog;
@@ -127,7 +126,7 @@
protected void startServiceForUser(int userId) {
UserBackupManagerService userBackupManagerService =
UserBackupManagerService.createAndInitializeService(
- userId, mContext, mTrampoline, mBackupThread, mTransportWhitelist);
+ userId, mContext, mTrampoline, mTransportWhitelist);
startServiceForUser(userId, userBackupManagerService);
}
@@ -139,20 +138,18 @@
mServiceUsers.put(userId, userBackupManagerService);
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup enable");
- try {
- // TODO(b/121198604): Make enable file per-user and clean up indirection.
- mTrampoline.setBackupEnabledForUser(
- userId, UserBackupManagerFilePersistedSettings.readBackupEnableState(userId));
- } catch (RemoteException e) {
- // Can't happen, it's a local object.
- }
+ userBackupManagerService.initializeBackupEnableState();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
/** Stops the backup service for user {@code userId} when the user is stopped. */
@VisibleForTesting
protected void stopServiceForUser(int userId) {
- mServiceUsers.remove(userId);
+ UserBackupManagerService userBackupManagerService = mServiceUsers.removeReturnOld(userId);
+
+ if (userBackupManagerService != null) {
+ userBackupManagerService.tearDownService();
+ }
}
SparseArray<UserBackupManagerService> getServiceUsers() {
diff --git a/services/backup/java/com/android/server/backup/FullBackupJob.java b/services/backup/java/com/android/server/backup/FullBackupJob.java
index 82638b4..5708b1c 100644
--- a/services/backup/java/com/android/server/backup/FullBackupJob.java
+++ b/services/backup/java/com/android/server/backup/FullBackupJob.java
@@ -24,15 +24,12 @@
import android.content.Context;
public class FullBackupJob extends JobService {
- private static final String TAG = "FullBackupJob";
- private static final boolean DEBUG = true;
-
private static ComponentName sIdleService =
new ComponentName("android", FullBackupJob.class.getName());
private static final int JOB_ID = 0x5038;
- JobParameters mParams;
+ private JobParameters mParams;
public static void schedule(Context ctx, long minDelay, BackupManagerConstants constants) {
JobScheduler js = (JobScheduler) ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE);
diff --git a/services/backup/java/com/android/server/backup/KeyValueAdbRestoreEngine.java b/services/backup/java/com/android/server/backup/KeyValueAdbRestoreEngine.java
index bed520e..3184bd8 100644
--- a/services/backup/java/com/android/server/backup/KeyValueAdbRestoreEngine.java
+++ b/services/backup/java/com/android/server/backup/KeyValueAdbRestoreEngine.java
@@ -13,8 +13,6 @@
import android.os.RemoteException;
import android.util.Slog;
-import com.android.server.backup.restore.PerformAdbRestoreTask;
-
import libcore.io.IoUtils;
import java.io.File;
@@ -42,11 +40,10 @@
private final UserBackupManagerService mBackupManagerService;
private final File mDataDir;
- FileMetadata mInfo;
- PerformAdbRestoreTask mRestoreTask;
- ParcelFileDescriptor mInFD;
- IBackupAgent mAgent;
- int mToken;
+ private final FileMetadata mInfo;
+ private final ParcelFileDescriptor mInFD;
+ private final IBackupAgent mAgent;
+ private final int mToken;
public KeyValueAdbRestoreEngine(UserBackupManagerService backupManagerService,
File dataDir, FileMetadata info, ParcelFileDescriptor inFD, IBackupAgent agent,
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerFiles.java b/services/backup/java/com/android/server/backup/UserBackupManagerFiles.java
index a0feaf9..aabd41a 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerFiles.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerFiles.java
@@ -17,29 +17,35 @@
package com.android.server.backup;
import android.os.Environment;
+import android.os.UserHandle;
import java.io.File;
/** Directories used for user specific backup/restore persistent state and book-keeping. */
-public final class UserBackupManagerFiles {
+final class UserBackupManagerFiles {
// Name of the directories the service stores bookkeeping data under.
private static final String BACKUP_PERSISTENT_DIR = "backup";
private static final String BACKUP_STAGING_DIR = "backup_stage";
+ private static File getBaseDir(int userId) {
+ return Environment.getDataSystemCeDirectory(userId);
+ }
+
static File getBaseStateDir(int userId) {
- // TODO (b/120424138) this should be per user
+ if (userId != UserHandle.USER_SYSTEM) {
+ return new File(getBaseDir(userId), BACKUP_PERSISTENT_DIR);
+ }
+ // TODO (b/120424138) remove if clause above and use same logic for system user.
+ // simultaneously, copy below dir to new system user dir
return new File(Environment.getDataDirectory(), BACKUP_PERSISTENT_DIR);
}
static File getDataDir(int userId) {
- // TODO (b/120424138) this should be per user
- // This dir on /cache is managed directly in init.rc
+ if (userId != UserHandle.USER_SYSTEM) {
+ return new File(getBaseDir(userId), BACKUP_STAGING_DIR);
+ }
+ // TODO (b/120424138) remove if clause above and use same logic for system user. Since this
+ // is a staging dir, we dont need to copy below dir to new system user dir
return new File(Environment.getDownloadCacheDirectory(), BACKUP_STAGING_DIR);
}
-
- /** Directory used by full backup engine to store state. */
- public static File getFullBackupEngineFilesDir(int userId) {
- // TODO (b/120424138) this should be per user
- return new File("/data/system");
- }
}
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 6425508..6b0adfb 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -39,6 +39,7 @@
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
import android.app.AlarmManager;
import android.app.AppGlobals;
import android.app.IActivityManager;
@@ -103,6 +104,7 @@
import com.android.internal.util.Preconditions;
import com.android.server.AppWidgetBackupBridge;
import com.android.server.EventLogTags;
+import com.android.server.LocalServices;
import com.android.server.backup.fullbackup.FullBackupEntry;
import com.android.server.backup.fullbackup.PerformFullTransportBackupTask;
import com.android.server.backup.internal.BackupHandler;
@@ -110,9 +112,9 @@
import com.android.server.backup.internal.OnTaskFinishedListener;
import com.android.server.backup.internal.Operation;
import com.android.server.backup.internal.PerformInitializeTask;
-import com.android.server.backup.internal.ProvisionedObserver;
import com.android.server.backup.internal.RunBackupReceiver;
import com.android.server.backup.internal.RunInitializeReceiver;
+import com.android.server.backup.internal.SetupObserver;
import com.android.server.backup.keyvalue.BackupRequest;
import com.android.server.backup.params.AdbBackupParams;
import com.android.server.backup.params.AdbParams;
@@ -208,8 +210,8 @@
public static final String RUN_BACKUP_ACTION = "android.app.backup.intent.RUN";
public static final String RUN_INITIALIZE_ACTION = "android.app.backup.intent.INIT";
- public static final String BACKUP_FINISHED_ACTION = "android.intent.action.BACKUP_FINISHED";
- public static final String BACKUP_FINISHED_PACKAGE_EXTRA = "packageName";
+ private static final String BACKUP_FINISHED_ACTION = "android.intent.action.BACKUP_FINISHED";
+ private static final String BACKUP_FINISHED_PACKAGE_EXTRA = "packageName";
// Bookkeeping of in-flight operations. The operation token is the index of the entry in the
// pending operations list.
@@ -245,26 +247,28 @@
private final @UserIdInt int mUserId;
private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
private final TransportManager mTransportManager;
+ private final HandlerThread mUserBackupThread;
private Context mContext;
private PackageManager mPackageManager;
private IPackageManager mPackageManagerBinder;
private IActivityManager mActivityManager;
+ private ActivityManagerInternal mActivityManagerInternal;
private PowerManager mPowerManager;
- private AlarmManager mAlarmManager;
- private IStorageManager mStorageManager;
- private BackupManagerConstants mConstants;
- private PowerManager.WakeLock mWakelock;
- private BackupHandler mBackupHandler;
+ private final AlarmManager mAlarmManager;
+ private final IStorageManager mStorageManager;
+ private final BackupManagerConstants mConstants;
+ private final PowerManager.WakeLock mWakelock;
+ private final BackupHandler mBackupHandler;
- private IBackupManager mBackupManagerBinder;
+ private final IBackupManager mBackupManagerBinder;
private boolean mEnabled; // access to this is synchronized on 'this'
- private boolean mProvisioned;
+ private boolean mSetupComplete;
private boolean mAutoRestore;
- private PendingIntent mRunBackupIntent;
- private PendingIntent mRunInitIntent;
+ private final PendingIntent mRunBackupIntent;
+ private final PendingIntent mRunInitIntent;
private final ArraySet<String> mPendingInits = new ArraySet<>(); // transport names
@@ -272,7 +276,7 @@
private final SparseArray<HashSet<String>> mBackupParticipants = new SparseArray<>();
// Backups that we haven't started yet. Keys are package names.
- private HashMap<String, BackupRequest> mPendingBackups = new HashMap<>();
+ private final HashMap<String, BackupRequest> mPendingBackups = new HashMap<>();
// locking around the pending-backup management
private final Object mQueueLock = new Object();
@@ -314,9 +318,6 @@
private ActiveRestoreSession mActiveRestoreSession;
- // Watch the device provisioning operation during setup
- private ContentObserver mProvisionedObserver;
-
/**
* mCurrentOperations contains the list of currently active operations.
*
@@ -342,7 +343,7 @@
private final SparseArray<Operation> mCurrentOperations = new SparseArray<>();
private final Object mCurrentOpLock = new Object();
private final Random mTokenGenerator = new Random();
- final AtomicInteger mNextToken = new AtomicInteger();
+ private final AtomicInteger mNextToken = new AtomicInteger();
// Where we keep our journal files and other bookkeeping.
private final File mBaseStateDir;
@@ -350,7 +351,7 @@
private final File mJournalDir;
@Nullable
private DataChangedJournal mJournal;
- private File mFullBackupScheduleFile;
+ private final File mFullBackupScheduleFile;
// Keep a log of all the apps we've ever backed up.
private ProcessedPackagesJournal mProcessedPackagesJournal;
@@ -371,11 +372,10 @@
@UserIdInt int userId,
Context context,
Trampoline trampoline,
- HandlerThread backupThread,
Set<ComponentName> transportWhitelist) {
String currentTransport =
- Settings.Secure.getString(
- context.getContentResolver(), Settings.Secure.BACKUP_TRANSPORT);
+ Settings.Secure.getStringForUser(
+ context.getContentResolver(), Settings.Secure.BACKUP_TRANSPORT, userId);
if (TextUtils.isEmpty(currentTransport)) {
currentTransport = null;
}
@@ -389,8 +389,21 @@
File baseStateDir = UserBackupManagerFiles.getBaseStateDir(userId);
File dataDir = UserBackupManagerFiles.getDataDir(userId);
+ HandlerThread userBackupThread =
+ new HandlerThread("backup-" + userId, Process.THREAD_PRIORITY_BACKGROUND);
+ userBackupThread.start();
+ if (DEBUG) {
+ Slog.d(TAG, "Started thread " + userBackupThread.getName() + " for user " + userId);
+ }
+
return createAndInitializeService(
- userId, context, trampoline, backupThread, baseStateDir, dataDir, transportManager);
+ userId,
+ context,
+ trampoline,
+ userBackupThread,
+ baseStateDir,
+ dataDir,
+ transportManager);
}
/**
@@ -399,7 +412,7 @@
* @param userId The user which this service is for.
* @param context The system server context.
* @param trampoline A reference to the proxy to {@link BackupManagerService}.
- * @param backupThread The thread running backup/restore operations for the user.
+ * @param userBackupThread The thread running backup/restore operations for the user.
* @param baseStateDir The directory we store the user's persistent bookkeeping data.
* @param dataDir The directory we store the user's temporary staging data.
* @param transportManager The {@link TransportManager} responsible for handling the user's
@@ -410,19 +423,38 @@
@UserIdInt int userId,
Context context,
Trampoline trampoline,
- HandlerThread backupThread,
+ HandlerThread userBackupThread,
File baseStateDir,
File dataDir,
TransportManager transportManager) {
return new UserBackupManagerService(
- userId, context, trampoline, backupThread, baseStateDir, dataDir, transportManager);
+ userId,
+ context,
+ trampoline,
+ userBackupThread,
+ baseStateDir,
+ dataDir,
+ transportManager);
+ }
+
+ /**
+ * Returns the value of {@link Settings.Secure#USER_SETUP_COMPLETE} for the specified user
+ * {@code userId} as a {@code boolean}.
+ */
+ public static boolean getSetupCompleteSettingForUser(Context context, int userId) {
+ return Settings.Secure.getIntForUser(
+ context.getContentResolver(),
+ Settings.Secure.USER_SETUP_COMPLETE,
+ 0,
+ userId)
+ != 0;
}
private UserBackupManagerService(
@UserIdInt int userId,
Context context,
Trampoline parent,
- HandlerThread backupThread,
+ HandlerThread userBackupThread,
File baseStateDir,
File dataDir,
TransportManager transportManager) {
@@ -431,6 +463,7 @@
mPackageManager = context.getPackageManager();
mPackageManagerBinder = AppGlobals.getPackageManager();
mActivityManager = ActivityManager.getService();
+ mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
@@ -443,21 +476,22 @@
BackupAgentTimeoutParameters(Handler.getMain(), mContext.getContentResolver());
mAgentTimeoutParameters.start();
- // spin up the backup/restore handler thread
- checkNotNull(backupThread, "backupThread cannot be null");
- mBackupHandler = new BackupHandler(this, backupThread.getLooper());
+ checkNotNull(userBackupThread, "userBackupThread cannot be null");
+ mUserBackupThread = userBackupThread;
+ mBackupHandler = new BackupHandler(this, userBackupThread.getLooper());
// Set up our bookkeeping
final ContentResolver resolver = context.getContentResolver();
- mProvisioned = Settings.Global.getInt(resolver,
- Settings.Global.DEVICE_PROVISIONED, 0) != 0;
- mAutoRestore = Settings.Secure.getInt(resolver,
- Settings.Secure.BACKUP_AUTO_RESTORE, 1) != 0;
+ mSetupComplete = getSetupCompleteSettingForUser(context, userId);
+ mAutoRestore = Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.BACKUP_AUTO_RESTORE, 1, userId) != 0;
- mProvisionedObserver = new ProvisionedObserver(this, mBackupHandler);
+ ContentObserver setupObserver = new SetupObserver(this, mBackupHandler);
resolver.registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
- false, mProvisionedObserver);
+ Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE),
+ /* notifyForDescendents */ false,
+ setupObserver,
+ mUserId);
mBaseStateDir = checkNotNull(baseStateDir, "baseStateDir cannot be null");
mBaseStateDir.mkdirs();
@@ -469,26 +503,46 @@
mBackupPasswordManager = new BackupPasswordManager(mContext, mBaseStateDir, mRng);
- // Alarm receivers for scheduled backups & initialization operations
- BroadcastReceiver mRunBackupReceiver = new RunBackupReceiver(this);
+ // Receivers for scheduled backups and transport initialization operations.
+ BroadcastReceiver runBackupReceiver = new RunBackupReceiver(this);
IntentFilter filter = new IntentFilter();
filter.addAction(RUN_BACKUP_ACTION);
- context.registerReceiver(mRunBackupReceiver, filter,
- android.Manifest.permission.BACKUP, null);
+ context.registerReceiverAsUser(
+ runBackupReceiver,
+ UserHandle.of(userId),
+ filter,
+ android.Manifest.permission.BACKUP,
+ /* scheduler */ null);
- BroadcastReceiver mRunInitReceiver = new RunInitializeReceiver(this);
+ BroadcastReceiver runInitReceiver = new RunInitializeReceiver(this);
filter = new IntentFilter();
filter.addAction(RUN_INITIALIZE_ACTION);
- context.registerReceiver(mRunInitReceiver, filter,
- android.Manifest.permission.BACKUP, null);
+ context.registerReceiverAsUser(
+ runInitReceiver,
+ UserHandle.of(userId),
+ filter,
+ android.Manifest.permission.BACKUP,
+ /* scheduler */ null);
Intent backupIntent = new Intent(RUN_BACKUP_ACTION);
backupIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- mRunBackupIntent = PendingIntent.getBroadcast(context, 0, backupIntent, 0);
+ mRunBackupIntent =
+ PendingIntent.getBroadcastAsUser(
+ context,
+ /* requestCode */ 0,
+ backupIntent,
+ /* flags */ 0,
+ UserHandle.of(userId));
Intent initIntent = new Intent(RUN_INITIALIZE_ACTION);
initIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- mRunInitIntent = PendingIntent.getBroadcast(context, 0, initIntent, 0);
+ mRunInitIntent =
+ PendingIntent.getBroadcastAsUser(
+ context,
+ /* requestCode */ 0,
+ initIntent,
+ /* flags */ 0,
+ UserHandle.of(userId));
// Set up the backup-request journaling
mJournalDir = new File(mBaseStateDir, "pending");
@@ -526,6 +580,20 @@
mWakelock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*backup*");
}
+ void initializeBackupEnableState() {
+ boolean isEnabled = UserBackupManagerFilePersistedSettings.readBackupEnableState(mUserId);
+ setBackupEnabled(isEnabled);
+ }
+
+ /** Cleans up state when the user of this service is stopped. */
+ void tearDownService() {
+ mUserBackupThread.quit();
+ }
+
+ public @UserIdInt int getUserId() {
+ return mUserId;
+ }
+
public BackupManagerConstants getConstants() {
return mConstants;
}
@@ -538,51 +606,27 @@
return mContext;
}
- public void setContext(Context context) {
- mContext = context;
- }
-
public PackageManager getPackageManager() {
return mPackageManager;
}
- public void setPackageManager(PackageManager packageManager) {
- mPackageManager = packageManager;
- }
-
public IPackageManager getPackageManagerBinder() {
return mPackageManagerBinder;
}
- public void setPackageManagerBinder(IPackageManager packageManagerBinder) {
- mPackageManagerBinder = packageManagerBinder;
- }
-
public IActivityManager getActivityManager() {
return mActivityManager;
}
- public void setActivityManager(IActivityManager activityManager) {
- mActivityManager = activityManager;
- }
-
public AlarmManager getAlarmManager() {
return mAlarmManager;
}
- public void setAlarmManager(AlarmManager alarmManager) {
- mAlarmManager = alarmManager;
- }
-
@VisibleForTesting
void setPowerManager(PowerManager powerManager) {
mPowerManager = powerManager;
}
- public void setBackupManagerBinder(IBackupManager backupManagerBinder) {
- mBackupManagerBinder = backupManagerBinder;
- }
-
public TransportManager getTransportManager() {
return mTransportManager;
}
@@ -595,12 +639,12 @@
mEnabled = enabled;
}
- public boolean isProvisioned() {
- return mProvisioned;
+ public boolean isSetupComplete() {
+ return mSetupComplete;
}
- public void setProvisioned(boolean provisioned) {
- mProvisioned = provisioned;
+ public void setSetupComplete(boolean setupComplete) {
+ mSetupComplete = setupComplete;
}
public PowerManager.WakeLock getWakelock() {
@@ -617,35 +661,18 @@
mWakelock.setWorkSource(workSource);
}
- public void setWakelock(PowerManager.WakeLock wakelock) {
- mWakelock = wakelock;
- }
-
public Handler getBackupHandler() {
return mBackupHandler;
}
- public void setBackupHandler(BackupHandler backupHandler) {
- mBackupHandler = backupHandler;
- }
-
public PendingIntent getRunInitIntent() {
return mRunInitIntent;
}
- public void setRunInitIntent(PendingIntent runInitIntent) {
- mRunInitIntent = runInitIntent;
- }
-
public HashMap<String, BackupRequest> getPendingBackups() {
return mPendingBackups;
}
- public void setPendingBackups(
- HashMap<String, BackupRequest> pendingBackups) {
- mPendingBackups = pendingBackups;
- }
-
public Object getQueueLock() {
return mQueueLock;
}
@@ -658,10 +685,6 @@
mBackupRunning = backupRunning;
}
- public long getLastBackupPass() {
- return mLastBackupPass;
- }
-
public void setLastBackupPass(long lastBackupPass) {
mLastBackupPass = lastBackupPass;
}
@@ -670,10 +693,6 @@
return mClearDataLock;
}
- public boolean isClearingData() {
- return mClearingData;
- }
-
public void setClearingData(boolean clearingData) {
mClearingData = clearingData;
}
@@ -694,11 +713,6 @@
return mActiveRestoreSession;
}
- public void setActiveRestoreSession(
- ActiveRestoreSession activeRestoreSession) {
- mActiveRestoreSession = activeRestoreSession;
- }
-
public SparseArray<Operation> getCurrentOperations() {
return mCurrentOperations;
}
@@ -732,18 +746,10 @@
return mRng;
}
- public Set<String> getAncestralPackages() {
- return mAncestralPackages;
- }
-
public void setAncestralPackages(Set<String> ancestralPackages) {
mAncestralPackages = ancestralPackages;
}
- public long getAncestralToken() {
- return mAncestralToken;
- }
-
public void setAncestralToken(long ancestralToken) {
mAncestralToken = ancestralToken;
}
@@ -844,19 +850,29 @@
mFullBackupQueue = readFullBackupSchedule();
}
- // Register for broadcasts about package install, etc., so we can
- // update the provider list.
+ // Register for broadcasts about package changes.
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addDataScheme("package");
- mContext.registerReceiver(mBroadcastReceiver, filter);
+ mContext.registerReceiverAsUser(
+ mBroadcastReceiver,
+ UserHandle.of(mUserId),
+ filter,
+ /* broadcastPermission */ null,
+ /* scheduler */ null);
+
// Register for events related to sdcard installation.
IntentFilter sdFilter = new IntentFilter();
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
- mContext.registerReceiver(mBroadcastReceiver, sdFilter);
+ mContext.registerReceiverAsUser(
+ mBroadcastReceiver,
+ UserHandle.of(mUserId),
+ sdFilter,
+ /* broadcastPermission */ null,
+ /* scheduler */ null);
}
private ArrayList<FullBackupEntry> readFullBackupSchedule() {
@@ -1121,17 +1137,23 @@
}
}
- // ----- Track installation/removal of packages -----
+ /**
+ * A {@link BroadcastReceiver} tracking changes to packages and sd cards in order to update our
+ * internal bookkeeping.
+ */
private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
- if (MORE_DEBUG) Slog.d(TAG, "Received broadcast " + intent);
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Received broadcast " + intent);
+ }
String action = intent.getAction();
boolean replacing = false;
boolean added = false;
boolean changed = false;
Bundle extras = intent.getExtras();
- String[] pkgList = null;
+ String[] packageList = null;
+
if (Intent.ACTION_PACKAGE_ADDED.equals(action)
|| Intent.ACTION_PACKAGE_REMOVED.equals(action)
|| Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
@@ -1139,69 +1161,70 @@
if (uri == null) {
return;
}
- final String pkgName = uri.getSchemeSpecificPart();
- if (pkgName != null) {
- pkgList = new String[]{pkgName};
- }
- changed = Intent.ACTION_PACKAGE_CHANGED.equals(action);
- // At package-changed we only care about looking at new transport states
+ String packageName = uri.getSchemeSpecificPart();
+ if (packageName != null) {
+ packageList = new String[]{packageName};
+ }
+
+ changed = Intent.ACTION_PACKAGE_CHANGED.equals(action);
if (changed) {
- final String[] components =
+ // Look at new transport states for package changed events.
+ String[] components =
intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
if (MORE_DEBUG) {
- Slog.i(TAG, "Package " + pkgName + " changed; rechecking");
+ Slog.i(TAG, "Package " + packageName + " changed");
for (int i = 0; i < components.length; i++) {
Slog.i(TAG, " * " + components[i]);
}
}
mBackupHandler.post(
- () -> mTransportManager.onPackageChanged(pkgName, components));
- return; // nothing more to do in the PACKAGE_CHANGED case
+ () -> mTransportManager.onPackageChanged(packageName, components));
+ return;
}
added = Intent.ACTION_PACKAGE_ADDED.equals(action);
replacing = extras.getBoolean(Intent.EXTRA_REPLACING, false);
} else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
added = true;
- pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ packageList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
} else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
added = false;
- pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ packageList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
}
- if (pkgList == null || pkgList.length == 0) {
+ if (packageList == null || packageList.length == 0) {
return;
}
- final int uid = extras.getInt(Intent.EXTRA_UID);
+ int uid = extras.getInt(Intent.EXTRA_UID);
if (added) {
synchronized (mBackupParticipants) {
if (replacing) {
- // This is the package-replaced case; we just remove the entry
- // under the old uid and fall through to re-add. If an app
- // just added key/value backup participation, this picks it up
- // as a known participant.
- removePackageParticipantsLocked(pkgList, uid);
+ // Remove the entry under the old uid and fall through to re-add. If an app
+ // just opted into key/value backup, add it as a known participant.
+ removePackageParticipantsLocked(packageList, uid);
}
- addPackageParticipantsLocked(pkgList);
+ addPackageParticipantsLocked(packageList);
}
- // If they're full-backup candidates, add them there instead
- final long now = System.currentTimeMillis();
- for (final String packageName : pkgList) {
+
+ long now = System.currentTimeMillis();
+ for (String packageName : packageList) {
try {
- PackageInfo app = mPackageManager.getPackageInfo(packageName, 0);
+ PackageInfo app =
+ mPackageManager.getPackageInfoAsUser(
+ packageName, /* flags */ 0, mUserId);
if (AppBackupUtils.appGetsFullBackup(app)
&& AppBackupUtils.appIsEligibleForBackup(
app.applicationInfo, mPackageManager)) {
enqueueFullBackup(packageName, now);
scheduleNextFullBackupJob(0);
} else {
- // The app might have just transitioned out of full-data into
- // doing key/value backups, or might have just disabled backups
- // entirely. Make sure it is no longer in the full-data queue.
+ // The app might have just transitioned out of full-data into doing
+ // key/value backups, or might have just disabled backups entirely. Make
+ // sure it is no longer in the full-data queue.
synchronized (mQueueLock) {
dequeueFullBackupLocked(packageName);
}
@@ -1210,32 +1233,28 @@
mBackupHandler.post(
() -> mTransportManager.onPackageAdded(packageName));
-
} catch (NameNotFoundException e) {
- // doesn't really exist; ignore it
if (DEBUG) {
Slog.w(TAG, "Can't resolve new app " + packageName);
}
}
}
- // Whenever a package is added or updated we need to update
- // the package metadata bookkeeping.
+ // Whenever a package is added or updated we need to update the package metadata
+ // bookkeeping.
dataChangedImpl(PACKAGE_MANAGER_SENTINEL);
} else {
- if (replacing) {
- // The package is being updated. We'll receive a PACKAGE_ADDED shortly.
- } else {
- // Outright removal. In the full-data case, the app will be dropped
- // from the queue when its (now obsolete) name comes up again for
- // backup.
+ if (!replacing) {
+ // Outright removal. In the full-data case, the app will be dropped from the
+ // queue when its (now obsolete) name comes up again for backup.
synchronized (mBackupParticipants) {
- removePackageParticipantsLocked(pkgList, uid);
+ removePackageParticipantsLocked(packageList, uid);
}
}
- for (final String pkgName : pkgList) {
+
+ for (String packageName : packageList) {
mBackupHandler.post(
- () -> mTransportManager.onPackageRemoved(pkgName));
+ () -> mTransportManager.onPackageRemoved(packageName));
}
}
}
@@ -1553,11 +1572,16 @@
throw new IllegalArgumentException("No packages are provided for backup");
}
- if (!mEnabled || !mProvisioned) {
- Slog.i(TAG, "Backup requested but e=" + mEnabled + " p=" + mProvisioned);
+ if (!mEnabled || !mSetupComplete) {
+ Slog.i(
+ TAG,
+ "Backup requested but enabled="
+ + mEnabled
+ + " setupComplete="
+ + mSetupComplete);
BackupObserverUtils.sendBackupFinished(observer,
BackupManager.ERROR_BACKUP_NOT_ALLOWED);
- final int logTag = mProvisioned
+ final int logTag = mSetupComplete
? BackupManagerMonitor.LOG_EVENT_ID_BACKUP_DISABLED
: BackupManagerMonitor.LOG_EVENT_ID_DEVICE_NOT_PROVISIONED;
monitor = BackupManagerMonitorUtils.monitorEvent(monitor, logTag, null,
@@ -1992,13 +2016,13 @@
FullBackupEntry entry = null;
long latency = fullBackupInterval;
- if (!mEnabled || !mProvisioned) {
+ if (!mEnabled || !mSetupComplete) {
// Backups are globally disabled, so don't proceed. We also don't reschedule
// the job driving automatic backups; that job will be scheduled again when
// the user enables backup.
if (MORE_DEBUG) {
- Slog.i(TAG, "beginFullBackup but e=" + mEnabled
- + " p=" + mProvisioned + "; ignoring");
+ Slog.i(TAG, "beginFullBackup but enabled=" + mEnabled
+ + " setupComplete=" + mSetupComplete + "; ignoring");
}
return false;
}
@@ -2087,7 +2111,8 @@
final int privFlags = appInfo.applicationInfo.privateFlags;
headBusy = (privFlags & PRIVATE_FLAG_BACKUP_IN_FOREGROUND) == 0
- && mActivityManager.isAppForeground(appInfo.applicationInfo.uid);
+ && mActivityManagerInternal.isAppForeground(
+ appInfo.applicationInfo.uid);
if (headBusy) {
final long nextEligible = System.currentTimeMillis()
@@ -2110,8 +2135,6 @@
// queue entirely and move on, but if there's nothing else in the queue
// we should bail entirely. headBusy cannot have been set to true yet.
runBackup = (mFullBackupQueue.size() > 1);
- } catch (RemoteException e) {
- // Cannot happen; the Activity Manager is in the same process
}
}
} while (headBusy);
@@ -2399,12 +2422,6 @@
}
}
- /** Returns {@code true} if the system user has gone through SUW. */
- public boolean deviceIsProvisioned() {
- final ContentResolver resolver = mContext.getContentResolver();
- return (Settings.Global.getInt(resolver, Settings.Global.DEVICE_PROVISIONED, 0) != 0);
- }
-
/**
* Used by 'adb backup' to run a backup pass for packages supplied via the command line, writing
* the resulting data stream to the supplied {@code fd}. This method is synchronous and does not
@@ -2437,8 +2454,7 @@
long oldId = Binder.clearCallingIdentity();
try {
- // Doesn't make sense to do a full backup prior to setup
- if (!deviceIsProvisioned()) {
+ if (!mSetupComplete) {
Slog.i(TAG, "Backup not supported before setup");
return;
}
@@ -2564,9 +2580,7 @@
long oldId = Binder.clearCallingIdentity();
try {
- // Check whether the device has been provisioned -- we don't handle
- // full restores prior to completing the setup process.
- if (!deviceIsProvisioned()) {
+ if (!mSetupComplete) {
Slog.i(TAG, "Full restore not permitted before setup");
return;
}
@@ -2721,7 +2735,7 @@
}
synchronized (mQueueLock) {
- if (enable && !wasEnabled && mProvisioned) {
+ if (enable && !wasEnabled && mSetupComplete) {
// if we've just been enabled, start scheduling backup passes
KeyValueBackupJob.schedule(mContext, mConstants);
scheduleNextFullBackupJob(0);
@@ -2734,7 +2748,7 @@
// This also constitutes an opt-out, so we wipe any data for
// this device from the backend. We start that process with
// an alarm in order to guarantee wakelock states.
- if (wasEnabled && mProvisioned) {
+ if (wasEnabled && mSetupComplete) {
// NOTE: we currently flush every registered transport, not just
// the currently-active one.
List<String> transportNames = new ArrayList<>();
@@ -2782,8 +2796,8 @@
final long oldId = Binder.clearCallingIdentity();
try {
synchronized (this) {
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.BACKUP_AUTO_RESTORE, doAutoRestore ? 1 : 0);
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.BACKUP_AUTO_RESTORE, doAutoRestore ? 1 : 0, mUserId);
mAutoRestore = doAutoRestore;
}
} finally {
@@ -2996,8 +3010,8 @@
private void updateStateForTransport(String newTransportName) {
// Publish the name change
- Settings.Secure.putString(mContext.getContentResolver(),
- Settings.Secure.BACKUP_TRANSPORT, newTransportName);
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.BACKUP_TRANSPORT, newTransportName, mUserId);
// And update our current-dataset bookkeeping
String callerLogString = "BMS.updateStateForTransport()";
@@ -3112,8 +3126,7 @@
synchronized (mAgentConnectLock) {
if (Binder.getCallingUid() == Process.SYSTEM_UID) {
Slog.d(TAG, "agentConnected pkg=" + packageName + " agent=" + agentBinder);
- IBackupAgent agent = IBackupAgent.Stub.asInterface(agentBinder);
- mConnectedAgent = agent;
+ mConnectedAgent = IBackupAgent.Stub.asInterface(agentBinder);
mConnecting = false;
} else {
Slog.w(TAG, "Non-system process uid=" + Binder.getCallingUid()
@@ -3431,7 +3444,7 @@
private void dumpInternal(PrintWriter pw) {
synchronized (mQueueLock) {
pw.println("Backup Manager is " + (mEnabled ? "enabled" : "disabled")
- + " / " + (!mProvisioned ? "not " : "") + "provisioned / "
+ + " / " + (!mSetupComplete ? "not " : "") + "setup complete / "
+ (this.mPendingInits.size() == 0 ? "not " : "") + "pending init");
pw.println("Auto-restore is " + (mAutoRestore ? "enabled" : "disabled"));
if (mBackupRunning) pw.println("Backup currently running");
diff --git a/services/backup/java/com/android/server/backup/fullbackup/AppMetadataBackupWriter.java b/services/backup/java/com/android/server/backup/fullbackup/AppMetadataBackupWriter.java
index bace1aa..d13f711 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/AppMetadataBackupWriter.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/AppMetadataBackupWriter.java
@@ -7,6 +7,7 @@
import static com.android.server.backup.UserBackupManagerService.BACKUP_WIDGET_METADATA_TOKEN;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.backup.FullBackup;
import android.app.backup.FullBackupDataOutput;
import android.content.pm.PackageInfo;
@@ -15,7 +16,6 @@
import android.content.pm.SigningInfo;
import android.os.Build;
import android.os.Environment;
-import android.os.UserHandle;
import android.util.Log;
import android.util.StringBuilderPrinter;
@@ -254,12 +254,11 @@
* for 'adb backup'.
*/
// TODO(b/113807190): Investigate and potentially remove.
- public void backupObb(PackageInfo packageInfo) {
+ public void backupObb(@UserIdInt int userId, PackageInfo packageInfo) {
// TODO: migrate this to SharedStorageBackup, since AID_SYSTEM doesn't have access to
// external storage.
- // TODO: http://b/22388012
Environment.UserEnvironment userEnv =
- new Environment.UserEnvironment(UserHandle.USER_SYSTEM);
+ new Environment.UserEnvironment(userId);
File obbDir = userEnv.buildExternalStorageAppObbDirs(packageInfo.packageName)[0];
if (obbDir != null) {
if (MORE_DEBUG) {
diff --git a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
index 45ca2af..7ea1892 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
@@ -34,14 +34,12 @@
import android.content.pm.PackageManager;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
-import android.os.UserHandle;
import android.util.Slog;
import com.android.internal.util.Preconditions;
import com.android.server.AppWidgetBackupBridge;
import com.android.server.backup.BackupAgentTimeoutParameters;
import com.android.server.backup.BackupRestoreTask;
-import com.android.server.backup.UserBackupManagerFiles;
import com.android.server.backup.UserBackupManagerService;
import com.android.server.backup.remote.RemoteCall;
import com.android.server.backup.utils.FullBackupUtils;
@@ -56,12 +54,12 @@
*/
public class FullBackupEngine {
private UserBackupManagerService backupManagerService;
- OutputStream mOutput;
- FullBackupPreflight mPreflightHook;
- BackupRestoreTask mTimeoutMonitor;
- IBackupAgent mAgent;
- boolean mIncludeApks;
- PackageInfo mPkg;
+ private OutputStream mOutput;
+ private FullBackupPreflight mPreflightHook;
+ private BackupRestoreTask mTimeoutMonitor;
+ private IBackupAgent mAgent;
+ private boolean mIncludeApks;
+ private PackageInfo mPkg;
private final long mQuota;
private final int mOpToken;
private final int mTransportFlags;
@@ -78,21 +76,21 @@
private final File mFilesDir;
FullBackupRunner(
+ UserBackupManagerService userBackupManagerService,
PackageInfo packageInfo,
IBackupAgent agent,
ParcelFileDescriptor pipe,
int token,
boolean includeApks)
throws IOException {
- // TODO: http://b/22388012
- mUserId = UserHandle.USER_SYSTEM;
+ mUserId = userBackupManagerService.getUserId();
mPackageManager = backupManagerService.getPackageManager();
mPackage = packageInfo;
mAgent = agent;
mPipe = ParcelFileDescriptor.dup(pipe.getFileDescriptor());
mToken = token;
mIncludeApks = includeApks;
- mFilesDir = UserBackupManagerFiles.getFullBackupEngineFilesDir(mUserId);
+ mFilesDir = userBackupManagerService.getDataDir();
}
@Override
@@ -132,7 +130,7 @@
// TODO(b/113807190): Look into removing, only used for 'adb backup'.
if (writeApk) {
appMetadataBackupWriter.backupApk(mPackage);
- appMetadataBackupWriter.backupObb(mPackage);
+ appMetadataBackupWriter.backupObb(mUserId, mPackage);
}
if (DEBUG) {
@@ -155,9 +153,12 @@
backupManagerService.getBackupManagerBinder(),
mTransportFlags);
} catch (IOException e) {
- Slog.e(TAG, "Error running full backup for " + mPackage.packageName);
+ Slog.e(TAG, "Error running full backup for " + mPackage.packageName, e);
} catch (RemoteException e) {
- Slog.e(TAG, "Remote agent vanished during full backup of " + mPackage.packageName);
+ Slog.e(
+ TAG,
+ "Remote agent vanished during full backup of " + mPackage.packageName,
+ e);
} finally {
try {
mPipe.close();
@@ -233,7 +234,13 @@
pipes = ParcelFileDescriptor.createPipe();
FullBackupRunner runner =
- new FullBackupRunner(mPkg, mAgent, pipes[1], mOpToken, mIncludeApks);
+ new FullBackupRunner(
+ backupManagerService,
+ mPkg,
+ mAgent,
+ pipes[1],
+ mOpToken,
+ mIncludeApks);
pipes[1].close(); // the runner has dup'd it
pipes[1] = null;
Thread t = new Thread(runner, "app-data-runner");
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
index 43a80c4..31786d7 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
@@ -66,24 +66,22 @@
*/
public class PerformAdbBackupTask extends FullBackupTask implements BackupRestoreTask {
- private UserBackupManagerService backupManagerService;
- FullBackupEngine mBackupEngine;
- final AtomicBoolean mLatch;
+ private final UserBackupManagerService mUserBackupManagerService;
+ private final AtomicBoolean mLatch;
- ParcelFileDescriptor mOutputFile;
- DeflaterOutputStream mDeflater;
- boolean mIncludeApks;
- boolean mIncludeObbs;
- boolean mIncludeShared;
- boolean mDoWidgets;
- boolean mAllApps;
- boolean mIncludeSystem;
- boolean mCompress;
- boolean mKeyValue;
- ArrayList<String> mPackages;
- PackageInfo mCurrentTarget;
- String mCurrentPassword;
- String mEncryptPassword;
+ private final ParcelFileDescriptor mOutputFile;
+ private final boolean mIncludeApks;
+ private final boolean mIncludeObbs;
+ private final boolean mIncludeShared;
+ private final boolean mDoWidgets;
+ private final boolean mAllApps;
+ private final boolean mIncludeSystem;
+ private final boolean mCompress;
+ private final boolean mKeyValue;
+ private final ArrayList<String> mPackages;
+ private PackageInfo mCurrentTarget;
+ private final String mCurrentPassword;
+ private final String mEncryptPassword;
private final int mCurrentOpToken;
public PerformAdbBackupTask(UserBackupManagerService backupManagerService,
@@ -92,7 +90,7 @@
String curPassword, String encryptPassword, boolean doAllApps, boolean doSystem,
boolean doCompress, boolean doKeyValue, String[] packages, AtomicBoolean latch) {
super(observer);
- this.backupManagerService = backupManagerService;
+ mUserBackupManagerService = backupManagerService;
mCurrentOpToken = backupManagerService.generateRandomIntegerToken();
mLatch = latch;
@@ -104,7 +102,7 @@
mAllApps = doAllApps;
mIncludeSystem = doSystem;
mPackages = (packages == null)
- ? new ArrayList<String>()
+ ? new ArrayList<>()
: new ArrayList<>(Arrays.asList(packages));
mCurrentPassword = curPassword;
// when backing up, if there is a current backup password, we require that
@@ -123,11 +121,11 @@
mKeyValue = doKeyValue;
}
- void addPackagesToSet(TreeMap<String, PackageInfo> set, List<String> pkgNames) {
+ private void addPackagesToSet(TreeMap<String, PackageInfo> set, List<String> pkgNames) {
for (String pkgName : pkgNames) {
if (!set.containsKey(pkgName)) {
try {
- PackageInfo info = backupManagerService.getPackageManager().getPackageInfo(
+ PackageInfo info = mUserBackupManagerService.getPackageManager().getPackageInfo(
pkgName,
PackageManager.GET_SIGNING_CERTIFICATES);
set.put(pkgName, info);
@@ -141,7 +139,7 @@
private OutputStream emitAesBackupHeader(StringBuilder headerbuf,
OutputStream ofstream) throws Exception {
// User key will be used to encrypt the master key.
- byte[] newUserSalt = backupManagerService
+ byte[] newUserSalt = mUserBackupManagerService
.randomBytes(PasswordUtils.PBKDF2_SALT_SIZE);
SecretKey userKey = PasswordUtils
.buildPasswordKey(PBKDF_CURRENT, mEncryptPassword,
@@ -150,8 +148,8 @@
// the master key is random for each backup
byte[] masterPw = new byte[256 / 8];
- backupManagerService.getRng().nextBytes(masterPw);
- byte[] checksumSalt = backupManagerService
+ mUserBackupManagerService.getRng().nextBytes(masterPw);
+ byte[] checksumSalt = mUserBackupManagerService
.randomBytes(PasswordUtils.PBKDF2_SALT_SIZE);
// primary encryption of the datastream with the random key
@@ -232,11 +230,11 @@
TreeMap<String, PackageInfo> packagesToBackup = new TreeMap<>();
FullBackupObbConnection obbConnection = new FullBackupObbConnection(
- backupManagerService);
+ mUserBackupManagerService);
obbConnection.establish(); // we'll want this later
sendStartBackup();
- PackageManager pm = backupManagerService.getPackageManager();
+ PackageManager pm = mUserBackupManagerService.getPackageManager();
// doAllApps supersedes the package set if any
if (mAllApps) {
@@ -245,7 +243,7 @@
for (int i = 0; i < allPackages.size(); i++) {
PackageInfo pkg = allPackages.get(i);
// Exclude system apps if we've been asked to do so
- if (mIncludeSystem == true
+ if (mIncludeSystem
|| ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0)) {
packagesToBackup.put(pkg.packageName, pkg);
}
@@ -316,7 +314,7 @@
boolean encrypting = (mEncryptPassword != null && mEncryptPassword.length() > 0);
// Only allow encrypted backups of encrypted devices
- if (backupManagerService.deviceIsEncrypted() && !encrypting) {
+ if (mUserBackupManagerService.deviceIsEncrypted() && !encrypting) {
Slog.e(TAG, "Unencrypted backup of encrypted device; aborting");
return;
}
@@ -325,7 +323,7 @@
// Verify that the given password matches the currently-active
// backup password, if any
- if (!backupManagerService.backupPasswordMatches(mCurrentPassword)) {
+ if (!mUserBackupManagerService.backupPasswordMatches(mCurrentPassword)) {
if (DEBUG) {
Slog.w(TAG, "Backup password mismatch; aborting");
}
@@ -390,7 +388,7 @@
// Shared storage if requested
if (mIncludeShared) {
try {
- pkg = backupManagerService.getPackageManager().getPackageInfo(
+ pkg = mUserBackupManagerService.getPackageManager().getPackageInfo(
SHARED_BACKUP_AGENT_PACKAGE, 0);
backupQueue.add(pkg);
} catch (NameNotFoundException e) {
@@ -410,9 +408,17 @@
pkg.packageName.equals(
SHARED_BACKUP_AGENT_PACKAGE);
- mBackupEngine = new FullBackupEngine(backupManagerService, out,
- null, pkg, mIncludeApks, this, Long.MAX_VALUE,
- mCurrentOpToken, /*transportFlags=*/ 0);
+ FullBackupEngine mBackupEngine =
+ new FullBackupEngine(
+ mUserBackupManagerService,
+ out,
+ null,
+ pkg,
+ mIncludeApks,
+ this,
+ Long.MAX_VALUE,
+ mCurrentOpToken,
+ /*transportFlags=*/ 0);
sendOnBackupPackage(isSharedStorage ? "Shared storage" : pkg.packageName);
// Don't need to check preflight result as there is no preflight hook.
@@ -437,10 +443,10 @@
}
KeyValueAdbBackupEngine kvBackupEngine =
new KeyValueAdbBackupEngine(out, keyValuePackage,
- backupManagerService,
- backupManagerService.getPackageManager(),
- backupManagerService.getBaseStateDir(),
- backupManagerService.getDataDir());
+ mUserBackupManagerService,
+ mUserBackupManagerService.getPackageManager(),
+ mUserBackupManagerService.getBaseStateDir(),
+ mUserBackupManagerService.getDataDir());
sendOnBackupPackage(keyValuePackage.packageName);
kvBackupEngine.backupOnePackage();
}
@@ -471,7 +477,7 @@
if (DEBUG) {
Slog.d(TAG, "Full backup pass complete.");
}
- backupManagerService.getWakelock().release();
+ mUserBackupManagerService.getWakelock().release();
}
}
@@ -493,8 +499,8 @@
Slog.w(TAG, "adb backup cancel of " + target);
}
if (target != null) {
- backupManagerService.tearDownAgentAndKill(mCurrentTarget.applicationInfo);
+ mUserBackupManagerService.tearDownAgentAndKill(mCurrentTarget.applicationInfo);
}
- backupManagerService.removeOperation(mCurrentOpToken);
+ mUserBackupManagerService.removeOperation(mCurrentOpToken);
}
}
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index 5b449c5..2ee96d1 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -317,16 +317,16 @@
int backupRunStatus = BackupManager.SUCCESS;
try {
- if (!backupManagerService.isEnabled() || !backupManagerService.isProvisioned()) {
+ if (!backupManagerService.isEnabled() || !backupManagerService.isSetupComplete()) {
// Backups are globally disabled, so don't proceed.
if (DEBUG) {
Slog.i(TAG, "full backup requested but enabled=" + backupManagerService
.isEnabled()
- + " provisioned=" + backupManagerService.isProvisioned()
+ + " setupComplete=" + backupManagerService.isSetupComplete()
+ "; ignoring");
}
int monitoringEvent;
- if (backupManagerService.isProvisioned()) {
+ if (backupManagerService.isSetupComplete()) {
monitoringEvent = BackupManagerMonitor.LOG_EVENT_ID_BACKUP_DISABLED;
} else {
monitoringEvent = BackupManagerMonitor.LOG_EVENT_ID_DEVICE_NOT_PROVISIONED;
diff --git a/services/backup/java/com/android/server/backup/internal/ProvisionedObserver.java b/services/backup/java/com/android/server/backup/internal/ProvisionedObserver.java
deleted file mode 100644
index 7e2ac796..0000000
--- a/services/backup/java/com/android/server/backup/internal/ProvisionedObserver.java
+++ /dev/null
@@ -1,62 +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.backup.internal;
-
-import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
-import static com.android.server.backup.BackupManagerService.TAG;
-
-import android.database.ContentObserver;
-import android.os.Handler;
-import android.util.Slog;
-
-import com.android.server.backup.KeyValueBackupJob;
-import com.android.server.backup.UserBackupManagerService;
-
-public class ProvisionedObserver extends ContentObserver {
-
- private UserBackupManagerService backupManagerService;
-
- public ProvisionedObserver(
- UserBackupManagerService backupManagerService, Handler handler) {
- super(handler);
- this.backupManagerService = backupManagerService;
- }
-
- public void onChange(boolean selfChange) {
- final boolean wasProvisioned = backupManagerService.isProvisioned();
- final boolean isProvisioned = backupManagerService.deviceIsProvisioned();
- // latch: never unprovision
- backupManagerService.setProvisioned(wasProvisioned || isProvisioned);
- if (MORE_DEBUG) {
- Slog.d(TAG, "Provisioning change: was=" + wasProvisioned
- + " is=" + isProvisioned + " now=" + backupManagerService.isProvisioned());
- }
-
- synchronized (backupManagerService.getQueueLock()) {
- if (backupManagerService.isProvisioned() && !wasProvisioned
- && backupManagerService.isEnabled()) {
- // we're now good to go, so start the backup alarms
- if (MORE_DEBUG) {
- Slog.d(TAG, "Now provisioned, so starting backups");
- }
- KeyValueBackupJob.schedule(backupManagerService.getContext(),
- backupManagerService.getConstants());
- backupManagerService.scheduleNextFullBackupJob(0);
- }
- }
- }
-}
diff --git a/services/backup/java/com/android/server/backup/internal/RunBackupReceiver.java b/services/backup/java/com/android/server/backup/internal/RunBackupReceiver.java
index 2a5d913..d37b106 100644
--- a/services/backup/java/com/android/server/backup/internal/RunBackupReceiver.java
+++ b/services/backup/java/com/android/server/backup/internal/RunBackupReceiver.java
@@ -26,61 +26,84 @@
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.os.Handler;
import android.os.Message;
import android.util.Slog;
import com.android.server.backup.UserBackupManagerService;
+/**
+ * A {@link BroadcastReceiver} for the action {@link UserBackupManagerService#RUN_BACKUP_ACTION}
+ * that runs an immediate backup operation if eligible.
+ */
public class RunBackupReceiver extends BroadcastReceiver {
+ private final UserBackupManagerService mUserBackupManagerService;
- private UserBackupManagerService backupManagerService;
-
- public RunBackupReceiver(UserBackupManagerService backupManagerService) {
- this.backupManagerService = backupManagerService;
+ public RunBackupReceiver(UserBackupManagerService userBackupManagerService) {
+ mUserBackupManagerService = userBackupManagerService;
}
+ /**
+ * Run a backup pass if we're eligible. We're eligible if the following conditions are met:
+ *
+ * <ul>
+ * <li>No transports are pending initialization (otherwise we kick off an initialization
+ * operation instead).
+ * <li>Backup is enabled for the user.
+ * <li>The user has completed setup.
+ * <li>No backup operation is currently running for the user.
+ * </ul>
+ */
public void onReceive(Context context, Intent intent) {
- if (RUN_BACKUP_ACTION.equals(intent.getAction())) {
- synchronized (backupManagerService.getQueueLock()) {
- if (backupManagerService.getPendingInits().size() > 0) {
- // If there are pending init operations, we process those
- // and then settle into the usual periodic backup schedule.
- if (MORE_DEBUG) {
- Slog.v(TAG, "Init pending at scheduled backup");
- }
- try {
- backupManagerService.getAlarmManager().cancel(
- backupManagerService.getRunInitIntent());
- backupManagerService.getRunInitIntent().send();
- } catch (PendingIntent.CanceledException ce) {
- Slog.e(TAG, "Run init intent cancelled");
- // can't really do more than bail here
- }
- } else {
- // Don't run backups now if we're disabled or not yet
- // fully set up.
- if (backupManagerService.isEnabled() && backupManagerService.isProvisioned()) {
- if (!backupManagerService.isBackupRunning()) {
- if (DEBUG) {
- Slog.v(TAG, "Running a backup pass");
- }
+ if (!RUN_BACKUP_ACTION.equals(intent.getAction())) {
+ return;
+ }
- // Acquire the wakelock and pass it to the backup thread. it will
- // be released once backup concludes.
- backupManagerService.setBackupRunning(true);
- backupManagerService.getWakelock().acquire();
-
- Message msg = backupManagerService.getBackupHandler().obtainMessage(
- MSG_RUN_BACKUP);
- backupManagerService.getBackupHandler().sendMessage(msg);
- } else {
- Slog.i(TAG, "Backup time but one already running");
- }
- } else {
- Slog.w(TAG, "Backup pass but e=" + backupManagerService.isEnabled() + " p="
- + backupManagerService.isProvisioned());
- }
+ synchronized (mUserBackupManagerService.getQueueLock()) {
+ if (mUserBackupManagerService.getPendingInits().size() > 0) {
+ // If there are pending init operations, we process those and then settle into the
+ // usual periodic backup schedule.
+ if (MORE_DEBUG) {
+ Slog.v(TAG, "Init pending at scheduled backup");
}
+ try {
+ PendingIntent runInitIntent = mUserBackupManagerService.getRunInitIntent();
+ mUserBackupManagerService.getAlarmManager().cancel(runInitIntent);
+ runInitIntent.send();
+ } catch (PendingIntent.CanceledException ce) {
+ Slog.w(TAG, "Run init intent cancelled");
+ }
+ } else {
+ // Don't run backups if we're disabled or not yet set up.
+ if (!mUserBackupManagerService.isEnabled()
+ || !mUserBackupManagerService.isSetupComplete()) {
+ Slog.w(
+ TAG,
+ "Backup pass but enabled="
+ + mUserBackupManagerService.isEnabled()
+ + " setupComplete="
+ + mUserBackupManagerService.isSetupComplete());
+ return;
+ }
+
+ // Don't run backups if one is already running.
+ if (mUserBackupManagerService.isBackupRunning()) {
+ Slog.i(TAG, "Backup time but one already running");
+ return;
+ }
+
+ if (DEBUG) {
+ Slog.v(TAG, "Running a backup pass");
+ }
+
+ // Acquire the wakelock and pass it to the backup thread. It will be released once
+ // backup concludes.
+ mUserBackupManagerService.setBackupRunning(true);
+ mUserBackupManagerService.getWakelock().acquire();
+
+ Handler backupHandler = mUserBackupManagerService.getBackupHandler();
+ Message message = backupHandler.obtainMessage(MSG_RUN_BACKUP);
+ backupHandler.sendMessage(message);
}
}
}
diff --git a/services/backup/java/com/android/server/backup/internal/RunInitializeReceiver.java b/services/backup/java/com/android/server/backup/internal/RunInitializeReceiver.java
index 38870cb..97711e3 100644
--- a/services/backup/java/com/android/server/backup/internal/RunInitializeReceiver.java
+++ b/services/backup/java/com/android/server/backup/internal/RunInitializeReceiver.java
@@ -24,41 +24,50 @@
import android.content.Context;
import android.content.Intent;
import android.os.PowerManager;
-import android.util.ArraySet;
import android.util.Slog;
import com.android.server.backup.UserBackupManagerService;
-public class RunInitializeReceiver extends BroadcastReceiver {
- private final UserBackupManagerService mBackupManagerService;
+import java.util.Set;
- public RunInitializeReceiver(UserBackupManagerService backupManagerService) {
- mBackupManagerService = backupManagerService;
+/**
+ * A {@link BroadcastReceiver} for the action {@link UserBackupManagerService#RUN_INITIALIZE_ACTION}
+ * that runs an initialization operation on all pending transports.
+ */
+public class RunInitializeReceiver extends BroadcastReceiver {
+ private final UserBackupManagerService mUserBackupManagerService;
+
+ public RunInitializeReceiver(UserBackupManagerService userBackupManagerService) {
+ mUserBackupManagerService = userBackupManagerService;
}
public void onReceive(Context context, Intent intent) {
- if (RUN_INITIALIZE_ACTION.equals(intent.getAction())) {
- synchronized (mBackupManagerService.getQueueLock()) {
- final ArraySet<String> pendingInits = mBackupManagerService.getPendingInits();
- if (DEBUG) {
- Slog.v(TAG, "Running a device init; " + pendingInits.size() + " pending");
- }
+ if (!RUN_INITIALIZE_ACTION.equals(intent.getAction())) {
+ return;
+ }
- if (pendingInits.size() > 0) {
- final String[] transports =
- pendingInits.toArray(new String[pendingInits.size()]);
+ synchronized (mUserBackupManagerService.getQueueLock()) {
+ Set<String> pendingInits = mUserBackupManagerService.getPendingInits();
+ if (DEBUG) {
+ Slog.v(TAG, "Running a device init; " + pendingInits.size() + " pending");
+ }
- mBackupManagerService.clearPendingInits();
+ if (pendingInits.size() > 0) {
+ String[] transports = pendingInits.toArray(new String[pendingInits.size()]);
- PowerManager.WakeLock wakelock = mBackupManagerService.getWakelock();
- wakelock.acquire();
- OnTaskFinishedListener listener = caller -> wakelock.release();
+ mUserBackupManagerService.clearPendingInits();
- Runnable task =
- new PerformInitializeTask(
- mBackupManagerService, transports, null, listener);
- mBackupManagerService.getBackupHandler().post(task);
- }
+ PowerManager.WakeLock wakelock = mUserBackupManagerService.getWakelock();
+ wakelock.acquire();
+ OnTaskFinishedListener listener = caller -> wakelock.release();
+
+ Runnable task =
+ new PerformInitializeTask(
+ mUserBackupManagerService,
+ transports,
+ /* observer */ null,
+ listener);
+ mUserBackupManagerService.getBackupHandler().post(task);
}
}
}
diff --git a/services/backup/java/com/android/server/backup/internal/SetupObserver.java b/services/backup/java/com/android/server/backup/internal/SetupObserver.java
new file mode 100644
index 0000000..62ae3eb
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/internal/SetupObserver.java
@@ -0,0 +1,85 @@
+/*
+ * 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.internal;
+
+import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
+import static com.android.server.backup.BackupManagerService.TAG;
+import static com.android.server.backup.UserBackupManagerService.getSetupCompleteSettingForUser;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.provider.Settings;
+import android.util.Slog;
+
+import com.android.server.backup.KeyValueBackupJob;
+import com.android.server.backup.UserBackupManagerService;
+
+/**
+ * A {@link ContentObserver} for changes to the setting {@link Settings.Secure#USER_SETUP_COMPLETE}
+ * for a particular user.
+ */
+public class SetupObserver extends ContentObserver {
+ private final UserBackupManagerService mUserBackupManagerService;
+ private final Context mContext;
+ private final int mUserId;
+
+ public SetupObserver(UserBackupManagerService userBackupManagerService, Handler handler) {
+ super(handler);
+ mUserBackupManagerService = userBackupManagerService;
+ mContext = userBackupManagerService.getContext();
+ mUserId = userBackupManagerService.getUserId();
+ }
+
+ /**
+ * Callback that executes when the setting {@link Settings.Secure#USER_SETUP_COMPLETE} changes
+ * for the user {@link #mUserId}. If the user is newly setup and backup is enabled, then we
+ * schedule a key value and full backup job for the user. If the user was previously setup and
+ * now the setting has changed to {@code false}, we don't reset the state as having gone through
+ * setup is a non-reversible action.
+ */
+ public void onChange(boolean selfChange) {
+ boolean previousSetupComplete = mUserBackupManagerService.isSetupComplete();
+ boolean newSetupComplete = getSetupCompleteSettingForUser(mContext, mUserId);
+
+ boolean resolvedSetupComplete = previousSetupComplete || newSetupComplete;
+ mUserBackupManagerService.setSetupComplete(resolvedSetupComplete);
+ if (MORE_DEBUG) {
+ Slog.d(
+ TAG,
+ "Setup complete change: was="
+ + previousSetupComplete
+ + " new="
+ + newSetupComplete
+ + " resolved="
+ + resolvedSetupComplete);
+ }
+
+ synchronized (mUserBackupManagerService.getQueueLock()) {
+ // Start backup if the user is newly setup and backup is enabled.
+ if (resolvedSetupComplete
+ && !previousSetupComplete
+ && mUserBackupManagerService.isEnabled()) {
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Setup complete so starting backups");
+ }
+ KeyValueBackupJob.schedule(mContext, mUserBackupManagerService.getConstants());
+ mUserBackupManagerService.scheduleNextFullBackupJob(0);
+ }
+ }
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
index 0d26ea5..45a398f 100644
--- a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
+++ b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
@@ -678,12 +678,13 @@
* Returns whether the package is in the list of the packages for which clear app data should
* be called despite the fact that they have backup agent.
*
- * <p>The list is read from {@link Settings.Secure.PACKAGES_TO_CLEAR_DATA_BEFORE_FULL_RESTORE}.
+ * <p>The list is read from {@link Settings.Secure#PACKAGES_TO_CLEAR_DATA_BEFORE_FULL_RESTORE}.
*/
private boolean shouldForceClearAppDataOnFullRestore(String packageName) {
- String packageListString = Settings.Secure.getString(
+ String packageListString = Settings.Secure.getStringForUser(
mBackupManagerService.getContext().getContentResolver(),
- Settings.Secure.PACKAGES_TO_CLEAR_DATA_BEFORE_FULL_RESTORE);
+ Settings.Secure.PACKAGES_TO_CLEAR_DATA_BEFORE_FULL_RESTORE,
+ mBackupManagerService.getUserId());
if (TextUtils.isEmpty(packageListString)) {
return false;
}
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 4b24ef9..dc0f602 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -39,6 +39,7 @@
import com.android.internal.os.IResultReceiver;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
+import com.android.internal.util.SyncResultReceiver;
import com.android.server.LocalServices;
import com.android.server.infra.AbstractMasterSystemService;
import com.android.server.infra.FrameworkResourcesServiceNameResolver;
@@ -50,8 +51,9 @@
/**
* A service used to observe the contents of the screen.
*
- * <p>The data collected by this service can be analyzed and combined with other sources to provide
- * contextual data in other areas of the system such as Autofill.
+ * <p>The data collected by this service can be analyzed on-device and combined
+ * with other sources to provide contextual data in other areas of the system
+ * such as Autofill.
*/
public final class ContentCaptureManagerService extends
AbstractMasterSystemService<ContentCaptureManagerService, ContentCapturePerUserService> {
@@ -196,6 +198,22 @@
}
@Override
+ public void getReceiverServiceComponentName(@UserIdInt int userId,
+ IResultReceiver receiver) {
+ ComponentName connectedServiceComponentName;
+ synchronized (mLock) {
+ final ContentCapturePerUserService service = getServiceForUserLocked(userId);
+ connectedServiceComponentName = service.getServiceComponentName();
+ }
+ try {
+ receiver.send(0, SyncResultReceiver.bundleFor(connectedServiceComponentName));
+ } catch (RemoteException e) {
+ // Ignore exception as we need to be resilient against app behavior.
+ Slog.w(TAG, "Unable to send service component name: " + e);
+ }
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
index c467935..09aa421 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
@@ -95,7 +95,7 @@
final ComponentName serviceComponentName = updateServiceInfoLocked();
if (serviceComponentName == null) {
- Slog.w(TAG, "updateRemoteService(): no service componennt name");
+ if (mMaster.debug) Slog.d(TAG, "updateRemoteService(): no service component name");
return;
}
@@ -163,7 +163,10 @@
@NonNull String sessionId, int uid, int flags,
@NonNull IResultReceiver clientReceiver) {
if (!isEnabledLocked()) {
- setClientState(clientReceiver, ContentCaptureSession.STATE_DISABLED, /* binder=*/ null);
+ // TODO: it would be better to split in differet reasons, like
+ // STATE_DISABLED_NO_SERVICE and STATE_DISABLED_BY_DEVICE_POLICY
+ setClientState(clientReceiver, ContentCaptureSession.STATE_DISABLED_NO_SERVICE,
+ /* binder= */ null);
return;
}
final ComponentName serviceComponentName = getServiceComponentName();
@@ -195,8 +198,8 @@
Slog.w(TAG, "startSession(id=" + existingSession + ", token=" + activityToken
+ ": ignoring because service is not set");
// TODO(b/111276913): use a new disabled state?
- setClientState(clientReceiver, ContentCaptureSession.STATE_DISABLED,
- /* binder=*/ null);
+ setClientState(clientReceiver, ContentCaptureSession.STATE_DISABLED_NO_SERVICE,
+ /* binder= */ null);
return;
}
@@ -285,7 +288,7 @@
final int numSessions = mSessions.size();
for (int i = 0; i < numSessions; i++) {
final ContentCaptureServerSession session = mSessions.valueAt(i);
- session.destroyLocked(true);
+ session.destroyLocked(/* notifyRemoteService= */ true);
}
mSessions.clear();
}
diff --git a/services/contentsuggestions/Android.bp b/services/contentsuggestions/Android.bp
new file mode 100644
index 0000000..fc09d2e
--- /dev/null
+++ b/services/contentsuggestions/Android.bp
@@ -0,0 +1,5 @@
+java_library_static {
+ name: "services.contentsuggestions",
+ srcs: ["java/**/*.java"],
+ libs: ["services.core"],
+}
\ No newline at end of file
diff --git a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java
new file mode 100644
index 0000000..58dbea4
--- /dev/null
+++ b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java
@@ -0,0 +1,205 @@
+/*
+ * 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.
+ */
+
+package com.android.server.contentsuggestions;
+
+import static android.Manifest.permission.MANAGE_CONTENT_SUGGESTIONS;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.contentsuggestions.ClassificationsRequest;
+import android.app.contentsuggestions.IClassificationsCallback;
+import android.app.contentsuggestions.IContentSuggestionsManager;
+import android.app.contentsuggestions.ISelectionsCallback;
+import android.app.contentsuggestions.SelectionsRequest;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.os.UserHandle;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+import com.android.server.infra.AbstractMasterSystemService;
+import com.android.server.infra.FrameworkResourcesServiceNameResolver;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+import java.io.FileDescriptor;
+
+/**
+ * The system service for providing recents / overview with content suggestion selections and
+ * classifications.
+ *
+ * <p>Calls are received here from
+ * {@link android.app.contentsuggestions.ContentSuggestionsManager} then delegated to
+ * a per user version of the service. From there they are routed to the remote actual implementation
+ * that provides the suggestion selections and classifications.
+ */
+public class ContentSuggestionsManagerService extends
+ AbstractMasterSystemService<
+ ContentSuggestionsManagerService, ContentSuggestionsPerUserService> {
+
+ private static final String TAG = ContentSuggestionsManagerService.class.getSimpleName();
+ private static final boolean VERBOSE = false; // TODO: make dynamic
+
+ private static final int MAX_TEMP_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes
+
+ private ActivityTaskManagerInternal mActivityTaskManagerInternal;
+
+ public ContentSuggestionsManagerService(Context context) {
+ super(context, new FrameworkResourcesServiceNameResolver(context,
+ com.android.internal.R.string.config_defaultContentSuggestionsService), null);
+ mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
+ }
+
+ @Override
+ protected ContentSuggestionsPerUserService newServiceLocked(int resolvedUserId,
+ boolean disabled) {
+ return new ContentSuggestionsPerUserService(this, mLock, resolvedUserId);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(
+ Context.CONTENT_SUGGESTIONS_SERVICE, new ContentSuggestionsManagerStub());
+ }
+
+ @Override
+ protected void enforceCallingPermissionForManagement() {
+ getContext().enforceCallingPermission(MANAGE_CONTENT_SUGGESTIONS, TAG);
+ }
+
+ @Override
+ protected int getMaximumTemporaryServiceDurationMs() {
+ return MAX_TEMP_SERVICE_DURATION_MS;
+ }
+
+ private boolean isCallerRecents(int userId) {
+ if (mServiceNameResolver.isTemporary(userId)) {
+ // If a temporary service is set then skip the recents check
+ return true;
+ }
+ return mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid());
+ }
+
+ private void enforceCallerIsRecents(int userId, String func) {
+ if (isCallerRecents(userId)) {
+ return;
+ }
+
+ String msg = "Permission Denial: " + func + " from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid()
+ + " expected caller is recents";
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+
+ private class ContentSuggestionsManagerStub extends IContentSuggestionsManager.Stub {
+ @Override
+ public void provideContextImage(int taskId, @NonNull Bundle imageContextRequestExtras) {
+ if (imageContextRequestExtras == null) {
+ throw new IllegalArgumentException("Expected non-null imageContextRequestExtras");
+ }
+
+ final int userId = UserHandle.getCallingUserId();
+ enforceCallerIsRecents(userId, "provideContextImage");
+
+ synchronized (mLock) {
+ final ContentSuggestionsPerUserService service = getServiceForUserLocked(userId);
+ if (service != null) {
+ service.provideContextImageLocked(taskId, imageContextRequestExtras);
+ } else {
+ if (VERBOSE) {
+ Slog.v(TAG, "provideContextImageLocked: no service for " + userId);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void suggestContentSelections(
+ @NonNull SelectionsRequest selectionsRequest,
+ @NonNull ISelectionsCallback selectionsCallback) {
+ final int userId = UserHandle.getCallingUserId();
+ enforceCallerIsRecents(userId, "suggestContentSelections");
+
+ synchronized (mLock) {
+ final ContentSuggestionsPerUserService service = getServiceForUserLocked(userId);
+ if (service != null) {
+ service.suggestContentSelectionsLocked(selectionsRequest, selectionsCallback);
+ } else {
+ if (VERBOSE) {
+ Slog.v(TAG, "suggestContentSelectionsLocked: no service for " + userId);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void classifyContentSelections(
+ @NonNull ClassificationsRequest classificationsRequest,
+ @NonNull IClassificationsCallback callback) {
+ final int userId = UserHandle.getCallingUserId();
+ enforceCallerIsRecents(userId, "classifyContentSelections");
+
+ synchronized (mLock) {
+ final ContentSuggestionsPerUserService service = getServiceForUserLocked(userId);
+ if (service != null) {
+ service.classifyContentSelectionsLocked(classificationsRequest, callback);
+ } else {
+ if (VERBOSE) {
+ Slog.v(TAG, "classifyContentSelectionsLocked: no service for " + userId);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void notifyInteraction(@NonNull String requestId, @NonNull Bundle bundle) {
+ final int userId = UserHandle.getCallingUserId();
+ enforceCallerIsRecents(userId, "notifyInteraction");
+
+ synchronized (mLock) {
+ final ContentSuggestionsPerUserService service = getServiceForUserLocked(userId);
+ if (service != null) {
+ service.notifyInteractionLocked(requestId, bundle);
+ } else {
+ if (VERBOSE) {
+ Slog.v(TAG, "reportInteractionLocked: no service for " + userId);
+ }
+ }
+ }
+ }
+
+ public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+ @Nullable FileDescriptor err,
+ @NonNull String[] args, @Nullable ShellCallback callback,
+ @NonNull ResultReceiver resultReceiver) throws RemoteException {
+ // Ensure that the caller is the shell process
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid != android.os.Process.SHELL_UID
+ && callingUid != android.os.Process.ROOT_UID) {
+ Slog.e(TAG, "Expected shell caller");
+ return;
+ }
+ new ContentSuggestionsManagerServiceShellCommand(ContentSuggestionsManagerService.this)
+ .exec(this, in, out, err, args, callback, resultReceiver);
+ }
+ }
+}
diff --git a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerServiceShellCommand.java b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerServiceShellCommand.java
new file mode 100644
index 0000000..e34f1ea
--- /dev/null
+++ b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerServiceShellCommand.java
@@ -0,0 +1,84 @@
+/*
+ * 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.
+ */
+
+package com.android.server.contentsuggestions;
+
+import android.annotation.NonNull;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+/**
+ * The shell command implementation for the ContentSuggestionsManagerService.
+ */
+public class ContentSuggestionsManagerServiceShellCommand extends ShellCommand {
+
+ private static final String TAG =
+ ContentSuggestionsManagerServiceShellCommand.class.getSimpleName();
+
+ private final ContentSuggestionsManagerService mService;
+
+ public ContentSuggestionsManagerServiceShellCommand(
+ @NonNull ContentSuggestionsManagerService service) {
+ mService = service;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+ final PrintWriter pw = getOutPrintWriter();
+ switch (cmd) {
+ case "set": {
+ final String what = getNextArgRequired();
+ switch (what) {
+ case "temporary-service": {
+ final int userId = Integer.parseInt(getNextArgRequired());
+ String serviceName = getNextArg();
+ if (serviceName == null) {
+ mService.resetTemporaryService(userId);
+ return 0;
+ }
+ final int duration = Integer.parseInt(getNextArgRequired());
+ mService.setTemporaryService(userId, serviceName, duration);
+ pw.println("ContentSuggestionsService temporarily set to " + serviceName
+ + " for " + duration + "ms");
+ break;
+ }
+ }
+ }
+ break;
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ return 0;
+ }
+
+ @Override
+ public void onHelp() {
+ try (PrintWriter pw = getOutPrintWriter()) {
+ pw.println("ContentSuggestionsManagerService commands:");
+ pw.println(" help");
+ pw.println(" Prints this help text.");
+ pw.println("");
+ pw.println(" set temporary-service USER_ID [COMPONENT_NAME DURATION]");
+ pw.println(" Temporarily (for DURATION ms) changes the service implemtation.");
+ pw.println(" To reset, call with just the USER_ID argument.");
+ pw.println("");
+ }
+ }
+}
diff --git a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java
new file mode 100644
index 0000000..385bc6c
--- /dev/null
+++ b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java
@@ -0,0 +1,162 @@
+/*
+ * 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.
+ */
+
+package com.android.server.contentsuggestions;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.AppGlobals;
+import android.app.contentsuggestions.ClassificationsRequest;
+import android.app.contentsuggestions.IClassificationsCallback;
+import android.app.contentsuggestions.ISelectionsCallback;
+import android.app.contentsuggestions.SelectionsRequest;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.graphics.GraphicBuffer;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.LocalServices;
+import com.android.server.infra.AbstractPerUserSystemService;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+/**
+ * Per user delegate of {@link ContentSuggestionsManagerService}.
+ *
+ * <p>Main job is to forward calls to the remote implementation that can provide suggestion
+ * selections and classifications.
+ */
+public final class ContentSuggestionsPerUserService extends
+ AbstractPerUserSystemService<
+ ContentSuggestionsPerUserService, ContentSuggestionsManagerService> {
+ private static final String TAG = ContentSuggestionsPerUserService.class.getSimpleName();
+
+ @Nullable
+ @GuardedBy("mLock")
+ private RemoteContentSuggestionsService mRemoteService;
+
+ @NonNull
+ private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
+
+ ContentSuggestionsPerUserService(
+ ContentSuggestionsManagerService master, Object lock, int userId) {
+ super(master, lock, userId);
+ mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
+ }
+
+ @GuardedBy("mLock")
+ @Override // from PerUserSystemService
+ protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
+ throws PackageManager.NameNotFoundException {
+ ServiceInfo si;
+ try {
+ si = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
+ PackageManager.GET_META_DATA, mUserId);
+ } catch (RemoteException e) {
+ throw new PackageManager.NameNotFoundException(
+ "Could not get service for " + serviceComponent);
+ }
+ return si;
+ }
+
+ @GuardedBy("mLock")
+ @Override // from PerUserSystemService
+ protected boolean updateLocked(boolean disabled) {
+ final boolean enabledChanged = super.updateLocked(disabled);
+ if (enabledChanged) {
+ if (!isEnabledLocked()) {
+ // Clear the remote service for the next call
+ mRemoteService = null;
+ }
+ }
+ return enabledChanged;
+ }
+
+ @GuardedBy("mLock")
+ void provideContextImageLocked(int taskId, @NonNull Bundle imageContextRequestExtras) {
+ RemoteContentSuggestionsService service = getRemoteServiceLocked();
+ if (service != null) {
+ ActivityManager.TaskSnapshot snapshot =
+ mActivityTaskManagerInternal.getTaskSnapshot(taskId, false);
+ GraphicBuffer snapshotBuffer = null;
+ if (snapshot != null) {
+ snapshotBuffer = snapshot.getSnapshot();
+ }
+
+ service.provideContextImage(taskId, snapshotBuffer, imageContextRequestExtras);
+ }
+ }
+
+ @GuardedBy("mLock")
+ void suggestContentSelectionsLocked(
+ @NonNull SelectionsRequest selectionsRequest,
+ @NonNull ISelectionsCallback selectionsCallback) {
+ RemoteContentSuggestionsService service = getRemoteServiceLocked();
+ if (service != null) {
+ service.suggestContentSelections(selectionsRequest, selectionsCallback);
+ }
+ }
+
+ @GuardedBy("mLock")
+ void classifyContentSelectionsLocked(
+ @NonNull ClassificationsRequest classificationsRequest,
+ @NonNull IClassificationsCallback callback) {
+ RemoteContentSuggestionsService service = getRemoteServiceLocked();
+ if (service != null) {
+ service.classifyContentSelections(classificationsRequest, callback);
+ }
+ }
+
+ @GuardedBy("mLock")
+ void notifyInteractionLocked(@NonNull String requestId, @NonNull Bundle bundle) {
+ RemoteContentSuggestionsService service = getRemoteServiceLocked();
+ if (service != null) {
+ service.notifyInteraction(requestId, bundle);
+ }
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ private RemoteContentSuggestionsService getRemoteServiceLocked() {
+ if (mRemoteService == null) {
+ final String serviceName = getComponentNameLocked();
+ if (serviceName == null) {
+ if (mMaster.verbose) {
+ Slog.v(TAG, "getRemoteServiceLocked(): not set");
+ }
+ return null;
+ }
+ ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
+
+ mRemoteService = new RemoteContentSuggestionsService(getContext(),
+ serviceComponent, mUserId,
+ new RemoteContentSuggestionsService.Callbacks() {
+ @Override
+ public void onServiceDied(
+ @NonNull RemoteContentSuggestionsService service) {
+ // TODO(b/120865921): properly implement
+ Slog.w(TAG, "remote content suggestions service died");
+ }
+ }, mMaster.isBindInstantServiceAllowed(), mMaster.verbose);
+ }
+
+ return mRemoteService;
+ }
+}
diff --git a/services/contentsuggestions/java/com/android/server/contentsuggestions/RemoteContentSuggestionsService.java b/services/contentsuggestions/java/com/android/server/contentsuggestions/RemoteContentSuggestionsService.java
new file mode 100644
index 0000000..bf48d76
--- /dev/null
+++ b/services/contentsuggestions/java/com/android/server/contentsuggestions/RemoteContentSuggestionsService.java
@@ -0,0 +1,97 @@
+/*
+ * 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.
+ */
+
+package com.android.server.contentsuggestions;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.contentsuggestions.ClassificationsRequest;
+import android.app.contentsuggestions.IClassificationsCallback;
+import android.app.contentsuggestions.ISelectionsCallback;
+import android.app.contentsuggestions.SelectionsRequest;
+import android.content.ComponentName;
+import android.content.Context;
+import android.graphics.GraphicBuffer;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.service.contentsuggestions.ContentSuggestionsService;
+import android.service.contentsuggestions.IContentSuggestionsService;
+import android.text.format.DateUtils;
+
+import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
+
+/**
+ * Delegates calls from {@link ContentSuggestionsPerUserService} to the remote actual implementation
+ * of the suggestion selection and classification service.
+ */
+public class RemoteContentSuggestionsService extends
+ AbstractMultiplePendingRequestsRemoteService<RemoteContentSuggestionsService,
+ IContentSuggestionsService> {
+
+ private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS;
+
+ RemoteContentSuggestionsService(Context context, ComponentName serviceName,
+ int userId, Callbacks callbacks,
+ boolean bindInstantServiceAllowed, boolean verbose) {
+ super(context, ContentSuggestionsService.SERVICE_INTERFACE, serviceName, userId, callbacks,
+ bindInstantServiceAllowed, verbose, /* initialCapacity= */ 1);
+ }
+
+ @Override
+ protected IContentSuggestionsService getServiceInterface(IBinder service) {
+ return IContentSuggestionsService.Stub.asInterface(service);
+ }
+
+ @Override
+ protected long getTimeoutIdleBindMillis() {
+ return PERMANENT_BOUND_TIMEOUT_MS;
+ }
+
+ @Override
+ protected long getRemoteRequestMillis() {
+ return TIMEOUT_REMOTE_REQUEST_MILLIS;
+ }
+
+ void provideContextImage(int taskId, @Nullable GraphicBuffer contextImage,
+ @NonNull Bundle imageContextRequestExtras) {
+ scheduleAsyncRequest((s) -> s.provideContextImage(taskId, contextImage,
+ imageContextRequestExtras));
+ }
+
+ void suggestContentSelections(
+ @NonNull SelectionsRequest selectionsRequest,
+ @NonNull ISelectionsCallback selectionsCallback) {
+ scheduleAsyncRequest(
+ (s) -> s.suggestContentSelections(selectionsRequest, selectionsCallback));
+ }
+
+ void classifyContentSelections(
+ @NonNull ClassificationsRequest classificationsRequest,
+ @NonNull IClassificationsCallback callback) {
+ scheduleAsyncRequest((s) -> s.classifyContentSelections(classificationsRequest, callback));
+ }
+
+ void notifyInteraction(@NonNull String requestId, @NonNull Bundle bundle) {
+ scheduleAsyncRequest((s) -> s.notifyInteraction(requestId, bundle));
+ }
+
+ interface Callbacks
+ extends VultureCallback<RemoteContentSuggestionsService> {
+ // NOTE: so far we don't need to notify the callback implementation
+ // (ContentSuggestionsManager) of the request results (success, timeouts, etc..), so this
+ // callback interface is empty.
+ }
+}
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 7bbc543..a333381 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -279,7 +279,7 @@
Slog.d(TAG,
"Airplane Mode change - current state: " + BluetoothAdapter.nameForState(
- st));
+ st) + ", isAirplaneModeOn()=" + isAirplaneModeOn());
if (isAirplaneModeOn()) {
// Clear registered LE apps to force shut-off
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 66ceae4..d0666b9 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -25,6 +25,7 @@
import static android.net.ConnectivityManager.TYPE_VPN;
import static android.net.ConnectivityManager.getNetworkTypeName;
import static android.net.ConnectivityManager.isNetworkTypeValid;
+import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_VALID;
import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
@@ -37,6 +38,8 @@
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.NetworkPolicyManager.RULE_NONE;
import static android.net.NetworkPolicyManager.uidRulesToString;
+import static android.net.NetworkStack.NETWORKSTACK_PACKAGE_NAME;
+import static android.net.shared.NetworkMonitorUtils.isValidationRequired;
import static android.os.Process.INVALID_UID;
import static android.system.OsConstants.IPPROTO_TCP;
import static android.system.OsConstants.IPPROTO_UDP;
@@ -62,6 +65,8 @@
import android.net.INetd;
import android.net.INetdEventCallback;
import android.net.INetworkManagementEventObserver;
+import android.net.INetworkMonitor;
+import android.net.INetworkMonitorCallbacks;
import android.net.INetworkPolicyListener;
import android.net.INetworkPolicyManager;
import android.net.INetworkStatsService;
@@ -79,9 +84,11 @@
import android.net.NetworkQuotaInfo;
import android.net.NetworkRequest;
import android.net.NetworkSpecifier;
+import android.net.NetworkStack;
import android.net.NetworkState;
import android.net.NetworkUtils;
import android.net.NetworkWatchlistManager;
+import android.net.PrivateDnsConfigParcel;
import android.net.ProxyInfo;
import android.net.RouteInfo;
import android.net.UidRange;
@@ -90,12 +97,13 @@
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.NetworkEvent;
import android.net.netlink.InetDiagMessage;
+import android.net.shared.NetworkMonitorUtils;
+import android.net.shared.PrivateDnsConfig;
import android.net.util.MultinetworkPolicyTracker;
import android.net.util.NetdService;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
-import android.os.FileUtils;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
@@ -123,8 +131,8 @@
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.LocalLog;
-import android.util.LocalLog.ReadOnlyLocalLog;
import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -149,7 +157,6 @@
import com.android.server.am.BatteryStatsService;
import com.android.server.connectivity.DataConnectionStats;
import com.android.server.connectivity.DnsManager;
-import com.android.server.connectivity.DnsManager.PrivateDnsConfig;
import com.android.server.connectivity.DnsManager.PrivateDnsValidationUpdate;
import com.android.server.connectivity.IpConnectivityMetrics;
import com.android.server.connectivity.KeepaliveTracker;
@@ -158,7 +165,6 @@
import com.android.server.connectivity.MultipathPolicyTracker;
import com.android.server.connectivity.NetworkAgentInfo;
import com.android.server.connectivity.NetworkDiagnostics;
-import com.android.server.connectivity.NetworkMonitor;
import com.android.server.connectivity.NetworkNotificationManager;
import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
import com.android.server.connectivity.PermissionMonitor;
@@ -186,7 +192,6 @@
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
-import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -442,6 +447,43 @@
*/
private static final int EVENT_DATA_SAVER_CHANGED = 40;
+ /**
+ * Event for NetworkMonitor/NetworkAgentInfo to inform ConnectivityService that the network has
+ * been tested.
+ * obj = String representing URL that Internet probe was redirect to, if it was redirected.
+ * arg1 = One of the NETWORK_TESTED_RESULT_* constants.
+ * arg2 = NetID.
+ */
+ public static final int EVENT_NETWORK_TESTED = 41;
+
+ /**
+ * Event for NetworkMonitor/NetworkAgentInfo to inform ConnectivityService that the private DNS
+ * config was resolved.
+ * obj = PrivateDnsConfig
+ * arg2 = netid
+ */
+ public static final int EVENT_PRIVATE_DNS_CONFIG_RESOLVED = 42;
+
+ /**
+ * Request ConnectivityService display provisioning notification.
+ * arg1 = Whether to make the notification visible.
+ * arg2 = NetID.
+ * obj = Intent to be launched when notification selected by user, null if !arg1.
+ */
+ public static final int EVENT_PROVISIONING_NOTIFICATION = 43;
+
+ /**
+ * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
+ * should be shown.
+ */
+ public static final int PROVISIONING_NOTIFICATION_SHOW = 1;
+
+ /**
+ * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
+ * should be hidden.
+ */
+ public static final int PROVISIONING_NOTIFICATION_HIDE = 0;
+
private static String eventName(int what) {
return sMagicDecoderRing.get(what, Integer.toString(what));
}
@@ -506,30 +548,6 @@
private long mMaxWakelockDurationMs = 0;
private long mLastWakeLockAcquireTimestamp = 0;
- // Array of <Network,ReadOnlyLocalLogs> tracking network validation and results
- private static final int MAX_VALIDATION_LOGS = 10;
- private static class ValidationLog {
- final Network mNetwork;
- final String mName;
- final ReadOnlyLocalLog mLog;
-
- ValidationLog(Network network, String name, ReadOnlyLocalLog log) {
- mNetwork = network;
- mName = name;
- mLog = log;
- }
- }
- private final ArrayDeque<ValidationLog> mValidationLogs = new ArrayDeque<>(MAX_VALIDATION_LOGS);
-
- private void addValidationLogs(ReadOnlyLocalLog log, Network network, String name) {
- synchronized (mValidationLogs) {
- while (mValidationLogs.size() >= MAX_VALIDATION_LOGS) {
- mValidationLogs.removeLast();
- }
- mValidationLogs.addFirst(new ValidationLog(network, name, log));
- }
- }
-
private final IpConnectivityLog mMetricsLog;
@GuardedBy("mBandwidthRequests")
@@ -1684,7 +1702,11 @@
// caller type. Need to re-factor NetdEventListenerService to allow multiple
// NetworkMonitor registrants.
if (nai != null && nai.satisfies(mDefaultRequest)) {
- nai.networkMonitor.sendMessage(NetworkMonitor.EVENT_DNS_NOTIFICATION, returnCode);
+ try {
+ nai.networkMonitor().notifyDnsResponse(returnCode);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
}
}
};
@@ -2266,17 +2288,6 @@
if (ArrayUtils.contains(args, SHORT_ARG) == false) {
pw.println();
- synchronized (mValidationLogs) {
- pw.println("mValidationLogs (most recent first):");
- for (ValidationLog p : mValidationLogs) {
- pw.println(p.mNetwork + " - " + p.mName);
- pw.increaseIndent();
- p.mLog.dump(fd, pw, args);
- pw.decreaseIndent();
- }
- }
-
- pw.println();
pw.println("mNetworkRequestInfoLogs (most recent first):");
pw.increaseIndent();
mNetworkRequestInfoLogs.reverseDump(fd, pw, args);
@@ -2455,11 +2466,11 @@
switch (msg.what) {
default:
return false;
- case NetworkMonitor.EVENT_NETWORK_TESTED: {
+ case EVENT_NETWORK_TESTED: {
final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2);
if (nai == null) break;
- final boolean valid = (msg.arg1 == NetworkMonitor.NETWORK_TEST_RESULT_VALID);
+ final boolean valid = (msg.arg1 == NETWORK_TEST_RESULT_VALID);
final boolean wasValidated = nai.lastValidated;
final boolean wasDefault = isDefaultNetwork(nai);
@@ -2497,7 +2508,7 @@
}
break;
}
- case NetworkMonitor.EVENT_PROVISIONING_NOTIFICATION: {
+ case EVENT_PROVISIONING_NOTIFICATION: {
final int netId = msg.arg2;
final boolean visible = toBool(msg.arg1);
final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(netId);
@@ -2530,7 +2541,7 @@
}
break;
}
- case NetworkMonitor.EVENT_PRIVATE_DNS_CONFIG_RESOLVED: {
+ case EVENT_PRIVATE_DNS_CONFIG_RESOLVED: {
final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2);
if (nai == null) break;
@@ -2572,8 +2583,61 @@
}
}
+ private class NetworkMonitorCallbacks extends INetworkMonitorCallbacks.Stub {
+ private final NetworkAgentInfo mNai;
+
+ private NetworkMonitorCallbacks(NetworkAgentInfo nai) {
+ mNai = nai;
+ }
+
+ @Override
+ public void onNetworkMonitorCreated(INetworkMonitor networkMonitor) {
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_AGENT,
+ new Pair<>(mNai, networkMonitor)));
+ }
+
+ @Override
+ public void notifyNetworkTested(int testResult, @Nullable String redirectUrl) {
+ mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(EVENT_NETWORK_TESTED,
+ testResult, mNai.network.netId, redirectUrl));
+ }
+
+ @Override
+ public void notifyPrivateDnsConfigResolved(PrivateDnsConfigParcel config) {
+ mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(
+ EVENT_PRIVATE_DNS_CONFIG_RESOLVED,
+ 0, mNai.network.netId, PrivateDnsConfig.fromParcel(config)));
+ }
+
+ @Override
+ public void showProvisioningNotification(String action) {
+ final Intent intent = new Intent(action);
+ intent.setPackage(NETWORKSTACK_PACKAGE_NAME);
+
+ final PendingIntent pendingIntent;
+ // Only the system server can register notifications with package "android"
+ final long token = Binder.clearCallingIdentity();
+ try {
+ pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(
+ EVENT_PROVISIONING_NOTIFICATION, PROVISIONING_NOTIFICATION_SHOW,
+ mNai.network.netId,
+ pendingIntent));
+ }
+
+ @Override
+ public void hideProvisioningNotification() {
+ mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(
+ EVENT_PROVISIONING_NOTIFICATION, PROVISIONING_NOTIFICATION_HIDE,
+ mNai.network.netId));
+ }
+ }
+
private boolean networkRequiresValidation(NetworkAgentInfo nai) {
- return NetworkMonitor.isValidationRequired(
+ return isValidationRequired(
mDefaultRequest.networkCapabilities, nai.networkCapabilities);
}
@@ -2603,10 +2667,14 @@
// Internet access and therefore also require validation.
if (!networkRequiresValidation(nai)) return;
- // Notify the NetworkMonitor thread in case it needs to cancel or
+ // Notify the NetworkAgentInfo/NetworkMonitor in case NetworkMonitor needs to cancel or
// schedule DNS resolutions. If a DNS resolution is required the
// result will be sent back to us.
- nai.networkMonitor.notifyPrivateDnsSettingsChanged(cfg);
+ try {
+ nai.networkMonitor().notifyPrivateDnsChanged(cfg.toParcel());
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
// With Private DNS bypass support, we can proceed to update the
// Private DNS config immediately, even if we're in strict mode
@@ -2736,7 +2804,11 @@
// Disable wakeup packet monitoring for each interface.
wakeupModifyInterface(iface, nai.networkCapabilities, false);
}
- nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_DISCONNECTED);
+ try {
+ nai.networkMonitor().notifyNetworkDisconnected();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
mNetworkAgentInfos.remove(nai.messenger);
nai.maybeStopClat();
synchronized (mNetworkForNetId) {
@@ -3096,7 +3168,11 @@
NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
if (nai == null) return;
if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) return;
- nai.networkMonitor.sendMessage(NetworkMonitor.CMD_LAUNCH_CAPTIVE_PORTAL_APP);
+ try {
+ nai.networkMonitor().launchCaptivePortalApp();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
});
}
@@ -3217,6 +3293,11 @@
return mMultinetworkPolicyTracker.getMeteredMultipathPreference();
}
+ @Override
+ public NetworkRequest getDefaultRequest() {
+ return mDefaultRequest;
+ }
+
private class InternalHandler extends Handler {
public InternalHandler(Looper looper) {
super(looper);
@@ -3247,7 +3328,9 @@
break;
}
case EVENT_REGISTER_NETWORK_AGENT: {
- handleRegisterNetworkAgent((NetworkAgentInfo)msg.obj);
+ final Pair<NetworkAgentInfo, INetworkMonitor> arg =
+ (Pair<NetworkAgentInfo, INetworkMonitor>) msg.obj;
+ handleRegisterNetworkAgent(arg.first, arg.second);
break;
}
case EVENT_REGISTER_NETWORK_REQUEST:
@@ -3305,7 +3388,14 @@
}
case EVENT_SYSTEM_READY: {
for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
- nai.networkMonitor.systemReady = true;
+ // Might have been called already in handleRegisterNetworkAgent since
+ // mSystemReady is set before sending EVENT_SYSTEM_READY, but calling
+ // this several times is fine.
+ try {
+ nai.networkMonitor().notifySystemReady();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
}
mMultipathPolicyTracker.start();
break;
@@ -3577,7 +3667,11 @@
if (isNetworkWithLinkPropertiesBlocked(lp, uid, false)) {
return;
}
- nai.networkMonitor.forceReevaluation(uid);
+ try {
+ nai.networkMonitor().forceReevaluation(uid);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
}
@Override
@@ -4785,27 +4879,49 @@
final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities);
final NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(),
new Network(reserveNetId()), new NetworkInfo(networkInfo), lp, nc, currentScore,
- mContext, mTrackerHandler, new NetworkMisc(networkMisc), mDefaultRequest, this);
+ mContext, mTrackerHandler, new NetworkMisc(networkMisc), this);
// Make sure the network capabilities reflect what the agent info says.
nai.networkCapabilities = mixInCapabilities(nai, nc);
- synchronized (this) {
- nai.networkMonitor.systemReady = mSystemReady;
- }
final String extraInfo = networkInfo.getExtraInfo();
final String name = TextUtils.isEmpty(extraInfo)
? nai.networkCapabilities.getSSID() : extraInfo;
- addValidationLogs(nai.networkMonitor.getValidationLogs(), nai.network, name);
if (DBG) log("registerNetworkAgent " + nai);
- mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_AGENT, nai));
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mContext.getSystemService(NetworkStack.class)
+ .makeNetworkMonitor(nai.network, name, new NetworkMonitorCallbacks(nai));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ // NetworkAgentInfo registration will finish when the NetworkMonitor is created.
+ // If the network disconnects or sends any other event before that, messages are deferred by
+ // NetworkAgent until nai.asyncChannel.connect(), which will be called when finalizing the
+ // registration.
return nai.network.netId;
}
- private void handleRegisterNetworkAgent(NetworkAgentInfo nai) {
+ private void handleRegisterNetworkAgent(NetworkAgentInfo nai, INetworkMonitor networkMonitor) {
+ nai.onNetworkMonitorCreated(networkMonitor);
if (VDBG) log("Got NetworkAgent Messenger");
mNetworkAgentInfos.put(nai.messenger, nai);
synchronized (mNetworkForNetId) {
mNetworkForNetId.put(nai.network.netId, nai);
}
+ synchronized (this) {
+ if (mSystemReady) {
+ try {
+ networkMonitor.notifySystemReady();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ try {
+ networkMonitor.start();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
nai.asyncChannel.connect(mContext, mTrackerHandler, nai.messenger);
NetworkInfo networkInfo = nai.networkInfo;
nai.networkInfo = null;
@@ -4855,6 +4971,11 @@
networkAgent.updateClat(mNMS);
notifyIfacesChangedForNetworkStats();
if (networkAgent.everConnected) {
+ try {
+ networkAgent.networkMonitor().notifyLinkPropertiesChanged();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_IP_CHANGED);
}
}
@@ -5092,6 +5213,11 @@
// If the requestable capabilities have changed or the score changed, we can't have been
// called by rematchNetworkAndRequests, so it's safe to start a rematch.
rematchAllNetworksAndRequests(nai, oldScore);
+ try {
+ nai.networkMonitor().notifyNetworkCapabilitiesChanged();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED);
}
@@ -5339,6 +5465,11 @@
}
if (capabilitiesChanged) {
+ try {
+ nai.networkMonitor().notifyNetworkCapabilitiesChanged();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED);
}
@@ -5739,7 +5870,15 @@
updateLinkProperties(networkAgent, new LinkProperties(networkAgent.linkProperties),
null);
- networkAgent.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED);
+ // Until parceled LinkProperties are sent directly to NetworkMonitor, the connect
+ // command must be sent after updating LinkProperties to maximize chances of
+ // NetworkMonitor seeing the correct LinkProperties when starting.
+ // TODO: pass LinkProperties to the NetworkMonitor in the notifyNetworkConnected call.
+ try {
+ networkAgent.networkMonitor().notifyNetworkConnected();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
scheduleUnvalidatedPrompt(networkAgent);
if (networkAgent.isVPN()) {
@@ -6020,7 +6159,7 @@
@Override
public String getCaptivePortalServerUrl() {
enforceConnectivityInternalPermission();
- return NetworkMonitor.getCaptivePortalServerHttpUrl(mContext);
+ return NetworkMonitorUtils.getCaptivePortalServerHttpUrl(mContext);
}
@Override
@@ -6113,12 +6252,6 @@
}
@VisibleForTesting
- public NetworkMonitor createNetworkMonitor(Context context, Handler handler,
- NetworkAgentInfo nai, NetworkRequest defaultRequest) {
- return new NetworkMonitor(context, handler, nai, defaultRequest);
- }
-
- @VisibleForTesting
MultinetworkPolicyTracker createMultinetworkPolicyTracker(Context c, Handler h, Runnable r) {
return new MultinetworkPolicyTracker(c, h, r);
}
diff --git a/services/core/java/com/android/server/IntentResolver.java b/services/core/java/com/android/server/IntentResolver.java
index ea80ac1..80fda19 100644
--- a/services/core/java/com/android/server/IntentResolver.java
+++ b/services/core/java/com/android/server/IntentResolver.java
@@ -16,6 +16,22 @@
package com.android.server;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.FastImmutableArraySet;
+import android.util.Log;
+import android.util.LogPrinter;
+import android.util.MutableInt;
+import android.util.PrintWriterPrinter;
+import android.util.Printer;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.util.FastPrintWriter;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
@@ -25,23 +41,6 @@
import java.util.List;
import java.util.Set;
-import android.net.Uri;
-import android.util.FastImmutableArraySet;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.MutableInt;
-import android.util.PrintWriterPrinter;
-import android.util.Slog;
-import android.util.LogPrinter;
-import android.util.Printer;
-
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.util.FastPrintWriter;
-
/**
* {@hide}
*/
@@ -788,6 +787,7 @@
+ filter.hasCategory(Intent.CATEGORY_DEFAULT));
if (!defaultOnly || filter.hasCategory(Intent.CATEGORY_DEFAULT)) {
final R oneResult = newResult(filter, match, userId);
+ if (debug) Slog.v(TAG, " Created result: " + oneResult);
if (oneResult != null) {
dest.add(oneResult);
if (debug) {
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 2346cfc..869d564 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -17,9 +17,14 @@
package com.android.server;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.location.LocationManager.FUSED_PROVIDER;
+import static android.location.LocationManager.GPS_PROVIDER;
+import static android.location.LocationManager.NETWORK_PROVIDER;
+import static android.location.LocationManager.PASSIVE_PROVIDER;
import static android.location.LocationProvider.AVAILABLE;
import static android.provider.Settings.Global.LOCATION_DISABLE_STATUS_CALLBACKS;
+import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.internal.util.Preconditions.checkState;
import android.Manifest;
@@ -29,7 +34,6 @@
import android.app.AppOpsManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -64,7 +68,6 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
-import android.os.Message;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
@@ -81,12 +84,14 @@
import android.util.Log;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageMonitor;
import com.android.internal.location.ProviderProperties;
import com.android.internal.location.ProviderRequest;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.Preconditions;
import com.android.server.location.AbstractLocationProvider;
import com.android.server.location.ActivityRecognitionProxy;
import com.android.server.location.GeocoderProxy;
@@ -116,7 +121,6 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
-import java.util.Set;
/**
* The service class that manages LocationProviders and issues location
@@ -137,16 +141,12 @@
private static final String ACCESS_LOCATION_EXTRA_COMMANDS =
android.Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS;
- private static final String INSTALL_LOCATION_PROVIDER =
- android.Manifest.permission.INSTALL_LOCATION_PROVIDER;
private static final String NETWORK_LOCATION_SERVICE_ACTION =
"com.android.location.service.v3.NetworkLocationProvider";
private static final String FUSED_LOCATION_SERVICE_ACTION =
"com.android.location.service.FusedLocationProvider";
- private static final int MSG_LOCATION_CHANGED = 1;
-
private static final long NANOS_PER_MILLI = 1000000L;
// The maximum interval a location request can have and still be considered "high power".
@@ -170,75 +170,62 @@
private static final LocationRequest DEFAULT_LOCATION_REQUEST = new LocationRequest();
- private final Context mContext;
- private final AppOpsManager mAppOps;
-
- // used internally for synchronization
private final Object mLock = new Object();
+ private final Context mContext;
+ private final Handler mHandler;
- // --- fields below are final after systemRunning() ---
- private LocationFudger mLocationFudger;
- private GeofenceManager mGeofenceManager;
+ private AppOpsManager mAppOps;
private PackageManager mPackageManager;
private PowerManager mPowerManager;
private ActivityManager mActivityManager;
private UserManager mUserManager;
+
+ private GeofenceManager mGeofenceManager;
+ private LocationFudger mLocationFudger;
private GeocoderProxy mGeocodeProvider;
private GnssStatusListenerHelper mGnssStatusProvider;
private INetInitiatedListener mNetInitiatedListener;
- private LocationWorkerHandler mLocationHandler;
private PassiveProvider mPassiveProvider; // track passive provider for special cases
private LocationBlacklist mBlacklist;
private GnssMeasurementsProvider mGnssMeasurementsProvider;
private GnssNavigationMessageProvider mGnssNavigationMessageProvider;
+ @GuardedBy("mLock")
private String mLocationControllerExtraPackage;
private boolean mLocationControllerExtraPackageEnabled;
private IGpsGeofenceHardware mGpsGeofenceProxy;
- // --- fields below are protected by mLock ---
+ // list of currently active providers
+ @GuardedBy("mLock")
+ private final ArrayList<LocationProvider> mProviders = new ArrayList<>();
- // Mock (test) providers
- private final HashMap<String, MockProvider> mMockProviders =
- new HashMap<>();
+ // list of non-mock providers, so that when mock providers replace real providers, they can be
+ // later re-replaced
+ @GuardedBy("mLock")
+ private final ArrayList<LocationProvider> mRealProviders = new ArrayList<>();
- // all receivers
+ @GuardedBy("mLock")
private final HashMap<Object, Receiver> mReceivers = new HashMap<>();
-
- // currently installed providers (with mocks replacing real providers)
- private final ArrayList<LocationProvider> mProviders =
- new ArrayList<>();
-
- // real providers, saved here when mocked out
- private final HashMap<String, LocationProvider> mRealProviders =
- new HashMap<>();
-
- // mapping from provider name to provider
- private final HashMap<String, LocationProvider> mProvidersByName =
- new HashMap<>();
-
- // mapping from provider name to all its UpdateRecords
private final HashMap<String, ArrayList<UpdateRecord>> mRecordsByProvider =
new HashMap<>();
private final LocationRequestStatistics mRequestStatistics = new LocationRequestStatistics();
// mapping from provider name to last known location
+ @GuardedBy("mLock")
private final HashMap<String, Location> mLastLocation = new HashMap<>();
// same as mLastLocation, but is not updated faster than LocationFudger.FASTEST_INTERVAL_MS.
// locations stored here are not fudged for coarse permissions.
+ @GuardedBy("mLock")
private final HashMap<String, Location> mLastLocationCoarseInterval =
new HashMap<>();
- // all providers that operate over proxy, for authorizing incoming location and whitelisting
- // throttling
- private final ArrayList<LocationProviderProxy> mProxyProviders =
- new ArrayList<>();
-
private final ArraySet<String> mBackgroundThrottlePackageWhitelist = new ArraySet<>();
+ @GuardedBy("mLock")
private final ArrayMap<IBinder, Identity> mGnssMeasurementsListeners = new ArrayMap<>();
+ @GuardedBy("mLock")
private final ArrayMap<IBinder, Identity>
mGnssNavigationMessageListeners = new ArrayMap<>();
@@ -246,22 +233,22 @@
private int mCurrentUserId = UserHandle.USER_SYSTEM;
private int[] mCurrentUserProfiles = new int[]{UserHandle.USER_SYSTEM};
- // Maximum age of last location returned to clients with foreground-only location permissions.
- private long mLastLocationMaxAgeMs;
-
private GnssLocationProvider.GnssSystemInfoProvider mGnssSystemInfoProvider;
private GnssLocationProvider.GnssMetricsProvider mGnssMetricsProvider;
private GnssBatchingProvider mGnssBatchingProvider;
+ @GuardedBy("mLock")
private IBatchedLocationCallback mGnssBatchingCallback;
+ @GuardedBy("mLock")
private LinkedCallback mGnssBatchingDeathCallback;
+ @GuardedBy("mLock")
private boolean mGnssBatchingInProgress = false;
public LocationManagerService(Context context) {
super();
mContext = context;
- mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
+ mHandler = BackgroundThread.getHandler();
// Let the package manager query which are the default location
// providers as they get certain permissions granted by default.
@@ -271,134 +258,110 @@
userId -> mContext.getResources().getStringArray(
com.android.internal.R.array.config_locationProviderPackageNames));
- if (D) Log.d(TAG, "Constructed");
-
// most startup is deferred until systemRunning()
}
public void systemRunning() {
synchronized (mLock) {
- if (D) Log.d(TAG, "systemRunning()");
-
- // fetch package manager
- mPackageManager = mContext.getPackageManager();
-
- // fetch power manager
- mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
-
- // fetch activity manager
- mActivityManager
- = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
-
- // prepare worker thread
- mLocationHandler = new LocationWorkerHandler(BackgroundThread.get().getLooper());
-
- // prepare mLocationHandler's dependents
- mLocationFudger = new LocationFudger(mContext, mLocationHandler);
- mBlacklist = new LocationBlacklist(mContext, mLocationHandler);
- mBlacklist.init();
- mGeofenceManager = new GeofenceManager(mContext, mBlacklist);
-
- // Monitor for app ops mode changes.
- AppOpsManager.OnOpChangedListener callback
- = new AppOpsManager.OnOpChangedInternalListener() {
- public void onOpChanged(int op, String packageName) {
- mLocationHandler.post(() -> {
- synchronized (mLock) {
- for (Receiver receiver : mReceivers.values()) {
- receiver.updateMonitoring(true);
- }
- applyAllProviderRequirementsLocked();
- }
- });
- }
- };
- mAppOps.startWatchingMode(AppOpsManager.OP_COARSE_LOCATION, null,
- AppOpsManager.WATCH_FOREGROUND_CHANGES, callback);
-
- PackageManager.OnPermissionsChangedListener permissionListener = uid -> {
- synchronized (mLock) {
- applyAllProviderRequirementsLocked();
- }
- };
- mPackageManager.addOnPermissionsChangeListener(permissionListener);
-
- // listen for background/foreground changes
- ActivityManager.OnUidImportanceListener uidImportanceListener =
- (uid, importance) -> mLocationHandler.post(
- () -> onUidImportanceChanged(uid, importance));
- mActivityManager.addOnUidImportanceListener(uidImportanceListener,
- FOREGROUND_IMPORTANCE_CUTOFF);
-
- mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- updateUserProfiles(mCurrentUserId);
-
- updateBackgroundThrottlingWhitelistLocked();
- updateLastLocationMaxAgeLocked();
-
- // prepare providers
- loadProvidersLocked();
- updateProvidersSettingsLocked();
- for (LocationProvider provider : mProviders) {
- applyRequirementsLocked(provider.getName());
- }
+ initializeLocked();
}
+ }
- // listen for settings changes
+ @GuardedBy("mLock")
+ private void initializeLocked() {
+ mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
+ mPackageManager = mContext.getPackageManager();
+ mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+ mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+
+ mLocationFudger = new LocationFudger(mContext, mHandler);
+ mBlacklist = new LocationBlacklist(mContext, mHandler);
+ mBlacklist.init();
+ mGeofenceManager = new GeofenceManager(mContext, mBlacklist);
+
+ // prepare providers
+ initializeProvidersLocked();
+
+ // add listeners
+ mAppOps.startWatchingMode(
+ AppOpsManager.OP_COARSE_LOCATION,
+ null,
+ AppOpsManager.WATCH_FOREGROUND_CHANGES,
+ new AppOpsManager.OnOpChangedInternalListener() {
+ public void onOpChanged(int op, String packageName) {
+ synchronized (mLock) {
+ onAppOpChangedLocked();
+ }
+ }
+ });
+ mPackageManager.addOnPermissionsChangeListener(
+ uid -> {
+ synchronized (mLock) {
+ onPermissionsChangedLocked();
+ }
+ });
+
+ mActivityManager.addOnUidImportanceListener(
+ (uid, importance) -> {
+ synchronized (mLock) {
+ onUidImportanceChangedLocked(uid, importance);
+ }
+ },
+ FOREGROUND_IMPORTANCE_CUTOFF);
mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.LOCATION_PROVIDERS_ALLOWED), true,
- new ContentObserver(mLocationHandler) {
+ Settings.Secure.getUriFor(Settings.Secure.LOCATION_MODE), true,
+ new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
synchronized (mLock) {
- updateProvidersSettingsLocked();
+ onLocationModeChangedLocked(true);
+ }
+ }
+ }, UserHandle.USER_ALL);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.LOCATION_PROVIDERS_ALLOWED), true,
+ new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ synchronized (mLock) {
+ onProviderAllowedChangedLocked(true);
}
}
}, UserHandle.USER_ALL);
mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS),
true,
- new ContentObserver(mLocationHandler) {
+ new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
synchronized (mLock) {
- for (LocationProvider provider : mProviders) {
- applyRequirementsLocked(provider.getName());
- }
+ onBackgroundThrottleIntervalChangedLocked();
}
}
}, UserHandle.USER_ALL);
mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.LOCATION_LAST_LOCATION_MAX_AGE_MILLIS),
- true,
- new ContentObserver(mLocationHandler) {
- @Override
- public void onChange(boolean selfChange) {
- synchronized (mLock) {
- updateLastLocationMaxAgeLocked();
- }
- }
- }
- );
- mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(
Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST),
true,
- new ContentObserver(mLocationHandler) {
+ new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
synchronized (mLock) {
- updateBackgroundThrottlingWhitelistLocked();
- for (LocationProvider provider : mProviders) {
- applyRequirementsLocked(provider.getName());
- }
+ onBackgroundThrottleWhitelistChangedLocked();
}
}
}, UserHandle.USER_ALL);
- mPackageMonitor.register(mContext, mLocationHandler.getLooper(), true);
+ new PackageMonitor() {
+ @Override
+ public void onPackageDisappeared(String packageName, int reason) {
+ synchronized (mLock) {
+ LocationManagerService.this.onPackageDisappearedLocked(packageName);
+ }
+ }
+ }.register(mContext, mHandler.getLooper(), true);
- // listen for user change
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
@@ -407,81 +370,152 @@
mContext.registerReceiverAsUser(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (Intent.ACTION_USER_SWITCHED.equals(action)) {
- switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
- } else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action)
- || Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
- updateUserProfiles(mCurrentUserId);
+ synchronized (mLock) {
+ String action = intent.getAction();
+ if (Intent.ACTION_USER_SWITCHED.equals(action)) {
+ onUserChangedLocked(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
+ } else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action)
+ || Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
+ onUserProfilesChangedLocked();
+ }
}
}
- }, UserHandle.ALL, intentFilter, null, mLocationHandler);
+ }, UserHandle.ALL, intentFilter, null, mHandler);
+
+ // switching the user from null to system here performs the bulk of the initialization work.
+ // the user being changed will cause a reload of all user specific settings, which causes
+ // provider initialization, and propagates changes until a steady state is reached
+ mCurrentUserId = UserHandle.USER_NULL;
+ onUserChangedLocked(UserHandle.USER_SYSTEM);
+
+ // initialize in-memory settings values
+ onBackgroundThrottleWhitelistChangedLocked();
}
- private void onUidImportanceChanged(int uid, int importance) {
+ @GuardedBy("mLock")
+ private void onAppOpChangedLocked() {
+ for (Receiver receiver : mReceivers.values()) {
+ receiver.updateMonitoring(true);
+ }
+ for (LocationProvider p : mProviders) {
+ applyRequirementsLocked(p);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void onPermissionsChangedLocked() {
+ for (LocationProvider p : mProviders) {
+ applyRequirementsLocked(p);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void onLocationModeChangedLocked(boolean broadcast) {
+ for (LocationProvider p : mProviders) {
+ p.onLocationModeChangedLocked();
+ }
+
+ if (broadcast) {
+ mContext.sendBroadcastAsUser(
+ new Intent(LocationManager.MODE_CHANGED_ACTION),
+ UserHandle.ALL);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void onProviderAllowedChangedLocked(boolean broadcast) {
+ for (LocationProvider p : mProviders) {
+ p.onAllowedChangedLocked();
+ }
+
+ if (broadcast) {
+ mContext.sendBroadcastAsUser(
+ new Intent(LocationManager.PROVIDERS_CHANGED_ACTION),
+ UserHandle.ALL);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void onPackageDisappearedLocked(String packageName) {
+ ArrayList<Receiver> deadReceivers = null;
+
+ for (Receiver receiver : mReceivers.values()) {
+ if (receiver.mIdentity.mPackageName.equals(packageName)) {
+ if (deadReceivers == null) {
+ deadReceivers = new ArrayList<>();
+ }
+ deadReceivers.add(receiver);
+ }
+ }
+
+ // perform removal outside of mReceivers loop
+ if (deadReceivers != null) {
+ for (Receiver receiver : deadReceivers) {
+ removeUpdatesLocked(receiver);
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void onUidImportanceChangedLocked(int uid, int importance) {
boolean foreground = isImportanceForeground(importance);
HashSet<String> affectedProviders = new HashSet<>(mRecordsByProvider.size());
- synchronized (mLock) {
- for (Entry<String, ArrayList<UpdateRecord>> entry
- : mRecordsByProvider.entrySet()) {
- String provider = entry.getKey();
- for (UpdateRecord record : entry.getValue()) {
- if (record.mReceiver.mIdentity.mUid == uid
- && record.mIsForegroundUid != foreground) {
- if (D) {
- Log.d(TAG, "request from uid " + uid + " is now "
- + (foreground ? "foreground" : "background)"));
- }
- record.updateForeground(foreground);
-
- if (!isThrottlingExemptLocked(record.mReceiver.mIdentity)) {
- affectedProviders.add(provider);
- }
- }
- }
- }
- for (String provider : affectedProviders) {
- applyRequirementsLocked(provider);
- }
-
- for (Entry<IBinder, Identity> entry : mGnssMeasurementsListeners.entrySet()) {
- Identity callerIdentity = entry.getValue();
- if (callerIdentity.mUid == uid) {
+ for (Entry<String, ArrayList<UpdateRecord>> entry : mRecordsByProvider.entrySet()) {
+ String provider = entry.getKey();
+ for (UpdateRecord record : entry.getValue()) {
+ if (record.mReceiver.mIdentity.mUid == uid
+ && record.mIsForegroundUid != foreground) {
if (D) {
- Log.d(TAG, "gnss measurements listener from uid " + uid
- + " is now " + (foreground ? "foreground" : "background)"));
- }
- if (foreground || isThrottlingExemptLocked(entry.getValue())) {
- mGnssMeasurementsProvider.addListener(
- IGnssMeasurementsListener.Stub.asInterface(entry.getKey()),
- callerIdentity.mUid, callerIdentity.mPackageName);
- } else {
- mGnssMeasurementsProvider.removeListener(
- IGnssMeasurementsListener.Stub.asInterface(entry.getKey()));
- }
- }
- }
-
- for (Entry<IBinder, Identity> entry : mGnssNavigationMessageListeners.entrySet()) {
- Identity callerIdentity = entry.getValue();
- if (callerIdentity.mUid == uid) {
- if (D) {
- Log.d(TAG, "gnss navigation message listener from uid "
- + uid + " is now "
+ Log.d(TAG, "request from uid " + uid + " is now "
+ (foreground ? "foreground" : "background)"));
}
- if (foreground || isThrottlingExemptLocked(entry.getValue())) {
- mGnssNavigationMessageProvider.addListener(
- IGnssNavigationMessageListener.Stub.asInterface(entry.getKey()),
- callerIdentity.mUid, callerIdentity.mPackageName);
- } else {
- mGnssNavigationMessageProvider.removeListener(
- IGnssNavigationMessageListener.Stub.asInterface(entry.getKey()));
+ record.updateForeground(foreground);
+
+ if (!isThrottlingExemptLocked(record.mReceiver.mIdentity)) {
+ affectedProviders.add(provider);
}
}
}
+ }
+ for (String provider : affectedProviders) {
+ applyRequirementsLocked(provider);
+ }
- // TODO(b/120449926): The GNSS status listeners should be handled similar to the above.
+ for (Entry<IBinder, Identity> entry : mGnssMeasurementsListeners.entrySet()) {
+ Identity callerIdentity = entry.getValue();
+ if (callerIdentity.mUid == uid) {
+ if (D) {
+ Log.d(TAG, "gnss measurements listener from uid " + uid
+ + " is now " + (foreground ? "foreground" : "background)"));
+ }
+ if (foreground || isThrottlingExemptLocked(entry.getValue())) {
+ mGnssMeasurementsProvider.addListener(
+ IGnssMeasurementsListener.Stub.asInterface(entry.getKey()),
+ callerIdentity.mUid, callerIdentity.mPackageName);
+ } else {
+ mGnssMeasurementsProvider.removeListener(
+ IGnssMeasurementsListener.Stub.asInterface(entry.getKey()));
+ }
+ }
+ }
+
+ for (Entry<IBinder, Identity> entry : mGnssNavigationMessageListeners.entrySet()) {
+ Identity callerIdentity = entry.getValue();
+ if (callerIdentity.mUid == uid) {
+ if (D) {
+ Log.d(TAG, "gnss navigation message listener from uid "
+ + uid + " is now "
+ + (foreground ? "foreground" : "background)"));
+ }
+ if (foreground || isThrottlingExemptLocked(entry.getValue())) {
+ mGnssNavigationMessageProvider.addListener(
+ IGnssNavigationMessageListener.Stub.asInterface(entry.getKey()),
+ callerIdentity.mUid, callerIdentity.mPackageName);
+ } else {
+ mGnssNavigationMessageProvider.removeListener(
+ IGnssNavigationMessageListener.Stub.asInterface(entry.getKey()));
+ }
+ }
}
}
@@ -489,30 +523,43 @@
return importance <= FOREGROUND_IMPORTANCE_CUTOFF;
}
- /**
- * Makes a list of userids that are related to the current user. This is
- * relevant when using managed profiles. Otherwise the list only contains
- * the current user.
- *
- * @param currentUserId the current user, who might have an alter-ego.
- */
- private void updateUserProfiles(int currentUserId) {
- int[] profileIds = mUserManager.getProfileIdsWithDisabled(currentUserId);
- synchronized (mLock) {
- mCurrentUserProfiles = profileIds;
+ @GuardedBy("mLock")
+ private void onBackgroundThrottleIntervalChangedLocked() {
+ for (LocationProvider provider : mProviders) {
+ applyRequirementsLocked(provider);
}
}
- /**
- * Checks if the specified userId matches any of the current foreground
- * users stored in mCurrentUserProfiles.
- */
- private boolean isCurrentProfile(int userId) {
- synchronized (mLock) {
- return ArrayUtils.contains(mCurrentUserProfiles, userId);
+ @GuardedBy("mLock")
+ private void onBackgroundThrottleWhitelistChangedLocked() {
+ String setting = Settings.Global.getString(
+ mContext.getContentResolver(),
+ Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST);
+ if (setting == null) {
+ setting = "";
+ }
+
+ mBackgroundThrottlePackageWhitelist.clear();
+ mBackgroundThrottlePackageWhitelist.addAll(
+ SystemConfig.getInstance().getAllowUnthrottledLocation());
+ mBackgroundThrottlePackageWhitelist.addAll(Arrays.asList(setting.split(",")));
+
+ for (LocationProvider p : mProviders) {
+ applyRequirementsLocked(p);
}
}
+ @GuardedBy("mLock")
+ private void onUserProfilesChangedLocked() {
+ mCurrentUserProfiles = mUserManager.getProfileIdsWithDisabled(mCurrentUserId);
+ }
+
+ @GuardedBy("mLock")
+ private boolean isCurrentProfileLocked(int userId) {
+ return ArrayUtils.contains(mCurrentUserProfiles, userId);
+ }
+
+ @GuardedBy("mLock")
private void ensureFallbackFusedProviderPresentLocked(String[] pkgs) {
PackageManager pm = mContext.getPackageManager();
String systemPackageName = mContext.getPackageName();
@@ -583,30 +630,30 @@
+ "partition. The fallback must also be marked coreApp=\"true\" in the manifest");
}
- private void loadProvidersLocked() {
+ @GuardedBy("mLock")
+ private void initializeProvidersLocked() {
// create a passive location provider, which is always enabled
- LocationProvider passiveProviderManager = new LocationProvider(
- LocationManager.PASSIVE_PROVIDER);
- PassiveProvider passiveProvider = new PassiveProvider(passiveProviderManager);
-
+ LocationProvider passiveProviderManager = new LocationProvider(PASSIVE_PROVIDER);
addProviderLocked(passiveProviderManager);
- mPassiveProvider = passiveProvider;
+ mPassiveProvider = new PassiveProvider(passiveProviderManager);
+ passiveProviderManager.attachLocked(mPassiveProvider);
if (GnssLocationProvider.isSupported()) {
// Create a gps location provider
- LocationProvider gnssProviderManager = new LocationProvider(
- LocationManager.GPS_PROVIDER);
+ LocationProvider gnssProviderManager = new LocationProvider(GPS_PROVIDER, true);
+ mRealProviders.add(gnssProviderManager);
+ addProviderLocked(gnssProviderManager);
+
GnssLocationProvider gnssProvider = new GnssLocationProvider(mContext,
gnssProviderManager,
- mLocationHandler.getLooper());
+ mHandler.getLooper());
+ gnssProviderManager.attachLocked(gnssProvider);
mGnssSystemInfoProvider = gnssProvider.getGnssSystemInfoProvider();
mGnssBatchingProvider = gnssProvider.getGnssBatchingProvider();
mGnssMetricsProvider = gnssProvider.getGnssMetricsProvider();
mGnssStatusProvider = gnssProvider.getGnssStatusProvider();
mNetInitiatedListener = gnssProvider.getNetInitiatedListener();
- addProviderLocked(gnssProviderManager);
- mRealProviders.put(LocationManager.GPS_PROVIDER, gnssProviderManager);
mGnssMeasurementsProvider = gnssProvider.getGnssMeasurementsProvider();
mGnssNavigationMessageProvider = gnssProvider.getGnssNavigationMessageProvider();
mGpsGeofenceProxy = gnssProvider.getGpsGeofenceProxy();
@@ -634,9 +681,7 @@
ensureFallbackFusedProviderPresentLocked(pkgs);
// bind to network provider
-
- LocationProvider networkProviderManager = new LocationProvider(
- LocationManager.NETWORK_PROVIDER);
+ LocationProvider networkProviderManager = new LocationProvider(NETWORK_PROVIDER, true);
LocationProviderProxy networkProvider = LocationProviderProxy.createAndBind(
mContext,
networkProviderManager,
@@ -645,16 +690,15 @@
com.android.internal.R.string.config_networkLocationProviderPackageName,
com.android.internal.R.array.config_locationProviderPackageNames);
if (networkProvider != null) {
- mRealProviders.put(LocationManager.NETWORK_PROVIDER, networkProviderManager);
- mProxyProviders.add(networkProvider);
+ mRealProviders.add(networkProviderManager);
addProviderLocked(networkProviderManager);
+ networkProviderManager.attachLocked(networkProvider);
} else {
Slog.w(TAG, "no network location provider found");
}
// bind to fused provider
- LocationProvider fusedProviderManager = new LocationProvider(
- LocationManager.FUSED_PROVIDER);
+ LocationProvider fusedProviderManager = new LocationProvider(FUSED_PROVIDER);
LocationProviderProxy fusedProvider = LocationProviderProxy.createAndBind(
mContext,
fusedProviderManager,
@@ -663,9 +707,9 @@
com.android.internal.R.string.config_fusedLocationProviderPackageName,
com.android.internal.R.array.config_locationProviderPackageNames);
if (fusedProvider != null) {
+ mRealProviders.add(fusedProviderManager);
addProviderLocked(fusedProviderManager);
- mProxyProviders.add(fusedProvider);
- mRealProviders.put(LocationManager.FUSED_PROVIDER, fusedProviderManager);
+ fusedProviderManager.attachLocked(fusedProvider);
} else {
Slog.e(TAG, "no fused location provider found",
new IllegalStateException("Location service needs a fused location provider"));
@@ -715,9 +759,6 @@
for (String testProviderString : testProviderStrings) {
String fragments[] = testProviderString.split(",");
String name = fragments[0].trim();
- if (mProvidersByName.get(name) != null) {
- throw new IllegalArgumentException("Provider \"" + name + "\" already exists");
- }
ProviderProperties properties = new ProviderProperties(
Boolean.parseBoolean(fragments[1]) /* requiresNetwork */,
Boolean.parseBoolean(fragments[2]) /* requiresSatellite */,
@@ -728,28 +769,37 @@
Boolean.parseBoolean(fragments[7]) /* supportsBearing */,
Integer.parseInt(fragments[8]) /* powerRequirement */,
Integer.parseInt(fragments[9]) /* accuracy */);
- addTestProviderLocked(name, properties);
+ LocationProvider testProviderManager = new LocationProvider(name);
+ addProviderLocked(testProviderManager);
+ new MockProvider(testProviderManager, properties);
}
}
- /**
- * Called when the device's active user changes.
- *
- * @param userId the new active user's UserId
- */
- private void switchUser(int userId) {
+ @GuardedBy("mLock")
+ private void onUserChangedLocked(int userId) {
if (mCurrentUserId == userId) {
return;
}
- mBlacklist.switchUser(userId);
- mLocationHandler.removeMessages(MSG_LOCATION_CHANGED);
- synchronized (mLock) {
- mLastLocation.clear();
- mLastLocationCoarseInterval.clear();
- updateUserProfiles(userId);
- updateProvidersSettingsLocked();
- mCurrentUserId = userId;
+
+ // this call has the side effect of forcing a write to the LOCATION_MODE setting in an OS
+ // upgrade case, and ensures that if anyone checks the LOCATION_MODE setting directly, they
+ // will see it in an appropriate state (at least after that user becomes foreground for the
+ // first time...)
+ isLocationEnabledForUser(userId);
+
+ // let providers know the current user is on the way out before changing the user
+ for (LocationProvider p : mProviders) {
+ p.onUserChangingLocked();
}
+
+ mCurrentUserId = userId;
+ onUserProfilesChangedLocked();
+
+ mBlacklist.switchUser(userId);
+
+ // if the user changes, per-user settings may also have changed
+ onLocationModeChangedLocked(false);
+ onProviderAllowedChangedLocked(false);
}
private static final class Identity {
@@ -767,158 +817,380 @@
private class LocationProvider implements AbstractLocationProvider.LocationProviderManager {
private final String mName;
- private AbstractLocationProvider mProvider;
- // whether the provider is enabled in location settings
- private boolean mSettingsEnabled;
+ // whether this provider should respect LOCATION_PROVIDERS_ALLOWED (ie gps and network)
+ private final boolean mIsManagedBySettings;
- // whether the provider considers itself enabled
- private volatile boolean mEnabled;
+ // remember to clear binder identity before invoking any provider operation
+ @GuardedBy("mLock")
+ @Nullable protected AbstractLocationProvider mProvider;
- @Nullable
- private volatile ProviderProperties mProperties;
+ @GuardedBy("mLock")
+ private boolean mUseable; // combined state
+ @GuardedBy("mLock")
+ private boolean mAllowed; // state of LOCATION_PROVIDERS_ALLOWED
+ @GuardedBy("mLock")
+ private boolean mEnabled; // state of provider
+
+ @GuardedBy("mLock")
+ @Nullable private ProviderProperties mProperties;
private LocationProvider(String name) {
- mName = name;
- // TODO: initialize settings enabled?
+ this(name, false);
}
- @Override
- public void onAttachProvider(AbstractLocationProvider provider, boolean initiallyEnabled) {
- checkState(mProvider == null);
+ private LocationProvider(String name, boolean isManagedBySettings) {
+ mName = name;
+ mIsManagedBySettings = isManagedBySettings;
- // the provider is not yet fully constructed at this point, so we may not do anything
- // except save a reference for later use here. do not call any provider methods.
- mProvider = provider;
- mEnabled = initiallyEnabled;
+ mProvider = null;
+ mUseable = false;
+ mAllowed = !mIsManagedBySettings;
+ mEnabled = false;
mProperties = null;
}
+ @GuardedBy("mLock")
+ public void attachLocked(AbstractLocationProvider provider) {
+ checkNotNull(provider);
+ checkState(mProvider == null);
+ mProvider = provider;
+
+ onUseableChangedLocked();
+ }
+
public String getName() {
return mName;
}
- public boolean isEnabled() {
- return mSettingsEnabled && mEnabled;
+ @GuardedBy("mLock")
+ @Nullable
+ public String getPackageLocked() {
+ if (mProvider == null) {
+ return null;
+ } else if (mProvider instanceof LocationProviderProxy) {
+ // safe to not clear binder context since this doesn't call into the actual provider
+ return ((LocationProviderProxy) mProvider).getConnectedPackageName();
+ } else {
+ return mContext.getPackageName();
+ }
}
+ public boolean isMock() {
+ return false;
+ }
+
+ @GuardedBy("mLock")
+ public boolean isPassiveLocked() {
+ return mProvider == mPassiveProvider;
+ }
+
+ @GuardedBy("mLock")
@Nullable
- public ProviderProperties getProperties() {
+ public ProviderProperties getPropertiesLocked() {
return mProperties;
}
- public void setRequest(ProviderRequest request, WorkSource workSource) {
- mProvider.setRequest(request, workSource);
+ @GuardedBy("mLock")
+ public void setRequestLocked(ProviderRequest request, WorkSource workSource) {
+ if (mProvider != null) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mProvider.setRequest(request, workSource);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
}
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ @GuardedBy("mLock")
+ public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println(mName + " provider:");
- pw.println(" setting=" + mSettingsEnabled);
+ if (isMock()) {
+ pw.println(" mock=true");
+ }
+ pw.println(" attached=" + (mProvider != null));
+ if (mIsManagedBySettings) {
+ pw.println(" allowed=" + mAllowed);
+ }
pw.println(" enabled=" + mEnabled);
+ pw.println(" useable=" + mUseable);
pw.println(" properties=" + mProperties);
- mProvider.dump(fd, pw, args);
+
+ if (mProvider != null) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mProvider.dump(fd, pw, args);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
}
- public long getStatusUpdateTime() {
- return mProvider.getStatusUpdateTime();
+ @GuardedBy("mLock")
+ public long getStatusUpdateTimeLocked() {
+ if (mProvider != null) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ return mProvider.getStatusUpdateTime();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ } else {
+ return 0;
+ }
}
- public int getStatus(Bundle extras) {
- return mProvider.getStatus(extras);
+ @GuardedBy("mLock")
+ public int getStatusLocked(Bundle extras) {
+ if (mProvider != null) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ return mProvider.getStatus(extras);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ } else {
+ return AVAILABLE;
+ }
}
- public void sendExtraCommand(String command, Bundle extras) {
- mProvider.sendExtraCommand(command, extras);
+ @GuardedBy("mLock")
+ public void sendExtraCommandLocked(String command, Bundle extras) {
+ if (mProvider != null) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mProvider.sendExtraCommand(command, extras);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
}
// called from any thread
@Override
public void onReportLocation(Location location) {
- runOnHandler(() -> LocationManagerService.this.reportLocation(location,
- mProvider == mPassiveProvider));
+ // no security check necessary because this is coming from an internal-only interface
+ // move calls coming from below LMS onto a different thread to avoid deadlock
+ runInternal(() -> {
+ synchronized (mLock) {
+ handleLocationChangedLocked(location, this);
+ }
+ });
}
// called from any thread
@Override
public void onReportLocation(List<Location> locations) {
- runOnHandler(() -> LocationManagerService.this.reportLocationBatch(locations));
+ // move calls coming from below LMS onto a different thread to avoid deadlock
+ runInternal(() -> {
+ synchronized (mLock) {
+ LocationProvider gpsProvider = getLocationProviderLocked(GPS_PROVIDER);
+ if (gpsProvider == null || !gpsProvider.isUseableLocked()) {
+ Slog.w(TAG, "reportLocationBatch() called without user permission");
+ return;
+ }
+
+ if (mGnssBatchingCallback == null) {
+ Slog.e(TAG, "reportLocationBatch() called without active Callback");
+ return;
+ }
+
+ try {
+ mGnssBatchingCallback.onLocationBatch(locations);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "mGnssBatchingCallback.onLocationBatch failed", e);
+ }
+ }
+ });
}
// called from any thread
@Override
public void onSetEnabled(boolean enabled) {
- runOnHandler(() -> {
- if (enabled == mEnabled) {
- return;
- }
-
- mEnabled = enabled;
-
- if (!mSettingsEnabled) {
- // this provider was disabled in settings anyways, so a change to it's own
- // enabled status won't have any affect.
- return;
- }
-
- // traditionally clients can listen for changes to the LOCATION_PROVIDERS_ALLOWED
- // setting to detect when providers are enabled or disabled (even though they aren't
- // supposed to). to continue to support this we must force a change to this setting.
- // we use the fused provider because this is forced to be always enabled in settings
- // anyways, and so won't have any visible effect beyond triggering content observers
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
- Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
- "+" + LocationManager.FUSED_PROVIDER, mCurrentUserId);
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
- Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
- "-" + LocationManager.FUSED_PROVIDER, mCurrentUserId);
-
+ // move calls coming from below LMS onto a different thread to avoid deadlock
+ runInternal(() -> {
synchronized (mLock) {
- if (!enabled) {
- // If any provider has been disabled, clear all last locations for all
- // providers. This is to be on the safe side in case a provider has location
- // derived from this disabled provider.
- mLastLocation.clear();
- mLastLocationCoarseInterval.clear();
+ if (enabled == mEnabled) {
+ return;
}
- updateProviderListenersLocked(mName);
- }
+ mEnabled = enabled;
- mContext.sendBroadcastAsUser(new Intent(LocationManager.PROVIDERS_CHANGED_ACTION),
- UserHandle.ALL);
+ // update provider allowed settings to reflect enabled status
+ if (mIsManagedBySettings) {
+ if (mEnabled && !mAllowed) {
+ Settings.Secure.putStringForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+ "+" + mName,
+ mCurrentUserId);
+ } else if (!mEnabled && mAllowed) {
+ Settings.Secure.putStringForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+ "-" + mName,
+ mCurrentUserId);
+ }
+ }
+
+ onUseableChangedLocked();
+ }
});
}
@Override
public void onSetProperties(ProviderProperties properties) {
- runOnHandler(() -> mProperties = properties);
+ // move calls coming from below LMS onto a different thread to avoid deadlock
+ runInternal(() -> {
+ synchronized (mLock) {
+ mProperties = properties;
+ }
+ });
}
- private void setSettingsEnabled(boolean enabled) {
- synchronized (mLock) {
- if (mSettingsEnabled == enabled) {
+ @GuardedBy("mLock")
+ public void onLocationModeChangedLocked() {
+ onUseableChangedLocked();
+ }
+
+ private boolean isAllowed() {
+ return isAllowedForUser(mCurrentUserId);
+ }
+
+ private boolean isAllowedForUser(int userId) {
+ String allowedProviders = Settings.Secure.getStringForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+ userId);
+ return TextUtils.delimitedStringContains(allowedProviders, ',', mName);
+ }
+
+ @GuardedBy("mLock")
+ public void onAllowedChangedLocked() {
+ if (mIsManagedBySettings) {
+ boolean allowed = isAllowed();
+ if (allowed == mAllowed) {
return;
}
+ mAllowed = allowed;
- mSettingsEnabled = enabled;
- if (!mSettingsEnabled) {
- // if any provider has been disabled, clear all last locations for all
- // providers. this is to be on the safe side in case a provider has location
- // derived from this disabled provider.
- mLastLocation.clear();
- mLastLocationCoarseInterval.clear();
- updateProviderListenersLocked(mName);
- } else if (mEnabled) {
- updateProviderListenersLocked(mName);
+ // make a best effort to keep the setting matching the real enabled state of the
+ // provider so that legacy applications aren't broken.
+ if (mAllowed && !mEnabled) {
+ Settings.Secure.putStringForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+ "-" + mName,
+ mCurrentUserId);
+ }
+
+ onUseableChangedLocked();
+ }
+ }
+
+ @GuardedBy("mLock")
+ public boolean isUseableLocked() {
+ return isUseableForUserLocked(mCurrentUserId);
+ }
+
+ @GuardedBy("mLock")
+ public boolean isUseableForUserLocked(int userId) {
+ return userId == mCurrentUserId && mUseable;
+ }
+
+ @GuardedBy("mLock")
+ public void onUseableChangedLocked() {
+ // if any property that contributes to "useability" here changes state, it MUST result
+ // in a direct or indrect call to onUseableChangedLocked. this allows the provider to
+ // guarantee that it will always eventually reach the correct state.
+ boolean useable = mProvider != null
+ && mProviders.contains(this) && isLocationEnabled() && mAllowed && mEnabled;
+ if (useable == mUseable) {
+ return;
+ }
+ mUseable = useable;
+
+ if (!mUseable) {
+ // If any provider has been disabled, clear all last locations for all
+ // providers. This is to be on the safe side in case a provider has location
+ // derived from this disabled provider.
+ mLastLocation.clear();
+ mLastLocationCoarseInterval.clear();
+ }
+
+ updateProviderUseableLocked(this);
+ }
+
+ @GuardedBy("mLock")
+ public void onUserChangingLocked() {
+ // when the user is about to change, we set this provider to un-useable, and notify all
+ // of the current user clients. when the user is finished changing, useability will be
+ // updated back via onLocationModeChanged() and onAllowedChanged().
+ mUseable = false;
+ updateProviderUseableLocked(this);
+ }
+
+ // binder transactions coming from below LMS (ie location providers) need to be moved onto
+ // a different thread to avoid potential deadlock as code reenters the location providers
+ private void runInternal(Runnable runnable) {
+ if (Looper.myLooper() == mHandler.getLooper()) {
+ runnable.run();
+ } else {
+ mHandler.post(runnable);
+ }
+ }
+ }
+
+ private class MockLocationProvider extends LocationProvider {
+
+ private MockLocationProvider(String name) {
+ super(name);
+ }
+
+ @Override
+ public void attachLocked(AbstractLocationProvider provider) {
+ checkState(provider instanceof MockProvider);
+ super.attachLocked(provider);
+ }
+
+ public boolean isMock() {
+ return true;
+ }
+
+ @GuardedBy("mLock")
+ public void setEnabledLocked(boolean enabled) {
+ if (mProvider != null) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ ((MockProvider) mProvider).setEnabled(enabled);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
}
}
- private void runOnHandler(Runnable runnable) {
- if (Looper.myLooper() == mLocationHandler.getLooper()) {
- runnable.run();
- } else {
- mLocationHandler.post(runnable);
+ @GuardedBy("mLock")
+ public void setLocationLocked(Location location) {
+ if (mProvider != null) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ ((MockProvider) mProvider).setLocation(location);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ public void setStatusLocked(int status, Bundle extras, long updateTime) {
+ if (mProvider != null) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ ((MockProvider) mProvider).setStatus(status, extras, updateTime);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
}
}
@@ -1022,19 +1294,18 @@
// See if receiver has any enabled update records. Also note if any update records
// are high power (has a high power provider with an interval under a threshold).
for (UpdateRecord updateRecord : mUpdateRecords.values()) {
- if (isAllowedByUserSettingsLockedForUser(updateRecord.mProvider,
- mCurrentUserId)) {
- requestingLocation = true;
- LocationManagerService.LocationProvider locationProvider
- = mProvidersByName.get(updateRecord.mProvider);
- ProviderProperties properties = locationProvider != null
- ? locationProvider.getProperties() : null;
- if (properties != null
- && properties.mPowerRequirement == Criteria.POWER_HIGH
- && updateRecord.mRequest.getInterval() < HIGH_POWER_INTERVAL_MS) {
- requestingHighPowerLocation = true;
- break;
- }
+ LocationProvider provider = getLocationProviderLocked(updateRecord.mProvider);
+ if (provider == null || !provider.isUseableLocked()) {
+ continue;
+ }
+
+ requestingLocation = true;
+ ProviderProperties properties = provider.getPropertiesLocked();
+ if (properties != null
+ && properties.mPowerRequirement == Criteria.POWER_HIGH
+ && updateRecord.mRequest.getInterval() < HIGH_POWER_INTERVAL_MS) {
+ requestingHighPowerLocation = true;
+ break;
}
}
}
@@ -1122,7 +1393,7 @@
synchronized (this) {
// synchronize to ensure incrementPendingBroadcastsLocked()
// is called before decrementPendingBroadcasts()
- mPendingIntent.send(mContext, 0, statusChanged, this, mLocationHandler,
+ mPendingIntent.send(mContext, 0, statusChanged, this, mHandler,
getResolutionPermission(mAllowedResolutionLevel),
PendingIntentUtils.createDontSendToRestrictedAppsBundle(null));
// call this after broadcasting so we do not increment
@@ -1158,7 +1429,7 @@
synchronized (this) {
// synchronize to ensure incrementPendingBroadcastsLocked()
// is called before decrementPendingBroadcasts()
- mPendingIntent.send(mContext, 0, locationChanged, this, mLocationHandler,
+ mPendingIntent.send(mContext, 0, locationChanged, this, mHandler,
getResolutionPermission(mAllowedResolutionLevel),
PendingIntentUtils.createDontSendToRestrictedAppsBundle(null));
// call this after broadcasting so we do not increment
@@ -1201,7 +1472,7 @@
synchronized (this) {
// synchronize to ensure incrementPendingBroadcastsLocked()
// is called before decrementPendingBroadcasts()
- mPendingIntent.send(mContext, 0, providerIntent, this, mLocationHandler,
+ mPendingIntent.send(mContext, 0, providerIntent, this, mHandler,
getResolutionPermission(mAllowedResolutionLevel),
PendingIntentUtils.createDontSendToRestrictedAppsBundle(null));
// call this after broadcasting so we do not increment
@@ -1273,16 +1544,16 @@
synchronized (receiver) {
// so wakelock calls will succeed
long identity = Binder.clearCallingIdentity();
- receiver.decrementPendingBroadcastsLocked();
- Binder.restoreCallingIdentity(identity);
+ try {
+ receiver.decrementPendingBroadcastsLocked();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
}
}
}
- /**
- * Returns the year of the GNSS hardware.
- */
@Override
public int getGnssYearOfHardware() {
if (mGnssSystemInfoProvider != null) {
@@ -1292,10 +1563,6 @@
}
}
-
- /**
- * Returns the model name of the GNSS hardware.
- */
@Override
@Nullable
public String getGnssHardwareModelName() {
@@ -1306,32 +1573,24 @@
}
}
- /**
- * Runs some checks for GNSS (FINE) level permissions, used by several methods which directly
- * (try to) access GNSS information at this layer.
- */
private boolean hasGnssPermissions(String packageName) {
- int allowedResolutionLevel = getCallerAllowedResolutionLevel();
- checkResolutionLevelIsSufficientForProviderUse(
- allowedResolutionLevel,
- LocationManager.GPS_PROVIDER);
+ synchronized (mLock) {
+ int allowedResolutionLevel = getCallerAllowedResolutionLevel();
+ checkResolutionLevelIsSufficientForProviderUseLocked(
+ allowedResolutionLevel,
+ GPS_PROVIDER);
- int pid = Binder.getCallingPid();
- int uid = Binder.getCallingUid();
- long identity = Binder.clearCallingIdentity();
- boolean hasLocationAccess;
- try {
- hasLocationAccess = checkLocationAccess(pid, uid, packageName, allowedResolutionLevel);
- } finally {
- Binder.restoreCallingIdentity(identity);
+ int pid = Binder.getCallingPid();
+ int uid = Binder.getCallingUid();
+ long identity = Binder.clearCallingIdentity();
+ try {
+ return checkLocationAccess(pid, uid, packageName, allowedResolutionLevel);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
-
- return hasLocationAccess;
}
- /**
- * Returns the GNSS batching size, if available.
- */
@Override
public int getGnssBatchSize(String packageName) {
mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE,
@@ -1344,10 +1603,6 @@
}
}
- /**
- * Adds a callback for GNSS Batching events, if permissions allow, which are transported
- * to potentially multiple listeners by the BatchedLocationCallbackTransport above this.
- */
@Override
public boolean addGnssBatchingCallback(IBatchedLocationCallback callback, String packageName) {
mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE,
@@ -1357,18 +1612,20 @@
return false;
}
- mGnssBatchingCallback = callback;
- mGnssBatchingDeathCallback = new LinkedCallback(callback);
- try {
- callback.asBinder().linkToDeath(mGnssBatchingDeathCallback, 0 /* flags */);
- } catch (RemoteException e) {
- // if the remote process registering the listener is already dead, just swallow the
- // exception and return
- Log.e(TAG, "Remote listener already died.", e);
- return false;
- }
+ synchronized (mLock) {
+ mGnssBatchingCallback = callback;
+ mGnssBatchingDeathCallback = new LinkedCallback(callback);
+ try {
+ callback.asBinder().linkToDeath(mGnssBatchingDeathCallback, 0 /* flags */);
+ } catch (RemoteException e) {
+ // if the remote process registering the listener is already dead, just swallow the
+ // exception and return
+ Log.e(TAG, "Remote listener already died.", e);
+ return false;
+ }
- return true;
+ return true;
+ }
}
private class LinkedCallback implements IBinder.DeathRecipient {
@@ -1391,27 +1648,22 @@
}
}
- /**
- * Removes callback for GNSS batching
- */
@Override
public void removeGnssBatchingCallback() {
- try {
- mGnssBatchingCallback.asBinder().unlinkToDeath(mGnssBatchingDeathCallback,
- 0 /* flags */);
- } catch (NoSuchElementException e) {
- // if the death callback isn't connected (it should be...), log error, swallow the
- // exception and return
- Log.e(TAG, "Couldn't unlink death callback.", e);
+ synchronized (mLock) {
+ try {
+ mGnssBatchingCallback.asBinder().unlinkToDeath(mGnssBatchingDeathCallback,
+ 0 /* flags */);
+ } catch (NoSuchElementException e) {
+ // if the death callback isn't connected (it should be...), log error, swallow the
+ // exception and return
+ Log.e(TAG, "Couldn't unlink death callback.", e);
+ }
+ mGnssBatchingCallback = null;
+ mGnssBatchingDeathCallback = null;
}
- mGnssBatchingCallback = null;
- mGnssBatchingDeathCallback = null;
}
-
- /**
- * Starts GNSS batching, if available.
- */
@Override
public boolean startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName) {
mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE,
@@ -1421,20 +1673,20 @@
return false;
}
- if (mGnssBatchingInProgress) {
- // Current design does not expect multiple starts to be called repeatedly
- Log.e(TAG, "startGnssBatch unexpectedly called w/o stopping prior batch");
- // Try to clean up anyway, and continue
- stopGnssBatch();
- }
+ synchronized (mLock) {
+ if (mGnssBatchingInProgress) {
+ // Current design does not expect multiple starts to be called repeatedly
+ Log.e(TAG, "startGnssBatch unexpectedly called w/o stopping prior batch");
+ // Try to clean up anyway, and continue
+ stopGnssBatch();
+ }
- mGnssBatchingInProgress = true;
- return mGnssBatchingProvider.start(periodNanos, wakeOnFifoFull);
+ mGnssBatchingInProgress = true;
+ return mGnssBatchingProvider.start(periodNanos, wakeOnFifoFull);
+ }
}
- /**
- * Flushes a GNSS batch in progress
- */
+
@Override
public void flushGnssBatch(String packageName) {
mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE,
@@ -1445,117 +1697,66 @@
return;
}
- if (!mGnssBatchingInProgress) {
- Log.w(TAG, "flushGnssBatch called with no batch in progress");
- }
+ synchronized (mLock) {
+ if (!mGnssBatchingInProgress) {
+ Log.w(TAG, "flushGnssBatch called with no batch in progress");
+ }
- if (mGnssBatchingProvider != null) {
- mGnssBatchingProvider.flush();
+ if (mGnssBatchingProvider != null) {
+ mGnssBatchingProvider.flush();
+ }
}
}
- /**
- * Stops GNSS batching
- */
@Override
public boolean stopGnssBatch() {
mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE,
"Location Hardware permission not granted to access hardware batching");
- if (mGnssBatchingProvider != null) {
- mGnssBatchingInProgress = false;
- return mGnssBatchingProvider.stop();
- } else {
- return false;
- }
- }
-
- @Override
- public void reportLocationBatch(List<Location> locations) {
- checkCallerIsProvider();
-
- // Currently used only for GNSS locations - update permissions check if changed
- if (isAllowedByUserSettingsLockedForUser(LocationManager.GPS_PROVIDER, mCurrentUserId)) {
- if (mGnssBatchingCallback == null) {
- Slog.e(TAG, "reportLocationBatch() called without active Callback");
- return;
- }
- try {
- mGnssBatchingCallback.onLocationBatch(locations);
- } catch (RemoteException e) {
- Slog.e(TAG, "mGnssBatchingCallback.onLocationBatch failed", e);
- }
- } else {
- Slog.w(TAG, "reportLocationBatch() called without user permission, locations blocked");
- }
- }
-
- private void addProviderLocked(LocationProvider provider) {
- mProviders.add(provider);
- mProvidersByName.put(provider.getName(), provider);
- }
-
- private void removeProviderLocked(LocationProvider provider) {
- mProviders.remove(provider);
- mProvidersByName.remove(provider.getName());
- }
-
- /**
- * Returns "true" if access to the specified location provider is allowed by the specified
- * user's settings. Access to all location providers is forbidden to non-location-provider
- * processes belonging to background users.
- *
- * @param provider the name of the location provider
- * @param userId the user id to query
- */
- private boolean isAllowedByUserSettingsLockedForUser(String provider, int userId) {
- if (LocationManager.PASSIVE_PROVIDER.equals(provider)) {
- return isLocationEnabledForUser(userId);
- }
- if (LocationManager.FUSED_PROVIDER.equals(provider)) {
- return isLocationEnabledForUser(userId);
- }
synchronized (mLock) {
- if (mMockProviders.containsKey(provider)) {
- return isLocationEnabledForUser(userId);
+ if (mGnssBatchingProvider != null) {
+ mGnssBatchingInProgress = false;
+ return mGnssBatchingProvider.stop();
+ } else {
+ return false;
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void addProviderLocked(LocationProvider provider) {
+ Preconditions.checkState(getLocationProviderLocked(provider.getName()) == null);
+
+ mProviders.add(provider);
+
+ provider.onAllowedChangedLocked(); // allowed state may change while provider was inactive
+ provider.onUseableChangedLocked();
+ }
+
+ @GuardedBy("mLock")
+ private void removeProviderLocked(LocationProvider provider) {
+ if (mProviders.remove(provider)) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ provider.onUseableChangedLocked();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ private LocationProvider getLocationProviderLocked(String providerName) {
+ for (LocationProvider provider : mProviders) {
+ if (providerName.equals(provider.getName())) {
+ return provider;
}
}
- long identity = Binder.clearCallingIdentity();
- try {
- // Use system settings
- ContentResolver cr = mContext.getContentResolver();
- String allowedProviders = Settings.Secure.getStringForUser(
- cr, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, userId);
- return TextUtils.delimitedStringContains(allowedProviders, ',', provider);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
+ return null;
}
-
- /**
- * Returns "true" if access to the specified location provider is allowed by the specified
- * user's settings. Access to all location providers is forbidden to non-location-provider
- * processes belonging to background users.
- *
- * @param provider the name of the location provider
- * @param uid the requestor's UID
- * @param userId the user id to query
- */
- private boolean isAllowedByUserSettingsLocked(String provider, int uid, int userId) {
- if (!isCurrentProfile(UserHandle.getUserId(uid)) && !isUidALocationProvider(uid)) {
- return false;
- }
- return isAllowedByUserSettingsLockedForUser(provider, userId);
- }
-
- /**
- * Returns the permission string associated with the specified resolution level.
- *
- * @param resolutionLevel the resolution level
- * @return the permission string
- */
private String getResolutionPermission(int resolutionLevel) {
switch (resolutionLevel) {
case RESOLUTION_LEVEL_FINE:
@@ -1567,13 +1768,6 @@
}
}
- /**
- * Returns the resolution level allowed to the given PID/UID pair.
- *
- * @param pid the PID
- * @param uid the UID
- * @return resolution level allowed to the pid/uid pair
- */
private int getAllowedResolutionLevel(int pid, int uid) {
if (mContext.checkPermission(android.Manifest.permission.ACCESS_FINE_LOCATION,
pid, uid) == PERMISSION_GRANTED) {
@@ -1586,39 +1780,22 @@
}
}
- /**
- * Returns the resolution level allowed to the caller
- *
- * @return resolution level allowed to caller
- */
private int getCallerAllowedResolutionLevel() {
return getAllowedResolutionLevel(Binder.getCallingPid(), Binder.getCallingUid());
}
- /**
- * Throw SecurityException if specified resolution level is insufficient to use geofences.
- *
- * @param allowedResolutionLevel resolution level allowed to caller
- */
private void checkResolutionLevelIsSufficientForGeofenceUse(int allowedResolutionLevel) {
if (allowedResolutionLevel < RESOLUTION_LEVEL_FINE) {
throw new SecurityException("Geofence usage requires ACCESS_FINE_LOCATION permission");
}
}
- /**
- * Return the minimum resolution level required to use the specified location provider.
- *
- * @param provider the name of the location provider
- * @return minimum resolution level required for provider
- */
- private int getMinimumResolutionLevelForProviderUse(String provider) {
- if (LocationManager.GPS_PROVIDER.equals(provider) ||
- LocationManager.PASSIVE_PROVIDER.equals(provider)) {
+ @GuardedBy("mLock")
+ private int getMinimumResolutionLevelForProviderUseLocked(String provider) {
+ if (GPS_PROVIDER.equals(provider) || PASSIVE_PROVIDER.equals(provider)) {
// gps and passive providers require FINE permission
return RESOLUTION_LEVEL_FINE;
- } else if (LocationManager.NETWORK_PROVIDER.equals(provider) ||
- LocationManager.FUSED_PROVIDER.equals(provider)) {
+ } else if (NETWORK_PROVIDER.equals(provider) || FUSED_PROVIDER.equals(provider)) {
// network and fused providers are ok with COARSE or FINE
return RESOLUTION_LEVEL_COARSE;
} else {
@@ -1627,7 +1804,7 @@
continue;
}
- ProviderProperties properties = lp.getProperties();
+ ProviderProperties properties = lp.getPropertiesLocked();
if (properties != null) {
if (properties.mRequiresSatellite) {
// provider requiring satellites require FINE permission
@@ -1643,16 +1820,10 @@
return RESOLUTION_LEVEL_FINE; // if in doubt, require FINE
}
- /**
- * Throw SecurityException if specified resolution level is insufficient to use the named
- * location provider.
- *
- * @param allowedResolutionLevel resolution level allowed to caller
- * @param providerName the name of the location provider
- */
- private void checkResolutionLevelIsSufficientForProviderUse(int allowedResolutionLevel,
+ @GuardedBy("mLock")
+ private void checkResolutionLevelIsSufficientForProviderUseLocked(int allowedResolutionLevel,
String providerName) {
- int requiredResolutionLevel = getMinimumResolutionLevelForProviderUse(providerName);
+ int requiredResolutionLevel = getMinimumResolutionLevelForProviderUseLocked(providerName);
if (allowedResolutionLevel < requiredResolutionLevel) {
switch (requiredResolutionLevel) {
case RESOLUTION_LEVEL_FINE:
@@ -1668,20 +1839,6 @@
}
}
- /**
- * Throw SecurityException if WorkSource use is not allowed (i.e. can't blame other packages
- * for battery).
- */
- private void checkDeviceStatsAllowed() {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.UPDATE_DEVICE_STATS, null);
- }
-
- private void checkUpdateAppOpsAllowed() {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.UPDATE_APP_OPS_STATS, null);
- }
-
public static int resolutionLevelToOp(int allowedResolutionLevel) {
if (allowedResolutionLevel != RESOLUTION_LEVEL_NONE) {
if (allowedResolutionLevel == RESOLUTION_LEVEL_COARSE) {
@@ -1739,19 +1896,17 @@
*/
@Override
public List<String> getAllProviders() {
- ArrayList<String> out;
synchronized (mLock) {
- out = new ArrayList<>(mProviders.size());
+ ArrayList<String> providers = new ArrayList<>(mProviders.size());
for (LocationProvider provider : mProviders) {
String name = provider.getName();
- if (LocationManager.FUSED_PROVIDER.equals(name)) {
+ if (FUSED_PROVIDER.equals(name)) {
continue;
}
- out.add(name);
+ providers.add(name);
}
+ return providers;
}
- if (D) Log.d(TAG, "getAllProviders()=" + out);
- return out;
}
/**
@@ -1762,37 +1917,28 @@
@Override
public List<String> getProviders(Criteria criteria, boolean enabledOnly) {
int allowedResolutionLevel = getCallerAllowedResolutionLevel();
- ArrayList<String> out;
- int uid = Binder.getCallingUid();
- long identity = Binder.clearCallingIdentity();
- try {
- synchronized (mLock) {
- out = new ArrayList<>(mProviders.size());
- for (LocationProvider provider : mProviders) {
- String name = provider.getName();
- if (LocationManager.FUSED_PROVIDER.equals(name)) {
- continue;
- }
- if (allowedResolutionLevel >= getMinimumResolutionLevelForProviderUse(name)) {
- if (enabledOnly
- && !isAllowedByUserSettingsLocked(name, uid, mCurrentUserId)) {
- continue;
- }
- if (criteria != null
- && !android.location.LocationProvider.propertiesMeetCriteria(
- name, provider.getProperties(), criteria)) {
- continue;
- }
- out.add(name);
- }
+ synchronized (mLock) {
+ ArrayList<String> providers = new ArrayList<>(mProviders.size());
+ for (LocationProvider provider : mProviders) {
+ String name = provider.getName();
+ if (FUSED_PROVIDER.equals(name)) {
+ continue;
}
+ if (allowedResolutionLevel < getMinimumResolutionLevelForProviderUseLocked(name)) {
+ continue;
+ }
+ if (enabledOnly && !provider.isUseableLocked()) {
+ continue;
+ }
+ if (criteria != null
+ && !android.location.LocationProvider.propertiesMeetCriteria(
+ name, provider.getPropertiesLocked(), criteria)) {
+ continue;
+ }
+ providers.add(name);
}
- } finally {
- Binder.restoreCallingIdentity(identity);
+ return providers;
}
-
- if (D) Log.d(TAG, "getProviders()=" + out);
- return out;
}
/**
@@ -1804,71 +1950,36 @@
*/
@Override
public String getBestProvider(Criteria criteria, boolean enabledOnly) {
- String result;
-
List<String> providers = getProviders(criteria, enabledOnly);
- if (!providers.isEmpty()) {
- result = pickBest(providers);
- if (D) Log.d(TAG, "getBestProvider(" + criteria + ", " + enabledOnly + ")=" + result);
- return result;
- }
- providers = getProviders(null, enabledOnly);
- if (!providers.isEmpty()) {
- result = pickBest(providers);
- if (D) Log.d(TAG, "getBestProvider(" + criteria + ", " + enabledOnly + ")=" + result);
- return result;
+ if (providers.isEmpty()) {
+ providers = getProviders(null, enabledOnly);
}
- if (D) Log.d(TAG, "getBestProvider(" + criteria + ", " + enabledOnly + ")=" + null);
+ if (!providers.isEmpty()) {
+ if (providers.contains(GPS_PROVIDER)) {
+ return GPS_PROVIDER;
+ } else if (providers.contains(NETWORK_PROVIDER)) {
+ return NETWORK_PROVIDER;
+ } else {
+ return providers.get(0);
+ }
+ }
+
return null;
}
- private String pickBest(List<String> providers) {
- if (providers.contains(LocationManager.GPS_PROVIDER)) {
- return LocationManager.GPS_PROVIDER;
- } else if (providers.contains(LocationManager.NETWORK_PROVIDER)) {
- return LocationManager.NETWORK_PROVIDER;
- } else {
- return providers.get(0);
- }
- }
-
- @Override
- public boolean providerMeetsCriteria(String provider, Criteria criteria) {
- LocationProvider p = mProvidersByName.get(provider);
- if (p == null) {
- throw new IllegalArgumentException("provider=" + provider);
- }
-
- boolean result = android.location.LocationProvider.propertiesMeetCriteria(
- p.getName(), p.getProperties(), criteria);
- if (D) Log.d(TAG, "providerMeetsCriteria(" + provider + ", " + criteria + ")=" + result);
- return result;
- }
-
- private void updateProvidersSettingsLocked() {
- for (LocationProvider p : mProviders) {
- p.setSettingsEnabled(isAllowedByUserSettingsLockedForUser(p.getName(), mCurrentUserId));
- }
-
- mContext.sendBroadcastAsUser(new Intent(LocationManager.MODE_CHANGED_ACTION),
- UserHandle.ALL);
- }
-
- private void updateProviderListenersLocked(String provider) {
- LocationProvider p = mProvidersByName.get(provider);
- if (p == null) return;
-
- boolean enabled = p.isEnabled();
+ @GuardedBy("mLock")
+ private void updateProviderUseableLocked(LocationProvider provider) {
+ boolean useable = provider.isUseableLocked();
ArrayList<Receiver> deadReceivers = null;
- ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider);
+ ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName());
if (records != null) {
for (UpdateRecord record : records) {
- if (isCurrentProfile(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) {
+ if (isCurrentProfileLocked(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) {
// Sends a notification message to the receiver
- if (!record.mReceiver.callProviderEnabledLocked(provider, enabled)) {
+ if (!record.mReceiver.callProviderEnabledLocked(provider.getName(), useable)) {
if (deadReceivers == null) {
deadReceivers = new ArrayList<>();
}
@@ -1887,25 +1998,37 @@
applyRequirementsLocked(provider);
}
- private void applyRequirementsLocked(String provider) {
- LocationProvider p = mProvidersByName.get(provider);
- if (p == null) return;
+ @GuardedBy("mLock")
+ private void applyRequirementsLocked(String providerName) {
+ LocationProvider provider = getLocationProviderLocked(providerName);
+ if (provider != null) {
+ applyRequirementsLocked(provider);
+ }
+ }
- ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider);
+ @GuardedBy("mLock")
+ private void applyRequirementsLocked(LocationProvider provider) {
+ ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName());
WorkSource worksource = new WorkSource();
ProviderRequest providerRequest = new ProviderRequest();
- ContentResolver resolver = mContext.getContentResolver();
- long backgroundThrottleInterval = Settings.Global.getLong(
- resolver,
- Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS,
- DEFAULT_BACKGROUND_THROTTLE_INTERVAL_MS);
+ long backgroundThrottleInterval;
- if (p.isEnabled() && records != null && !records.isEmpty()) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ backgroundThrottleInterval = Settings.Global.getLong(
+ mContext.getContentResolver(),
+ Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS,
+ DEFAULT_BACKGROUND_THROTTLE_INTERVAL_MS);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+
+ if (provider.isUseableLocked() && records != null && !records.isEmpty()) {
// initialize the low power mode to true and set to false if any of the records requires
providerRequest.lowPowerMode = true;
for (UpdateRecord record : records) {
- if (isCurrentProfile(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) {
+ if (isCurrentProfileLocked(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) {
if (checkLocationAccess(
record.mReceiver.mIdentity.mPid,
record.mReceiver.mIdentity.mUid,
@@ -1945,7 +2068,8 @@
// under that threshold.
long thresholdInterval = (providerRequest.interval + 1000) * 3 / 2;
for (UpdateRecord record : records) {
- if (isCurrentProfile(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) {
+ if (isCurrentProfileLocked(
+ UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) {
LocationRequest locationRequest = record.mRequest;
// Don't assign battery blame for update records whose
@@ -1972,7 +2096,7 @@
}
if (D) Log.d(TAG, "provider request: " + provider + " " + providerRequest);
- p.setRequest(providerRequest, worksource);
+ provider.setRequestLocked(providerRequest, worksource);
}
/**
@@ -1995,34 +2119,11 @@
@Override
public String[] getBackgroundThrottlingWhitelist() {
synchronized (mLock) {
- return mBackgroundThrottlePackageWhitelist.toArray(
- new String[0]);
+ return mBackgroundThrottlePackageWhitelist.toArray(new String[0]);
}
}
- private void updateBackgroundThrottlingWhitelistLocked() {
- String setting = Settings.Global.getString(
- mContext.getContentResolver(),
- Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST);
- if (setting == null) {
- setting = "";
- }
-
- mBackgroundThrottlePackageWhitelist.clear();
- mBackgroundThrottlePackageWhitelist.addAll(
- SystemConfig.getInstance().getAllowUnthrottledLocation());
- mBackgroundThrottlePackageWhitelist.addAll(
- Arrays.asList(setting.split(",")));
- }
-
- private void updateLastLocationMaxAgeLocked() {
- mLastLocationMaxAgeMs =
- Settings.Global.getLong(
- mContext.getContentResolver(),
- Settings.Global.LOCATION_LAST_LOCATION_MAX_AGE_MILLIS,
- DEFAULT_LAST_LOCATION_MAX_AGE_MS);
- }
-
+ @GuardedBy("mLock")
private boolean isThrottlingExemptLocked(Identity identity) {
if (identity.mUid == Process.SYSTEM_UID) {
return true;
@@ -2032,8 +2133,8 @@
return true;
}
- for (LocationProviderProxy provider : mProxyProviders) {
- if (identity.mPackageName.equals(provider.getConnectedPackageName())) {
+ for (LocationProvider provider : mProviders) {
+ if (identity.mPackageName.equals(provider.getPackageLocked())) {
return true;
}
}
@@ -2119,6 +2220,7 @@
}
}
+ @GuardedBy("mLock")
private Receiver getReceiverLocked(ILocationListener listener, int pid, int uid,
String packageName, WorkSource workSource, boolean hideFromAppOps) {
IBinder binder = listener.asBinder();
@@ -2137,6 +2239,7 @@
return receiver;
}
+ @GuardedBy("mLock")
private Receiver getReceiverLocked(PendingIntent intent, int pid, int uid, String packageName,
WorkSource workSource, boolean hideFromAppOps) {
Receiver receiver = mReceivers.get(intent);
@@ -2202,67 +2305,65 @@
throw new SecurityException("invalid package name: " + packageName);
}
- private void checkPendingIntent(PendingIntent intent) {
- if (intent == null) {
- throw new IllegalArgumentException("invalid pending intent: " + null);
- }
- }
-
- private Receiver checkListenerOrIntentLocked(ILocationListener listener, PendingIntent intent,
- int pid, int uid, String packageName, WorkSource workSource, boolean hideFromAppOps) {
- if (intent == null && listener == null) {
- throw new IllegalArgumentException("need either listener or intent");
- } else if (intent != null && listener != null) {
- throw new IllegalArgumentException("cannot register both listener and intent");
- } else if (intent != null) {
- checkPendingIntent(intent);
- return getReceiverLocked(intent, pid, uid, packageName, workSource, hideFromAppOps);
- } else {
- return getReceiverLocked(listener, pid, uid, packageName, workSource, hideFromAppOps);
- }
- }
-
@Override
public void requestLocationUpdates(LocationRequest request, ILocationListener listener,
PendingIntent intent, String packageName) {
- if (request == null) request = DEFAULT_LOCATION_REQUEST;
- checkPackageName(packageName);
- int allowedResolutionLevel = getCallerAllowedResolutionLevel();
- checkResolutionLevelIsSufficientForProviderUse(allowedResolutionLevel,
- request.getProvider());
- WorkSource workSource = request.getWorkSource();
- if (workSource != null && !workSource.isEmpty()) {
- checkDeviceStatsAllowed();
- }
- boolean hideFromAppOps = request.getHideFromAppOps();
- if (hideFromAppOps) {
- checkUpdateAppOpsAllowed();
- }
- boolean callerHasLocationHardwarePermission =
- mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE)
- == PERMISSION_GRANTED;
- LocationRequest sanitizedRequest = createSanitizedRequest(request, allowedResolutionLevel,
- callerHasLocationHardwarePermission);
-
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
- // providers may use public location API's, need to clear identity
- long identity = Binder.clearCallingIdentity();
- try {
- // We don't check for MODE_IGNORED here; we will do that when we go to deliver
- // a location.
- checkLocationAccess(pid, uid, packageName, allowedResolutionLevel);
-
- synchronized (mLock) {
- Receiver recevier = checkListenerOrIntentLocked(listener, intent, pid, uid,
- packageName, workSource, hideFromAppOps);
- requestLocationUpdatesLocked(sanitizedRequest, recevier, uid, packageName);
+ synchronized (mLock) {
+ if (request == null) request = DEFAULT_LOCATION_REQUEST;
+ checkPackageName(packageName);
+ int allowedResolutionLevel = getCallerAllowedResolutionLevel();
+ checkResolutionLevelIsSufficientForProviderUseLocked(allowedResolutionLevel,
+ request.getProvider());
+ WorkSource workSource = request.getWorkSource();
+ if (workSource != null && !workSource.isEmpty()) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.UPDATE_DEVICE_STATS, null);
}
- } finally {
- Binder.restoreCallingIdentity(identity);
+ boolean hideFromAppOps = request.getHideFromAppOps();
+ if (hideFromAppOps) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.UPDATE_APP_OPS_STATS, null);
+ }
+ boolean callerHasLocationHardwarePermission =
+ mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE)
+ == PERMISSION_GRANTED;
+ LocationRequest sanitizedRequest = createSanitizedRequest(request,
+ allowedResolutionLevel,
+ callerHasLocationHardwarePermission);
+
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+
+ long identity = Binder.clearCallingIdentity();
+ try {
+
+ // We don't check for MODE_IGNORED here; we will do that when we go to deliver
+ // a location.
+ checkLocationAccess(pid, uid, packageName, allowedResolutionLevel);
+
+ if (intent == null && listener == null) {
+ throw new IllegalArgumentException("need either listener or intent");
+ } else if (intent != null && listener != null) {
+ throw new IllegalArgumentException(
+ "cannot register both listener and intent");
+ }
+
+ Receiver receiver;
+ if (intent != null) {
+ receiver = getReceiverLocked(intent, pid, uid, packageName, workSource,
+ hideFromAppOps);
+ } else {
+ receiver = getReceiverLocked(listener, pid, uid, packageName, workSource,
+ hideFromAppOps);
+ }
+ requestLocationUpdatesLocked(sanitizedRequest, receiver, uid, packageName);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
}
+ @GuardedBy("mLock")
private void requestLocationUpdatesLocked(LocationRequest request, Receiver receiver,
int uid, String packageName) {
// Figure out the provider. Either its explicitly request (legacy use cases), or
@@ -2273,7 +2374,7 @@
throw new IllegalArgumentException("provider name must not be null");
}
- LocationProvider provider = mProvidersByName.get(name);
+ LocationProvider provider = getLocationProviderLocked(name);
if (provider == null) {
throw new IllegalArgumentException("provider doesn't exist: " + name);
}
@@ -2292,7 +2393,7 @@
oldRecord.disposeLocked(false);
}
- if (provider.isEnabled()) {
+ if (provider.isUseableLocked()) {
applyRequirementsLocked(name);
} else {
// Notify the listener that updates are currently disabled
@@ -2308,14 +2409,23 @@
String packageName) {
checkPackageName(packageName);
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
+ int pid = Binder.getCallingPid();
+ int uid = Binder.getCallingUid();
+
+ if (intent == null && listener == null) {
+ throw new IllegalArgumentException("need either listener or intent");
+ } else if (intent != null && listener != null) {
+ throw new IllegalArgumentException("cannot register both listener and intent");
+ }
synchronized (mLock) {
- Receiver receiver = checkListenerOrIntentLocked(listener, intent, pid, uid,
- packageName, null, false);
+ Receiver receiver;
+ if (intent != null) {
+ receiver = getReceiverLocked(intent, pid, uid, packageName, null, false);
+ } else {
+ receiver = getReceiverLocked(listener, pid, uid, packageName, null, false);
+ }
- // providers may use public location API's, need to clear identity
long identity = Binder.clearCallingIdentity();
try {
removeUpdatesLocked(receiver);
@@ -2325,6 +2435,7 @@
}
}
+ @GuardedBy("mLock")
private void removeUpdatesLocked(Receiver receiver) {
if (D) Log.i(TAG, "remove " + Integer.toHexString(System.identityHashCode(receiver)));
@@ -2356,51 +2467,53 @@
}
}
- private void applyAllProviderRequirementsLocked() {
- for (LocationProvider p : mProviders) {
- applyRequirementsLocked(p.getName());
- }
- }
-
@Override
- public Location getLastLocation(LocationRequest request, String packageName) {
- if (D) Log.d(TAG, "getLastLocation: " + request);
- if (request == null) request = DEFAULT_LOCATION_REQUEST;
- int allowedResolutionLevel = getCallerAllowedResolutionLevel();
- checkPackageName(packageName);
- checkResolutionLevelIsSufficientForProviderUse(allowedResolutionLevel,
- request.getProvider());
- // no need to sanitize this request, as only the provider name is used
+ public Location getLastLocation(LocationRequest r, String packageName) {
+ if (D) Log.d(TAG, "getLastLocation: " + r);
+ synchronized (mLock) {
+ LocationRequest request = r != null ? r : DEFAULT_LOCATION_REQUEST;
+ int allowedResolutionLevel = getCallerAllowedResolutionLevel();
+ checkPackageName(packageName);
+ checkResolutionLevelIsSufficientForProviderUseLocked(allowedResolutionLevel,
+ request.getProvider());
+ // no need to sanitize this request, as only the provider name is used
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
- final long identity = Binder.clearCallingIdentity();
- try {
- if (mBlacklist.isBlacklisted(packageName)) {
- if (D) {
- Log.d(TAG, "not returning last loc for blacklisted app: " +
- packageName);
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (mBlacklist.isBlacklisted(packageName)) {
+ if (D) {
+ Log.d(TAG, "not returning last loc for blacklisted app: "
+ + packageName);
+ }
+ return null;
}
- return null;
- }
- if (!reportLocationAccessNoThrow(pid, uid, packageName, allowedResolutionLevel)) {
- if (D) {
- Log.d(TAG, "not returning last loc for no op app: " +
- packageName);
+ if (!reportLocationAccessNoThrow(pid, uid, packageName, allowedResolutionLevel)) {
+ if (D) {
+ Log.d(TAG, "not returning last loc for no op app: "
+ + packageName);
+ }
+ return null;
}
- return null;
- }
- synchronized (mLock) {
// Figure out the provider. Either its explicitly request (deprecated API's),
// or use the fused provider
String name = request.getProvider();
if (name == null) name = LocationManager.FUSED_PROVIDER;
- LocationProvider provider = mProvidersByName.get(name);
+ LocationProvider provider = getLocationProviderLocked(name);
if (provider == null) return null;
- if (!isAllowedByUserSettingsLocked(name, uid, mCurrentUserId)) return null;
+ // only the current user or location providers may get location this way
+ if (!isCurrentProfileLocked(UserHandle.getUserId(uid)) && !isLocationProviderLocked(
+ uid)) {
+ return null;
+ }
+
+ if (!provider.isUseableLocked()) {
+ return null;
+ }
Location location;
if (allowedResolutionLevel < RESOLUTION_LEVEL_FINE) {
@@ -2416,9 +2529,12 @@
// Don't return stale location to apps with foreground-only location permission.
String op = resolutionLevelToOpStr(allowedResolutionLevel);
- long locationAgeMs = SystemClock.elapsedRealtime() -
- location.getElapsedRealtimeNanos() / NANOS_PER_MILLI;
- if ((locationAgeMs > mLastLocationMaxAgeMs)
+ long locationAgeMs = SystemClock.elapsedRealtime()
+ - location.getElapsedRealtimeNanos() / NANOS_PER_MILLI;
+ if ((locationAgeMs > Settings.Global.getLong(
+ mContext.getContentResolver(),
+ Settings.Global.LOCATION_LAST_LOCATION_MAX_AGE_MILLIS,
+ DEFAULT_LAST_LOCATION_MAX_AGE_MS))
&& (mAppOps.unsafeCheckOp(op, uid, packageName)
== AppOpsManager.MODE_FOREGROUND)) {
return null;
@@ -2433,24 +2549,13 @@
} else {
return new Location(location);
}
+ return null;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
- return null;
- } finally {
- Binder.restoreCallingIdentity(identity);
}
}
- /**
- * Provides an interface to inject and set the last location if location is not available
- * currently.
- *
- * This helps in cases where the product (Cars for example) has saved the last known location
- * before powering off. This interface lets the client inject the saved location while the GPS
- * chipset is getting its first fix, there by improving user experience.
- *
- * @param location - Location object to inject
- * @return true if update was successful, false if not
- */
@Override
public boolean injectLocation(Location location) {
mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE,
@@ -2464,38 +2569,23 @@
}
return false;
}
- LocationProvider p = null;
- String provider = location.getProvider();
- if (provider != null) {
- p = mProvidersByName.get(provider);
- }
- if (p == null) {
- if (D) {
- Log.d(TAG, "injectLocation(): unknown provider");
- }
- return false;
- }
+
synchronized (mLock) {
- if (!isAllowedByUserSettingsLockedForUser(provider, mCurrentUserId)) {
- if (D) {
- Log.d(TAG, "Location disabled in Settings for current user:" + mCurrentUserId);
- }
+ LocationProvider provider = getLocationProviderLocked(location.getProvider());
+ if (provider == null || !provider.isUseableLocked()) {
return false;
- } else {
- // NOTE: If last location is already available, location is not injected. If
- // provider's normal source (like a GPS chipset) have already provided an output,
- // there is no need to inject this location.
- if (mLastLocation.get(provider) == null) {
- updateLastLocationLocked(location, provider);
- } else {
- if (D) {
- Log.d(TAG, "injectLocation(): Location exists. Not updating");
- }
- return false;
- }
}
+
+ // NOTE: If last location is already available, location is not injected. If
+ // provider's normal source (like a GPS chipset) have already provided an output
+ // there is no need to inject this location.
+ if (mLastLocation.get(provider.getName()) != null) {
+ return false;
+ }
+
+ updateLastLocationLocked(location, provider.getName());
+ return true;
}
- return true;
}
@Override
@@ -2504,39 +2594,49 @@
if (request == null) request = DEFAULT_LOCATION_REQUEST;
int allowedResolutionLevel = getCallerAllowedResolutionLevel();
checkResolutionLevelIsSufficientForGeofenceUse(allowedResolutionLevel);
- checkPendingIntent(intent);
- checkPackageName(packageName);
- checkResolutionLevelIsSufficientForProviderUse(allowedResolutionLevel,
- request.getProvider());
- // Require that caller can manage given document
- boolean callerHasLocationHardwarePermission =
- mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE)
- == PERMISSION_GRANTED;
- LocationRequest sanitizedRequest = createSanitizedRequest(request, allowedResolutionLevel,
- callerHasLocationHardwarePermission);
-
- if (D) Log.d(TAG, "requestGeofence: " + sanitizedRequest + " " + geofence + " " + intent);
-
- // geo-fence manager uses the public location API, need to clear identity
- int uid = Binder.getCallingUid();
- // TODO: http://b/23822629
- if (UserHandle.getUserId(uid) != UserHandle.USER_SYSTEM) {
- // temporary measure until geofences work for secondary users
- Log.w(TAG, "proximity alerts are currently available only to the primary user");
- return;
+ if (intent == null) {
+ throw new IllegalArgumentException("invalid pending intent: " + null);
}
- long identity = Binder.clearCallingIdentity();
- try {
- mGeofenceManager.addFence(sanitizedRequest, geofence, intent, allowedResolutionLevel,
- uid, packageName);
- } finally {
- Binder.restoreCallingIdentity(identity);
+ checkPackageName(packageName);
+ synchronized (mLock) {
+ checkResolutionLevelIsSufficientForProviderUseLocked(allowedResolutionLevel,
+ request.getProvider());
+ // Require that caller can manage given document
+ boolean callerHasLocationHardwarePermission =
+ mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE)
+ == PERMISSION_GRANTED;
+ LocationRequest sanitizedRequest = createSanitizedRequest(request,
+ allowedResolutionLevel,
+ callerHasLocationHardwarePermission);
+
+ if (D) {
+ Log.d(TAG, "requestGeofence: " + sanitizedRequest + " " + geofence + " " + intent);
+ }
+
+ // geo-fence manager uses the public location API, need to clear identity
+ int uid = Binder.getCallingUid();
+ // TODO: http://b/23822629
+ if (UserHandle.getUserId(uid) != UserHandle.USER_SYSTEM) {
+ // temporary measure until geofences work for secondary users
+ Log.w(TAG, "proximity alerts are currently available only to the primary user");
+ return;
+ }
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mGeofenceManager.addFence(sanitizedRequest, geofence, intent,
+ allowedResolutionLevel,
+ uid, packageName);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
}
@Override
public void removeGeofence(Geofence geofence, PendingIntent intent, String packageName) {
- checkPendingIntent(intent);
+ if (intent == null) {
+ throw new IllegalArgumentException("invalid pending intent: " + null);
+ }
checkPackageName(packageName);
if (D) Log.d(TAG, "removeGeofence: " + geofence + " " + intent);
@@ -2641,6 +2741,7 @@
synchronized (mLock) {
Identity callerIdentity
= new Identity(Binder.getCallingUid(), Binder.getCallingPid(), packageName);
+
// TODO(b/120481270): Register for client death notification and update map.
mGnssNavigationMessageListeners.put(listener.asBinder(), callerIdentity);
long identity = Binder.clearCallingIdentity();
@@ -2670,25 +2771,26 @@
}
@Override
- public boolean sendExtraCommand(String provider, String command, Bundle extras) {
- if (provider == null) {
+ public boolean sendExtraCommand(String providerName, String command, Bundle extras) {
+ if (providerName == null) {
// throw NullPointerException to remain compatible with previous implementation
throw new NullPointerException();
}
- checkResolutionLevelIsSufficientForProviderUse(getCallerAllowedResolutionLevel(),
- provider);
-
- // and check for ACCESS_LOCATION_EXTRA_COMMANDS
- if ((mContext.checkCallingOrSelfPermission(ACCESS_LOCATION_EXTRA_COMMANDS)
- != PERMISSION_GRANTED)) {
- throw new SecurityException("Requires ACCESS_LOCATION_EXTRA_COMMANDS permission");
- }
-
synchronized (mLock) {
- LocationProvider p = mProvidersByName.get(provider);
- if (p == null) return false;
+ checkResolutionLevelIsSufficientForProviderUseLocked(getCallerAllowedResolutionLevel(),
+ providerName);
- p.sendExtraCommand(command, extras);
+ // and check for ACCESS_LOCATION_EXTRA_COMMANDS
+ if ((mContext.checkCallingOrSelfPermission(ACCESS_LOCATION_EXTRA_COMMANDS)
+ != PERMISSION_GRANTED)) {
+ throw new SecurityException("Requires ACCESS_LOCATION_EXTRA_COMMANDS permission");
+ }
+
+ LocationProvider provider = getLocationProviderLocked(providerName);
+ if (provider != null) {
+ provider.sendExtraCommandLocked(command, extras);
+ }
+
return true;
}
}
@@ -2707,44 +2809,29 @@
}
}
- /**
- * @return null if the provider does not exist
- * @throws SecurityException if the provider is not allowed to be
- * accessed by the caller
- */
@Override
- public ProviderProperties getProviderProperties(String provider) {
- checkResolutionLevelIsSufficientForProviderUse(getCallerAllowedResolutionLevel(),
- provider);
-
- LocationProvider p;
+ public ProviderProperties getProviderProperties(String providerName) {
synchronized (mLock) {
- p = mProvidersByName.get(provider);
- }
+ checkResolutionLevelIsSufficientForProviderUseLocked(getCallerAllowedResolutionLevel(),
+ providerName);
- if (p == null) return null;
- return p.getProperties();
+ LocationProvider provider = getLocationProviderLocked(providerName);
+ if (provider == null) {
+ return null;
+ }
+ return provider.getPropertiesLocked();
+ }
}
- /**
- * @return null if the provider does not exist
- * @throws SecurityException if the provider is not allowed to be
- * accessed by the caller
- */
@Override
public String getNetworkProviderPackage() {
- LocationProvider p;
synchronized (mLock) {
- p = mProvidersByName.get(LocationManager.NETWORK_PROVIDER);
+ LocationProvider provider = getLocationProviderLocked(NETWORK_PROVIDER);
+ if (provider == null) {
+ return null;
+ }
+ return provider.getPackageLocked();
}
-
- if (p == null) {
- return null;
- }
- if (p.mProvider instanceof LocationProviderProxy) {
- return ((LocationProviderProxy) p.mProvider).getConnectedPackageName();
- }
- return null;
}
@Override
@@ -2780,241 +2867,96 @@
}
}
- /**
- * Returns the current location enabled/disabled status for a user
- *
- * @param userId the id of the user
- * @return true if location is enabled
- */
+ private boolean isLocationEnabled() {
+ return isLocationEnabledForUser(mCurrentUserId);
+ }
+
@Override
public boolean isLocationEnabledForUser(int userId) {
// Check INTERACT_ACROSS_USERS permission if userId is not current user id.
- checkInteractAcrossUsersPermission(userId);
+ if (UserHandle.getCallingUserId() != userId) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS,
+ "Requires INTERACT_ACROSS_USERS permission");
+ }
long identity = Binder.clearCallingIdentity();
try {
- synchronized (mLock) {
- final String allowedProviders = Settings.Secure.getStringForUser(
+ boolean enabled;
+ try {
+ enabled = Settings.Secure.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.LOCATION_MODE,
+ userId) != Settings.Secure.LOCATION_MODE_OFF;
+ } catch (Settings.SettingNotFoundException e) {
+ // OS upgrade case where mode isn't set yet
+ enabled = !TextUtils.isEmpty(Settings.Secure.getStringForUser(
mContext.getContentResolver(),
Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
- userId);
- if (allowedProviders == null) {
- return false;
+ userId));
+
+ try {
+ Settings.Secure.putIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.LOCATION_MODE,
+ enabled
+ ? Settings.Secure.LOCATION_MODE_HIGH_ACCURACY
+ : Settings.Secure.LOCATION_MODE_OFF,
+ userId);
+ } catch (RuntimeException ex) {
+ // any problem with writing should not be propagated
+ Slog.e(TAG, "error updating location mode", ex);
}
- final List<String> providerList = Arrays.asList(allowedProviders.split(","));
- for (String provider : mRealProviders.keySet()) {
- if (provider.equals(LocationManager.PASSIVE_PROVIDER)
- || provider.equals(LocationManager.FUSED_PROVIDER)) {
- continue;
- }
- if (providerList.contains(provider)) {
- return true;
- }
- }
- return false;
}
+ return enabled;
} finally {
Binder.restoreCallingIdentity(identity);
}
}
- /**
- * Enable or disable location for a user
- *
- * @param enabled true to enable location, false to disable location
- * @param userId the id of the user
- */
- @Override
- public void setLocationEnabledForUser(boolean enabled, int userId) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.WRITE_SECURE_SETTINGS,
- "Requires WRITE_SECURE_SETTINGS permission");
-
- // Check INTERACT_ACROSS_USERS permission if userId is not current user id.
- checkInteractAcrossUsersPermission(userId);
-
- long identity = Binder.clearCallingIdentity();
- try {
- synchronized (mLock) {
- final Set<String> allRealProviders = mRealProviders.keySet();
- // Update all providers on device plus gps and network provider when disabling
- // location
- Set<String> allProvidersSet = new ArraySet<>(allRealProviders.size() + 2);
- allProvidersSet.addAll(allRealProviders);
- // When disabling location, disable gps and network provider that could have been
- // enabled by location mode api.
- if (!enabled) {
- allProvidersSet.add(LocationManager.GPS_PROVIDER);
- allProvidersSet.add(LocationManager.NETWORK_PROVIDER);
- }
- if (allProvidersSet.isEmpty()) {
- return;
- }
- // to ensure thread safety, we write the provider name with a '+' or '-'
- // and let the SettingsProvider handle it rather than reading and modifying
- // the list of enabled providers.
- final String prefix = enabled ? "+" : "-";
- StringBuilder locationProvidersAllowed = new StringBuilder();
- for (String provider : allProvidersSet) {
- if (provider.equals(LocationManager.PASSIVE_PROVIDER)
- || provider.equals(LocationManager.FUSED_PROVIDER)) {
- continue;
- }
- locationProvidersAllowed.append(prefix);
- locationProvidersAllowed.append(provider);
- locationProvidersAllowed.append(",");
- }
- // Remove the trailing comma
- locationProvidersAllowed.setLength(locationProvidersAllowed.length() - 1);
- Settings.Secure.putStringForUser(
- mContext.getContentResolver(),
- Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
- locationProvidersAllowed.toString(),
- userId);
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- /**
- * Returns the current enabled/disabled status of a location provider and user
- *
- * @param providerName name of the provider
- * @param userId the id of the user
- * @return true if the provider exists and is enabled
- */
@Override
public boolean isProviderEnabledForUser(String providerName, int userId) {
// Check INTERACT_ACROSS_USERS permission if userId is not current user id.
- checkInteractAcrossUsersPermission(userId);
-
- if (!isLocationEnabledForUser(userId)) {
- return false;
+ if (UserHandle.getCallingUserId() != userId) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS,
+ "Requires INTERACT_ACROSS_USERS permission");
}
// Fused provider is accessed indirectly via criteria rather than the provider-based APIs,
// so we discourage its use
- if (LocationManager.FUSED_PROVIDER.equals(providerName)) return false;
+ if (FUSED_PROVIDER.equals(providerName)) return false;
- long identity = Binder.clearCallingIdentity();
- try {
- LocationProvider provider;
- synchronized (mLock) {
- provider = mProvidersByName.get(providerName);
- }
- return provider != null && provider.isEnabled();
- } finally {
- Binder.restoreCallingIdentity(identity);
+ synchronized (mLock) {
+ LocationProvider provider = getLocationProviderLocked(providerName);
+ return provider != null && provider.isUseableForUserLocked(userId);
}
}
- /**
- * Enable or disable a single location provider.
- *
- * @param provider name of the provider
- * @param enabled true to enable the provider. False to disable the provider
- * @param userId the id of the user to set
- * @return true if the value was set, false on errors
- */
- @Override
- public boolean setProviderEnabledForUser(String provider, boolean enabled, int userId) {
- return false;
- }
-
- /**
- * Method for checking INTERACT_ACROSS_USERS permission if specified user id is not the same as
- * current user id
- *
- * @param userId the user id to get or set value
- */
- private void checkInteractAcrossUsersPermission(int userId) {
- int uid = Binder.getCallingUid();
- if (UserHandle.getUserId(uid) != userId) {
- if (ActivityManager.checkComponentPermission(
- android.Manifest.permission.INTERACT_ACROSS_USERS, uid, -1, true)
- != PERMISSION_GRANTED) {
- throw new SecurityException("Requires INTERACT_ACROSS_USERS permission");
- }
- }
- }
-
- /**
- * Returns "true" if the UID belongs to a bound location provider.
- *
- * @param uid the uid
- * @return true if uid belongs to a bound location provider
- */
- private boolean isUidALocationProvider(int uid) {
+ @GuardedBy("mLock")
+ private boolean isLocationProviderLocked(int uid) {
if (uid == Process.SYSTEM_UID) {
return true;
}
- if (mGeocodeProvider != null) {
- if (doesUidHavePackage(uid, mGeocodeProvider.getConnectedPackageName())) return true;
- }
- for (LocationProviderProxy proxy : mProxyProviders) {
- if (doesUidHavePackage(uid, proxy.getConnectedPackageName())) return true;
- }
- return false;
- }
- private void checkCallerIsProvider() {
- if (mContext.checkCallingOrSelfPermission(INSTALL_LOCATION_PROVIDER)
- == PERMISSION_GRANTED) {
- return;
- }
-
- // Previously we only used the INSTALL_LOCATION_PROVIDER
- // check. But that is system or signature
- // protection level which is not flexible enough for
- // providers installed oustide the system image. So
- // also allow providers with a UID matching the
- // currently bound package name
-
- if (isUidALocationProvider(Binder.getCallingUid())) {
- return;
- }
-
- throw new SecurityException("need INSTALL_LOCATION_PROVIDER permission, " +
- "or UID of a currently bound location provider");
- }
-
- /**
- * Returns true if the given package belongs to the given uid.
- */
- private boolean doesUidHavePackage(int uid, String packageName) {
- if (packageName == null) {
- return false;
- }
String[] packageNames = mPackageManager.getPackagesForUid(uid);
if (packageNames == null) {
return false;
}
- for (String name : packageNames) {
- if (packageName.equals(name)) {
+ for (LocationProvider provider : mProviders) {
+ String packageName = provider.getPackageLocked();
+ if (packageName == null) {
+ continue;
+ }
+ if (ArrayUtils.contains(packageNames, packageName)) {
return true;
}
}
return false;
}
- @Override
- public void reportLocation(Location location, boolean passive) {
- checkCallerIsProvider();
-
- if (!location.isComplete()) {
- Log.w(TAG, "Dropping incomplete location: " + location);
- return;
- }
-
- mLocationHandler.removeMessages(MSG_LOCATION_CHANGED, location);
- Message m = Message.obtain(mLocationHandler, MSG_LOCATION_CHANGED, location);
- m.arg1 = (passive ? 1 : 0);
- mLocationHandler.sendMessageAtFrontOfQueue(m);
- }
-
-
- private static boolean shouldBroadcastSafe(
+ @GuardedBy("mLock")
+ private static boolean shouldBroadcastSafeLocked(
Location loc, Location lastLoc, UpdateRecord record, long now) {
// Always broadcast the first update
if (lastLoc == null) {
@@ -3046,26 +2988,36 @@
return record.mRealRequest.getExpireAt() >= now;
}
- private void handleLocationChangedLocked(Location location, boolean passive) {
+ @GuardedBy("mLock")
+ private void handleLocationChangedLocked(Location location, LocationProvider provider) {
+ if (!mProviders.contains(provider)) {
+ return;
+ }
+ if (!location.isComplete()) {
+ Log.w(TAG, "Dropping incomplete location: " + location);
+ return;
+ }
+
+ if (!provider.isPassiveLocked()) {
+ // notify passive provider of the new location
+ mPassiveProvider.updateLocation(location);
+ }
+
if (D) Log.d(TAG, "incoming location: " + location);
long now = SystemClock.elapsedRealtime();
- String provider = (passive ? LocationManager.PASSIVE_PROVIDER : location.getProvider());
- // Skip if the provider is unknown.
- LocationProvider p = mProvidersByName.get(provider);
- if (p == null) return;
- updateLastLocationLocked(location, provider);
+ updateLastLocationLocked(location, provider.getName());
// mLastLocation should have been updated from the updateLastLocationLocked call above.
- Location lastLocation = mLastLocation.get(provider);
+ Location lastLocation = mLastLocation.get(provider.getName());
if (lastLocation == null) {
Log.e(TAG, "handleLocationChangedLocked() updateLastLocation failed");
return;
}
// Update last known coarse interval location if enough time has passed.
- Location lastLocationCoarseInterval = mLastLocationCoarseInterval.get(provider);
+ Location lastLocationCoarseInterval = mLastLocationCoarseInterval.get(provider.getName());
if (lastLocationCoarseInterval == null) {
lastLocationCoarseInterval = new Location(location);
- mLastLocationCoarseInterval.put(provider, lastLocationCoarseInterval);
+ mLastLocationCoarseInterval.put(provider.getName(), lastLocationCoarseInterval);
}
long timeDiffNanos = location.getElapsedRealtimeNanos()
- lastLocationCoarseInterval.getElapsedRealtimeNanos();
@@ -3079,7 +3031,7 @@
lastLocationCoarseInterval.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION);
// Skip if there are no UpdateRecords for this provider.
- ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider);
+ ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName());
if (records == null || records.size() == 0) return;
// Fetch coarse location
@@ -3088,13 +3040,6 @@
coarseLocation = mLocationFudger.getOrCreate(noGPSLocation);
}
- // Fetch latest status update time
- long newStatusUpdateTime = p.getStatusUpdateTime();
-
- // Get latest status
- Bundle extras = new Bundle();
- int status = p.getStatus(extras);
-
ArrayList<Receiver> deadReceivers = null;
ArrayList<UpdateRecord> deadUpdateRecords = null;
@@ -3104,8 +3049,8 @@
boolean receiverDead = false;
int receiverUserId = UserHandle.getUserId(receiver.mIdentity.mUid);
- if (!isCurrentProfile(receiverUserId)
- && !isUidALocationProvider(receiver.mIdentity.mUid)) {
+ if (!isCurrentProfileLocked(receiverUserId)
+ && !isLocationProviderLocked(receiver.mIdentity.mUid)) {
if (D) {
Log.d(TAG, "skipping loc update for background user " + receiverUserId +
" (current user: " + mCurrentUserId + ", app: " +
@@ -3142,7 +3087,8 @@
}
if (notifyLocation != null) {
Location lastLoc = r.mLastFixBroadcast;
- if ((lastLoc == null) || shouldBroadcastSafe(notifyLocation, lastLoc, r, now)) {
+ if ((lastLoc == null)
+ || shouldBroadcastSafeLocked(notifyLocation, lastLoc, r, now)) {
if (lastLoc == null) {
lastLoc = new Location(notifyLocation);
r.mLastFixBroadcast = lastLoc;
@@ -3150,7 +3096,8 @@
lastLoc.set(notifyLocation);
}
if (!receiver.callLocationChangedLocked(notifyLocation)) {
- Slog.w(TAG, "RemoteException calling onLocationChanged on " + receiver);
+ Slog.w(TAG, "RemoteException calling onLocationChanged on "
+ + receiver);
receiverDead = true;
}
r.mRealRequest.decrementNumUpdates();
@@ -3161,12 +3108,16 @@
// guarded behind this setting now. should be removed completely post-Q
if (Settings.Global.getInt(mContext.getContentResolver(),
LOCATION_DISABLE_STATUS_CALLBACKS, 1) == 0) {
+ long newStatusUpdateTime = provider.getStatusUpdateTimeLocked();
+ Bundle extras = new Bundle();
+ int status = provider.getStatusLocked(extras);
+
long prevStatusUpdateTime = r.mLastStatusBroadcast;
if ((newStatusUpdateTime > prevStatusUpdateTime)
&& (prevStatusUpdateTime != 0 || status != AVAILABLE)) {
r.mLastStatusBroadcast = newStatusUpdateTime;
- if (!receiver.callStatusChangedLocked(provider, status, extras)) {
+ if (!receiver.callStatusChangedLocked(provider.getName(), status, extras)) {
receiverDead = true;
Slog.w(TAG, "RemoteException calling onStatusChanged on " + receiver);
}
@@ -3205,12 +3156,7 @@
}
}
- /**
- * Updates last location with the given location
- *
- * @param location new location to update
- * @param provider Location provider to update for
- */
+ @GuardedBy("mLock")
private void updateLastLocationLocked(Location location, String provider) {
Location noGPSLocation = location.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION);
Location lastNoGPSLocation;
@@ -3229,75 +3175,6 @@
lastLocation.set(location);
}
- private class LocationWorkerHandler extends Handler {
- public LocationWorkerHandler(Looper looper) {
- super(looper, null, true);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_LOCATION_CHANGED:
- handleLocationChanged((Location) msg.obj, msg.arg1 == 1);
- break;
- }
- }
- }
-
- private boolean isMockProvider(String provider) {
- synchronized (mLock) {
- return mMockProviders.containsKey(provider);
- }
- }
-
- private void handleLocationChanged(Location location, boolean passive) {
- // create a working copy of the incoming Location so that the service can modify it without
- // disturbing the caller's copy
- Location myLocation = new Location(location);
- String provider = myLocation.getProvider();
-
- // set "isFromMockProvider" bit if location came from a mock provider. we do not clear this
- // bit if location did not come from a mock provider because passive/fused providers can
- // forward locations from mock providers, and should not grant them legitimacy in doing so.
- if (!myLocation.isFromMockProvider() && isMockProvider(provider)) {
- myLocation.setIsFromMockProvider(true);
- }
-
- synchronized (mLock) {
- if (!passive) {
- // notify passive provider of the new location
- mPassiveProvider.updateLocation(myLocation);
- }
- handleLocationChangedLocked(myLocation, passive);
- }
- }
-
- private final PackageMonitor mPackageMonitor = new PackageMonitor() {
- @Override
- public void onPackageDisappeared(String packageName, int reason) {
- // remove all receivers associated with this package name
- synchronized (mLock) {
- ArrayList<Receiver> deadReceivers = null;
-
- for (Receiver receiver : mReceivers.values()) {
- if (receiver.mIdentity.mPackageName.equals(packageName)) {
- if (deadReceivers == null) {
- deadReceivers = new ArrayList<>();
- }
- deadReceivers.add(receiver);
- }
- }
-
- // perform removal outside of mReceivers loop
- if (deadReceivers != null) {
- for (Receiver receiver : deadReceivers) {
- removeUpdatesLocked(receiver);
- }
- }
- }
- }
- };
-
// Geocoder
@Override
@@ -3343,63 +3220,60 @@
return;
}
- if (LocationManager.PASSIVE_PROVIDER.equals(name)) {
+ if (PASSIVE_PROVIDER.equals(name)) {
throw new IllegalArgumentException("Cannot mock the passive location provider");
}
- long identity = Binder.clearCallingIdentity();
synchronized (mLock) {
- // remove the real provider if we are replacing GPS or network provider
- if (LocationManager.GPS_PROVIDER.equals(name)
- || LocationManager.NETWORK_PROVIDER.equals(name)
- || LocationManager.FUSED_PROVIDER.equals(name)) {
- LocationProvider p = mProvidersByName.get(name);
- if (p != null) {
- removeProviderLocked(p);
+ long identity = Binder.clearCallingIdentity();
+ try {
+ LocationProvider oldProvider = getLocationProviderLocked(name);
+ if (oldProvider != null) {
+ if (oldProvider.isMock()) {
+ throw new IllegalArgumentException(
+ "Provider \"" + name + "\" already exists");
+ }
+
+ removeProviderLocked(oldProvider);
}
+
+ MockLocationProvider mockProviderManager = new MockLocationProvider(name);
+ addProviderLocked(mockProviderManager);
+ mockProviderManager.attachLocked(new MockProvider(mockProviderManager, properties));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
- addTestProviderLocked(name, properties);
}
- Binder.restoreCallingIdentity(identity);
- }
-
- private void addTestProviderLocked(String name, ProviderProperties properties) {
- if (mProvidersByName.get(name) != null) {
- throw new IllegalArgumentException("Provider \"" + name + "\" already exists");
- }
-
- LocationProvider provider = new LocationProvider(name);
- MockProvider mockProvider = new MockProvider(provider, properties);
-
- addProviderLocked(provider);
- mMockProviders.put(name, mockProvider);
- mLastLocation.put(name, null);
- mLastLocationCoarseInterval.put(name, null);
}
@Override
- public void removeTestProvider(String provider, String opPackageName) {
+ public void removeTestProvider(String name, String opPackageName) {
if (!canCallerAccessMockLocation(opPackageName)) {
return;
}
synchronized (mLock) {
- MockProvider mockProvider = mMockProviders.remove(provider);
- if (mockProvider == null) {
- throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
- }
-
long identity = Binder.clearCallingIdentity();
try {
- removeProviderLocked(mProvidersByName.get(provider));
+ LocationProvider testProvider = getLocationProviderLocked(name);
+ if (testProvider == null || !testProvider.isMock()) {
+ throw new IllegalArgumentException("Provider \"" + name + "\" unknown");
+ }
+
+ removeProviderLocked(testProvider);
// reinstate real provider if available
- LocationProvider realProvider = mRealProviders.get(provider);
+ LocationProvider realProvider = null;
+ for (LocationProvider provider : mRealProviders) {
+ if (name.equals(provider.getName())) {
+ realProvider = provider;
+ break;
+ }
+ }
+
if (realProvider != null) {
addProviderLocked(realProvider);
}
- mLastLocation.put(provider, null);
- mLastLocationCoarseInterval.put(provider, null);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -3407,78 +3281,60 @@
}
@Override
- public void setTestProviderLocation(String provider, Location loc, String opPackageName) {
- if (!canCallerAccessMockLocation(opPackageName)) {
- return;
- }
-
- synchronized (mLock) {
- MockProvider mockProvider = mMockProviders.get(provider);
- if (mockProvider == null) {
- throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
- }
-
- // Ensure that the location is marked as being mock. There's some logic to do this in
- // handleLocationChanged(), but it fails if loc has the wrong provider (bug 33091107).
- Location mock = new Location(loc);
- mock.setIsFromMockProvider(true);
-
- if (!TextUtils.isEmpty(loc.getProvider()) && !provider.equals(loc.getProvider())) {
- // The location has an explicit provider that is different from the mock provider
- // name. The caller may be trying to fool us via bug 33091107.
- EventLog.writeEvent(0x534e4554, "33091107", Binder.getCallingUid(),
- provider + "!=" + loc.getProvider());
- }
-
- // clear calling identity so INSTALL_LOCATION_PROVIDER permission is not required
- long identity = Binder.clearCallingIdentity();
- try {
- mockProvider.setLocation(mock);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
- }
-
- @Override
- public void setTestProviderEnabled(String provider, boolean enabled, String opPackageName) {
- if (!canCallerAccessMockLocation(opPackageName)) {
- return;
- }
-
- synchronized (mLock) {
- MockProvider mockProvider = mMockProviders.get(provider);
- if (mockProvider == null) {
- throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
- }
- long identity = Binder.clearCallingIdentity();
- try {
- mockProvider.setEnabled(enabled);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
- }
-
- @Override
- public void setTestProviderStatus(String provider, int status, Bundle extras, long updateTime,
+ public void setTestProviderLocation(String providerName, Location location,
String opPackageName) {
if (!canCallerAccessMockLocation(opPackageName)) {
return;
}
synchronized (mLock) {
- MockProvider mockProvider = mMockProviders.get(provider);
- if (mockProvider == null) {
- throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
+ LocationProvider testProvider = getLocationProviderLocked(providerName);
+ if (testProvider == null || !testProvider.isMock()) {
+ throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown");
}
- mockProvider.setStatus(status, extras, updateTime);
+
+ String locationProvider = location.getProvider();
+ if (!TextUtils.isEmpty(locationProvider) && !providerName.equals(locationProvider)) {
+ // The location has an explicit provider that is different from the mock
+ // provider name. The caller may be trying to fool us via b/33091107.
+ EventLog.writeEvent(0x534e4554, "33091107", Binder.getCallingUid(),
+ providerName + "!=" + location.getProvider());
+ }
+
+ ((MockLocationProvider) testProvider).setLocationLocked(location);
}
}
- private void log(String log) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Slog.d(TAG, log);
+ @Override
+ public void setTestProviderEnabled(String providerName, boolean enabled, String opPackageName) {
+ if (!canCallerAccessMockLocation(opPackageName)) {
+ return;
+ }
+
+ synchronized (mLock) {
+ LocationProvider testProvider = getLocationProviderLocked(providerName);
+ if (testProvider == null || !testProvider.isMock()) {
+ throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown");
+ }
+
+ ((MockLocationProvider) testProvider).setEnabledLocked(enabled);
+ }
+ }
+
+ @Override
+ public void setTestProviderStatus(String providerName, int status, Bundle extras,
+ long updateTime, String opPackageName) {
+ if (!canCallerAccessMockLocation(opPackageName)) {
+ return;
+ }
+
+ synchronized (mLock) {
+ LocationProvider testProvider = getLocationProviderLocked(providerName);
+ if (testProvider == null || !testProvider.isMock()) {
+ throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown");
+ }
+
+ ((MockLocationProvider) testProvider).setStatusLocked(status, extras, updateTime);
}
}
@@ -3494,6 +3350,7 @@
return;
}
pw.println("Current Location Manager state:");
+ pw.println(" Location Mode: " + isLocationEnabled());
pw.println(" Location Listeners:");
for (Receiver receiver : mReceivers.values()) {
pw.println(" " + receiver);
@@ -3548,12 +3405,6 @@
pw.append(" ");
mBlacklist.dump(pw);
- if (mMockProviders.size() > 0) {
- pw.println(" Mock Providers:");
- for (Map.Entry<String, MockProvider> i : mMockProviders.entrySet()) {
- i.getValue().dump(fd, pw, args);
- }
- }
if (mLocationControllerExtraPackage != null) {
pw.println(" Location controller extra package: " + mLocationControllerExtraPackage
@@ -3574,7 +3425,7 @@
return;
}
for (LocationProvider provider : mProviders) {
- provider.dump(fd, pw, args);
+ provider.dumpLocked(fd, pw, args);
}
if (mGnssBatchingInProgress) {
pw.println(" GNSS batching in progress");
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index f27d373..8adc416 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -16,6 +16,7 @@
package com.android.server;
+import android.annotation.Nullable;
import android.content.Context;
import android.os.Environment;
import android.os.Handler;
@@ -29,6 +30,7 @@
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.XmlUtils;
@@ -46,8 +48,10 @@
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Iterator;
import java.util.List;
+import java.util.Set;
/**
* Monitors the health of packages on the system and notifies interested observers when packages
@@ -58,7 +62,7 @@
// Duration to count package failures before it resets to 0
private static final int TRIGGER_DURATION_MS = 60000;
// Number of package failures within the duration above before we notify observers
- private static final int TRIGGER_FAILURE_COUNT = 5;
+ static final int TRIGGER_FAILURE_COUNT = 5;
private static final int DB_VERSION = 1;
private static final String TAG_PACKAGE_WATCHDOG = "package-watchdog";
private static final String TAG_PACKAGE = "package";
@@ -75,20 +79,13 @@
// Handler to run package cleanup runnables
private final Handler mTimerHandler;
private final Handler mIoHandler;
- // Contains (observer-name -> external-observer-handle) that have been registered during the
- // current boot.
- // It is populated when observers call #registerHealthObserver and it does not survive reboots.
- @GuardedBy("mLock")
- final ArrayMap<String, PackageHealthObserver> mRegisteredObservers = new ArrayMap<>();
- // Contains (observer-name -> internal-observer-handle) that have ever been registered from
+ // Contains (observer-name -> observer-handle) that have ever been registered from
// previous boots. Observers with all packages expired are periodically pruned.
// It is saved to disk on system shutdown and repouplated on startup so it survives reboots.
@GuardedBy("mLock")
- final ArrayMap<String, ObserverInternal> mAllObservers = new ArrayMap<>();
+ private final ArrayMap<String, ObserverInternal> mAllObservers = new ArrayMap<>();
// File containing the XML data of monitored packages /data/system/package-watchdog.xml
- private final AtomicFile mPolicyFile =
- new AtomicFile(new File(new File(Environment.getDataDirectory(), "system"),
- "package-watchdog.xml"));
+ private final AtomicFile mPolicyFile;
// Runnable to prune monitored packages that have expired
private final Runnable mPackageCleanup;
// Last SystemClock#uptimeMillis a package clean up was executed.
@@ -98,14 +95,32 @@
// 0 if mPackageCleanup not running.
private long mDurationAtLastReschedule;
+ // TODO(zezeozue): Remove redundant context param
private PackageWatchdog(Context context) {
mContext = context;
+ mPolicyFile = new AtomicFile(new File(new File(Environment.getDataDirectory(), "system"),
+ "package-watchdog.xml"));
mTimerHandler = new Handler(Looper.myLooper());
mIoHandler = BackgroundThread.getHandler();
mPackageCleanup = this::rescheduleCleanup;
loadFromFile();
}
+ /**
+ * Creates a PackageWatchdog for testing that uses the same {@code looper} for all handlers
+ * and creates package-watchdog.xml in an apps data directory.
+ */
+ @VisibleForTesting
+ PackageWatchdog(Context context, Looper looper) {
+ mContext = context;
+ mPolicyFile = new AtomicFile(new File(context.getFilesDir(), "package-watchdog.xml"));
+ mTimerHandler = new Handler(looper);
+ mIoHandler = mTimerHandler;
+ mPackageCleanup = this::rescheduleCleanup;
+ loadFromFile();
+ }
+
+
/** Creates or gets singleton instance of PackageWatchdog. */
public static PackageWatchdog getInstance(Context context) {
synchronized (PackageWatchdog.class) {
@@ -124,7 +139,10 @@
*/
public void registerHealthObserver(PackageHealthObserver observer) {
synchronized (mLock) {
- mRegisteredObservers.put(observer.getName(), observer);
+ ObserverInternal internalObserver = mAllObservers.get(observer.getName());
+ if (internalObserver != null) {
+ internalObserver.mRegisteredObserver = observer;
+ }
if (mDurationAtLastReschedule == 0) {
// Nothing running, schedule
rescheduleCleanup();
@@ -143,7 +161,7 @@
* or {@code durationMs} is less than 1
*/
public void startObservingHealth(PackageHealthObserver observer, List<String> packageNames,
- int durationMs) {
+ long durationMs) {
if (packageNames.isEmpty() || durationMs < 1) {
throw new IllegalArgumentException("Observation not started, no packages specified"
+ "or invalid duration");
@@ -180,11 +198,32 @@
public void unregisterHealthObserver(PackageHealthObserver observer) {
synchronized (mLock) {
mAllObservers.remove(observer.getName());
- mRegisteredObservers.remove(observer.getName());
}
saveToFileAsync();
}
+ /**
+ * Returns packages observed by {@code observer}
+ *
+ * @return an empty set if {@code observer} has some packages observerd from a previous boot
+ * but has not registered itself in the current boot to receive notifications. Returns null
+ * if there are no active packages monitored from any boot.
+ */
+ @Nullable
+ public Set<String> getPackages(PackageHealthObserver observer) {
+ synchronized (mLock) {
+ for (int i = 0; i < mAllObservers.size(); i++) {
+ if (observer.getName().equals(mAllObservers.keyAt(i))) {
+ if (observer.equals(mAllObservers.valueAt(i).mRegisteredObserver)) {
+ return mAllObservers.valueAt(i).mPackages.keySet();
+ }
+ return Collections.emptySet();
+ }
+ }
+ }
+ return null;
+ }
+
// TODO(zezeozue:) Accept current versionCodes of failing packages?
/**
* Called when a process fails either due to a crash or ANR.
@@ -198,24 +237,23 @@
public void onPackageFailure(String[] packages) {
ArrayMap<String, List<PackageHealthObserver>> packagesToReport = new ArrayMap<>();
synchronized (mLock) {
- if (mRegisteredObservers.isEmpty()) {
+ if (mAllObservers.isEmpty()) {
return;
}
for (int pIndex = 0; pIndex < packages.length; pIndex++) {
+ // Observers interested in receiving packageName failures
+ List<PackageHealthObserver> observersToNotify = new ArrayList<>();
for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
- // Observers interested in receiving packageName failures
- List<PackageHealthObserver> observersToNotify = new ArrayList<>();
- PackageHealthObserver activeObserver =
- mRegisteredObservers.get(mAllObservers.valueAt(oIndex).mName);
- if (activeObserver != null) {
- observersToNotify.add(activeObserver);
+ PackageHealthObserver registeredObserver =
+ mAllObservers.valueAt(oIndex).mRegisteredObserver;
+ if (registeredObserver != null) {
+ observersToNotify.add(registeredObserver);
}
-
- // Save interested observers and notify them outside the lock
- if (!observersToNotify.isEmpty()) {
- packagesToReport.put(packages[pIndex], observersToNotify);
- }
+ }
+ // Save interested observers and notify them outside the lock
+ if (!observersToNotify.isEmpty()) {
+ packagesToReport.put(packages[pIndex], observersToNotify);
}
}
}
@@ -223,8 +261,11 @@
// Notify observers
for (int pIndex = 0; pIndex < packagesToReport.size(); pIndex++) {
List<PackageHealthObserver> observers = packagesToReport.valueAt(pIndex);
+ String packageName = packages[pIndex];
for (int oIndex = 0; oIndex < observers.size(); oIndex++) {
- if (observers.get(oIndex).onHealthCheckFailed(packages[pIndex])) {
+ PackageHealthObserver observer = observers.get(oIndex);
+ if (mAllObservers.get(observer.getName()).onPackageFailure(packageName)
+ && observer.onHealthCheckFailed(packageName)) {
// Observer has handled, do not notify others
break;
}
@@ -275,10 +316,12 @@
// O if mPackageCleanup not running
long elapsedDurationMs = mUptimeAtLastRescheduleMs == 0
? 0 : uptimeMs - mUptimeAtLastRescheduleMs;
- // O if mPackageCleanup not running
+ // Less than O if mPackageCleanup unexpectedly didn't run yet even though
+ // and we are past the last duration scheduled to run
long remainingDurationMs = mDurationAtLastReschedule - elapsedDurationMs;
-
- if (mUptimeAtLastRescheduleMs == 0 || nextDurationToScheduleMs < remainingDurationMs) {
+ if (mUptimeAtLastRescheduleMs == 0
+ || remainingDurationMs <= 0
+ || nextDurationToScheduleMs < remainingDurationMs) {
// First schedule or an earlier reschedule
pruneObservers(elapsedDurationMs);
mTimerHandler.removeCallbacks(mPackageCleanup);
@@ -305,6 +348,7 @@
}
}
Slog.v(TAG, "Earliest package time is " + shortestDurationMs);
+
return shortestDurationMs;
}
@@ -409,6 +453,8 @@
static class ObserverInternal {
public final String mName;
public final ArrayMap<String, MonitoredPackage> mPackages;
+ @Nullable
+ public PackageHealthObserver mRegisteredObserver;
ObserverInternal(String name, List<MonitoredPackage> packages) {
mName = name;
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
new file mode 100644
index 0000000..93e1dd3
--- /dev/null
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksMockingServicesTests",
+ "options": [
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "exclude-annotation": "android.support.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ]
+}
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index d07cf78..cbf6d04 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -34,6 +34,7 @@
import android.os.UserHandle;
import android.telephony.CellInfo;
import android.telephony.CellLocation;
+import android.telephony.DataFailCause;
import android.telephony.DisconnectCause;
import android.telephony.LocationAccessPolicy;
import android.telephony.PhoneCapability;
@@ -47,6 +48,7 @@
import android.telephony.SignalStrength;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import android.telephony.data.ApnSetting;
import android.telephony.emergency.EmergencyNumber;
import android.util.LocalLog;
import android.util.StatsLog;
@@ -1313,16 +1315,17 @@
}
}
- public void notifyDataConnection(int state, boolean isDataAllowed,
- String reason, String apn, String apnType, LinkProperties linkProperties,
- NetworkCapabilities networkCapabilities, int networkType, boolean roaming) {
+ public void notifyDataConnection(int state, boolean isDataAllowed, String apn, String apnType,
+ LinkProperties linkProperties,
+ NetworkCapabilities networkCapabilities, int networkType,
+ boolean roaming) {
notifyDataConnectionForSubscriber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, state,
- isDataAllowed,reason, apn, apnType, linkProperties,
- networkCapabilities, networkType, roaming);
+ isDataAllowed, apn, apnType, linkProperties,
+ networkCapabilities, networkType, roaming);
}
- public void notifyDataConnectionForSubscriber(int subId, int state,
- boolean isDataAllowed, String reason, String apn, String apnType,
+ public void notifyDataConnectionForSubscriber(int subId, int state, boolean isDataAllowed,
+ String apn, String apnType,
LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
int networkType, boolean roaming) {
if (!checkNotifyPermission("notifyDataConnection()" )) {
@@ -1331,7 +1334,6 @@
if (VDBG) {
log("notifyDataConnectionForSubscriber: subId=" + subId
+ " state=" + state + " isDataAllowed=" + isDataAllowed
- + " reason='" + reason
+ "' apn='" + apn + "' apnType=" + apnType + " networkType=" + networkType
+ " mRecords.size()=" + mRecords.size());
}
@@ -1366,7 +1368,8 @@
mDataConnectionNetworkType[phoneId] = networkType;
}
mPreciseDataConnectionState = new PreciseDataConnectionState(state, networkType,
- apnType, apn, reason, linkProperties, "");
+ ApnSetting.getApnTypesBitmaskFromString(apnType), apn,
+ linkProperties, DataFailCause.NONE);
for (Record r : mRecords) {
if (r.matchPhoneStateListenerEvent(
PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)) {
@@ -1381,30 +1384,30 @@
}
handleRemoveListLocked();
}
- broadcastDataConnectionStateChanged(state, isDataAllowed, reason, apn,
- apnType, linkProperties, networkCapabilities, roaming, subId);
- broadcastPreciseDataConnectionStateChanged(state, networkType, apnType, apn, reason,
- linkProperties, "");
+ broadcastDataConnectionStateChanged(state, isDataAllowed, apn, apnType, linkProperties,
+ networkCapabilities, roaming, subId);
+ broadcastPreciseDataConnectionStateChanged(state, networkType, apnType, apn,
+ linkProperties, DataFailCause.NONE);
}
- public void notifyDataConnectionFailed(String reason, String apnType) {
+ public void notifyDataConnectionFailed(String apnType) {
notifyDataConnectionFailedForSubscriber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
- reason, apnType);
+ apnType);
}
- public void notifyDataConnectionFailedForSubscriber(int subId,
- String reason, String apnType) {
+ public void notifyDataConnectionFailedForSubscriber(int subId, String apnType) {
if (!checkNotifyPermission("notifyDataConnectionFailed()")) {
return;
}
if (VDBG) {
log("notifyDataConnectionFailedForSubscriber: subId=" + subId
- + " reason=" + reason + " apnType=" + apnType);
+ + " apnType=" + apnType);
}
synchronized (mRecords) {
mPreciseDataConnectionState = new PreciseDataConnectionState(
TelephonyManager.DATA_UNKNOWN,TelephonyManager.NETWORK_TYPE_UNKNOWN,
- apnType, "", reason, null, "");
+ ApnSetting.getApnTypesBitmaskFromString(apnType), "", null,
+ DataFailCause.NONE);
for (Record r : mRecords) {
if (r.matchPhoneStateListenerEvent(
PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)) {
@@ -1417,9 +1420,10 @@
}
handleRemoveListLocked();
}
- broadcastDataConnectionFailed(reason, apnType, subId);
+ broadcastDataConnectionFailed(apnType, subId);
broadcastPreciseDataConnectionStateChanged(TelephonyManager.DATA_UNKNOWN,
- TelephonyManager.NETWORK_TYPE_UNKNOWN, apnType, "", reason, null, "");
+ TelephonyManager.NETWORK_TYPE_UNKNOWN, apnType, "", null,
+ DataFailCause.NONE);
}
public void notifyCellLocation(Bundle cellLocation) {
@@ -1529,15 +1533,15 @@
}
}
- public void notifyPreciseDataConnectionFailed(String reason, String apnType,
- String apn, String failCause) {
+ public void notifyPreciseDataConnectionFailed(String apnType,
+ String apn, @DataFailCause.FailCause int failCause) {
if (!checkNotifyPermission("notifyPreciseDataConnectionFailed()")) {
return;
}
synchronized (mRecords) {
mPreciseDataConnectionState = new PreciseDataConnectionState(
TelephonyManager.DATA_UNKNOWN, TelephonyManager.NETWORK_TYPE_UNKNOWN,
- apnType, apn, reason, null, failCause);
+ ApnSetting.getApnTypesBitmaskFromString(apnType), apn, null, failCause);
for (Record r : mRecords) {
if (r.matchPhoneStateListenerEvent(
PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)) {
@@ -1551,7 +1555,7 @@
handleRemoveListLocked();
}
broadcastPreciseDataConnectionStateChanged(TelephonyManager.DATA_UNKNOWN,
- TelephonyManager.NETWORK_TYPE_UNKNOWN, apnType, apn, reason, null, failCause);
+ TelephonyManager.NETWORK_TYPE_UNKNOWN, apnType, apn, null, failCause);
}
@Override
@@ -1881,10 +1885,10 @@
android.Manifest.permission.READ_CALL_LOG});
}
- private void broadcastDataConnectionStateChanged(int state,
- boolean isDataAllowed,
- String reason, String apn, String apnType, LinkProperties linkProperties,
- NetworkCapabilities networkCapabilities, boolean roaming, int subId) {
+ private void broadcastDataConnectionStateChanged(int state, boolean isDataAllowed, String apn,
+ String apnType, LinkProperties linkProperties,
+ NetworkCapabilities networkCapabilities,
+ boolean roaming, int subId) {
// Note: not reporting to the battery stats service here, because the
// status bar takes care of that after taking into account all of the
// required info.
@@ -1894,9 +1898,6 @@
if (!isDataAllowed) {
intent.putExtra(PhoneConstants.NETWORK_UNAVAILABLE_KEY, true);
}
- if (reason != null) {
- intent.putExtra(PhoneConstants.STATE_CHANGE_REASON_KEY, reason);
- }
if (linkProperties != null) {
intent.putExtra(PhoneConstants.DATA_LINK_PROPERTIES_KEY, linkProperties);
String iface = linkProperties.getInterfaceName();
@@ -1915,17 +1916,15 @@
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
- private void broadcastDataConnectionFailed(String reason, String apnType,
- int subId) {
+ private void broadcastDataConnectionFailed(String apnType, int subId) {
Intent intent = new Intent(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED);
- intent.putExtra(PhoneConstants.FAILURE_REASON_KEY, reason);
intent.putExtra(PhoneConstants.DATA_APN_TYPE_KEY, apnType);
intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
private void broadcastPreciseCallStateChanged(int ringingCallState, int foregroundCallState,
- int backgroundCallState) {
+ int backgroundCallState) {
Intent intent = new Intent(TelephonyManager.ACTION_PRECISE_CALL_STATE_CHANGED);
intent.putExtra(TelephonyManager.EXTRA_RINGING_CALL_STATE, ringingCallState);
intent.putExtra(TelephonyManager.EXTRA_FOREGROUND_CALL_STATE, foregroundCallState);
@@ -1935,18 +1934,17 @@
}
private void broadcastPreciseDataConnectionStateChanged(int state, int networkType,
- String apnType, String apn, String reason, LinkProperties linkProperties,
- String failCause) {
+ String apnType, String apn, LinkProperties linkProperties,
+ @DataFailCause.FailCause int failCause) {
Intent intent = new Intent(TelephonyManager.ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED);
intent.putExtra(PhoneConstants.STATE_KEY, state);
intent.putExtra(PhoneConstants.DATA_NETWORK_TYPE_KEY, networkType);
- if (reason != null) intent.putExtra(PhoneConstants.STATE_CHANGE_REASON_KEY, reason);
if (apnType != null) intent.putExtra(PhoneConstants.DATA_APN_TYPE_KEY, apnType);
if (apn != null) intent.putExtra(PhoneConstants.DATA_APN_KEY, apn);
if (linkProperties != null) {
- intent.putExtra(PhoneConstants.DATA_LINK_PROPERTIES_KEY,linkProperties);
+ intent.putExtra(PhoneConstants.DATA_LINK_PROPERTIES_KEY, linkProperties);
}
- if (failCause != null) intent.putExtra(PhoneConstants.DATA_FAILURE_CAUSE_KEY, failCause);
+ intent.putExtra(PhoneConstants.DATA_FAILURE_CAUSE_KEY, failCause);
mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
android.Manifest.permission.READ_PRECISE_PHONE_STATE);
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 9f353a8..ea6d435 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -141,6 +141,7 @@
private boolean mLowPowerMode;
private int mHapticFeedbackIntensity;
private int mNotificationIntensity;
+ private int mRingIntensity;
native static boolean vibratorExists();
native static void vibratorInit();
@@ -149,6 +150,8 @@
native static boolean vibratorSupportsAmplitudeControl();
native static void vibratorSetAmplitude(int amplitude);
native static long vibratorPerformEffect(long effect, long strength);
+ static native boolean vibratorSupportsExternalControl();
+ static native void vibratorSetExternalControl(boolean enabled);
private final IUidObserver mUidObserver = new IUidObserver.Stub() {
@Override public void onUidStateChanged(int uid, int procState, long procStateSeq) {
@@ -426,6 +429,10 @@
Settings.System.getUriFor(Settings.System.NOTIFICATION_VIBRATION_INTENSITY),
true, mSettingObserver, UserHandle.USER_ALL);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.System.getUriFor(Settings.System.RING_VIBRATION_INTENSITY),
+ true, mSettingObserver, UserHandle.USER_ALL);
+
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -532,12 +539,6 @@
return;
}
verifyIncomingUid(uid);
- if (mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
- > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
- Slog.e(TAG, "Ignoring incoming vibration as process with uid = "
- + uid + " is background");
- return;
- }
if (!verifyVibrationEffect(effect)) {
return;
}
@@ -575,6 +576,13 @@
}
Vibration vib = new Vibration(token, effect, usageHint, uid, opPkg, reason);
+ if (mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
+ > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
+ && vib.isHapticFeedback()) {
+ Slog.e(TAG, "Ignoring incoming vibration as process with uid = "
+ + uid + " is background");
+ return;
+ }
linkVibration(vib);
long ident = Binder.clearCallingIdentity();
try {
@@ -745,7 +753,9 @@
}
private int getCurrentIntensityLocked(Vibration vib) {
- if (vib.isNotification() || vib.isRingtone()){
+ if (vib.isRingtone()) {
+ return mRingIntensity;
+ } else if (vib.isNotification()) {
return mNotificationIntensity;
} else if (vib.isHapticFeedback()) {
return mHapticFeedbackIntensity;
@@ -767,7 +777,9 @@
}
final int defaultIntensity;
- if (vib.isNotification() || vib.isRingtone()) {
+ if (vib.isRingtone()) {
+ defaultIntensity = mVibrator.getDefaultRingVibrationIntensity();
+ } else if (vib.isNotification()) {
defaultIntensity = mVibrator.getDefaultNotificationVibrationIntensity();
} else if (vib.isHapticFeedback()) {
defaultIntensity = mVibrator.getDefaultHapticFeedbackIntensity();
@@ -930,6 +942,9 @@
mNotificationIntensity = Settings.System.getIntForUser(mContext.getContentResolver(),
Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
mVibrator.getDefaultNotificationVibrationIntensity(), UserHandle.USER_CURRENT);
+ mRingIntensity = Settings.System.getIntForUser(mContext.getContentResolver(),
+ Settings.System.RING_VIBRATION_INTENSITY,
+ mVibrator.getDefaultRingVibrationIntensity(), UserHandle.USER_CURRENT);
}
@Override
@@ -1278,6 +1293,7 @@
pw.println(" mLowPowerMode=" + mLowPowerMode);
pw.println(" mHapticFeedbackIntensity=" + mHapticFeedbackIntensity);
pw.println(" mNotificationIntensity=" + mNotificationIntensity);
+ pw.println(" mRingIntensity=" + mRingIntensity);
pw.println("");
pw.println(" Previous vibrations:");
for (VibrationInfo info : mPreviousVibrations) {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index a94fa12..8ca4193 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -1654,6 +1654,10 @@
}
}
+ if ((flags & Context.BIND_RESTRICT_ASSOCIATIONS) != 0) {
+ mAm.requireAllowedAssociationsLocked(s.appInfo.packageName);
+ }
+
mAm.startAssociationLocked(callerApp.uid, callerApp.processName,
callerApp.getCurProcState(), s.appInfo.uid, s.appInfo.longVersionCode,
s.instanceName, s.processName);
@@ -2503,6 +2507,9 @@
&& r.serviceInfo.packageName.equals(WebViewZygote.getPackageName())) {
hostingType = "webview_service";
}
+ if ((r.serviceInfo.flags & ServiceInfo.FLAG_USE_APP_ZYGOTE) != 0) {
+ hostingType = "app_zygote";
+ }
}
// Not running -- get it started, and enqueue this service record
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b053979..bb239ac 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -50,6 +50,7 @@
import static android.os.IServiceManager.DUMP_FLAG_PROTO;
import static android.os.Process.BLUETOOTH_UID;
import static android.os.Process.FIRST_APPLICATION_UID;
+import static android.os.Process.NETWORK_STACK_UID;
import static android.os.Process.NFC_UID;
import static android.os.Process.PHONE_UID;
import static android.os.Process.PROC_CHAR;
@@ -235,6 +236,7 @@
import android.net.Proxy;
import android.net.ProxyInfo;
import android.net.Uri;
+import android.os.AppZygote;
import android.os.BatteryStats;
import android.os.Binder;
import android.os.BinderProxy;
@@ -327,7 +329,7 @@
import com.android.internal.util.function.QuadFunction;
import com.android.internal.util.function.TriFunction;
import com.android.server.AlarmManagerInternal;
-import com.android.server.AppOpsService;
+import com.android.server.appop.AppOpsService;
import com.android.server.AttributeCache;
import com.android.server.DeviceIdleController;
import com.android.server.DisplayThread;
@@ -452,6 +454,9 @@
// before we decide it must be hung.
static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10*1000;
+ // How long we wait to kill an application zygote, after the last process using
+ // it has gone away.
+ static final int KILL_APP_ZYGOTE_DELAY_MS = 5 * 1000;
/**
* How long we wait for an provider to be published. Should be longer than
* {@link #CONTENT_PROVIDER_PUBLISH_TIMEOUT}.
@@ -1441,6 +1446,7 @@
static final int PUSH_TEMP_WHITELIST_UI_MSG = 68;
static final int SERVICE_FOREGROUND_CRASH_MSG = 69;
static final int DISPATCH_OOM_ADJ_OBSERVER_MSG = 70;
+ static final int KILL_APP_ZYGOTE_MSG = 71;
static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -1644,6 +1650,12 @@
false, userId, reason);
}
} break;
+ case KILL_APP_ZYGOTE_MSG: {
+ synchronized (ActivityManagerService.this) {
+ final AppZygote appZygote = (AppZygote) msg.obj;
+ mProcessList.killAppZygoteIfNeededLocked(appZygote);
+ }
+ } break;
case CHECK_EXCESSIVE_POWER_USE_MSG: {
synchronized (ActivityManagerService.this) {
checkExcessivePowerUsageLocked();
@@ -1932,7 +1944,8 @@
synchronized (this) {
ProcessRecord app = mProcessList.newProcessRecordLocked(info, info.processName,
false,
- 0);
+ 0,
+ false);
app.setPersistent(true);
app.pid = MY_PID;
app.getWindowProcessController().setPid(MY_PID);
@@ -2417,16 +2430,36 @@
return mBackgroundLaunchBroadcasts;
}
+ /**
+ * Ensures that the given package name has an explicit set of allowed associations.
+ * If it does not, give it an empty set.
+ */
+ void requireAllowedAssociationsLocked(String packageName) {
+ if (mAllowedAssociations == null) {
+ mAllowedAssociations = new ArrayMap<>(
+ SystemConfig.getInstance().getAllowedAssociations());
+ }
+ if (mAllowedAssociations.get(packageName) == null) {
+ mAllowedAssociations.put(packageName, new ArraySet<>());
+ }
+ }
+
+ /**
+ * Returns true if the package {@code pkg1} running under user handle {@code uid1} is
+ * allowed association with the package {@code pkg2} running under user handle {@code uid2}.
+ * <p> If either of the packages are running as part of the core system, then the
+ * association is implicitly allowed.
+ */
boolean validateAssociationAllowedLocked(String pkg1, int uid1, String pkg2, int uid2) {
if (mAllowedAssociations == null) {
- mAllowedAssociations = SystemConfig.getInstance().getAllowedAssociations();
+ mAllowedAssociations = new ArrayMap<>(
+ SystemConfig.getInstance().getAllowedAssociations());
}
// Interactions with the system uid are always allowed, since that is the core system
- // that everyone needs to be able to interact with.
- if (UserHandle.getAppId(uid1) == SYSTEM_UID) {
- return true;
- }
- if (UserHandle.getAppId(uid2) == SYSTEM_UID) {
+ // that everyone needs to be able to interact with. Also allow reflexive associations
+ // within the same uid.
+ if (uid1 == uid2 || UserHandle.getAppId(uid1) == SYSTEM_UID
+ || UserHandle.getAppId(uid2) == SYSTEM_UID) {
return true;
}
// We won't allow this association if either pkg1 or pkg2 has a limit on the
@@ -2442,6 +2475,8 @@
if (pkgs != null) {
return pkgs.contains(pkg1);
}
+ // If no explicit associations are provided in the manifest, then assume the app is
+ // allowed associations with any package.
return true;
}
@@ -5310,16 +5345,7 @@
}
}
- @Override
- public boolean isAppForeground(int uid) {
- int callerUid = Binder.getCallingUid();
- if (UserHandle.isCore(callerUid) || callerUid == uid) {
- return isAppForegroundInternal(uid);
- }
- return false;
- }
-
- private boolean isAppForegroundInternal(int uid) {
+ private boolean isAppForeground(int uid) {
synchronized (this) {
UidRecord uidRec = mActiveUids.get(uid);
if (uidRec == null || uidRec.idle) {
@@ -7396,7 +7422,7 @@
}
if (app == null) {
- app = mProcessList.newProcessRecordLocked(info, customProcess, isolated, 0);
+ app = mProcessList.newProcessRecordLocked(info, customProcess, isolated, 0, false);
mProcessList.updateLruProcessLocked(app, false, null);
updateOomAdjLocked();
}
@@ -14333,6 +14359,7 @@
case BLUETOOTH_UID:
case NFC_UID:
case SE_UID:
+ case NETWORK_STACK_UID:
isCallerSystem = true;
break;
default:
@@ -19675,6 +19702,11 @@
return pr == null ? Zygote.MOUNT_EXTERNAL_NONE : pr.mountMode;
}
}
+
+ @Override
+ public boolean isAppForeground(int uid) {
+ return ActivityManagerService.this.isAppForeground(uid);
+ }
}
long inputDispatchingTimedOut(int pid, final boolean aboveSystem, String reason) {
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index f9a77af..d7cb2bd 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -68,6 +68,7 @@
sGlobalSettingToTypeMap.put(Settings.Global.GPU_DEBUG_LAYER_APP, String.class);
sGlobalSettingToTypeMap.put(Settings.Global.SMS_ACCESS_RESTRICTION_ENABLED, int.class);
sGlobalSettingToTypeMap.put(Settings.Global.GUP_DEV_OPT_IN_APPS, String.class);
+ sGlobalSettingToTypeMap.put(Settings.Global.GUP_DEV_OPT_OUT_APPS, String.class);
sGlobalSettingToTypeMap.put(Settings.Global.GUP_BLACK_LIST, String.class);
// add other global settings here...
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index d133dea..9898d06 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -19,8 +19,6 @@
import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
import static android.app.ActivityThread.PROC_START_SEQ_IDENT;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO;
-import static android.os.Process.FIRST_ISOLATED_UID;
-import static android.os.Process.LAST_ISOLATED_UID;
import static android.os.Process.SYSTEM_UID;
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
import static android.os.Process.getFreeMemory;
@@ -34,6 +32,8 @@
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.ActivityManagerService.KILL_APP_ZYGOTE_DELAY_MS;
+import static com.android.server.am.ActivityManagerService.KILL_APP_ZYGOTE_MSG;
import static com.android.server.am.ActivityManagerService.PERSISTENT_MASK;
import static com.android.server.am.ActivityManagerService.PROC_START_TIMEOUT;
import static com.android.server.am.ActivityManagerService.PROC_START_TIMEOUT_MSG;
@@ -58,6 +58,7 @@
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.net.Uri;
+import android.os.AppZygote;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -75,10 +76,12 @@
import android.os.storage.StorageManager;
import android.os.storage.StorageManagerInternal;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.EventLog;
import android.util.LongSparseArray;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.util.StatsLog;
import android.view.Display;
@@ -108,6 +111,7 @@
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.BitSet;
import java.util.List;
/**
@@ -354,10 +358,136 @@
final SparseArray<ProcessRecord> mIsolatedProcesses = new SparseArray<>();
/**
- * Counter for assigning isolated process uids, to avoid frequently reusing the
- * same ones.
+ * The currently running application zygotes.
*/
- int mNextIsolatedProcessUid = 0;
+ final ProcessMap<AppZygote> mAppZygotes = new ProcessMap<AppZygote>();
+
+ /**
+ * The processes that are forked off an application zygote.
+ */
+ final ArrayMap<AppZygote, ArrayList<ProcessRecord>> mAppZygoteProcesses =
+ new ArrayMap<AppZygote, ArrayList<ProcessRecord>>();
+
+ final class IsolatedUidRange {
+ @VisibleForTesting
+ public final int mFirstUid;
+ @VisibleForTesting
+ public final int mLastUid;
+
+ @GuardedBy("ProcessList.this.mService")
+ private final SparseBooleanArray mUidUsed = new SparseBooleanArray();
+
+ @GuardedBy("ProcessList.this.mService")
+ private int mNextUid;
+
+ IsolatedUidRange(int firstUid, int lastUid) {
+ mFirstUid = firstUid;
+ mLastUid = lastUid;
+ mNextUid = firstUid;
+ }
+
+ @GuardedBy("ProcessList.this.mService")
+ int allocateIsolatedUidLocked(int userId) {
+ int uid;
+ int stepsLeft = (mLastUid - mFirstUid + 1);
+ for (int i = 0; i < stepsLeft; ++i) {
+ if (mNextUid < mFirstUid || mNextUid > mLastUid) {
+ mNextUid = mFirstUid;
+ }
+ uid = UserHandle.getUid(userId, mNextUid);
+ mNextUid++;
+ if (!mUidUsed.get(uid, false)) {
+ mUidUsed.put(uid, true);
+ return uid;
+ }
+ }
+ return -1;
+ }
+
+ @GuardedBy("ProcessList.this.mService")
+ void freeIsolatedUidLocked(int uid) {
+ // Strip out userId
+ final int appId = UserHandle.getAppId(uid);
+ mUidUsed.delete(appId);
+ }
+ };
+
+ /**
+ * A class that allocates ranges of isolated UIDs per application, and keeps track of them.
+ */
+ final class IsolatedUidRangeAllocator {
+ private final int mFirstUid;
+ private final int mNumUidRanges;
+ private final int mNumUidsPerRange;
+ /**
+ * We map the uid range [mFirstUid, mFirstUid + mNumUidRanges * mNumUidsPerRange)
+ * back to an underlying bitset of [0, mNumUidRanges) and allocate out of that.
+ */
+ @GuardedBy("ProcessList.this.mService")
+ private final BitSet mAvailableUidRanges;
+ @GuardedBy("ProcessList.this.mService")
+ private final ProcessMap<IsolatedUidRange> mAppRanges = new ProcessMap<IsolatedUidRange>();
+
+ IsolatedUidRangeAllocator(int firstUid, int lastUid, int numUidsPerRange) {
+ mFirstUid = firstUid;
+ mNumUidsPerRange = numUidsPerRange;
+ mNumUidRanges = (lastUid - firstUid + 1) / numUidsPerRange;
+ mAvailableUidRanges = new BitSet(mNumUidRanges);
+ // Mark all as available
+ mAvailableUidRanges.set(0, mNumUidRanges);
+ }
+
+ @GuardedBy("ProcessList.this.mService")
+ IsolatedUidRange getIsolatedUidRangeLocked(ApplicationInfo info) {
+ return mAppRanges.get(info.processName, info.uid);
+ }
+
+ @GuardedBy("ProcessList.this.mService")
+ IsolatedUidRange getOrCreateIsolatedUidRangeLocked(ApplicationInfo info) {
+ IsolatedUidRange range = getIsolatedUidRangeLocked(info);
+ if (range == null) {
+ int uidRangeIndex = mAvailableUidRanges.nextSetBit(0);
+ if (uidRangeIndex < 0) {
+ // No free range
+ return null;
+ }
+ mAvailableUidRanges.clear(uidRangeIndex);
+ int actualUid = mFirstUid + uidRangeIndex * mNumUidsPerRange;
+ range = new IsolatedUidRange(actualUid, actualUid + mNumUidsPerRange - 1);
+ mAppRanges.put(info.processName, info.uid, range);
+ }
+ return range;
+ }
+
+ @GuardedBy("ProcessList.this.mService")
+ void freeUidRangeLocked(ApplicationInfo info) {
+ // Find the UID range
+ IsolatedUidRange range = mAppRanges.get(info.processName, info.uid);
+ if (range != null) {
+ // Map back to starting uid
+ final int uidRangeIndex = (range.mFirstUid - mFirstUid) / mNumUidsPerRange;
+ // Mark it as available in the underlying bitset
+ mAvailableUidRanges.set(uidRangeIndex);
+ // And the map
+ mAppRanges.remove(info.processName, info.uid);
+ }
+ }
+ }
+
+ /**
+ * The available isolated UIDs for processes that are not spawned from an application zygote.
+ */
+ @VisibleForTesting
+ IsolatedUidRange mGlobalIsolatedUids = new IsolatedUidRange(Process.FIRST_ISOLATED_UID,
+ Process.LAST_ISOLATED_UID);
+
+ /**
+ * An allocator for isolated UID ranges for apps that use an application zygote.
+ */
+ @VisibleForTesting
+ IsolatedUidRangeAllocator mAppIsolatedUidRangeAllocator =
+ new IsolatedUidRangeAllocator(Process.FIRST_APP_ZYGOTE_ISOLATED_UID,
+ Process.LAST_APP_ZYGOTE_ISOLATED_UID, Process.NUM_UIDS_PER_APP_ZYGOTE);
/**
* Processes that are being forcibly torn down.
@@ -1505,6 +1635,67 @@
}
}
+ @GuardedBy("mService")
+ public void killAppZygoteIfNeededLocked(AppZygote appZygote) {
+ final ApplicationInfo appInfo = appZygote.getAppInfo();
+ ArrayList<ProcessRecord> zygoteProcesses = mAppZygoteProcesses.get(appZygote);
+ if (zygoteProcesses.size() == 0) { // Only remove if no longer in use now
+ mAppZygotes.remove(appInfo.processName, appInfo.uid);
+ mAppZygoteProcesses.remove(appZygote);
+ mAppIsolatedUidRangeAllocator.freeUidRangeLocked(appInfo);
+ appZygote.stopZygote();
+ }
+ }
+
+ @GuardedBy("mService")
+ private void removeProcessFromAppZygoteLocked(final ProcessRecord app) {
+ // Free the isolated uid for this process
+ final IsolatedUidRange appUidRange =
+ mAppIsolatedUidRangeAllocator.getIsolatedUidRangeLocked(app.info);
+ if (appUidRange != null) {
+ appUidRange.freeIsolatedUidLocked(app.uid);
+ }
+
+ final AppZygote appZygote = mAppZygotes.get(app.info.processName, app.info.uid);
+ if (appZygote != null) {
+ ArrayList<ProcessRecord> zygoteProcesses = mAppZygoteProcesses.get(appZygote);
+ zygoteProcesses.remove(app);
+ if (zygoteProcesses.size() == 0) {
+ Message msg = mService.mHandler.obtainMessage(KILL_APP_ZYGOTE_MSG);
+ msg.obj = appZygote;
+ mService.mHandler.sendMessageDelayed(msg, KILL_APP_ZYGOTE_DELAY_MS);
+ }
+ }
+ }
+
+ private AppZygote createAppZygoteForProcessIfNeeded(final ProcessRecord app) {
+ synchronized (mService) {
+ AppZygote appZygote = mAppZygotes.get(app.info.processName, app.info.uid);
+ final ArrayList<ProcessRecord> zygoteProcessList;
+ if (appZygote == null) {
+ final int userId = UserHandle.getUserId(app.info.uid);
+ final IsolatedUidRange uidRange =
+ mAppIsolatedUidRangeAllocator.getIsolatedUidRangeLocked(app.info);
+ // Allocate an isolated UID out of this range for the Zygote itself
+ final int zygoteIsolatedUid = uidRange.allocateIsolatedUidLocked(userId);
+ appZygote = new AppZygote(app.info, zygoteIsolatedUid);
+ mAppZygotes.put(app.info.processName, app.info.uid, appZygote);
+ zygoteProcessList = new ArrayList<ProcessRecord>();
+ mAppZygoteProcesses.put(appZygote, zygoteProcessList);
+ } else {
+ mService.mHandler.removeMessages(KILL_APP_ZYGOTE_MSG, appZygote);
+ zygoteProcessList = mAppZygoteProcesses.get(appZygote);
+ }
+ // Note that we already add the app to mAppZygoteProcesses here;
+ // this is so that another thread can't come in and kill the zygote
+ // before we've even tried to start the process. If the process launch
+ // goes wrong, we'll clean this up in removeProcessNameLocked()
+ zygoteProcessList.add(app);
+
+ return appZygote;
+ }
+ }
+
private Process.ProcessStartResult startProcess(String hostingType, String entryPoint,
ProcessRecord app, int uid, int[] gids, int runtimeFlags, int mountExternal,
String seInfo, String requiredAbi, String instructionSet, String invokeWith,
@@ -1525,6 +1716,15 @@
app.info.dataDir, null, app.info.packageName,
packageNames, visibleVolIds,
new String[] {PROC_START_SEQ_IDENT + app.startSeq});
+ } else if (hostingType.equals("app_zygote")) {
+ final AppZygote appZygote = createAppZygoteForProcessIfNeeded(app);
+
+ startResult = appZygote.getProcess().start(entryPoint,
+ app.processName, uid, uid, gids, runtimeFlags, mountExternal,
+ app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
+ app.info.dataDir, null, app.info.packageName,
+ packageNames, visibleVolIds,
+ new String[] {PROC_START_SEQ_IDENT + app.startSeq});
} else {
startResult = Process.start(entryPoint,
app.processName, uid, uid, gids, runtimeFlags, mountExternal,
@@ -1614,13 +1814,6 @@
app.addPackage(info.packageName, info.versionCode, mService.mProcessStats);
checkSlow(startTime, "startProcess: done, added package to proc");
return app;
- } else if (app.getActiveInstrumentation() != null) {
- // We don't want to kill running instrumentation.
- if (DEBUG_PROCESSES) {
- Slog.v(TAG_PROCESSES, "Instrumentation already running: " + app);
- }
- checkSlow(startTime, "startProcess: keep instrumentation proc");
- return app;
}
// An application record is attached to a previous process,
@@ -1636,8 +1829,9 @@
? hostingName.flattenToShortString() : null;
if (app == null) {
+ final boolean fromAppZygote = "app_zygote".equals(hostingType);
checkSlow(startTime, "startProcess: creating new process record");
- app = newProcessRecordLocked(info, processName, isolated, isolatedUid);
+ app = newProcessRecordLocked(info, processName, isolated, isolatedUid, fromAppZygote);
if (app == null) {
Slog.w(TAG, "Failed making new process record for "
+ processName + "/" + info.uid + " isolated=" + isolated);
@@ -2010,29 +2204,31 @@
}
@GuardedBy("mService")
+ private IsolatedUidRange getOrCreateIsolatedUidRangeLocked(ApplicationInfo info,
+ boolean fromAppZygote) {
+ if (!fromAppZygote) {
+ // Allocate an isolated UID from the global range
+ return mGlobalIsolatedUids;
+ } else {
+ return mAppIsolatedUidRangeAllocator.getOrCreateIsolatedUidRangeLocked(info);
+ }
+ }
+
+ @GuardedBy("mService")
final ProcessRecord newProcessRecordLocked(ApplicationInfo info, String customProcess,
- boolean isolated, int isolatedUid) {
+ boolean isolated, int isolatedUid, boolean fromAppZygote) {
String proc = customProcess != null ? customProcess : info.processName;
final int userId = UserHandle.getUserId(info.uid);
int uid = info.uid;
if (isolated) {
if (isolatedUid == 0) {
- int stepsLeft = LAST_ISOLATED_UID - FIRST_ISOLATED_UID + 1;
- while (true) {
- if (mNextIsolatedProcessUid < FIRST_ISOLATED_UID
- || mNextIsolatedProcessUid > LAST_ISOLATED_UID) {
- mNextIsolatedProcessUid = FIRST_ISOLATED_UID;
- }
- uid = UserHandle.getUid(userId, mNextIsolatedProcessUid);
- mNextIsolatedProcessUid++;
- if (mIsolatedProcesses.indexOfKey(uid) < 0) {
- // No process for this uid, use it.
- break;
- }
- stepsLeft--;
- if (stepsLeft <= 0) {
- return null;
- }
+ IsolatedUidRange uidRange = getOrCreateIsolatedUidRangeLocked(info, fromAppZygote);
+ if (uidRange == null) {
+ return null;
+ }
+ uid = uidRange.allocateIsolatedUidLocked(userId);
+ if (uid == -1) {
+ return null;
}
} else {
// Special case for startIsolatedProcess (internal only), where
@@ -2100,6 +2296,13 @@
old.uidRecord = null;
}
mIsolatedProcesses.remove(uid);
+ mGlobalIsolatedUids.freeIsolatedUidLocked(uid);
+ // Remove the (expected) ProcessRecord from the app zygote
+ final ProcessRecord record = expecting != null ? expecting : old;
+ if (record != null && record.appZygote) {
+ removeProcessFromAppZygoteLocked(record);
+ }
+
return old;
}
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 6f0a562..0d0824a 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -76,6 +76,7 @@
private final ActivityManagerService mService; // where we came from
final ApplicationInfo info; // all about the first app in the process
final boolean isolated; // true if this is a special isolated process
+ final boolean appZygote; // true if this is forked from the app zygote
final int uid; // uid of process; may be different from 'info' if isolated
final int userId; // user of process.
final String processName; // name of the process
@@ -559,6 +560,8 @@
mService = _service;
info = _info;
isolated = _info.uid != _uid;
+ appZygote = (UserHandle.getAppId(_uid) >= Process.FIRST_APP_ZYGOTE_ISOLATED_UID
+ && UserHandle.getAppId(_uid) <= Process.LAST_APP_ZYGOTE_ISOLATED_UID);
uid = _uid;
userId = UserHandle.getUserId(_uid);
processName = _processName;
diff --git a/services/core/java/com/android/server/am/TEST_MAPPING b/services/core/java/com/android/server/am/TEST_MAPPING
index 48b4145..c84b5c7 100644
--- a/services/core/java/com/android/server/am/TEST_MAPPING
+++ b/services/core/java/com/android/server/am/TEST_MAPPING
@@ -7,7 +7,7 @@
"include-annotation": "android.platform.test.annotations.Presubmit"
},
{
- "exclude-annotation": "android.support.test.filters.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
},
@@ -21,7 +21,7 @@
"include-annotation": "android.platform.test.annotations.Presubmit"
},
{
- "exclude-annotation": "android.support.test.filters.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
},
diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
similarity index 94%
rename from services/core/java/com/android/server/AppOpsService.java
rename to services/core/java/com/android/server/appop/AppOpsService.java
index 3cdf09e..7ede6dc 100644
--- a/services/core/java/com/android/server/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-package com.android.server;
+package com.android.server.appop;
import static android.app.AppOpsManager.OP_PLAY_AUDIO;
+import static android.app.AppOpsManager.OP_NONE;
import static android.app.AppOpsManager.UID_STATE_BACKGROUND;
import static android.app.AppOpsManager.UID_STATE_CACHED;
import static android.app.AppOpsManager.UID_STATE_FOREGROUND;
@@ -35,8 +36,7 @@
import android.app.ActivityThread;
import android.app.AppGlobals;
import android.app.AppOpsManager;
-import android.app.AppOpsManager.HistoricalOpEntry;
-import android.app.AppOpsManager.HistoricalPackageOps;
+import android.app.AppOpsManager.HistoricalOps;
import android.app.AppOpsManagerInternal;
import android.app.AppOpsManagerInternal.CheckOpsDelegate;
import android.content.BroadcastReceiver;
@@ -48,7 +48,6 @@
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
-import android.content.pm.ParceledListSlice;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.media.AudioAttributes;
@@ -59,6 +58,7 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Process;
+import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
@@ -95,6 +95,8 @@
import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.server.LocalServices;
+import com.android.server.LockGuard;
import libcore.util.EmptyArray;
import org.xmlpull.v1.XmlPullParser;
@@ -213,6 +215,8 @@
@VisibleForTesting
final SparseArray<UidState> mUidStates = new SparseArray<>();
+ private final HistoricalRegistry mHistoricalRegistry = new HistoricalRegistry(this);
+
long mLastRealtime;
/*
@@ -654,6 +658,7 @@
public void systemReady() {
mConstants.startMonitoring(mContext.getContentResolver());
+ mHistoricalRegistry.systemReady(mContext.getContentResolver());
synchronized (this) {
boolean changed = false;
@@ -1012,113 +1017,99 @@
}
@Override
- public @Nullable ParceledListSlice getAllHistoricalPackagesOps(@Nullable String[] opNames,
- long beginTimeMillis, long endTimeMillis) {
+ public void getHistoricalOps(int uid, @NonNull String packageName,
+ @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis,
+ @NonNull RemoteCallback callback) {
+ Preconditions.checkArgument(uid == Process.INVALID_UID || uid >= 0,
+ "uid must be " + Process.INVALID_UID + " or non negative");
Preconditions.checkArgument(beginTimeMillis >= 0 && beginTimeMillis < endTimeMillis,
"beginTimeMillis must be non negative and lesser than endTimeMillis");
+ Preconditions.checkNotNull(callback, "callback cannot be null");
+ checkValidOpsOrNull(opNames);
mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
- Binder.getCallingPid(), Binder.getCallingUid(), "getAllHistoricalPackagesOps");
+ Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
- ArrayList<HistoricalPackageOps> historicalPackageOpsList = null;
-
- final int uidStateCount = mUidStates.size();
- for (int i = 0; i < uidStateCount; i++) {
- final UidState uidState = mUidStates.valueAt(i);
- if (uidState.pkgOps == null || uidState.pkgOps.isEmpty()) {
- continue;
- }
- final ArrayMap<String, Ops> packages = uidState.pkgOps;
- final int packageCount = packages.size();
- for (int j = 0; j < packageCount; j++) {
- final Ops pkgOps = packages.valueAt(j);
- final AppOpsManager.HistoricalPackageOps historicalPackageOps =
- createHistoricalPackageOps(uidState.uid, pkgOps, opNames,
- beginTimeMillis, endTimeMillis);
- if (historicalPackageOps != null) {
- if (historicalPackageOpsList == null) {
- historicalPackageOpsList = new ArrayList<>();
- }
- historicalPackageOpsList.add(historicalPackageOps);
- }
- }
+ if (mHistoricalRegistry.getMode() == AppOpsManager.HISTORICAL_MODE_DISABLED) {
+ // TODO (bug:122218838): Remove once the feature fully enabled.
+ getHistoricalPackagesOpsCompat(uid, packageName, opNames, beginTimeMillis,
+ endTimeMillis, callback);
+ } else {
+ // Must not hold the appops lock
+ mHistoricalRegistry.getHistoricalOps(uid, packageName, opNames,
+ beginTimeMillis, endTimeMillis, callback);
}
-
- if (historicalPackageOpsList == null) {
- return null;
- }
-
- return new ParceledListSlice<>(historicalPackageOpsList);
}
- private static @Nullable HistoricalPackageOps createHistoricalPackageOps(int uid,
- @Nullable Ops pkgOps, @Nullable String[] opNames, long beginTimeMillis,
- long endTimeMillis) {
- // TODO: Implement historical data collection
- if (pkgOps == null) {
- return null;
+ private void getHistoricalPackagesOpsCompat(int uid, @NonNull String packageName,
+ @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis,
+ @NonNull RemoteCallback callback) {
+ synchronized (AppOpsService.this) {
+ final HistoricalOps ops = new HistoricalOps(beginTimeMillis, endTimeMillis);
+ if (opNames == null) {
+ opNames = AppOpsManager.getOpStrs();
+ }
+ final int uidStateCount = mUidStates.size();
+ for (int uidIdx = 0; uidIdx < uidStateCount; uidIdx++) {
+ final UidState uidState = mUidStates.valueAt(uidIdx);
+ if (uidState.pkgOps == null || uidState.pkgOps.isEmpty()
+ || (uid != Process.INVALID_UID && uid != uidState.uid)) {
+ continue;
+ }
+ final ArrayMap<String, Ops> packages = uidState.pkgOps;
+ final int packageCount = packages.size();
+ for (int pkgIdx = 0; pkgIdx < packageCount; pkgIdx++) {
+ final Ops pkgOps = packages.valueAt(pkgIdx);
+ if (packageName != null && !packageName.equals(pkgOps.packageName)) {
+ continue;
+ }
+ final int opCount = opNames.length;
+ for (int opIdx = 0; opIdx < opCount; opIdx++) {
+ final String opName = opNames[opIdx];
+ if (!ArrayUtils.contains(opNames, opName)) {
+ continue;
+ }
+ final int opCode = AppOpsManager.strOpToOp(opName);
+ final Op op = pkgOps.get(opCode);
+ if (op == null) {
+ continue;
+ }
+ final int stateCount = AppOpsManager._NUM_UID_STATE;
+ for (int stateIdx = 0; stateIdx < stateCount; stateIdx++) {
+ if (op.rejectTime[stateIdx] != 0) {
+ ops.increaseRejectCount(opCode, uidState.uid,
+ pkgOps.packageName, stateIdx, 1);
+ } else if (op.time[stateIdx] != 0) {
+ ops.increaseAccessCount(opCode, uidState.uid,
+ pkgOps.packageName, stateIdx, 1);
+ }
+ }
+ }
+ }
+ }
+ final Bundle payload = new Bundle();
+ payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, ops);
+ callback.sendResult(payload);
}
-
- final HistoricalPackageOps historicalPackageOps = new HistoricalPackageOps(uid,
- pkgOps.packageName);
-
- if (opNames == null) {
- opNames = AppOpsManager.getOpStrs();
- }
- for (String opName : opNames) {
- addHistoricOpEntry(AppOpsManager.strOpToOp(opName), pkgOps, historicalPackageOps);
- }
-
- return historicalPackageOps;
}
@Override
- public @Nullable HistoricalPackageOps getHistoricalPackagesOps(int uid,
- @NonNull String packageName, @Nullable String[] opNames,
- long beginTimeMillis, long endTimeMillis) {
- Preconditions.checkNotNull(packageName,
- "packageName cannot be null");
+ public void getHistoricalOpsFromDiskRaw(int uid, @NonNull String packageName,
+ @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis,
+ @NonNull RemoteCallback callback) {
+ Preconditions.checkArgument(uid == Process.INVALID_UID || uid >= 0,
+ "uid must be " + Process.INVALID_UID + " or non negative");
Preconditions.checkArgument(beginTimeMillis >= 0 && beginTimeMillis < endTimeMillis,
"beginTimeMillis must be non negative and lesser than endTimeMillis");
+ Preconditions.checkNotNull(callback, "callback cannot be null");
+ checkValidOpsOrNull(opNames);
mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
- Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalPackagesOps");
+ Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
- final String resolvedPackageName = resolvePackageName(uid, packageName);
- if (resolvedPackageName == null) {
- return null;
- }
-
- // TODO: Implement historical data collection
- final Ops pkgOps = getOpsRawLocked(uid, resolvedPackageName, false /* edit */,
- false /* uidMismatchExpected */);
- return createHistoricalPackageOps(uid, pkgOps, opNames, beginTimeMillis, endTimeMillis);
- }
-
- private static void addHistoricOpEntry(int opCode, @NonNull Ops ops,
- @NonNull HistoricalPackageOps outHistoricalPackageOps) {
- final Op op = ops.get(opCode);
- if (op == null) {
- return;
- }
-
- final HistoricalOpEntry historicalOpEntry = new HistoricalOpEntry(opCode);
-
- // TODO: Keep per UID state duration
- for (int uidState = 0; uidState < AppOpsManager._NUM_UID_STATE; uidState++) {
- final int acceptCount;
- final int rejectCount;
- if (op.rejectTime[uidState] == 0) {
- acceptCount = 1;
- rejectCount = 0;
- } else {
- acceptCount = 0;
- rejectCount = 1;
- }
- historicalOpEntry.addEntry(uidState, acceptCount, rejectCount, 0);
- }
-
- outHistoricalPackageOps.addEntry(historicalOpEntry);
+ // Must not hold the appops lock
+ mHistoricalRegistry.getHistoricalOpsFromDiskRaw(uid, packageName, opNames,
+ beginTimeMillis, endTimeMillis, callback);
}
@Override
@@ -1884,6 +1875,8 @@
+ packageName);
op.rejectTime[uidState.state] = System.currentTimeMillis();
scheduleOpNotedIfNeededLocked(code, uid, packageName, uidMode);
+ mHistoricalRegistry.incrementOpRejected(op.op, uid, packageName,
+ uidState.state);
return uidMode;
}
} else {
@@ -1895,12 +1888,16 @@
+ packageName);
op.rejectTime[uidState.state] = System.currentTimeMillis();
scheduleOpNotedIfNeededLocked(op.op, uid, packageName, mode);
+ mHistoricalRegistry.incrementOpRejected(op.op, uid, packageName,
+ uidState.state);
return mode;
}
}
if (DEBUG) Slog.d(TAG, "noteOperation: allowing code " + code + " uid " + uid
+ " package " + packageName);
op.time[uidState.state] = System.currentTimeMillis();
+ mHistoricalRegistry.incrementOpAccessedCount(op.op, uid, packageName,
+ uidState.state);
op.rejectTime[uidState.state] = 0;
op.proxyUid = proxyUid;
op.proxyPackageName = proxyPackageName;
@@ -2035,6 +2032,8 @@
+ switchCode + " (" + code + ") uid " + uid + " package "
+ resolvedPackageName);
op.rejectTime[uidState.state] = System.currentTimeMillis();
+ mHistoricalRegistry.incrementOpRejected(op.op, uid, packageName,
+ uidState.state);
return uidMode;
}
} else {
@@ -2046,6 +2045,8 @@
+ switchCode + " (" + code + ") uid " + uid + " package "
+ resolvedPackageName);
op.rejectTime[uidState.state] = System.currentTimeMillis();
+ mHistoricalRegistry.incrementOpRejected(op.op, uid, packageName,
+ uidState.state);
return mode;
}
}
@@ -2054,6 +2055,8 @@
if (op.startNesting == 0) {
op.startRealtime = SystemClock.elapsedRealtime();
op.time[uidState.state] = System.currentTimeMillis();
+ mHistoricalRegistry.incrementOpAccessedCount(op.op, uid, packageName,
+ uidState.state);
op.rejectTime[uidState.state] = 0;
op.duration = -1;
scheduleOpActiveChangedIfNeededLocked(code, uid, packageName, true);
@@ -2216,6 +2219,8 @@
if (op.startNesting <= 1 || finishNested) {
if (op.startNesting == 1 || finishNested) {
op.duration = (int)(SystemClock.elapsedRealtime() - op.startRealtime);
+ mHistoricalRegistry.increaseOpAccessDuration(op.op, op.uid, op.packageName,
+ op.uidState.state, op.duration);
op.time[op.uidState.state] = System.currentTimeMillis();
} else {
Slog.w(TAG, "Finishing op nesting under-run: uid " + op.uid + " pkg "
@@ -2344,7 +2349,7 @@
ApplicationInfo appInfo = ActivityThread.getPackageManager()
.getApplicationInfo(packageName,
PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
UserHandle.getUserId(uid));
if (appInfo != null) {
pkgUid = appInfo.uid;
@@ -3391,6 +3396,8 @@
pw.println(" Limit output to data associated with the given app op mode.");
pw.println(" --package [PACKAGE]");
pw.println(" Limit output to data associated with the given package name.");
+ pw.println(" --watchers");
+ pw.println(" Only output the watcher sections.");
}
private void dumpTimesLocked(PrintWriter pw, String firstPrefix, String prefix, long[] times,
@@ -3425,10 +3432,11 @@
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
- int dumpOp = -1;
+ int dumpOp = OP_NONE;
String dumpPackage = null;
- int dumpUid = -1;
+ int dumpUid = Process.INVALID_UID;
int dumpMode = -1;
+ boolean dumpWatchers = false;
if (args != null) {
for (int i=0; i<args.length; i++) {
@@ -3476,6 +3484,8 @@
if (dumpMode < 0) {
return;
}
+ } else if ("--watchers".equals(arg)) {
+ dumpWatchers = true;
} else if (arg.length() > 0 && arg.charAt(0) == '-'){
pw.println("Unknown option: " + arg);
return;
@@ -3496,7 +3506,8 @@
final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
final Date date = new Date();
boolean needSep = false;
- if (dumpOp < 0 && dumpMode < 0 && dumpPackage == null && mProfileOwners != null) {
+ if (dumpOp < 0 && dumpMode < 0 && dumpPackage == null && mProfileOwners != null
+ && !dumpWatchers) {
pw.println(" Profile owners:");
for (int poi = 0; poi < mProfileOwners.size(); poi++) {
pw.print(" User #");
@@ -3517,7 +3528,7 @@
ArraySet<ModeCallback> callbacks = mOpModeWatchers.valueAt(i);
for (int j=0; j<callbacks.size(); j++) {
final ModeCallback cb = callbacks.valueAt(j);
- if (dumpPackage != null && cb.mWatchingUid >= 0
+ if (dumpPackage != null
&& dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
continue;
}
@@ -3561,7 +3572,7 @@
boolean printedHeader = false;
for (int i=0; i<mModeWatchers.size(); i++) {
final ModeCallback cb = mModeWatchers.valueAt(i);
- if (dumpPackage != null && cb.mWatchingUid >= 0
+ if (dumpPackage != null
&& dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
continue;
}
@@ -3587,7 +3598,7 @@
if (dumpOp >= 0 && activeWatchers.indexOfKey(dumpOp) < 0) {
continue;
}
- if (dumpPackage != null && cb.mWatchingUid >= 0
+ if (dumpPackage != null
&& dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
continue;
}
@@ -3627,7 +3638,7 @@
if (dumpOp >= 0 && notedWatchers.indexOfKey(dumpOp) < 0) {
continue;
}
- if (dumpPackage != null && cb.mWatchingUid >= 0
+ if (dumpPackage != null
&& dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
continue;
}
@@ -3655,7 +3666,7 @@
pw.println(cb);
}
}
- if (mClients.size() > 0 && dumpMode < 0) {
+ if (mClients.size() > 0 && dumpMode < 0 && !dumpWatchers) {
needSep = true;
boolean printedHeader = false;
for (int i=0; i<mClients.size(); i++) {
@@ -3692,7 +3703,7 @@
}
}
if (mAudioRestrictions.size() > 0 && dumpOp < 0 && dumpPackage != null
- && dumpMode < 0) {
+ && dumpMode < 0 && !dumpWatchers) {
boolean printedHeader = false;
for (int o=0; o<mAudioRestrictions.size(); o++) {
final String op = AppOpsManager.opToName(mAudioRestrictions.keyAt(o));
@@ -3725,6 +3736,9 @@
final SparseIntArray opModes = uidState.opModes;
final ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
+ if (dumpWatchers) {
+ continue;
+ }
if (dumpOp >= 0 || dumpPackage != null || dumpMode >= 0) {
boolean hasOp = dumpOp < 0 || (uidState.opModes != null
&& uidState.opModes.indexOfKey(dumpOp) >= 0);
@@ -3739,8 +3753,8 @@
}
if (pkgOps != null) {
for (int pkgi = 0;
- (!hasOp || !hasPackage || !hasMode) && pkgi < pkgOps.size();
- pkgi++) {
+ (!hasOp || !hasPackage || !hasMode) && pkgi < pkgOps.size();
+ pkgi++) {
Ops ops = pkgOps.valueAt(pkgi);
if (!hasOp && ops != null && ops.indexOfKey(dumpOp) >= 0) {
hasOp = true;
@@ -3880,18 +3894,34 @@
for (int i = 0; i < userRestrictionCount; i++) {
IBinder token = mOpUserRestrictions.keyAt(i);
ClientRestrictionState restrictionState = mOpUserRestrictions.valueAt(i);
- pw.println(" User restrictions for token " + token + ":");
+ boolean printedTokenHeader = false;
+
+ if (dumpMode >= 0 || dumpWatchers) {
+ continue;
+ }
final int restrictionCount = restrictionState.perUserRestrictions != null
? restrictionState.perUserRestrictions.size() : 0;
- if (restrictionCount > 0) {
- pw.println(" Restricted ops:");
+ if (restrictionCount > 0 && dumpPackage == null) {
+ boolean printedOpsHeader = false;
for (int j = 0; j < restrictionCount; j++) {
int userId = restrictionState.perUserRestrictions.keyAt(j);
boolean[] restrictedOps = restrictionState.perUserRestrictions.valueAt(j);
if (restrictedOps == null) {
continue;
}
+ if (dumpOp >= 0 && (dumpOp >= restrictedOps.length
+ || !restrictedOps[dumpOp])) {
+ continue;
+ }
+ if (!printedTokenHeader) {
+ pw.println(" User restrictions for token " + token + ":");
+ printedTokenHeader = true;
+ }
+ if (!printedOpsHeader) {
+ pw.println(" Restricted ops:");
+ printedOpsHeader = true;
+ }
StringBuilder restrictedOpsValue = new StringBuilder();
restrictedOpsValue.append("[");
final int restrictedOpCount = restrictedOps.length;
@@ -3911,17 +3941,46 @@
final int excludedPackageCount = restrictionState.perUserExcludedPackages != null
? restrictionState.perUserExcludedPackages.size() : 0;
- if (excludedPackageCount > 0) {
- pw.println(" Excluded packages:");
+ if (excludedPackageCount > 0 && dumpOp < 0) {
+ boolean printedPackagesHeader = false;
for (int j = 0; j < excludedPackageCount; j++) {
int userId = restrictionState.perUserExcludedPackages.keyAt(j);
String[] packageNames = restrictionState.perUserExcludedPackages.valueAt(j);
+ if (packageNames == null) {
+ continue;
+ }
+ boolean hasPackage;
+ if (dumpPackage != null) {
+ hasPackage = false;
+ for (String pkg : packageNames) {
+ if (dumpPackage.equals(pkg)) {
+ hasPackage = true;
+ break;
+ }
+ }
+ } else {
+ hasPackage = true;
+ }
+ if (!hasPackage) {
+ continue;
+ }
+ if (!printedTokenHeader) {
+ pw.println(" User restrictions for token " + token + ":");
+ printedTokenHeader = true;
+ }
+ if (!printedPackagesHeader) {
+ pw.println(" Excluded packages:");
+ printedPackagesHeader = true;
+ }
pw.print(" "); pw.print("user: "); pw.print(userId);
pw.print(" packages: "); pw.println(Arrays.toString(packageNames));
}
}
}
}
+
+ // Must not hold the appops lock
+ mHistoricalRegistry.dump(" ", pw, dumpUid, dumpPackage, dumpOp);
}
private static final class Restriction {
@@ -4042,6 +4101,47 @@
return false;
}
+ @Override
+ public void setHistoryParameters(@AppOpsManager.HistoricalMode int mode,
+ long baseSnapshotInterval, int compressionStep) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+ "setHistoryParameters");
+ // Must not hold the appops lock
+ mHistoricalRegistry.setHistoryParameters(mode, baseSnapshotInterval, compressionStep);
+ }
+
+ @Override
+ public void offsetHistory(long offsetMillis) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+ "offsetHistory");
+ // Must not hold the appops lock
+ mHistoricalRegistry.offsetHistory(offsetMillis);
+ }
+
+ @Override
+ public void addHistoricalOps(HistoricalOps ops) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+ "addHistoricalOps");
+ // Must not hold the appops lock
+ mHistoricalRegistry.addHistoricalOps(ops);
+ }
+
+ @Override
+ public void resetHistoryParameters() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+ "resetHistoryParameters");
+ // Must not hold the appops lock
+ mHistoricalRegistry.resetHistoryParameters();
+ }
+
+ @Override
+ public void clearHistory() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+ "clearHistory");
+ // Must not hold the appops lock
+ mHistoricalRegistry.clearHistory();
+ }
+
private void removeUidsForUserLocked(int userHandle) {
for (int i = mUidStates.size() - 1; i >= 0; --i) {
final int uid = mUidStates.keyAt(i);
@@ -4112,6 +4212,16 @@
return packageNames;
}
+ private static void checkValidOpsOrNull(String[] opNames) {
+ if (opNames != null) {
+ for (String opName : opNames) {
+ if (AppOpsManager.strOpToOp(opName) == AppOpsManager.OP_NONE) {
+ throw new IllegalArgumentException("Unknown op: " + opName);
+ }
+ }
+ }
+ }
+
private final class ClientRestrictionState implements DeathRecipient {
private final IBinder token;
SparseArray<boolean[]> perUserRestrictions;
diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java
new file mode 100644
index 0000000..8d7811f
--- /dev/null
+++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java
@@ -0,0 +1,1495 @@
+/*
+ * 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.
+ */
+package com.android.server.appop;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
+import android.app.AppOpsManager.HistoricalMode;
+import android.app.AppOpsManager.HistoricalOp;
+import android.app.AppOpsManager.HistoricalOps;
+import android.app.AppOpsManager.HistoricalPackageOps;
+import android.app.AppOpsManager.HistoricalUidOps;
+import android.app.AppOpsManager.UidState;
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteCallback;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.TimeUtils;
+import android.util.Xml;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.AtomicDirectory;
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.XmlUtils;
+import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.server.FgThread;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This class managers historical app op state. This includes reading, persistence,
+ * accounting, querying.
+ * <p>
+ * The history is kept forever in multiple files. Each file time contains the
+ * relative offset from the current time which time is encoded in the file name.
+ * The files contain historical app op state snapshots which have times that
+ * are relative to the time of the container file.
+ *
+ * The data in the files are stored in a logarithmic fashion where where every
+ * subsequent file would contain data for ten times longer interval with ten
+ * times more time distance between snapshots. Hence, the more time passes
+ * the lesser the fidelity.
+ * <p>
+ * For example, the first file would contain data for 1 days with snapshots
+ * every 0.1 days, the next file would contain data for the period 1 to 10
+ * days with snapshots every 1 days, and so on.
+ * <p>
+ * THREADING AND LOCKING: Reported ops must be processed as quickly as possible.
+ * We keep ops pending to be persisted in memory and write to disk on a background
+ * thread. Hence, methods that report op changes are locking only the in memory
+ * state guarded by the mInMemoryLock which happens to be the app ops service lock
+ * avoiding a lock addition on the critical path. When a query comes we need to
+ * evaluate it based off both in memory and on disk state. This means they need to
+ * be frozen with respect to each other and not change from the querying caller's
+ * perspective. To achieve this we add a dedicated mOnDiskLock to guard the on
+ * disk state. To have fast critical path we need to limit the locking of the
+ * mInMemoryLock, thus for operations that touch in memory and on disk state one
+ * must grab first the mOnDiskLock and then the mInMemoryLock and limit the
+ * in memory lock to extraction of relevant data. Locking order is critical to
+ * avoid deadlocks. The convention is that xxxDLocked suffix means the method
+ * must be called with the mOnDiskLock lock, xxxMLocked suffix means the method
+ * must be called with the mInMemoryLock, xxxDMLocked suffix means the method
+ * must be called with the mOnDiskLock and mInMemoryLock locks acquired in that
+ * exact order.
+ */
+// TODO (bug:122218838): Make sure we handle start of epoch time
+// TODO (bug:122218838): Validate changed time is handled correctly
+final class HistoricalRegistry {
+ private static final boolean DEBUG = false;
+
+ private static final String LOG_TAG = HistoricalRegistry.class.getSimpleName();
+
+ private static final String PARAMETER_DELIMITER = ",";
+ private static final String PARAMETER_ASSIGNMENT = "=";
+
+ @GuardedBy("mLock")
+ private @NonNull LinkedList<HistoricalOps> mPendingWrites = new LinkedList<>();
+
+ // Lock for read/write access to on disk state
+ private final Object mOnDiskLock = new Object();
+
+ //Lock for read/write access to in memory state
+ private final @NonNull Object mInMemoryLock;
+
+ private static final int MSG_WRITE_PENDING_HISTORY = 1;
+
+ // See mMode
+ private static final int DEFAULT_MODE = AppOpsManager.HISTORICAL_MODE_DISABLED;
+
+ // See mBaseSnapshotInterval
+ private static final long DEFAULT_SNAPSHOT_INTERVAL_MILLIS = TimeUnit.MINUTES.toMillis(15);
+
+ // See mIntervalCompressionMultiplier
+ private static final long DEFAULT_COMPRESSION_STEP = 10;
+
+ /**
+ * Whether history is enabled.
+ */
+ @GuardedBy("mInMemoryLock")
+ private int mMode = AppOpsManager.HISTORICAL_MODE_DISABLED;
+
+ /**
+ * This granularity has been chosen to allow clean delineation for intervals
+ * humans understand, 15 min, 60, min, a day, a week, a month (30 days).
+ */
+ @GuardedBy("mInMemoryLock")
+ private long mBaseSnapshotInterval = DEFAULT_SNAPSHOT_INTERVAL_MILLIS;
+
+ /**
+ * The compression between steps. Each subsequent step is this much longer
+ * in terms of duration and each snapshot is this much more apart from the
+ * previous step.
+ */
+ @GuardedBy("mInMemoryLock")
+ private long mIntervalCompressionMultiplier = DEFAULT_COMPRESSION_STEP;
+
+ // The current ops to which to add statistics.
+ @GuardedBy("mInMemoryLock")
+ private @Nullable HistoricalOps mCurrentHistoricalOps;
+
+ // The time we should write the next snapshot.
+ @GuardedBy("mInMemoryLock")
+ private long mNextPersistDueTimeMillis;
+
+ // How much to offset the history on the next write.
+ @GuardedBy("mInMemoryLock")
+ private long mPendingHistoryOffsetMillis;
+
+ // Object managing persistence (read/write)
+ @GuardedBy("mOnDiskLock")
+ private Persistence mPersistence = new Persistence(mBaseSnapshotInterval,
+ mIntervalCompressionMultiplier);
+
+ HistoricalRegistry(@NonNull Object lock) {
+ mInMemoryLock = lock;
+ if (mMode != AppOpsManager.HISTORICAL_MODE_DISABLED) {
+ synchronized (mInMemoryLock) {
+ // When starting always adjust history to now.
+ mPendingHistoryOffsetMillis = System.currentTimeMillis()
+ - mPersistence.getLastPersistTimeMillisDLocked();
+ }
+ }
+ }
+
+ void systemReady(@NonNull ContentResolver resolver) {
+ updateParametersFromSetting(resolver);
+ final Uri uri = Settings.Global.getUriFor(Settings.Global.APPOP_HISTORY_PARAMETERS);
+ resolver.registerContentObserver(uri, false, new ContentObserver(
+ FgThread.getHandler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateParametersFromSetting(resolver);
+ }
+ });
+ }
+
+ private void updateParametersFromSetting(@NonNull ContentResolver resolver) {
+ final String setting = Settings.Global.getString(resolver,
+ Settings.Global.APPOP_HISTORY_PARAMETERS);
+ if (setting == null) {
+ return;
+ }
+ String modeValue = null;
+ String baseSnapshotIntervalValue = null;
+ String intervalMultiplierValue = null;
+ final String[] parameters = setting.split(PARAMETER_DELIMITER);
+ for (String parameter : parameters) {
+ final String[] parts = parameter.split(PARAMETER_ASSIGNMENT);
+ if (parts.length == 2) {
+ final String key = parts[0].trim();
+ switch (key) {
+ case Settings.Global.APPOP_HISTORY_MODE: {
+ modeValue = parts[1].trim();
+ } break;
+ case Settings.Global.APPOP_HISTORY_BASE_INTERVAL_MILLIS: {
+ baseSnapshotIntervalValue = parts[1].trim();
+ } break;
+ case Settings.Global.APPOP_HISTORY_INTERVAL_MULTIPLIER: {
+ intervalMultiplierValue = parts[1].trim();
+ } break;
+ default: {
+ Slog.w(LOG_TAG, "Unknown parameter: " + parameter);
+ }
+ }
+ }
+ }
+ if (modeValue != null && baseSnapshotIntervalValue != null
+ && intervalMultiplierValue != null) {
+ try {
+ final int mode = AppOpsManager.parseHistoricalMode(modeValue);
+ final long baseSnapshotInterval = Long.parseLong(baseSnapshotIntervalValue);
+ final int intervalCompressionMultiplier = Integer.parseInt(intervalMultiplierValue);
+ setHistoryParameters(mode, baseSnapshotInterval,intervalCompressionMultiplier);
+ return;
+ } catch (NumberFormatException ignored) {}
+ }
+ Slog.w(LOG_TAG, "Bad value for" + Settings.Global.APPOP_HISTORY_PARAMETERS
+ + "=" + setting + " resetting!");
+ }
+
+ void dump(String prefix, PrintWriter pw, int filterUid,
+ String filterPackage, int filterOp) {
+ synchronized (mOnDiskLock) {
+ synchronized (mInMemoryLock) {
+ pw.println();
+ pw.print(prefix);
+ pw.print("History:");
+
+ pw.print(" mode=");
+ pw.println(AppOpsManager.historicalModeToString(mMode));
+
+ final StringDumpVisitor visitor = new StringDumpVisitor(prefix + " ",
+ pw, filterUid, filterPackage, filterOp);
+ final long nowMillis = System.currentTimeMillis();
+
+ // Dump in memory state first
+ final HistoricalOps currentOps = getUpdatedPendingHistoricalOpsMLocked(
+ nowMillis);
+ makeRelativeToEpochStart(currentOps, nowMillis);
+ currentOps.accept(visitor);
+
+ final List<HistoricalOps> ops = mPersistence.readHistoryDLocked();
+ if (ops != null) {
+ // TODO (bug:122218838): Make sure this is properly dumped
+ final long remainingToFillBatchMillis = mNextPersistDueTimeMillis
+ - nowMillis - mBaseSnapshotInterval;
+ final int opCount = ops.size();
+ for (int i = 0; i < opCount; i++) {
+ final HistoricalOps op = ops.get(i);
+ op.offsetBeginAndEndTime(remainingToFillBatchMillis);
+ makeRelativeToEpochStart(op, nowMillis);
+ op.accept(visitor);
+ }
+ } else {
+ pw.println(" Empty");
+ }
+ }
+ }
+ }
+
+ @HistoricalMode int getMode() {
+ synchronized (mInMemoryLock) {
+ return mMode;
+ }
+ }
+
+ @Nullable void getHistoricalOpsFromDiskRaw(int uid, @NonNull String packageName,
+ @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis,
+ @NonNull RemoteCallback callback) {
+ final HistoricalOps result = new HistoricalOps(beginTimeMillis, endTimeMillis);
+ mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, opNames,
+ beginTimeMillis, endTimeMillis);
+ final Bundle payload = new Bundle();
+ payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, result);
+ callback.sendResult(payload);
+ }
+
+ @Nullable void getHistoricalOps(int uid, @NonNull String packageName,
+ @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis,
+ @NonNull RemoteCallback callback) {
+ final long currentTimeMillis = System.currentTimeMillis();
+ if (endTimeMillis == Long.MAX_VALUE) {
+ endTimeMillis = currentTimeMillis;
+ }
+
+ // Argument times are based off epoch start while our internal store is
+ // based off now, so take this into account.
+ final long inMemoryAdjBeginTimeMillis = Math.max(currentTimeMillis - endTimeMillis, 0);
+ final long inMemoryAdjEndTimeMillis = Math.max(currentTimeMillis - beginTimeMillis, 0);
+ final HistoricalOps result = new HistoricalOps(inMemoryAdjBeginTimeMillis,
+ inMemoryAdjEndTimeMillis);
+
+ synchronized (mOnDiskLock) {
+ final List<HistoricalOps> pendingWrites;
+ final HistoricalOps currentOps;
+ synchronized (mInMemoryLock) {
+ currentOps = getUpdatedPendingHistoricalOpsMLocked(currentTimeMillis);
+ if (!(inMemoryAdjBeginTimeMillis >= currentOps.getEndTimeMillis()
+ || inMemoryAdjEndTimeMillis <= currentOps.getBeginTimeMillis())) {
+ // Some of the current batch falls into the query, so extract that.
+ final HistoricalOps currentOpsCopy = new HistoricalOps(currentOps);
+ currentOpsCopy.filter(uid, packageName, opNames, inMemoryAdjBeginTimeMillis,
+ inMemoryAdjEndTimeMillis);
+ result.merge(currentOpsCopy);
+ }
+ pendingWrites = new ArrayList<>(mPendingWrites);
+ mPendingWrites.clear();
+ }
+
+ // If the query was only for in-memory state - done.
+ if (inMemoryAdjEndTimeMillis > currentOps.getEndTimeMillis()) {
+ // If there is a write in flight we need to force it now
+ persistPendingHistory(pendingWrites);
+ // Collect persisted state.
+ final long onDiskAndInMemoryOffsetMillis = currentTimeMillis
+ - mNextPersistDueTimeMillis + mBaseSnapshotInterval;
+ final long onDiskAdjBeginTimeMillis = Math.max(inMemoryAdjBeginTimeMillis
+ - onDiskAndInMemoryOffsetMillis, 0);
+ final long onDiskAdjEndTimeMillis = Math.max(inMemoryAdjEndTimeMillis
+ - onDiskAndInMemoryOffsetMillis, 0);
+ mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, opNames,
+ onDiskAdjBeginTimeMillis, onDiskAdjEndTimeMillis);
+ }
+
+ // Rebase the result time to be since epoch.
+ result.setBeginAndEndTime(beginTimeMillis, endTimeMillis);
+
+ // Send back the result.
+ final Bundle payload = new Bundle();
+ payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, result);
+ callback.sendResult(payload);
+ }
+ }
+
+ void incrementOpAccessedCount(int op, int uid, @NonNull String packageName,
+ @UidState int uidState) {
+ synchronized (mInMemoryLock) {
+ if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
+ getUpdatedPendingHistoricalOpsMLocked(System.currentTimeMillis())
+ .increaseAccessCount(op, uid, packageName, uidState, 1);
+
+ }
+ }
+ }
+
+ void incrementOpRejected(int op, int uid, @NonNull String packageName,
+ @UidState int uidState) {
+ synchronized (mInMemoryLock) {
+ if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
+ getUpdatedPendingHistoricalOpsMLocked(System.currentTimeMillis())
+ .increaseRejectCount(op, uid, packageName, uidState, 1);
+ }
+ }
+ }
+
+ void increaseOpAccessDuration(int op, int uid, @NonNull String packageName,
+ @UidState int uidState, long increment) {
+ synchronized (mInMemoryLock) {
+ if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
+ getUpdatedPendingHistoricalOpsMLocked(System.currentTimeMillis())
+ .increaseAccessDuration(op, uid, packageName, uidState, increment);
+ }
+ }
+ }
+
+ void setHistoryParameters(@HistoricalMode int mode,
+ long baseSnapshotInterval, long intervalCompressionMultiplier) {
+ synchronized (mOnDiskLock) {
+ synchronized (mInMemoryLock) {
+ boolean resampleHistory = false;
+ Slog.i(LOG_TAG, "New history parameters: mode:"
+ + AppOpsManager.historicalModeToString(mMode) + " baseSnapshotInterval:"
+ + baseSnapshotInterval + " intervalCompressionMultiplier:"
+ + intervalCompressionMultiplier);
+ if (mMode != mode) {
+ mMode = mode;
+ if (mMode == AppOpsManager.HISTORICAL_MODE_DISABLED) {
+ clearHistoryOnDiskLocked();
+ }
+ }
+ if (mBaseSnapshotInterval != baseSnapshotInterval) {
+ mBaseSnapshotInterval = baseSnapshotInterval;
+ resampleHistory = true;
+ }
+ if (mIntervalCompressionMultiplier != intervalCompressionMultiplier) {
+ mIntervalCompressionMultiplier = intervalCompressionMultiplier;
+ resampleHistory = true;
+ }
+ if (resampleHistory) {
+ resampleHistoryOnDiskInMemoryDMLocked(0);
+ }
+ }
+ }
+ }
+
+ void offsetHistory(long offsetMillis) {
+ synchronized (mOnDiskLock) {
+ synchronized (mInMemoryLock) {
+ final List<HistoricalOps> history = mPersistence.readHistoryDLocked();
+ clearHistory();
+ if (history != null) {
+ final int historySize = history.size();
+ for (int i = 0; i < historySize; i++) {
+ final HistoricalOps ops = history.get(i);
+ ops.offsetBeginAndEndTime(offsetMillis);
+ }
+ if (offsetMillis < 0) {
+ pruneFutureOps(history);
+ }
+ mPersistence.persistHistoricalOpsDLocked(history);
+ }
+ }
+ }
+ }
+
+ void addHistoricalOps(HistoricalOps ops) {
+ final List<HistoricalOps> pendingWrites;
+ synchronized (mInMemoryLock) {
+ // The history files start from mBaseSnapshotInterval - take this into account.
+ ops.offsetBeginAndEndTime(mBaseSnapshotInterval);
+ mPendingWrites.offerFirst(ops);
+ pendingWrites = new ArrayList<>(mPendingWrites);
+ mPendingWrites.clear();
+ }
+ persistPendingHistory(pendingWrites);
+ }
+
+ private void resampleHistoryOnDiskInMemoryDMLocked(long offsetMillis) {
+ mPersistence = new Persistence(mBaseSnapshotInterval, mIntervalCompressionMultiplier);
+ offsetHistory(offsetMillis);
+ }
+
+ void resetHistoryParameters() {
+ setHistoryParameters(DEFAULT_MODE, DEFAULT_SNAPSHOT_INTERVAL_MILLIS,
+ DEFAULT_COMPRESSION_STEP);
+ }
+
+ void clearHistory() {
+ synchronized (mOnDiskLock) {
+ clearHistoryOnDiskLocked();
+ }
+ }
+
+ private void clearHistoryOnDiskLocked() {
+ BackgroundThread.getHandler().removeMessages(MSG_WRITE_PENDING_HISTORY);
+ synchronized (mInMemoryLock) {
+ mCurrentHistoricalOps = null;
+ mNextPersistDueTimeMillis = System.currentTimeMillis();
+ mPendingWrites.clear();
+ }
+ mPersistence.clearHistoryDLocked();
+ }
+
+ private @NonNull HistoricalOps getUpdatedPendingHistoricalOpsMLocked(long now) {
+ if (mCurrentHistoricalOps != null) {
+ final long remainingTimeMillis = mNextPersistDueTimeMillis - now;
+ if (remainingTimeMillis > mBaseSnapshotInterval) {
+ // If time went backwards we need to push history to the future with the
+ // overflow over our snapshot interval. If time went forward do nothing
+ // as we would naturally push history into the past on the next write.
+ mPendingHistoryOffsetMillis = remainingTimeMillis - mBaseSnapshotInterval;
+ }
+ final long elapsedTimeMillis = mBaseSnapshotInterval - remainingTimeMillis;
+ mCurrentHistoricalOps.setEndTime(elapsedTimeMillis);
+ if (remainingTimeMillis > 0) {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Returning current in-memory state");
+ }
+ return mCurrentHistoricalOps;
+ }
+ if (mCurrentHistoricalOps.isEmpty()) {
+ mCurrentHistoricalOps.setBeginAndEndTime(0, 0);
+ mNextPersistDueTimeMillis = now + mBaseSnapshotInterval;
+ return mCurrentHistoricalOps;
+ }
+ // The current batch is full, so persist taking into account overdue persist time.
+ mCurrentHistoricalOps.offsetBeginAndEndTime(mBaseSnapshotInterval);
+ mCurrentHistoricalOps.setBeginTime(mCurrentHistoricalOps.getEndTimeMillis()
+ - mBaseSnapshotInterval);
+ final long overdueTimeMillis = Math.abs(remainingTimeMillis);
+ mCurrentHistoricalOps.offsetBeginAndEndTime(overdueTimeMillis);
+ schedulePersistHistoricalOpsMLocked(mCurrentHistoricalOps);
+ }
+ // The current batch is in the future, i.e. not complete yet.
+ mCurrentHistoricalOps = new HistoricalOps(0, 0);
+ mNextPersistDueTimeMillis = now + mBaseSnapshotInterval;
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Returning new in-memory state");
+ }
+ return mCurrentHistoricalOps;
+ }
+
+ private void persistPendingHistory() {
+ final List<HistoricalOps> pendingWrites;
+ synchronized (mOnDiskLock) {
+ synchronized (mInMemoryLock) {
+ pendingWrites = new ArrayList<>(mPendingWrites);
+ mPendingWrites.clear();
+ if (mPendingHistoryOffsetMillis != 0) {
+ resampleHistoryOnDiskInMemoryDMLocked(mPendingHistoryOffsetMillis);
+ mPendingHistoryOffsetMillis = 0;
+ }
+ }
+ persistPendingHistory(pendingWrites);
+ }
+ }
+ private void persistPendingHistory(@NonNull List<HistoricalOps> pendingWrites) {
+ synchronized (mOnDiskLock) {
+ BackgroundThread.getHandler().removeMessages(MSG_WRITE_PENDING_HISTORY);
+ if (pendingWrites.isEmpty()) {
+ return;
+ }
+ final int opCount = pendingWrites.size();
+ // Pending writes are offset relative to each other, so take this
+ // into account to persist everything in one shot - single write.
+ for (int i = 0; i < opCount; i++) {
+ final HistoricalOps current = pendingWrites.get(i);
+ if (i > 0) {
+ final HistoricalOps previous = pendingWrites.get(i - 1);
+ current.offsetBeginAndEndTime(previous.getBeginTimeMillis());
+ }
+ }
+ mPersistence.persistHistoricalOpsDLocked(pendingWrites);
+ }
+ }
+
+ private void schedulePersistHistoricalOpsMLocked(@NonNull HistoricalOps ops) {
+ final Message message = PooledLambda.obtainMessage(
+ HistoricalRegistry::persistPendingHistory, HistoricalRegistry.this);
+ message.what = MSG_WRITE_PENDING_HISTORY;
+ BackgroundThread.getHandler().sendMessage(message);
+ mPendingWrites.offerFirst(ops);
+ }
+
+ private static void makeRelativeToEpochStart(@NonNull HistoricalOps ops, long nowMillis) {
+ ops.setBeginAndEndTime(nowMillis - ops.getEndTimeMillis(),
+ nowMillis- ops.getBeginTimeMillis());
+ }
+
+ private void pruneFutureOps(@NonNull List<HistoricalOps> ops) {
+ final int opCount = ops.size();
+ for (int i = opCount - 1; i >= 0; i--) {
+ final HistoricalOps op = ops.get(i);
+ if (op.getEndTimeMillis() <= mBaseSnapshotInterval) {
+ ops.remove(i);
+ } else if (op.getBeginTimeMillis() < mBaseSnapshotInterval) {
+ final double filterScale = (double) (op.getEndTimeMillis() - mBaseSnapshotInterval)
+ / (double) op.getDurationMillis();
+ Persistence.spliceFromBeginning(op, filterScale);
+ }
+ }
+ }
+
+ private static final class Persistence {
+ private static final boolean DEBUG = false;
+
+ private static final String LOG_TAG = Persistence.class.getSimpleName();
+
+ private static final String HISTORY_FILE_SUFFIX = ".xml";
+
+ private static final String TAG_HISTORY = "history";
+ private static final String TAG_OPS = "ops";
+ private static final String TAG_UID = "uid";
+ private static final String TAG_PACKAGE = "package";
+ private static final String TAG_OP = "op";
+ private static final String TAG_STATE = "state";
+
+ private static final String ATTR_VERSION = "version";
+ private static final String ATTR_NAME = "name";
+ private static final String ATTR_ACCESS_COUNT = "accessCount";
+ private static final String ATTR_REJECT_COUNT = "rejectCount";
+ private static final String ATTR_ACCESS_DURATION = "accessDuration";
+ private static final String ATTR_BEGIN_TIME = "beginTime";
+ private static final String ATTR_END_TIME = "endTime";
+ private static final String ATTR_OVERFLOW = "overflow";
+
+ private static final int CURRENT_VERSION = 1;
+
+ private final long mBaseSnapshotInterval;
+ private final long mIntervalCompressionMultiplier;
+
+ Persistence(long baseSnapshotInterval, long intervalCompressionMultiplier) {
+ mBaseSnapshotInterval = baseSnapshotInterval;
+ mIntervalCompressionMultiplier = intervalCompressionMultiplier;
+ }
+
+ private final AtomicDirectory mHistoricalAppOpsDir = new AtomicDirectory(
+ new File(new File(Environment.getDataSystemDeDirectory(), "appops"), "history"));
+
+ private File generateFile(@NonNull File baseDir, int depth) {
+ final long globalBeginMillis = computeGlobalIntervalBeginMillis(depth);
+ return new File(baseDir, Long.toString(globalBeginMillis) + HISTORY_FILE_SUFFIX);
+ }
+
+ void clearHistoryDLocked() {
+ mHistoricalAppOpsDir.delete();
+ }
+
+ void persistHistoricalOpsDLocked(@NonNull List<HistoricalOps> ops) {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Persisting ops:\n" + opsToDebugString(ops));
+ enforceOpsWellFormed(ops);
+ }
+ try {
+ final File newBaseDir = mHistoricalAppOpsDir.startWrite();
+ final File oldBaseDir = mHistoricalAppOpsDir.getBackupDirectory();
+ handlePersistHistoricalOpsRecursiveDLocked(newBaseDir, oldBaseDir, ops, 0);
+ mHistoricalAppOpsDir.finishWrite();
+ } catch (Throwable t) {
+ Slog.wtf(LOG_TAG, "Failed to write historical app ops, restoring backup", t);
+ mHistoricalAppOpsDir.failWrite();
+ }
+ }
+
+ @Nullable List<HistoricalOps> readHistoryRawDLocked() {
+ return collectHistoricalOpsBaseDLocked(Process.INVALID_UID /*filterUid*/,
+ null /*filterPackageName*/, null /*filterOpNames*/,
+ 0 /*filterBeginTimeMills*/, Long.MAX_VALUE /*filterEndTimeMills*/);
+ }
+
+ @Nullable List<HistoricalOps> readHistoryDLocked() {
+ final List<HistoricalOps> result = readHistoryRawDLocked();
+ // Take into account in memory state duration.
+ if (result != null) {
+ final int opCount = result.size();
+ for (int i = 0; i < opCount; i++) {
+ result.get(i).offsetBeginAndEndTime(mBaseSnapshotInterval);
+ }
+ }
+ return result;
+ }
+
+ long getLastPersistTimeMillisDLocked() {
+ try {
+ final File baseDir = mHistoricalAppOpsDir.startRead();
+ final File file = generateFile(baseDir, 0);
+ if (file.exists()) {
+ return file.lastModified();
+ }
+ mHistoricalAppOpsDir.finishRead();
+ } catch (IOException e) {
+ Slog.wtf("Error reading historical app ops. Deleting history.", e);
+ mHistoricalAppOpsDir.delete();
+ }
+ return 0;
+ }
+
+ private void collectHistoricalOpsDLocked(@NonNull HistoricalOps currentOps,
+ int filterUid, @NonNull String filterPackageName, @Nullable String[] filterOpNames,
+ long filterBeingMillis, long filterEndMillis) {
+ final List<HistoricalOps> readOps = collectHistoricalOpsBaseDLocked(filterUid,
+ filterPackageName, filterOpNames, filterBeingMillis, filterEndMillis);
+ if (readOps != null) {
+ final int readCount = readOps.size();
+ for (int i = 0; i < readCount; i++) {
+ final HistoricalOps readOp = readOps.get(i);
+ currentOps.merge(readOp);
+ }
+ }
+ }
+
+ private @Nullable LinkedList<HistoricalOps> collectHistoricalOpsBaseDLocked(
+ int filterUid, @NonNull String filterPackageName, @Nullable String[] filterOpNames,
+ long filterBeginTimeMillis, long filterEndTimeMillis) {
+ try {
+ final File baseDir = mHistoricalAppOpsDir.startRead();
+ final File[] files = baseDir.listFiles();
+ if (files == null) {
+ return null;
+ }
+ final ArraySet<File> historyFiles = new ArraySet<>(files.length);
+ for (File file : files) {
+ if (file.isFile() && file.getName().endsWith(HISTORY_FILE_SUFFIX)) {
+ historyFiles.add(file);
+ }
+ }
+ final long[] globalContentOffsetMillis = {0};
+ final LinkedList<HistoricalOps> ops = collectHistoricalOpsRecursiveDLocked(
+ baseDir, filterUid, filterPackageName, filterOpNames, filterBeginTimeMillis,
+ filterEndTimeMillis, globalContentOffsetMillis, null /*outOps*/,
+ 0 /*depth*/, historyFiles);
+ mHistoricalAppOpsDir.finishRead();
+ return ops;
+ } catch (IOException | XmlPullParserException e) {
+ Slog.wtf("Error reading historical app ops. Deleting history.", e);
+ mHistoricalAppOpsDir.delete();
+ }
+ return null;
+ }
+
+ private @Nullable LinkedList<HistoricalOps> collectHistoricalOpsRecursiveDLocked(
+ @NonNull File baseDir, int filterUid, @NonNull String filterPackageName,
+ @Nullable String[] filterOpNames, long filterBeginTimeMillis,
+ long filterEndTimeMillis, @NonNull long[] globalContentOffsetMillis,
+ @Nullable LinkedList<HistoricalOps> outOps, int depth,
+ @NonNull ArraySet<File> historyFiles)
+ throws IOException, XmlPullParserException {
+ final long previousIntervalEndMillis = (long) Math.pow(mIntervalCompressionMultiplier,
+ depth) * mBaseSnapshotInterval;
+ final long currentIntervalEndMillis = (long) Math.pow(mIntervalCompressionMultiplier,
+ depth + 1) * mBaseSnapshotInterval;
+
+ filterBeginTimeMillis = Math.max(filterBeginTimeMillis - previousIntervalEndMillis, 0);
+ filterEndTimeMillis = filterEndTimeMillis - previousIntervalEndMillis;
+
+ // Read historical data at this level
+ final List<HistoricalOps> readOps = readHistoricalOpsLocked(baseDir,
+ previousIntervalEndMillis, currentIntervalEndMillis, filterUid,
+ filterPackageName, filterOpNames, filterBeginTimeMillis, filterEndTimeMillis,
+ globalContentOffsetMillis, depth, historyFiles);
+
+ // Empty is a special signal to stop diving
+ if (readOps != null && readOps.isEmpty()) {
+ return outOps;
+ }
+
+ // Collect older historical data from subsequent levels
+ outOps = collectHistoricalOpsRecursiveDLocked(
+ baseDir, filterUid, filterPackageName, filterOpNames, filterBeginTimeMillis,
+ filterEndTimeMillis, globalContentOffsetMillis, outOps, depth + 1,
+ historyFiles);
+
+ // Make older historical data relative to the current historical level
+ if (outOps != null) {
+ final int opCount = outOps.size();
+ for (int i = 0; i < opCount; i++) {
+ final HistoricalOps collectedOp = outOps.get(i);
+ collectedOp.offsetBeginAndEndTime(currentIntervalEndMillis);
+ }
+ }
+
+ if (readOps != null) {
+ if (outOps == null) {
+ outOps = new LinkedList<>();
+ }
+ // Add the read ops to output
+ final int opCount = readOps.size();
+ for (int i = opCount - 1; i >= 0; i--) {
+ outOps.offerFirst(readOps.get(i));
+ }
+ }
+
+ return outOps;
+ }
+
+ private boolean createHardLinkToExistingFile(@NonNull File fromFile, @NonNull File toFile)
+ throws IOException {
+ if (!fromFile.exists()) {
+ return false;
+ }
+ Files.createLink(toFile.toPath(), fromFile.toPath());
+ return true;
+ }
+
+ private void handlePersistHistoricalOpsRecursiveDLocked(@NonNull File newBaseDir,
+ @NonNull File oldBaseDir, @Nullable List<HistoricalOps> passedOps, int depth)
+ throws IOException, XmlPullParserException {
+ final long previousIntervalEndMillis = (long) Math.pow(mIntervalCompressionMultiplier,
+ depth) * mBaseSnapshotInterval;
+ final long currentIntervalEndMillis = (long) Math.pow(mIntervalCompressionMultiplier,
+ depth + 1) * mBaseSnapshotInterval;
+
+ if (passedOps == null || passedOps.isEmpty()) {
+ // If there is an old file we need to copy it over to the new state.
+ final File oldFile = generateFile(oldBaseDir, depth);
+ final File newFile = generateFile(newBaseDir, depth);
+ if (createHardLinkToExistingFile(oldFile, newFile)) {
+ handlePersistHistoricalOpsRecursiveDLocked(newBaseDir, oldBaseDir,
+ passedOps, depth + 1);
+ }
+ return;
+ }
+
+ if (DEBUG) {
+ enforceOpsWellFormed(passedOps);
+ }
+
+ // Normalize passed ops time to be based off this interval start
+ final int passedOpCount = passedOps.size();
+ for (int i = 0; i < passedOpCount; i++) {
+ final HistoricalOps passedOp = passedOps.get(i);
+ passedOp.offsetBeginAndEndTime(-previousIntervalEndMillis);
+ }
+
+ if (DEBUG) {
+ enforceOpsWellFormed(passedOps);
+ }
+
+ // Read persisted ops for this interval
+ final List<HistoricalOps> existingOps = readHistoricalOpsLocked(oldBaseDir,
+ previousIntervalEndMillis, currentIntervalEndMillis,
+ Process.INVALID_UID /*filterUid*/, null /*filterPackageName*/,
+ null /*filterOpNames*/, Long.MIN_VALUE /*filterBeginTimeMillis*/,
+ Long.MAX_VALUE /*filterEndTimeMillis*/, null, depth,
+ null /*historyFiles*/);
+
+ if (DEBUG) {
+ enforceOpsWellFormed(existingOps);
+ }
+
+ // Offset existing ops to account for elapsed time
+ final int existingOpCount = existingOps.size();
+ if (existingOpCount > 0) {
+ // Compute elapsed time
+ final long elapsedTimeMillis = passedOps.get(passedOps.size() - 1)
+ .getEndTimeMillis();
+ for (int i = 0; i < existingOpCount; i++) {
+ final HistoricalOps existingOp = existingOps.get(i);
+ existingOp.offsetBeginAndEndTime(elapsedTimeMillis);
+ }
+ }
+
+ if (DEBUG) {
+ enforceOpsWellFormed(existingOps);
+ }
+
+ final long slotDurationMillis = previousIntervalEndMillis;
+
+ // Consolidate passed ops at the current slot duration ensuring each snapshot is
+ // full. To achieve this we put all passed and existing ops in a list and will
+ // merge them to ensure each represents a snapshot at the current granularity.
+ final List<HistoricalOps> allOps = new LinkedList<>();
+ allOps.addAll(passedOps);
+ allOps.addAll(existingOps);
+
+ if (DEBUG) {
+ enforceOpsWellFormed(allOps);
+ }
+
+ // Compute ops to persist and overflow ops
+ List<HistoricalOps> persistedOps = null;
+ List<HistoricalOps> overflowedOps = null;
+
+ // We move a snapshot into the next level only if the start time is
+ // after the end of the current interval. This avoids rewriting all
+ // files to propagate the information added to the history on every
+ // iteration. Instead, we would rewrite the next level file only if
+ // an entire snapshot from the previous level is being propagated.
+ // The trade off is that we need to store how much the last snapshot
+ // of the current interval overflows past the interval end. We write
+ // the overflow data to avoid parsing all snapshots on query.
+ long intervalOverflowMillis = 0;
+ final int opCount = allOps.size();
+ for (int i = 0; i < opCount; i++) {
+ final HistoricalOps op = allOps.get(i);
+ final HistoricalOps persistedOp;
+ final HistoricalOps overflowedOp;
+ if (op.getEndTimeMillis() <= currentIntervalEndMillis) {
+ persistedOp = op;
+ overflowedOp = null;
+ } else if (op.getBeginTimeMillis() < currentIntervalEndMillis) {
+ persistedOp = op;
+ intervalOverflowMillis = op.getEndTimeMillis() - currentIntervalEndMillis;
+ if (intervalOverflowMillis > previousIntervalEndMillis) {
+ final double splitScale = (double) intervalOverflowMillis
+ / op.getDurationMillis();
+ overflowedOp = spliceFromEnd(op, splitScale);
+ intervalOverflowMillis = op.getEndTimeMillis() - currentIntervalEndMillis;
+ } else {
+ overflowedOp = null;
+ }
+ } else {
+ persistedOp = null;
+ overflowedOp = op;
+ }
+ if (persistedOp != null) {
+ if (persistedOps == null) {
+ persistedOps = new ArrayList<>();
+ }
+ persistedOps.add(persistedOp);
+ }
+ if (overflowedOp != null) {
+ if (overflowedOps == null) {
+ overflowedOps = new ArrayList<>();
+ }
+ overflowedOps.add(overflowedOp);
+ }
+ }
+
+ if (DEBUG) {
+ enforceOpsWellFormed(persistedOps);
+ enforceOpsWellFormed(overflowedOps);
+ }
+
+ if (persistedOps != null) {
+ normalizeSnapshotForSlotDuration(persistedOps, slotDurationMillis);
+ final File newFile = generateFile(newBaseDir, depth);
+ writeHistoricalOpsDLocked(persistedOps, intervalOverflowMillis, newFile);
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Persisted at depth: " + depth
+ + " ops:\n" + opsToDebugString(persistedOps));
+ enforceOpsWellFormed(persistedOps);
+ }
+ }
+
+ handlePersistHistoricalOpsRecursiveDLocked(newBaseDir, oldBaseDir,
+ overflowedOps, depth + 1);
+ }
+
+ private @NonNull List<HistoricalOps> readHistoricalOpsLocked(File baseDir,
+ long intervalBeginMillis, long intervalEndMillis, int filterUid,
+ @Nullable String filterPackageName, @Nullable String[] filterOpNames,
+ long filterBeginTimeMillis, long filterEndTimeMillis,
+ @Nullable long[] cumulativeOverflowMillis, int depth,
+ @NonNull ArraySet<File> historyFiles)
+ throws IOException, XmlPullParserException {
+ final File file = generateFile(baseDir, depth);
+ if (historyFiles != null) {
+ historyFiles.remove(file);
+ }
+ if (filterBeginTimeMillis >= filterEndTimeMillis
+ || filterEndTimeMillis < intervalBeginMillis) {
+ // Don't go deeper
+ return Collections.emptyList();
+ }
+ if (filterBeginTimeMillis >= (intervalEndMillis
+ + ((intervalEndMillis - intervalBeginMillis) / mIntervalCompressionMultiplier)
+ + (cumulativeOverflowMillis != null ? cumulativeOverflowMillis[0] : 0))
+ || !file.exists()) {
+ if (historyFiles == null || historyFiles.isEmpty()) {
+ // Don't go deeper
+ return Collections.emptyList();
+ } else {
+ // Keep diving
+ return null;
+ }
+ }
+ return readHistoricalOpsLocked(file, filterUid, filterPackageName, filterOpNames,
+ filterBeginTimeMillis, filterEndTimeMillis, cumulativeOverflowMillis);
+ }
+
+ private @Nullable List<HistoricalOps> readHistoricalOpsLocked(@NonNull File file,
+ int filterUid, @Nullable String filterPackageName, @Nullable String[] filterOpNames,
+ long filterBeginTimeMillis, long filterEndTimeMillis,
+ @Nullable long[] cumulativeOverflowMillis)
+ throws IOException, XmlPullParserException {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Reading ops from:" + file);
+ }
+ List<HistoricalOps> allOps = null;
+ try (FileInputStream stream = new FileInputStream(file)) {
+ final XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(stream, StandardCharsets.UTF_8.name());
+ XmlUtils.beginDocument(parser, TAG_HISTORY);
+ final long overflowMillis = XmlUtils.readLongAttribute(parser, ATTR_OVERFLOW, 0);
+ final int depth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, depth)) {
+ if (TAG_OPS.equals(parser.getName())) {
+ final HistoricalOps ops = readeHistoricalOpsDLocked(parser,
+ filterUid, filterPackageName, filterOpNames, filterBeginTimeMillis,
+ filterEndTimeMillis, cumulativeOverflowMillis);
+ if (ops == null) {
+ continue;
+ }
+ if (ops.isEmpty()) {
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ if (allOps == null) {
+ allOps = new ArrayList<>();
+ }
+ allOps.add(ops);
+ }
+ }
+ if (cumulativeOverflowMillis != null) {
+ cumulativeOverflowMillis[0] += overflowMillis;
+ }
+ } catch (FileNotFoundException e) {
+ Slog.i(LOG_TAG, "No history file: " + file.getName());
+ return Collections.emptyList();
+ }
+ if (DEBUG) {
+ if (allOps != null) {
+ Slog.i(LOG_TAG, "Read from file: " + file + "ops:\n"
+ + opsToDebugString(allOps));
+ enforceOpsWellFormed(allOps);
+ }
+ }
+ return allOps;
+ }
+
+ private @Nullable HistoricalOps readeHistoricalOpsDLocked(
+ @NonNull XmlPullParser parser, int filterUid, @Nullable String filterPackageName,
+ @Nullable String[] filterOpNames, long filterBeginTimeMillis,
+ long filterEndTimeMillis, @Nullable long[] cumulativeOverflowMillis)
+ throws IOException, XmlPullParserException {
+ final long beginTimeMillis = XmlUtils.readLongAttribute(parser, ATTR_BEGIN_TIME, 0)
+ + (cumulativeOverflowMillis != null ? cumulativeOverflowMillis[0] : 0);
+ final long endTimeMillis = XmlUtils.readLongAttribute(parser, ATTR_END_TIME, 0)
+ + (cumulativeOverflowMillis != null ? cumulativeOverflowMillis[0] : 0);
+ // Keep reading as subsequent records may start matching
+ if (filterEndTimeMillis < beginTimeMillis) {
+ return null;
+ }
+ // Stop reading as subsequent records will not match
+ if (filterBeginTimeMillis > endTimeMillis) {
+ return new HistoricalOps(0, 0);
+ }
+ final long filteredBeginTimeMillis = Math.max(beginTimeMillis, filterBeginTimeMillis);
+ final long filteredEndTimeMillis = Math.min(endTimeMillis, filterEndTimeMillis);
+ final double filterScale = (double) (filteredEndTimeMillis - filteredBeginTimeMillis)
+ / (double) (endTimeMillis - beginTimeMillis);
+ HistoricalOps ops = null;
+ final int depth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, depth)) {
+ if (TAG_UID.equals(parser.getName())) {
+ final HistoricalOps returnedOps = readHistoricalUidOpsDLocked(ops, parser,
+ filterUid, filterPackageName, filterOpNames, filterScale);
+ if (ops == null) {
+ ops = returnedOps;
+ }
+ }
+ }
+ if (ops != null) {
+ ops.setBeginAndEndTime(filteredBeginTimeMillis, filteredEndTimeMillis);
+ }
+ return ops;
+ }
+
+ private @Nullable HistoricalOps readHistoricalUidOpsDLocked(
+ @Nullable HistoricalOps ops, @NonNull XmlPullParser parser, int filterUid,
+ @Nullable String filterPackageName, @Nullable String[] filterOpNames,
+ double filterScale) throws IOException, XmlPullParserException {
+ final int uid = XmlUtils.readIntAttribute(parser, ATTR_NAME);
+ if (filterUid != Process.INVALID_UID && filterUid != uid) {
+ XmlUtils.skipCurrentTag(parser);
+ return null;
+ }
+ final int depth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, depth)) {
+ if (TAG_PACKAGE.equals(parser.getName())) {
+ final HistoricalOps returnedOps = readHistoricalPackageOpsDLocked(ops,
+ uid, parser, filterPackageName, filterOpNames, filterScale);
+ if (ops == null) {
+ ops = returnedOps;
+ }
+ }
+ }
+ return ops;
+ }
+
+ private @Nullable HistoricalOps readHistoricalPackageOpsDLocked(
+ @Nullable HistoricalOps ops, int uid, @NonNull XmlPullParser parser,
+ @Nullable String filterPackageName, @Nullable String[] filterOpNames,
+ double filterScale) throws IOException, XmlPullParserException {
+ final String packageName = XmlUtils.readStringAttribute(parser, ATTR_NAME);
+ if (filterPackageName != null && !filterPackageName.equals(packageName)) {
+ XmlUtils.skipCurrentTag(parser);
+ return null;
+ }
+ final int depth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, depth)) {
+ if (TAG_OP.equals(parser.getName())) {
+ final HistoricalOps returnedOps = readHistoricalOpDLocked(ops, uid,
+ packageName, parser, filterOpNames, filterScale);
+ if (ops == null) {
+ ops = returnedOps;
+ }
+ }
+ }
+ return ops;
+ }
+
+ private @Nullable HistoricalOps readHistoricalOpDLocked(@Nullable HistoricalOps ops,
+ int uid, String packageName, @NonNull XmlPullParser parser,
+ @Nullable String[] filterOpNames, double filterScale)
+ throws IOException, XmlPullParserException {
+ final int op = XmlUtils.readIntAttribute(parser, ATTR_NAME);
+ if (filterOpNames != null && !ArrayUtils.contains(filterOpNames,
+ AppOpsManager.opToName(op))) {
+ XmlUtils.skipCurrentTag(parser);
+ return null;
+ }
+ final int depth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, depth)) {
+ if (TAG_STATE.equals(parser.getName())) {
+ final HistoricalOps returnedOps = readUidStateDLocked(ops, uid,
+ packageName, op, parser, filterScale);
+ if (ops == null) {
+ ops = returnedOps;
+ }
+ }
+ }
+ return ops;
+ }
+
+ private @Nullable HistoricalOps readUidStateDLocked(@Nullable HistoricalOps ops,
+ int uid, String packageName, int op, @NonNull XmlPullParser parser,
+ double filterScale) throws IOException {
+ final int uidState = XmlUtils.readIntAttribute(parser, ATTR_NAME);
+ long accessCount = XmlUtils.readLongAttribute(parser, ATTR_ACCESS_COUNT, 0);
+ if (accessCount > 0) {
+ if (!Double.isNaN(filterScale)) {
+ accessCount = (long) HistoricalOps.round(
+ (double) accessCount * filterScale);
+ }
+ if (ops == null) {
+ ops = new HistoricalOps(0, 0);
+ }
+ ops.increaseAccessCount(op, uid, packageName, uidState, accessCount);
+ }
+ long rejectCount = XmlUtils.readLongAttribute(parser, ATTR_REJECT_COUNT, 0);
+ if (rejectCount > 0) {
+ if (!Double.isNaN(filterScale)) {
+ rejectCount = (long) HistoricalOps.round(
+ (double) rejectCount * filterScale);
+ }
+ if (ops == null) {
+ ops = new HistoricalOps(0, 0);
+ }
+ ops.increaseRejectCount(op, uid, packageName, uidState, rejectCount);
+ }
+ long accessDuration = XmlUtils.readLongAttribute(parser, ATTR_ACCESS_DURATION, 0);
+ if (accessDuration > 0) {
+ if (!Double.isNaN(filterScale)) {
+ accessDuration = (long) HistoricalOps.round(
+ (double) accessDuration * filterScale);
+ }
+ if (ops == null) {
+ ops = new HistoricalOps(0, 0);
+ }
+ ops.increaseAccessDuration(op, uid, packageName, uidState, accessDuration);
+ }
+ return ops;
+ }
+
+ private void writeHistoricalOpsDLocked(@Nullable List<HistoricalOps> allOps,
+ long intervalOverflowMillis, @NonNull File file) throws IOException {
+ final FileOutputStream output = mHistoricalAppOpsDir.openWrite(file);
+ try {
+ final XmlSerializer serializer = Xml.newSerializer();
+ serializer.setOutput(output, StandardCharsets.UTF_8.name());
+ serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output",
+ true);
+ serializer.startDocument(null, true);
+ serializer.startTag(null, TAG_HISTORY);
+ serializer.attribute(null, ATTR_VERSION, String.valueOf(CURRENT_VERSION));
+ if (intervalOverflowMillis != 0) {
+ serializer.attribute(null, ATTR_OVERFLOW,
+ Long.toString(intervalOverflowMillis));
+ }
+ if (allOps != null) {
+ final int opsCount = allOps.size();
+ for (int i = 0; i < opsCount; i++) {
+ final HistoricalOps ops = allOps.get(i);
+ writeHistoricalOpDLocked(ops, serializer);
+ }
+ }
+ serializer.endTag(null, TAG_HISTORY);
+ serializer.endDocument();
+ mHistoricalAppOpsDir.closeWrite(output);
+ } catch (IOException e) {
+ mHistoricalAppOpsDir.failWrite(output);
+ throw e;
+ }
+ }
+
+ private void writeHistoricalOpDLocked(@NonNull HistoricalOps ops,
+ @NonNull XmlSerializer serializer) throws IOException {
+ serializer.startTag(null, TAG_OPS);
+ serializer.attribute(null, ATTR_BEGIN_TIME, Long.toString(ops.getBeginTimeMillis()));
+ serializer.attribute(null, ATTR_END_TIME, Long.toString(ops.getEndTimeMillis()));
+ final int uidCount = ops.getUidCount();
+ for (int i = 0; i < uidCount; i++) {
+ final HistoricalUidOps uidOp = ops.getUidOpsAt(i);
+ writeHistoricalUidOpsDLocked(uidOp, serializer);
+ }
+ serializer.endTag(null, TAG_OPS);
+ }
+
+ private void writeHistoricalUidOpsDLocked(@NonNull HistoricalUidOps uidOps,
+ @NonNull XmlSerializer serializer) throws IOException {
+ serializer.startTag(null, TAG_UID);
+ serializer.attribute(null, ATTR_NAME, Integer.toString(uidOps.getUid()));
+ final int packageCount = uidOps.getPackageCount();
+ for (int i = 0; i < packageCount; i++) {
+ final HistoricalPackageOps packageOps = uidOps.getPackageOpsAt(i);
+ writeHistoricalPackageOpsDLocked(packageOps, serializer);
+ }
+ serializer.endTag(null, TAG_UID);
+ }
+
+ private void writeHistoricalPackageOpsDLocked(@NonNull HistoricalPackageOps packageOps,
+ @NonNull XmlSerializer serializer) throws IOException {
+ serializer.startTag(null, TAG_PACKAGE);
+ serializer.attribute(null, ATTR_NAME, packageOps.getPackageName());
+ final int opCount = packageOps.getOpCount();
+ for (int i = 0; i < opCount; i++) {
+ final HistoricalOp op = packageOps.getOpAt(i);
+ writeHistoricalOpDLocked(op, serializer);
+ }
+ serializer.endTag(null, TAG_PACKAGE);
+ }
+
+ private void writeHistoricalOpDLocked(@NonNull HistoricalOp op,
+ @NonNull XmlSerializer serializer) throws IOException {
+ serializer.startTag(null, TAG_OP);
+ serializer.attribute(null, ATTR_NAME, Integer.toString(op.getOpCode()));
+ for (int uidState = 0; uidState < AppOpsManager._NUM_UID_STATE; uidState++) {
+ writeUidStateOnLocked(op, uidState, serializer);
+ }
+ serializer.endTag(null, TAG_OP);
+ }
+
+ private void writeUidStateOnLocked(@NonNull HistoricalOp op, @UidState int uidState,
+ @NonNull XmlSerializer serializer) throws IOException {
+ final long accessCount = op.getAccessCount(uidState);
+ final long rejectCount = op.getRejectCount(uidState);
+ final long accessDuration = op.getAccessDuration(uidState);
+ if (accessCount == 0 && rejectCount == 0 && accessDuration == 0) {
+ return;
+ }
+ serializer.startTag(null, TAG_STATE);
+ serializer.attribute(null, ATTR_NAME, Integer.toString(uidState));
+ if (accessCount > 0) {
+ serializer.attribute(null, ATTR_ACCESS_COUNT, Long.toString(accessCount));
+ }
+ if (rejectCount > 0) {
+ serializer.attribute(null, ATTR_REJECT_COUNT, Long.toString(rejectCount));
+ }
+ if (accessDuration > 0) {
+ serializer.attribute(null, ATTR_ACCESS_DURATION, Long.toString(accessDuration));
+ }
+ serializer.endTag(null, TAG_STATE);
+ }
+
+ private static void enforceOpsWellFormed(@NonNull List<HistoricalOps> ops) {
+ if (ops == null) {
+ return;
+ }
+ HistoricalOps previous;
+ HistoricalOps current = null;
+ final int opsCount = ops.size();
+ for (int i = 0; i < opsCount; i++) {
+ previous = current;
+ current = ops.get(i);
+ if (current.isEmpty()) {
+ throw new IllegalStateException("Empty ops:\n"
+ + opsToDebugString(ops));
+ }
+ if (current.getEndTimeMillis() < current.getBeginTimeMillis()) {
+ throw new IllegalStateException("Begin after end:\n"
+ + opsToDebugString(ops));
+ }
+ if (previous != null) {
+ if (previous.getEndTimeMillis() > current.getBeginTimeMillis()) {
+ throw new IllegalStateException("Intersecting ops:\n"
+ + opsToDebugString(ops));
+ }
+ if (previous.getBeginTimeMillis() > current.getBeginTimeMillis()) {
+ throw new IllegalStateException("Non increasing ops:\n"
+ + opsToDebugString(ops));
+ }
+ }
+ }
+ }
+
+ private long computeGlobalIntervalBeginMillis(int depth) {
+ long beginTimeMillis = 0;
+ for (int i = 0; i < depth + 1; i++) {
+ beginTimeMillis += Math.pow(mIntervalCompressionMultiplier, i);
+ }
+ return beginTimeMillis * mBaseSnapshotInterval;
+ }
+
+ private static @NonNull HistoricalOps spliceFromEnd(@NonNull HistoricalOps ops,
+ double spliceRatio) {
+ if (DEBUG) {
+ Slog.w(LOG_TAG, "Splicing from end:" + ops + " ratio:" + spliceRatio);
+ }
+ final HistoricalOps splice = ops.spliceFromEnd(spliceRatio);
+ if (DEBUG) {
+ Slog.w(LOG_TAG, "Spliced into:" + ops + " and:" + splice);
+ }
+ return splice;
+ }
+
+
+ private static @NonNull HistoricalOps spliceFromBeginning(@NonNull HistoricalOps ops,
+ double spliceRatio) {
+ if (DEBUG) {
+ Slog.w(LOG_TAG, "Splicing from beginning:" + ops + " ratio:" + spliceRatio);
+ }
+ final HistoricalOps splice = ops.spliceFromBeginning(spliceRatio);
+ if (DEBUG) {
+ Slog.w(LOG_TAG, "Spliced into:" + ops + " and:" + splice);
+ }
+ return splice;
+ }
+
+ private static void normalizeSnapshotForSlotDuration(@NonNull List<HistoricalOps> ops,
+ long slotDurationMillis) {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Normalizing for slot duration: " + slotDurationMillis
+ + " ops:\n" + opsToDebugString(ops));
+ enforceOpsWellFormed(ops);
+ }
+ long slotBeginTimeMillis;
+ final int opCount = ops.size();
+ for (int processedIdx = opCount - 1; processedIdx >= 0; processedIdx--) {
+ final HistoricalOps processedOp = ops.get(processedIdx);
+ slotBeginTimeMillis = Math.max(processedOp.getEndTimeMillis()
+ - slotDurationMillis, 0);
+ for (int candidateIdx = processedIdx - 1; candidateIdx >= 0; candidateIdx--) {
+ final HistoricalOps candidateOp = ops.get(candidateIdx);
+ final long candidateSlotIntersectionMillis = candidateOp.getEndTimeMillis()
+ - Math.min(slotBeginTimeMillis, processedOp.getBeginTimeMillis());
+ if (candidateSlotIntersectionMillis <= 0) {
+ break;
+ }
+ final float candidateSplitRatio = candidateSlotIntersectionMillis
+ / (float) candidateOp.getDurationMillis();
+ if (Float.compare(candidateSplitRatio, 1.0f) >= 0) {
+ ops.remove(candidateIdx);
+ processedIdx--;
+ processedOp.merge(candidateOp);
+ } else {
+ final HistoricalOps endSplice = spliceFromEnd(candidateOp,
+ candidateSplitRatio);
+ if (endSplice != null) {
+ processedOp.merge(endSplice);
+ }
+ if (candidateOp.isEmpty()) {
+ ops.remove(candidateIdx);
+ processedIdx--;
+ }
+ }
+ }
+ }
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Normalized for slot duration: " + slotDurationMillis
+ + " ops:\n" + opsToDebugString(ops));
+ enforceOpsWellFormed(ops);
+ }
+ }
+
+ private static @NonNull String opsToDebugString(@NonNull List<HistoricalOps> ops) {
+ StringBuilder builder = new StringBuilder();
+ final int opCount = ops.size();
+ for (int i = 0; i < opCount; i++) {
+ builder.append(" ");
+ builder.append(ops.get(i));
+ if (i < opCount - 1) {
+ builder.append('\n');
+ }
+ }
+ return builder.toString();
+ }
+ }
+
+ private final class StringDumpVisitor implements AppOpsManager.HistoricalOpsVisitor {
+ private final long mNow = System.currentTimeMillis();
+
+ private final SimpleDateFormat mDateFormatter = new SimpleDateFormat(
+ "yyyy-MM-dd HH:mm:ss.SSS");
+ private final Date mDate = new Date();
+
+ private final @NonNull String mOpsPrefix;
+ private final @NonNull String mUidPrefix;
+ private final @NonNull String mPackagePrefix;
+ private final @NonNull String mEntryPrefix;
+ private final @NonNull String mUidStatePrefix;
+ private final @NonNull PrintWriter mWriter;
+ private final int mFilterUid;
+ private final String mFilterPackage;
+ private final int mFilterOp;
+
+ StringDumpVisitor(@NonNull String prefix, @NonNull PrintWriter writer,
+ int filterUid, String filterPackage, int filterOp) {
+ mOpsPrefix = prefix + " ";
+ mUidPrefix = mOpsPrefix + " ";
+ mPackagePrefix = mUidPrefix + " ";
+ mEntryPrefix = mPackagePrefix + " ";
+ mUidStatePrefix = mEntryPrefix + " ";
+ mWriter = writer;
+ mFilterUid = filterUid;
+ mFilterPackage = filterPackage;
+ mFilterOp = filterOp;
+ }
+
+ @Override
+ public void visitHistoricalOps(HistoricalOps ops) {
+ mWriter.println();
+ mWriter.print(mOpsPrefix);
+ mWriter.println("snapshot:");
+ mWriter.print(mUidPrefix);
+ mWriter.print("begin = ");
+ mDate.setTime(ops.getBeginTimeMillis());
+ mWriter.print(mDateFormatter.format(mDate));
+ mWriter.print(" (");
+ TimeUtils.formatDuration(ops.getBeginTimeMillis() - mNow, mWriter);
+ mWriter.println(")");
+ mWriter.print(mUidPrefix);
+ mWriter.print("end = ");
+ mDate.setTime(ops.getEndTimeMillis());
+ mWriter.print(mDateFormatter.format(mDate));
+ mWriter.print(" (");
+ TimeUtils.formatDuration(ops.getEndTimeMillis() - mNow, mWriter);
+ mWriter.println(")");
+ }
+
+ @Override
+ public void visitHistoricalUidOps(HistoricalUidOps ops) {
+ if (mFilterUid != Process.INVALID_UID && mFilterUid != ops.getUid()) {
+ return;
+ }
+ mWriter.println();
+ mWriter.print(mUidPrefix);
+ mWriter.print("Uid ");
+ UserHandle.formatUid(mWriter, ops.getUid());
+ mWriter.println(":");
+ }
+
+ @Override
+ public void visitHistoricalPackageOps(HistoricalPackageOps ops) {
+ if (mFilterPackage != null && !mFilterPackage.equals(ops.getPackageName())) {
+ return;
+ }
+ mWriter.print(mPackagePrefix);
+ mWriter.print("Package ");
+ mWriter.print(ops.getPackageName());
+ mWriter.println(":");
+ }
+
+ @Override
+ public void visitHistoricalOp(HistoricalOp ops) {
+ if (mFilterOp != AppOpsManager.OP_NONE && mFilterOp != ops.getOpCode()) {
+ return;
+ }
+ mWriter.print(mEntryPrefix);
+ mWriter.print(AppOpsManager.opToName(ops.getOpCode()));
+ mWriter.println(":");
+ for (int uidState = 0; uidState < AppOpsManager._NUM_UID_STATE; uidState++) {
+ boolean printedUidState = false;
+ final long accessCount = ops.getAccessCount(uidState);
+ if (accessCount > 0) {
+ if (!printedUidState) {
+ mWriter.print(mUidStatePrefix);
+ mWriter.print(AppOpsManager.uidStateToString(uidState));
+ mWriter.print("[");
+ printedUidState = true;
+ }
+ mWriter.print("access=");
+ mWriter.print(accessCount);
+ }
+ final long rejectCount = ops.getRejectCount(uidState);
+ if (rejectCount > 0) {
+ if (!printedUidState) {
+ mWriter.print(mUidStatePrefix);
+ mWriter.print(AppOpsManager.uidStateToString(uidState));
+ mWriter.print("[");
+ printedUidState = true;
+ } else {
+ mWriter.print(",");
+ }
+ mWriter.print("reject=");
+ mWriter.print(rejectCount);
+ }
+ final long accessDuration = ops.getAccessDuration(uidState);
+ if (accessDuration > 0) {
+ if (!printedUidState) {
+ mWriter.print(mUidStatePrefix);
+ mWriter.print(AppOpsManager.uidStateToString(uidState));
+ printedUidState = true;
+ } else {
+ mWriter.print(",");
+ }
+ mWriter.print("duration=");
+ mWriter.print(accessDuration);
+ }
+ if (printedUidState) {
+ mWriter.println("]");
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/appop/TEST_MAPPING b/services/core/java/com/android/server/appop/TEST_MAPPING
new file mode 100644
index 0000000..4901d3a
--- /dev/null
+++ b/services/core/java/com/android/server/appop/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsAppOpsTestCases",
+ }
+ ]
+}
diff --git a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
index 905f826..9d6628c 100644
--- a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
@@ -206,7 +206,7 @@
switch (event) {
case AudioManager.RECORD_CONFIG_EVENT_STOP:
// return failure if an unknown recording session stopped
- configChanged = (mRecordConfigs.remove(new Integer(session)) != null);
+ configChanged = (mRecordConfigs.remove(new Integer(portId)) != null);
if (configChanged) {
sEventLogger.log(new RecordingEvent(event, uid, session, source, null));
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 36ca4dc..fc1e326 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -132,10 +132,13 @@
Settings.Secure.getUriFor(Settings.Secure.FACE_UNLOCK_KEYGUARD_ENABLED);
private final Uri FACE_UNLOCK_APP_ENABLED =
Settings.Secure.getUriFor(Settings.Secure.FACE_UNLOCK_APP_ENABLED);
+ private final Uri FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION =
+ Settings.Secure.getUriFor(Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION);
private final ContentResolver mContentResolver;
private boolean mFaceEnabledOnKeyguard;
private boolean mFaceEnabledForApps;
+ private boolean mFaceAlwaysRequireConfirmation;
/**
* Creates a content observer.
@@ -158,10 +161,15 @@
false /* notifyForDescendents */,
this /* observer */,
UserHandle.USER_CURRENT);
+ mContentResolver.registerContentObserver(FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
+ false /* notifyForDescendents */,
+ this /* observer */,
+ UserHandle.USER_CURRENT);
// Update the value immediately
onChange(true /* selfChange */, FACE_UNLOCK_KEYGUARD_ENABLED);
onChange(true /* selfChange */, FACE_UNLOCK_APP_ENABLED);
+ onChange(true /* selfChange */, FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION);
}
@Override
@@ -185,6 +193,13 @@
Settings.Secure.FACE_UNLOCK_APP_ENABLED,
1 /* default */,
UserHandle.USER_CURRENT) != 0;
+ } else if (FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION.equals(uri)) {
+ mFaceAlwaysRequireConfirmation =
+ Settings.Secure.getIntForUser(
+ mContentResolver,
+ Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
+ 0 /* default */,
+ UserHandle.USER_CURRENT) != 0;
}
}
@@ -195,6 +210,10 @@
boolean getFaceEnabledForApps() {
return mFaceEnabledForApps;
}
+
+ boolean getFaceAlwaysRequireConfirmation() {
+ return mFaceAlwaysRequireConfirmation;
+ }
}
private final class EnabledOnKeyguardCallback implements IBinder.DeathRecipient {
@@ -395,7 +414,7 @@
// Notify SysUI that the biometric has been authenticated. SysUI already knows
// the implicit/explicit state and will react accordingly.
- mStatusBarService.onBiometricAuthenticated();
+ mStatusBarService.onBiometricAuthenticated(true);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
@@ -412,17 +431,20 @@
return;
}
- mStatusBarService.onBiometricHelp(getContext().getResources().getString(
- com.android.internal.R.string.biometric_not_recognized));
- if (requireConfirmation) {
+ mStatusBarService.onBiometricAuthenticated(false);
+
+ // TODO: This logic will need to be updated if BP is multi-modal
+ if ((mCurrentAuthSession.mModality & TYPE_FACE) != 0) {
+ // Pause authentication. onBiometricAuthenticated(false) causes the
+ // dialog to show a "try again" button for passive modalities.
mCurrentAuthSession.mState = STATE_AUTH_PAUSED;
- mStatusBarService.showBiometricTryAgain();
// Cancel authentication. Skip the token/package check since we are
// cancelling from system server. The interface is permission protected so
// this is fine.
cancelInternal(null /* token */, null /* package */,
false /* fromClient */);
}
+
mCurrentAuthSession.mClientReceiver.onAuthenticationFailed();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
@@ -579,8 +601,10 @@
}
if (mPendingAuthSession.mModalitiesWaiting.isEmpty()) {
- final boolean mContinuing = mCurrentAuthSession != null
- && mCurrentAuthSession.mState == STATE_AUTH_PAUSED;
+ final boolean continuing = mCurrentAuthSession != null &&
+ (mCurrentAuthSession.mState == STATE_AUTH_PAUSED
+ || mCurrentAuthSession.mState == STATE_AUTH_PAUSED_CANCELED);
+
mCurrentAuthSession = mPendingAuthSession;
mPendingAuthSession = null;
@@ -602,7 +626,7 @@
modality |= pair.getKey();
}
- if (!mContinuing) {
+ if (!continuing) {
mStatusBarService.showBiometricDialog(mCurrentAuthSession.mBundle,
mInternalReceiver, modality, requireConfirmation, userId);
mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
@@ -706,7 +730,8 @@
mCurrentModality = modality;
- // Actually start authentication
+ // Start preparing for authentication. Authentication starts when
+ // all modalities requested have invoked onReadyForAuthentication.
authenticateInternal(token, sessionId, userId, receiver, opPackageName, bundle,
callingUid, callingPid, callingUserId, modality);
});
@@ -725,6 +750,9 @@
IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle,
int callingUid, int callingPid, int callingUserId, int modality) {
try {
+ boolean requireConfirmation = bundle.getBoolean(
+ BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true /* default */);
+
// Generate random cookies to pass to the services that should prepare to start
// authenticating. Store the cookie here and wait for all services to "ack"
// with the cookie. Once all cookies are received, we can show the prompt
@@ -748,7 +776,10 @@
Slog.w(TAG, "Iris unsupported");
}
if ((modality & TYPE_FACE) != 0) {
- mFaceService.prepareForAuthentication(true /* requireConfirmation */,
+ // Check if the user has forced confirmation to be required in Settings.
+ requireConfirmation = requireConfirmation
+ || mSettingObserver.getFaceAlwaysRequireConfirmation();
+ mFaceService.prepareForAuthentication(requireConfirmation,
token, sessionId, userId, mInternalReceiver, opPackageName,
cookie, callingUid, callingPid, callingUserId);
}
diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java
index 72f73f6..f4d8d4b 100644
--- a/services/core/java/com/android/server/biometrics/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/face/FaceService.java
@@ -156,7 +156,7 @@
mDaemonWrapper, mHalDeviceId, token,
new BiometricPromptServiceListenerImpl(wrapperReceiver),
mCurrentUserId, 0 /* groupId */, opId, restricted, opPackageName, cookie,
- true /* requireConfirmation */);
+ requireConfirmation);
authenticateInternal(client, opId, opPackageName, callingUid, callingPid,
callingUserId);
}
diff --git a/services/core/java/com/android/server/connectivity/ConnectivityConstants.java b/services/core/java/com/android/server/connectivity/ConnectivityConstants.java
index 24865bc..6fa98b8 100644
--- a/services/core/java/com/android/server/connectivity/ConnectivityConstants.java
+++ b/services/core/java/com/android/server/connectivity/ConnectivityConstants.java
@@ -21,22 +21,6 @@
* @hide
*/
public class ConnectivityConstants {
- // IPC constants
- public static final String ACTION_NETWORK_CONDITIONS_MEASURED =
- "android.net.conn.NETWORK_CONDITIONS_MEASURED";
- public static final String EXTRA_CONNECTIVITY_TYPE = "extra_connectivity_type";
- public static final String EXTRA_NETWORK_TYPE = "extra_network_type";
- public static final String EXTRA_RESPONSE_RECEIVED = "extra_response_received";
- public static final String EXTRA_IS_CAPTIVE_PORTAL = "extra_is_captive_portal";
- public static final String EXTRA_CELL_ID = "extra_cellid";
- public static final String EXTRA_SSID = "extra_ssid";
- public static final String EXTRA_BSSID = "extra_bssid";
- /** real time since boot */
- public static final String EXTRA_REQUEST_TIMESTAMP_MS = "extra_request_timestamp_ms";
- public static final String EXTRA_RESPONSE_TIMESTAMP_MS = "extra_response_timestamp_ms";
-
- public static final String PERMISSION_ACCESS_NETWORK_CONDITIONS =
- "android.permission.ACCESS_NETWORK_CONDITIONS";
// Penalty applied to scores of Networks that have not been validated.
public static final int UNVALIDATED_SCORE_PENALTY = 40;
diff --git a/services/core/java/com/android/server/connectivity/DnsManager.java b/services/core/java/com/android/server/connectivity/DnsManager.java
index b8f057d..d8bb635 100644
--- a/services/core/java/com/android/server/connectivity/DnsManager.java
+++ b/services/core/java/com/android/server/connectivity/DnsManager.java
@@ -18,10 +18,9 @@
import static android.net.ConnectivityManager.PRIVATE_DNS_DEFAULT_MODE_FALLBACK;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
-import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
-import static android.provider.Settings.Global.DNS_RESOLVER_MIN_SAMPLES;
import static android.provider.Settings.Global.DNS_RESOLVER_MAX_SAMPLES;
+import static android.provider.Settings.Global.DNS_RESOLVER_MIN_SAMPLES;
import static android.provider.Settings.Global.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS;
import static android.provider.Settings.Global.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT;
import static android.provider.Settings.Global.PRIVATE_DNS_DEFAULT_MODE;
@@ -35,6 +34,7 @@
import android.net.Network;
import android.net.NetworkUtils;
import android.net.Uri;
+import android.net.shared.PrivateDnsConfig;
import android.os.Binder;
import android.os.INetworkManagementService;
import android.os.UserHandle;
@@ -43,10 +43,7 @@
import android.util.Pair;
import android.util.Slog;
-import com.android.server.connectivity.MockableSystemProperties;
-
import java.net.InetAddress;
-import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@@ -54,10 +51,8 @@
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
-import java.util.Objects;
-import java.util.stream.Collectors;
import java.util.Set;
-import java.util.StringJoiner;
+import java.util.stream.Collectors;
/**
@@ -123,43 +118,6 @@
private static final int DNS_RESOLVER_DEFAULT_MIN_SAMPLES = 8;
private static final int DNS_RESOLVER_DEFAULT_MAX_SAMPLES = 64;
- public static class PrivateDnsConfig {
- public final boolean useTls;
- public final String hostname;
- public final InetAddress[] ips;
-
- public PrivateDnsConfig() {
- this(false);
- }
-
- public PrivateDnsConfig(boolean useTls) {
- this.useTls = useTls;
- this.hostname = "";
- this.ips = new InetAddress[0];
- }
-
- public PrivateDnsConfig(String hostname, InetAddress[] ips) {
- this.useTls = !TextUtils.isEmpty(hostname);
- this.hostname = useTls ? hostname : "";
- this.ips = (ips != null) ? ips : new InetAddress[0];
- }
-
- public PrivateDnsConfig(PrivateDnsConfig cfg) {
- useTls = cfg.useTls;
- hostname = cfg.hostname;
- ips = cfg.ips;
- }
-
- public boolean inStrictMode() {
- return useTls && !TextUtils.isEmpty(hostname);
- }
-
- public String toString() {
- return PrivateDnsConfig.class.getSimpleName() +
- "{" + useTls + ":" + hostname + "/" + Arrays.toString(ips) + "}";
- }
- }
-
public static PrivateDnsConfig getPrivateDnsConfig(ContentResolver cr) {
final String mode = getPrivateDnsMode(cr);
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 262184b..54c89aa 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -16,9 +16,8 @@
package com.android.server.connectivity;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
-
import android.content.Context;
+import android.net.INetworkMonitor;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
@@ -29,7 +28,6 @@
import android.os.Handler;
import android.os.INetworkManagementService;
import android.os.Messenger;
-import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
import android.util.SparseArray;
@@ -37,11 +35,8 @@
import com.android.internal.util.AsyncChannel;
import com.android.internal.util.WakeupMessage;
import com.android.server.ConnectivityService;
-import com.android.server.connectivity.NetworkMonitor;
import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Comparator;
import java.util.Objects;
import java.util.SortedSet;
import java.util.TreeSet;
@@ -126,7 +121,6 @@
public LinkProperties linkProperties;
// This should only be modified via ConnectivityService.updateCapabilities().
public NetworkCapabilities networkCapabilities;
- public final NetworkMonitor networkMonitor;
public final NetworkMisc networkMisc;
// Indicates if netd has been told to create this Network. From this point on the appropriate
// routing rules are setup and routes are added so packets can begin flowing over the Network.
@@ -239,6 +233,9 @@
// Used by ConnectivityService to keep track of 464xlat.
public Nat464Xlat clatd;
+ // Set after asynchronous creation of the NetworkMonitor.
+ private volatile INetworkMonitor mNetworkMonitor;
+
private static final String TAG = ConnectivityService.class.getSimpleName();
private static final boolean VDBG = false;
private final ConnectivityService mConnService;
@@ -247,7 +244,7 @@
public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, Network net, NetworkInfo info,
LinkProperties lp, NetworkCapabilities nc, int score, Context context, Handler handler,
- NetworkMisc misc, NetworkRequest defaultRequest, ConnectivityService connService) {
+ NetworkMisc misc, ConnectivityService connService) {
this.messenger = messenger;
asyncChannel = ac;
network = net;
@@ -258,10 +255,16 @@
mConnService = connService;
mContext = context;
mHandler = handler;
- networkMonitor = mConnService.createNetworkMonitor(context, handler, this, defaultRequest);
networkMisc = misc;
}
+ /**
+ * Inform NetworkAgentInfo that a new NetworkMonitor was created.
+ */
+ public void onNetworkMonitorCreated(INetworkMonitor networkMonitor) {
+ mNetworkMonitor = networkMonitor;
+ }
+
public ConnectivityService connService() {
return mConnService;
}
@@ -278,6 +281,15 @@
return network;
}
+ /**
+ * Get the INetworkMonitor in this NetworkAgentInfo.
+ *
+ * <p>This will be null before {@link #onNetworkMonitorCreated(INetworkMonitor)} is called.
+ */
+ public INetworkMonitor networkMonitor() {
+ return mNetworkMonitor;
+ }
+
// Functions for manipulating the requests satisfied by this network.
//
// These functions must only called on ConnectivityService's main thread.
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 9dfdddb..eb5be77 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -1837,7 +1837,7 @@
final TetherState tetherState = new TetherState(
new IpServer(iface, mLooper, interfaceType, mLog, mNMService, mStatsService,
makeControlCallback(), mConfig.enableLegacyDhcpServer,
- mDeps.getIpServerDependencies()));
+ mDeps.getIpServerDependencies(mContext)));
mTetherStates.put(iface, tetherState);
tetherState.ipServer.start();
}
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java
index d56b167..a42efe9 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java
@@ -34,32 +34,53 @@
* @hide
*/
public class TetheringDependencies {
+ /**
+ * Get a reference to the offload hardware interface to be used by tethering.
+ */
public OffloadHardwareInterface getOffloadHardwareInterface(Handler h, SharedLog log) {
return new OffloadHardwareInterface(h, log);
}
+ /**
+ * Get a reference to the UpstreamNetworkMonitor to be used by tethering.
+ */
public UpstreamNetworkMonitor getUpstreamNetworkMonitor(Context ctx, StateMachine target,
SharedLog log, int what) {
return new UpstreamNetworkMonitor(ctx, target, log, what);
}
+ /**
+ * Get a reference to the IPv6TetheringCoordinator to be used by tethering.
+ */
public IPv6TetheringCoordinator getIPv6TetheringCoordinator(
ArrayList<IpServer> notifyList, SharedLog log) {
return new IPv6TetheringCoordinator(notifyList, log);
}
- public IpServer.Dependencies getIpServerDependencies() {
- return new IpServer.Dependencies();
+ /**
+ * Get dependencies to be used by IpServer.
+ */
+ public IpServer.Dependencies getIpServerDependencies(Context context) {
+ return new IpServer.Dependencies(context);
}
+ /**
+ * Indicates whether tethering is supported on the device.
+ */
public boolean isTetheringSupported() {
return true;
}
+ /**
+ * Get the NetworkRequest that should be fulfilled by the default network.
+ */
public NetworkRequest getDefaultNetworkRequest() {
return null;
}
+ /**
+ * Get a reference to the EntitlementManager to be used by tethering.
+ */
public EntitlementManager getEntitlementManager(Context ctx, SharedLog log,
MockableSystemProperties systemProperties) {
return new EntitlementManager(ctx, log, systemProperties);
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 52eccca..d57431e 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -17,13 +17,12 @@
package com.android.server.display;
import android.annotation.Nullable;
-import android.app.ActivityManager;
import android.app.ActivityManager.StackInfo;
import android.app.ActivityTaskManager;
import android.app.IActivityTaskManager;
import android.app.TaskStackListener;
import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageManager;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
@@ -34,7 +33,6 @@
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
-import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
@@ -45,7 +43,6 @@
import com.android.internal.os.BackgroundThread;
import com.android.server.EventLogTags;
-import com.android.server.LocalServices;
import java.io.PrintWriter;
@@ -66,13 +63,6 @@
// the user is satisfied with the result before storing the sample.
private static final int BRIGHTNESS_ADJUSTMENT_SAMPLE_DEBOUNCE_MILLIS = 10000;
- // Timeout after which we remove the effects any user interactions might've had on the
- // brightness mapping. This timeout doesn't start until we transition to a non-interactive
- // display policy so that we don't reset while users are using their devices, but also so that
- // we don't erroneously keep the short-term model if the device is dozing but the display is
- // fully on.
- private static final int SHORT_TERM_MODEL_TIMEOUT_MILLIS = 30000;
-
private static final int MSG_UPDATE_AMBIENT_LUX = 1;
private static final int MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE = 2;
private static final int MSG_INVALIDATE_SHORT_TERM_MODEL = 3;
@@ -140,6 +130,13 @@
private boolean mLoggingEnabled;
+ // Timeout after which we remove the effects any user interactions might've had on the
+ // brightness mapping. This timeout doesn't start until we transition to a non-interactive
+ // display policy so that we don't reset while users are using their devices, but also so that
+ // we don't erroneously keep the short-term model if the device is dozing but the display is
+ // fully on.
+ private long mShortTermModelTimeout;
+
// Amount of time to delay auto-brightness after screen on while waiting for
// the light sensor to warm-up in milliseconds.
// May be 0 if no warm-up is required.
@@ -210,14 +207,13 @@
// package name and category, which is done by registering a TaskStackListener to call back to
// us onTaskStackChanged, and then using the ActivityTaskManager to get the foreground app's
// package namd and PackageManager to get its category (so might as well cache them).
- private int mUserId;
private String mForegroundAppPackageName;
private String mPendingForegroundAppPackageName;
private @ApplicationInfo.Category int mForegroundAppCategory;
private @ApplicationInfo.Category int mPendingForegroundAppCategory;
private TaskStackListenerImpl mTaskStackListener;
private IActivityTaskManager mActivityTaskManager;
- private PackageManagerInternal mPackageManagerInternal;
+ private PackageManager mPackageManager;
public AutomaticBrightnessController(Callbacks callbacks, Looper looper,
SensorManager sensorManager, BrightnessMappingStrategy mapper,
@@ -225,7 +221,8 @@
int lightSensorRate, int initialLightSensorRate, long brighteningLightDebounceConfig,
long darkeningLightDebounceConfig, boolean resetAmbientLuxAfterWarmUpConfig,
HysteresisLevels ambientBrightnessThresholds,
- HysteresisLevels screenBrightnessThresholds) {
+ HysteresisLevels screenBrightnessThresholds, long shortTermModelTimeout,
+ PackageManager packageManager) {
mCallbacks = callbacks;
mSensorManager = sensorManager;
mBrightnessMapper = mapper;
@@ -243,6 +240,7 @@
mWeightingIntercept = AMBIENT_LIGHT_LONG_HORIZON_MILLIS;
mAmbientBrightnessThresholds = ambientBrightnessThresholds;
mScreenBrightnessThresholds = screenBrightnessThresholds;
+ mShortTermModelTimeout = shortTermModelTimeout;
mShortTermModelValid = true;
mShortTermModelAnchor = -1;
@@ -254,9 +252,8 @@
mLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
}
- mUserId = ActivityManager.getCurrentUser();
mActivityTaskManager = ActivityTaskManager.getService();
- mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+ mPackageManager = packageManager;
mTaskStackListener = new TaskStackListenerImpl();
mForegroundAppPackageName = null;
mPendingForegroundAppPackageName = null;
@@ -281,16 +278,6 @@
return true;
}
- /**
- * Update the current user's ID.
- *
- * @param userId
- * The current user's ID.
- */
- public void onSwitchUser(int userId) {
- mUserId = userId;
- }
-
public int getAutomaticScreenBrightness() {
if (!mAmbientLuxValid) {
return -1;
@@ -358,7 +345,7 @@
}
if (!isInteractivePolicy(policy) && isInteractivePolicy(oldPolicy)) {
mHandler.sendEmptyMessageDelayed(MSG_INVALIDATE_SHORT_TERM_MODEL,
- SHORT_TERM_MODEL_TIMEOUT_MILLIS);
+ mShortTermModelTimeout);
} else if (isInteractivePolicy(policy) && !isInteractivePolicy(oldPolicy)) {
mHandler.removeMessages(MSG_INVALIDATE_SHORT_TERM_MODEL);
}
@@ -440,13 +427,13 @@
pw.println(" mAmbientLightRingBuffer=" + mAmbientLightRingBuffer);
pw.println(" mScreenAutoBrightness=" + mScreenAutoBrightness);
pw.println(" mDisplayPolicy=" + DisplayPowerRequest.policyToString(mDisplayPolicy));
+ pw.println(" mShortTermModelTimeout=" + mShortTermModelTimeout);
pw.println(" mShortTermModelAnchor=" + mShortTermModelAnchor);
pw.println(" mShortTermModelValid=" + mShortTermModelValid);
pw.println(" mBrightnessAdjustmentSamplePending=" + mBrightnessAdjustmentSamplePending);
pw.println(" mBrightnessAdjustmentSampleOldLux=" + mBrightnessAdjustmentSampleOldLux);
pw.println(" mBrightnessAdjustmentSampleOldBrightness="
+ mBrightnessAdjustmentSampleOldBrightness);
- pw.println(" mUserId=" + mUserId);
pw.println(" mForegroundAppPackageName=" + mForegroundAppPackageName);
pw.println(" mPendingForegroundAppPackageName=" + mPendingForegroundAppPackageName);
pw.println(" mForegroundAppCategory=" + mForegroundAppCategory);
@@ -787,6 +774,13 @@
BRIGHTNESS_ADJUSTMENT_SAMPLE_DEBOUNCE_MILLIS);
}
+ private void cancelBrightnessAdjustmentSample() {
+ if (mBrightnessAdjustmentSamplePending) {
+ mBrightnessAdjustmentSamplePending = false;
+ mHandler.removeMessages(MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE);
+ }
+ }
+
private void collectBrightnessAdjustmentSample() {
if (mBrightnessAdjustmentSamplePending) {
mBrightnessAdjustmentSamplePending = false;
@@ -816,6 +810,9 @@
// call it explicitly to get the current foreground app's info.
updateForegroundApp();
} catch (RemoteException e) {
+ if (mLoggingEnabled) {
+ Slog.e(TAG, "Failed to register foreground app updater: " + e);
+ }
// Nothing to do.
}
}
@@ -832,6 +829,9 @@
// Set the foreground app's package name and category, so brightness can be corrected per app.
private void updateForegroundApp() {
+ if (mLoggingEnabled) {
+ Slog.d(TAG, "Attempting to update foreground app");
+ }
// The ActivityTaskManager's lock tends to get contended, so this is done in a background
// thread and applied via this thread's handler synchronously.
BackgroundThread.getHandler().post(new Runnable() {
@@ -850,18 +850,27 @@
return;
}
mPendingForegroundAppPackageName = packageName;
- ApplicationInfo app = mPackageManagerInternal.getApplicationInfo(packageName,
- 0 /* flags */, Process.SYSTEM_UID /* filterCallingUid */, mUserId);
- mPendingForegroundAppCategory = app.category;
+ mPendingForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED;
+ try {
+ ApplicationInfo app = mPackageManager.getApplicationInfo(packageName,
+ PackageManager.MATCH_ANY_USER);
+ mPendingForegroundAppCategory = app.category;
+ } catch (PackageManager.NameNotFoundException e) {
+ // Nothing to do
+ }
mHandler.sendEmptyMessage(MSG_UPDATE_FOREGROUND_APP_SYNC);
} catch (RemoteException e) {
- // Nothing to do.
+ // Nothing to do
}
}
});
}
private void updateForegroundAppSync() {
+ if (mLoggingEnabled) {
+ Slog.d(TAG, "Updating foreground app: packageName=" + mPendingForegroundAppPackageName
+ + ", category=" + mPendingForegroundAppCategory);
+ }
mForegroundAppPackageName = mPendingForegroundAppPackageName;
mPendingForegroundAppPackageName = null;
mForegroundAppCategory = mPendingForegroundAppCategory;
diff --git a/services/core/java/com/android/server/display/ColorDisplayService.java b/services/core/java/com/android/server/display/ColorDisplayService.java
index 73d3d95..b332c47 100644
--- a/services/core/java/com/android/server/display/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/ColorDisplayService.java
@@ -18,7 +18,9 @@
import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_DISPLAY_WHITE_BALANCE;
import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_NIGHT_DISPLAY;
+import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_SATURATION;
+import android.Manifest;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.TypeEvaluator;
@@ -31,6 +33,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.hardware.display.ColorDisplayManager;
import android.hardware.display.IColorDisplayManager;
@@ -39,6 +42,7 @@
import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
+import android.os.Message;
import android.os.UserHandle;
import android.provider.Settings.Secure;
import android.provider.Settings.System;
@@ -61,6 +65,7 @@
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeParseException;
+import java.util.Arrays;
/**
* Controls the display's color transforms.
@@ -83,6 +88,10 @@
Matrix.setIdentityM(MATRIX_IDENTITY, 0);
}
+ private static final int MSG_APPLY_NIGHT_DISPLAY_IMMEDIATE = 0;
+ private static final int MSG_APPLY_NIGHT_DISPLAY_ANIMATED = 1;
+ private static final int MSG_APPLY_GLOBAL_SATURATION = 2;
+
/**
* Evaluator used to animate color matrix transitions.
*/
@@ -161,6 +170,53 @@
}
};
+ private final TintController mGlobalSaturationTintController = new TintController() {
+
+ private float[] mMatrixGlobalSaturation = new float[16];
+
+ @Override
+ public void setUp(Context context, boolean needsLinear) {
+ }
+
+ @Override
+ public float[] getMatrix() {
+ return Arrays.copyOf(mMatrixGlobalSaturation, mMatrixGlobalSaturation.length);
+ }
+
+ @Override
+ public void setMatrix(int saturationLevel) {
+ if (saturationLevel < 0) {
+ saturationLevel = 0;
+ } else if (saturationLevel > 100) {
+ saturationLevel = 100;
+ }
+ Slog.d(TAG, "Setting saturation level: " + saturationLevel);
+
+ if (saturationLevel == 100) {
+ Matrix.setIdentityM(mMatrixGlobalSaturation, 0);
+ } else {
+ float saturation = saturationLevel * 0.1f;
+ float desaturation = 1.0f - saturation;
+ float[] luminance = {0.231f * desaturation, 0.715f * desaturation,
+ 0.072f * desaturation};
+ mMatrixGlobalSaturation[0] = luminance[0] + saturation;
+ mMatrixGlobalSaturation[1] = luminance[0];
+ mMatrixGlobalSaturation[2] = luminance[0];
+ mMatrixGlobalSaturation[4] = luminance[1];
+ mMatrixGlobalSaturation[5] = luminance[1] + saturation;
+ mMatrixGlobalSaturation[6] = luminance[1];
+ mMatrixGlobalSaturation[8] = luminance[2];
+ mMatrixGlobalSaturation[9] = luminance[2];
+ mMatrixGlobalSaturation[10] = luminance[2] + saturation;
+ }
+ }
+
+ @Override
+ public int getLevel() {
+ return LEVEL_COLOR_MATRIX_SATURATION;
+ }
+ };
+
private final Handler mHandler;
private int mCurrentUser = UserHandle.USER_NULL;
@@ -178,7 +234,7 @@
public ColorDisplayService(Context context) {
super(context);
- mHandler = new Handler(Looper.getMainLooper());
+ mHandler = new TintHandler(Looper.getMainLooper());
}
@Override
@@ -904,6 +960,23 @@
void onDisplayWhiteBalanceStatusChanged(boolean enabled);
}
+ private final class TintHandler extends Handler {
+
+ TintHandler(Looper looper) {
+ super(looper, null, true /* async */);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_APPLY_GLOBAL_SATURATION:
+ mGlobalSaturationTintController.setMatrix(msg.arg1);
+ applyTint(mGlobalSaturationTintController, false);
+ break;
+ }
+ }
+ }
+
private final class BinderService extends IColorDisplayManager.Stub {
@Override
@@ -915,5 +988,27 @@
Binder.restoreCallingIdentity(token);
}
}
+
+ @Override
+ public boolean setSaturationLevel(int level) {
+ final boolean hasTransformsPermission = getContext()
+ .checkCallingPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
+ == PackageManager.PERMISSION_GRANTED;
+ final boolean hasLegacyPermission = getContext()
+ .checkCallingPermission(Manifest.permission.CONTROL_DISPLAY_SATURATION)
+ == PackageManager.PERMISSION_GRANTED;
+ if (!hasTransformsPermission && !hasLegacyPermission) {
+ throw new SecurityException("Permission required to set display saturation level");
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ final Message message = mHandler.obtainMessage(MSG_APPLY_GLOBAL_SATURATION);
+ message.arg1 = level;
+ mHandler.sendMessage(message);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return true;
+ }
}
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index b1ba05c..4a17c65 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -724,27 +724,6 @@
}
}
- private void setSaturationLevelInternal(float level) {
- if (level < 0 || level > 1) {
- throw new IllegalArgumentException("Saturation level must be between 0 and 1");
- }
- float[] matrix = (level == 1.0f ? null : computeSaturationMatrix(level));
- DisplayTransformManager dtm = LocalServices.getService(DisplayTransformManager.class);
- dtm.setColorMatrix(DisplayTransformManager.LEVEL_COLOR_MATRIX_SATURATION, matrix);
- }
-
- private static float[] computeSaturationMatrix(float saturation) {
- float desaturation = 1.0f - saturation;
- float[] luminance = {0.231f * desaturation, 0.715f * desaturation, 0.072f * desaturation};
- float[] matrix = {
- luminance[0] + saturation, luminance[0], luminance[0], 0,
- luminance[1], luminance[1] + saturation, luminance[1], 0,
- luminance[2], luminance[2], luminance[2] + saturation, 0,
- 0, 0, 0, 1
- };
- return matrix;
- }
-
private int createVirtualDisplayInternal(IVirtualDisplayCallback callback,
IMediaProjection projection, int callingUid, String packageName, String name, int width,
int height, int densityDpi, Surface surface, int flags, String uniqueId) {
@@ -1836,19 +1815,6 @@
}
@Override // Binder call
- public void setSaturationLevel(float level) {
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.CONTROL_DISPLAY_SATURATION,
- "Permission required to set display saturation level");
- final long token = Binder.clearCallingIdentity();
- try {
- setSaturationLevelInternal(level);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override // Binder call
public int createVirtualDisplay(IVirtualDisplayCallback callback,
IMediaProjection projection, String packageName, String name,
int width, int height, int densityDpi, Surface surface, int flags,
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index c9ed9f7..db3928e 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -461,6 +461,8 @@
+ initialLightSensorRate + ") to be less than or equal to "
+ "config_autoBrightnessLightSensorRate (" + lightSensorRate + ").");
}
+ int shortTermModelTimeout = resources.getInteger(
+ com.android.internal.R.integer.config_autoBrightnessShortTermModelTimeout);
mBrightnessMapper = BrightnessMappingStrategy.create(resources);
if (mBrightnessMapper != null) {
@@ -470,7 +472,8 @@
mScreenBrightnessRangeMaximum, dozeScaleFactor, lightSensorRate,
initialLightSensorRate, brighteningLightDebounce, darkeningLightDebounce,
autoBrightnessResetAmbientLuxAfterWarmUp, ambientBrightnessThresholds,
- screenBrightnessThresholds);
+ screenBrightnessThresholds, shortTermModelTimeout,
+ context.getPackageManager());
} else {
mUseSoftwareAutoBrightnessConfig = false;
}
@@ -523,9 +526,6 @@
public void onSwitchUser(@UserIdInt int newUserId) {
handleSettingsChange(true /* userSwitch */);
mBrightnessTracker.onSwitchUser(newUserId);
- if (mAutomaticBrightnessController != null) {
- mAutomaticBrightnessController.onSwitchUser(newUserId);
- }
}
public ParceledListSlice<AmbientBrightnessDayStats> getAmbientBrightnessStats(
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index ba05b49..bffa8f4 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -18,13 +18,6 @@
import static android.Manifest.permission.BIND_DREAM_SERVICE;
-import com.android.internal.hardware.AmbientDisplayConfiguration;
-import com.android.internal.util.DumpUtils;
-import com.android.server.FgThread;
-import com.android.server.LocalServices;
-import com.android.server.SystemService;
-
-import android.Manifest;
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -53,6 +46,12 @@
import android.util.Slog;
import android.view.Display;
+import com.android.internal.hardware.AmbientDisplayConfiguration;
+import com.android.internal.util.DumpUtils;
+import com.android.server.FgThread;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -84,6 +83,7 @@
private boolean mCurrentDreamCanDoze;
private boolean mCurrentDreamIsDozing;
private boolean mCurrentDreamIsWaking;
+ private boolean mForceAmbientDisplayEnabled;
private int mCurrentDreamDozeScreenState = Display.STATE_UNKNOWN;
private int mCurrentDreamDozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT;
@@ -139,6 +139,7 @@
pw.println("mCurrentDreamCanDoze=" + mCurrentDreamCanDoze);
pw.println("mCurrentDreamIsDozing=" + mCurrentDreamIsDozing);
pw.println("mCurrentDreamIsWaking=" + mCurrentDreamIsWaking);
+ pw.println("mForceAmbientDisplayEnabled=" + mForceAmbientDisplayEnabled);
pw.println("mCurrentDreamDozeScreenState="
+ Display.stateToString(mCurrentDreamDozeScreenState));
pw.println("mCurrentDreamDozeScreenBrightness=" + mCurrentDreamDozeScreenBrightness);
@@ -257,6 +258,16 @@
}
}
+ private void forceAmbientDisplayEnabledInternal(boolean enabled) {
+ if (DEBUG) {
+ Slog.d(TAG, "Force ambient display enabled: " + enabled);
+ }
+
+ synchronized (mLock) {
+ mForceAmbientDisplayEnabled = enabled;
+ }
+ }
+
private ComponentName chooseDreamForUser(boolean doze, int userId) {
if (doze) {
ComponentName dozeComponent = getDozeComponent(userId);
@@ -328,7 +339,7 @@
}
private ComponentName getDozeComponent(int userId) {
- if (mDozeConfig.enabled(userId)) {
+ if (mForceAmbientDisplayEnabled || mDozeConfig.enabled(userId)) {
return ComponentName.unflattenFromString(mDozeConfig.ambientDisplayComponent());
} else {
return null;
@@ -631,6 +642,18 @@
Binder.restoreCallingIdentity(ident);
}
}
+
+ @Override // Binder call
+ public void forceAmbientDisplayEnabled(boolean enabled) {
+ checkPermission(android.Manifest.permission.DEVICE_POWER);
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ forceAmbientDisplayEnabledInternal(enabled);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
}
private final class LocalService extends DreamManagerInternal {
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index d154830..6f5a196 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -18,6 +18,7 @@
import android.annotation.IntDef;
import android.hardware.hdmi.HdmiDeviceInfo;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -255,6 +256,41 @@
static final int USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON = 1;
static final int NEVER_SYSTEM_AUDIO_CONTROL_ON_POWER_ON = 2;
+ // Port id to record local active port for Routing Control features
+ // They are used to map to corresponding Inputs
+ // Current interface is only implemented for specific device.
+ // Developers can add more port number and map them to corresponding inputs on demand.
+ @IntDef({
+ CEC_SWITCH_HOME,
+ CEC_SWITCH_HDMI1,
+ CEC_SWITCH_HDMI2,
+ CEC_SWITCH_HDMI3,
+ CEC_SWITCH_HDMI4,
+ CEC_SWITCH_HDMI5,
+ CEC_SWITCH_HDMI6,
+ CEC_SWITCH_HDMI7,
+ CEC_SWITCH_HDMI8,
+ CEC_SWITCH_ARC,
+ CEC_SWITCH_BLUETOOTH,
+ CEC_SWITCH_OPTICAL,
+ CEC_SWITCH_AUX
+ })
+ @interface LocalActivePort {}
+ static final int CEC_SWITCH_HOME = 0;
+ static final int CEC_SWITCH_HDMI1 = 1;
+ static final int CEC_SWITCH_HDMI2 = 2;
+ static final int CEC_SWITCH_HDMI3 = 3;
+ static final int CEC_SWITCH_HDMI4 = 4;
+ static final int CEC_SWITCH_HDMI5 = 5;
+ static final int CEC_SWITCH_HDMI6 = 6;
+ static final int CEC_SWITCH_HDMI7 = 7;
+ static final int CEC_SWITCH_HDMI8 = 8;
+ static final int CEC_SWITCH_ARC = 17;
+ static final int CEC_SWITCH_BLUETOOTH = 18;
+ static final int CEC_SWITCH_OPTICAL = 19;
+ static final int CEC_SWITCH_AUX = 20;
+ static final int CEC_SWITCH_PORT_MAX = 21;
+
static final String PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM =
"persist.sys.hdmi.addr.audiosystem";
static final String PROPERTY_PREFERRED_ADDRESS_PLAYBACK = "persist.sys.hdmi.addr.playback";
@@ -272,6 +308,49 @@
// 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.
+ */
+ static final String PROPERTY_SYSTEM_AUDIO_DEVICE_ARC_PORT =
+ "ro.hdmi.property_sytem_audio_device_arc_port";
+
+ /**
+ * Property to disable muting logic in System Audio Control handling. Default is true.
+ *
+ * <p>True means enabling muting logic.
+ * <p>False means never mute device.
+ */
+ static final String PROPERTY_SYSTEM_AUDIO_MODE_MUTING_ENABLE =
+ "ro.hdmi.property_system_audio_mode_muting_enable";
+
+ /**
+ * When set to true the HdmiControlService will never request a Logical Address for the
+ * playback device type. Default is false.
+ *
+ * <p> This is useful when HDMI CEC multiple device types is not supported by the cec driver
+ */
+ static final String PROPERTY_HDMI_CEC_NEVER_CLAIM_PLAYBACK_LOGICAL_ADDRESS =
+ "ro.hdmi.property_hdmi_cec_never_claim_playback_logical_address";
+
+ /**
+ * A comma separated list of logical addresses that HdmiControlService
+ * will never assign local CEC devices to.
+ *
+ * <p> This is useful when HDMI CEC hardware module can't assign multiple logical addresses
+ * in the range same range of 0-7 or 8-15.
+ */
+ static final String PROPERTY_HDMI_CEC_NEVER_ASSIGN_LOGICAL_ADDRESSES =
+ "ro.hdmi.property_hdmi_cec_never_assign_logical_addresses";
+
+ /**
+ * Property to indicate if the current device is a cec switch device.
+ *
+ * <p> Default is false.
+ */
+ static final String PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH =
+ "ro.hdmi.property_is_device_hdmi_cec_switch";
+
// Set to false to allow playback device to go to suspend mode even
// when it's an active source. True by default.
static final String PROPERTY_KEEP_AWAKE = "persist.sys.hdmi.keep_awake";
@@ -299,7 +378,7 @@
* <p>Default is true.
*/
static final String PROPERTY_ARC_SUPPORT =
- "persist.sys.hdmi.property_arc_support";
+ "persist.sys.hdmi.property_arc_support";
/**
* Property to save the audio port to switch to when system audio control is on.
@@ -309,14 +388,16 @@
* <p>Default is ARC port.
*/
static final String PROPERTY_SYSTEM_AUDIO_MODE_AUDIO_PORT =
- "persist.sys.hdmi.property_sytem_audio_mode_audio_port";
+ "persist.sys.hdmi.property_sytem_audio_mode_audio_port";
/**
- * 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.
+ * Property to indicate if a CEC audio device should forward volume keys when system audio mode
+ * is off.
+ *
+ * <p>Default is false.
*/
- static final String PROPERTY_SYSTEM_AUDIO_DEVICE_ARC_PORT =
- "persist.sys.hdmi.property_sytem_audio_device_arc_port";
+ static final String PROPERTY_CEC_AUDIO_DEVICE_FORWARD_VOLUME_KEYS_SYSTEM_AUDIO_MODE_OFF =
+ "persist.sys.hdmi.property_cec_audio_device_forward_volume_keys_system_audio_mode_off";
/**
* Property to strip local audio of amplifier and use local speaker
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index de6cdd4..e777ce8 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -22,20 +22,25 @@
import android.os.Handler;
import android.os.Looper;
import android.os.MessageQueue;
+import android.os.SystemProperties;
import android.util.Slog;
import android.util.SparseArray;
+
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.hdmi.HdmiAnnotations.IoThreadOnly;
import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
import com.android.server.hdmi.HdmiControlService.DevicePollingCallback;
+
+import libcore.util.EmptyArray;
+
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
-import java.util.function.Predicate;
import java.util.concurrent.ArrayBlockingQueue;
-import libcore.util.EmptyArray;
+import java.util.function.Predicate;
+
import sun.util.locale.LanguageTag;
/**
@@ -112,10 +117,18 @@
private final NativeWrapper mNativeWrapperImpl;
+ /** List of logical addresses that should not be assigned to the current device.
+ *
+ * <p>Parsed from {@link Constants#PROPERTY_HDMI_CEC_NEVER_ASSIGN_LOGICAL_ADDRESSES}
+ */
+ private final List<Integer> mNeverAssignLogicalAddresses;
+
// Private constructor. Use HdmiCecController.create().
private HdmiCecController(HdmiControlService service, NativeWrapper nativeWrapper) {
mService = service;
mNativeWrapperImpl = nativeWrapper;
+ mNeverAssignLogicalAddresses = mService.getIntList(SystemProperties.get(
+ Constants.PROPERTY_HDMI_CEC_NEVER_ASSIGN_LOGICAL_ADDRESSES));
}
/**
@@ -208,7 +221,8 @@
for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
int curAddress = (startAddress + i) % NUM_LOGICAL_ADDRESS;
if (curAddress != Constants.ADDR_UNREGISTERED
- && deviceType == HdmiUtils.getTypeFromAddress(curAddress)) {
+ && deviceType == HdmiUtils.getTypeFromAddress(curAddress)
+ && !mNeverAssignLogicalAddresses.contains(curAddress)) {
boolean acked = false;
for (int j = 0; j < HdmiConfig.ADDRESS_ALLOCATION_RETRY; ++j) {
if (sendPollMessage(curAddress, curAddress, 1)) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java b/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java
index 11faa56..2da698b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java
@@ -252,6 +252,10 @@
return (HdmiCecLocalDevicePlayback) mSource;
}
+ protected final HdmiCecLocalDeviceSource source() {
+ return (HdmiCecLocalDeviceSource) mSource;
+ }
+
protected final HdmiCecLocalDeviceTv tv() {
return (HdmiCecLocalDeviceTv) mSource;
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 7860122..528e0a4 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -27,10 +27,13 @@
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
+
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.hdmi.Constants.LocalActivePort;
import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
@@ -51,6 +54,11 @@
// Within the timer, a received <User Control Pressed> will start "Press and Hold" behavior.
// When it expires, we can assume <User Control Release> is received.
private static final int FOLLOWER_SAFETY_TIMEOUT = 550;
+ /**
+ * Return value of {@link #getLocalPortFromPhysicalAddress(int)}
+ */
+ private static final int TARGET_NOT_UNDER_LOCAL_DEVICE = -1;
+ private static final int TARGET_SAME_PHYSICAL_ADDRESS = 0;
protected final HdmiControlService mService;
protected final int mDeviceType;
@@ -434,10 +442,14 @@
return true;
}
+ // Audio System device with no Playback device type
+ // needs to refactor this function if it's also a switch
protected boolean handleRoutingChange(HdmiCecMessage message) {
return false;
}
+ // Audio System device with no Playback device type
+ // needs to refactor this function if it's also a switch
protected boolean handleRoutingInformation(HdmiCecMessage message) {
return false;
}
@@ -1033,4 +1045,68 @@
pw.println("mActiveSource: " + mActiveSource);
pw.println(String.format("mActiveRoutingPath: 0x%04x", mActiveRoutingPath));
}
+
+ /**
+ * Method to parse target physical address to the port number on the current device.
+ *
+ * <p>This check assumes target address is valid.
+ * @param targetPhysicalAddress is the physical address of the target device
+ * @return
+ * If the target device is under the current device, return the port number of current device
+ * that the target device is connected to.
+ *
+ * <p>If the target device has the same physical address as the current device, return
+ * {@link #TARGET_SAME_PHYSICAL_ADDRESS}.
+ *
+ * <p>If the target device is not under the current device, return
+ * {@link #TARGET_NOT_UNDER_LOCAL_DEVICE}.
+ */
+ protected int getLocalPortFromPhysicalAddress(int targetPhysicalAddress) {
+ int myPhysicalAddress = mService.getPhysicalAddress();
+ if (myPhysicalAddress == targetPhysicalAddress) {
+ return TARGET_SAME_PHYSICAL_ADDRESS;
+ }
+ int finalMask = 0xF000;
+ int mask;
+ int port = 0;
+ for (mask = 0x0F00; mask > 0x000F; mask >>= 4) {
+ if ((myPhysicalAddress & mask) == 0) {
+ port = mask & targetPhysicalAddress;
+ break;
+ } else {
+ finalMask |= mask;
+ }
+ }
+ if (finalMask != 0xFFFF && (finalMask & targetPhysicalAddress) == myPhysicalAddress) {
+ while (mask != 0x000F) {
+ mask >>= 4;
+ port >>= 4;
+ }
+ return port;
+ }
+ return TARGET_NOT_UNDER_LOCAL_DEVICE;
+ }
+
+ /** Calculates the physical address for {@code activePortId}.
+ *
+ * <p>This method assumes current device physical address is valid.
+ * <p>If the current device is already the leaf of the whole CEC system
+ * and can't have devices under it, will return its own physical address.
+ *
+ * @param activePortId is the local active port Id
+ * @return the calculated physical address of the port
+ */
+ protected int getActivePathOnSwitchFromActivePortId(@LocalActivePort int activePortId) {
+ int myPhysicalAddress = mService.getPhysicalAddress();
+ int finalMask = activePortId << 8;
+ int mask;
+ for (mask = 0x0F00; mask > 0x000F; mask >>= 4) {
+ if ((myPhysicalAddress & mask) == 0) {
+ break;
+ } else {
+ finalMask >>= 4;
+ }
+ }
+ return finalMask | myPhysicalAddress;
+ }
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index 3845954..048a0e4 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -20,22 +20,27 @@
import static com.android.server.hdmi.Constants.USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON;
import android.annotation.Nullable;
+import android.content.Intent;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.AudioSystem;
+import android.media.tv.TvContract;
import android.os.SystemProperties;
+import android.provider.Settings.Global;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.hdmi.Constants.AudioCodec;
import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
+import java.util.HashMap;
+
/**
* Represent a logical device of type {@link HdmiDeviceInfo#DEVICE_AUDIO_SYSTEM} residing in Android
* system.
*/
-public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice {
+public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource {
private static final String TAG = "HdmiCecLocalDeviceAudioSystem";
@@ -50,16 +55,43 @@
private boolean mTvSystemAudioModeSupport;
+ // Whether the auido system will turn TV off when it's powering off
+ private boolean mAutoTvOff;
+ // Whether the auido system will broadcast standby to the system when it's powering off
+ private boolean mAutoDeviceOff;
+
// Whether ARC is available or not. "true" means that ARC is established between TV and
// AVR as audio receiver.
@ServiceThreadOnly private boolean mArcEstablished = false;
+ // If the current device uses TvInput for ARC. We assume all other inputs also use TvInput
+ // when ARC is using TvInput.
+ private boolean mArcIntentUsed = SystemProperties
+ .get(Constants.PROPERTY_SYSTEM_AUDIO_DEVICE_ARC_PORT, "0").contains("tvinput");
+
+ // Keeps the mapping (HDMI port ID to TV input URI) to keep track of the TV inputs ready to
+ // accept input switching request from HDMI devices. Requests for which the corresponding
+ // input ID is not yet registered by TV input framework need to be buffered for delayed
+ // processing.
+ private final HashMap<Integer, String> mTvInputs = new HashMap<>();
+
protected HdmiCecLocalDeviceAudioSystem(HdmiControlService service) {
super(service, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
mSystemAudioControlFeatureEnabled = true;
// TODO(amyjojo) make System Audio Control controllable by users
/*mSystemAudioControlFeatureEnabled =
mService.readBooleanSetting(Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED, true);*/
+ mAutoDeviceOff = mService.readBooleanSetting(
+ Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, true);
+ mAutoTvOff = mService.readBooleanSetting(
+ Global.HDMI_CONTROL_AUTO_TV_OFF_ENABLED, true);
+ // TODO(amyjojo): make the map ro property.
+ mTvInputs.put(Constants.CEC_SWITCH_HDMI1,
+ "com.droidlogic.tvinput/.services.Hdmi1InputService/HW5");
+ mTvInputs.put(Constants.CEC_SWITCH_HDMI2,
+ "com.droidlogic.tvinput/.services.Hdmi2InputService/HW6");
+ mTvInputs.put(Constants.CEC_SWITCH_HDMI3,
+ "com.droidlogic.tvinput/.services.Hdmi3InputService/HW7");
}
@Override
@@ -74,6 +106,21 @@
mSystemAudioActivated ? "true" : "false");
}
terminateSystemAudioMode();
+
+ HdmiLogger.debug(TAG + " onStandby, initiatedByCec:" + initiatedByCec
+ + ", mAutoDeviceOff: " + mAutoDeviceOff + ", mAutoTvOff: " + mAutoTvOff);
+ if (!mService.isControlEnabled() || initiatedByCec) {
+ return;
+ }
+ if (mAutoDeviceOff) {
+ mService.sendCecCommand(
+ HdmiCecMessageBuilder.buildStandby(mAddress, Constants.ADDR_BROADCAST));
+ } else if (mAutoTvOff) {
+ mService.sendCecCommand(
+ HdmiCecMessageBuilder.buildStandby(mAddress, Constants.ADDR_TV));
+ }
+ return;
+
}
@Override
@@ -95,34 +142,27 @@
startQueuedActions();
}
+ @Override
+ protected int findKeyReceiverAddress() {
+ return Constants.ADDR_TV;
+ }
+
@VisibleForTesting
protected void systemAudioControlOnPowerOn(
int systemAudioOnPowerOnProp, boolean lastSystemAudioControlStatus) {
if ((systemAudioOnPowerOnProp == ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON)
|| ((systemAudioOnPowerOnProp == USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON)
- && lastSystemAudioControlStatus)) {
+ && lastSystemAudioControlStatus)) {
addAndStartAction(new SystemAudioInitiationActionFromAvr(this));
}
}
- @ServiceThreadOnly
- protected boolean handleActiveSource(HdmiCecMessage message) {
- assertRunOnServiceThread();
- int logicalAddress = message.getSource();
- int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
- ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress);
- if (!mActiveSource.equals(activeSource)) {
- setActiveSource(activeSource);
- }
- return true;
- }
-
@Override
@ServiceThreadOnly
protected int getPreferredAddress() {
assertRunOnServiceThread();
return SystemProperties.getInt(
- Constants.PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM, Constants.ADDR_UNREGISTERED);
+ Constants.PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM, Constants.ADDR_UNREGISTERED);
}
@Override
@@ -266,7 +306,7 @@
for (int i = 0; i < params.length; i++) {
byte val = params[i];
audioFormatCodes[i] =
- val >= 1 && val <= Constants.AUDIO_CODEC_MAX ? val : Constants.AUDIO_CODEC_NONE;
+ val >= 1 && val <= Constants.AUDIO_CODEC_MAX ? val : Constants.AUDIO_CODEC_NONE;
}
return audioFormatCodes;
}
@@ -276,7 +316,23 @@
protected boolean handleSystemAudioModeRequest(HdmiCecMessage message) {
assertRunOnServiceThread();
boolean systemAudioStatusOn = message.getParams().length != 0;
- if (!setSystemAudioMode(systemAudioStatusOn)) {
+ // Check if the request comes from a non-TV device.
+ // Need to check if TV supports System Audio Control
+ // if non-TV device tries to turn on the feature
+ if (message.getSource() != Constants.ADDR_TV) {
+ if (systemAudioStatusOn) {
+ handleSystemAudioModeOnFromNonTvDevice(message);
+ return true;
+ }
+ } else {
+ // If TV request the feature on
+ // cache TV supporting System Audio Control
+ // until Audio System loses its physical address.
+ setTvSystemAudioModeSupport(true);
+ }
+ // If TV or Audio System does not support the feature,
+ // will send abort command.
+ if (!checkSupportAndSetSystemAudioMode(systemAudioStatusOn)) {
mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
return true;
}
@@ -291,7 +347,8 @@
@ServiceThreadOnly
protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
assertRunOnServiceThread();
- if (!setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message))) {
+ if (!checkSupportAndSetSystemAudioMode(
+ HdmiUtils.parseCommandParamSystemAudioStatus(message))) {
mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
}
return true;
@@ -301,7 +358,8 @@
@ServiceThreadOnly
protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
assertRunOnServiceThread();
- if (!setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message))) {
+ if (!checkSupportAndSetSystemAudioMode(
+ HdmiUtils.parseCommandParamSystemAudioStatus(message))) {
mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
}
return true;
@@ -333,7 +391,7 @@
private void notifyArcStatusToAudioService(boolean enabled) {
// Note that we don't set any name to ARC.
mService.getAudioManager()
- .setWiredDeviceConnectionState(AudioSystem.DEVICE_IN_HDMI, enabled ? 1 : 0, "", "");
+ .setWiredDeviceConnectionState(AudioSystem.DEVICE_IN_HDMI, enabled ? 1 : 0, "", "");
}
private void reportAudioStatus(HdmiCecMessage message) {
@@ -349,7 +407,16 @@
mAddress, message.getSource(), scaledVolume, mute));
}
- protected boolean setSystemAudioMode(boolean newSystemAudioMode) {
+ /**
+ * Method to check if device support System Audio Control. If so, wake up device if necessary.
+ *
+ * <p> then call {@link #setSystemAudioMode(boolean)} to turn on or off System Audio Mode
+ * @param newSystemAudioMode turning feature on or off. True is on. False is off.
+ * @return true or false.
+ *
+ * <p>False when device does not support the feature. Otherwise returns true.
+ */
+ protected boolean checkSupportAndSetSystemAudioMode(boolean newSystemAudioMode) {
if (!isSystemAudioControlFeatureEnabled()) {
HdmiLogger.debug(
"Cannot turn "
@@ -365,11 +432,37 @@
if (newSystemAudioMode && mService.isPowerStandbyOrTransient()) {
mService.wakeUp();
}
+ setSystemAudioMode(newSystemAudioMode);
+ return true;
+ }
+
+ /**
+ * Real work to turn on or off System Audio Mode.
+ *
+ * Use {@link #checkSupportAndSetSystemAudioMode(boolean)}
+ * if trying to turn on or off the feature.
+ */
+ private void setSystemAudioMode(boolean newSystemAudioMode) {
int targetPhysicalAddress = getActiveSource().physicalAddress;
- if (newSystemAudioMode && !isPhysicalAddressMeOrBelow(targetPhysicalAddress)) {
+ int port = getLocalPortFromPhysicalAddress(targetPhysicalAddress);
+ if (newSystemAudioMode && port >= 0) {
switchToAudioInput();
}
- // TODO(b/80297700): Mute device when TV terminates the system audio control
+ // Mute device when feature is turned off and unmute device when feature is turned on.
+ // PROPERTY_SYSTEM_AUDIO_MODE_MUTING_ENABLE is false when device never needs to be muted.
+ boolean currentMuteStatus =
+ mService.getAudioManager().isStreamMute(AudioManager.STREAM_MUSIC);
+ if (SystemProperties.getBoolean(
+ Constants.PROPERTY_SYSTEM_AUDIO_MODE_MUTING_ENABLE, true)
+ && currentMuteStatus == newSystemAudioMode) {
+ mService.getAudioManager()
+ .adjustStreamVolume(
+ AudioManager.STREAM_MUSIC,
+ newSystemAudioMode
+ ? AudioManager.ADJUST_UNMUTE
+ : AudioManager.ADJUST_MUTE,
+ 0);
+ }
updateAudioManagerForSystemAudio(newSystemAudioMode);
synchronized (mLock) {
if (mSystemAudioActivated != newSystemAudioMode) {
@@ -377,31 +470,13 @@
mService.announceSystemAudioModeChange(newSystemAudioMode);
}
}
- return true;
- }
-
- /**
- * Method to check if the target device belongs to the subtree of the current device or not.
- *
- * <p>Return true if it does or if the two devices share the same physical address.
- *
- * <p>This check assumes both device physical address and target address are valid.
- *
- * @param targetPhysicalAddress is the physical address of the target device
- */
- protected boolean isPhysicalAddressMeOrBelow(int targetPhysicalAddress) {
- int myPhysicalAddress = mService.getPhysicalAddress();
- int xor = targetPhysicalAddress ^ myPhysicalAddress;
- // Return true if two addresses are the same
- // or if they only differs for one byte, but not the first byte,
- // and myPhysicalAddress is 0 after that byte
- if (xor == 0
- || ((xor & 0x0f00) == xor && (myPhysicalAddress & 0x0fff) == 0)
- || ((xor & 0x00f0) == xor && (myPhysicalAddress & 0x00ff) == 0)
- || ((xor & 0x000f) == xor && (myPhysicalAddress & 0x000f) == 0)) {
- return true;
+ // Init arc whenever System Audio Mode is on
+ // Since some TV like LG don't request ARC on with System Audio Mode on request
+ if (newSystemAudioMode
+ && SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)
+ && !isArcEnabled() && isDirectConnectToTv()) {
+ addAndStartAction(new ArcInitiationActionFromAvr(this));
}
- return false;
}
protected void switchToAudioInput() {
@@ -445,7 +520,7 @@
return;
}
- if (setSystemAudioMode(false)) {
+ if (checkSupportAndSetSystemAudioMode(false)) {
// send <Set System Audio Mode> [“Off”]
mService.sendCecCommand(
HdmiCecMessageBuilder.buildSetSystemAudioMode(
@@ -468,6 +543,7 @@
* <p>The result of the query may be cached until Audio device type is put in standby or loses
* its physical address.
*/
+ // TODO(amyjojo): making mTvSystemAudioModeSupport null originally and fix the logic.
void queryTvSystemAudioModeSupport(TvSystemAudioModeSupportedCallback callback) {
if (!mTvSystemAudioModeSupport) {
addAndStartAction(new DetectTvSystemAudioModeSupportAction(this, callback));
@@ -476,6 +552,37 @@
}
}
+ /**
+ * Handler of System Audio Mode Request on from non TV device
+ */
+ void handleSystemAudioModeOnFromNonTvDevice(HdmiCecMessage message) {
+ if (!isSystemAudioControlFeatureEnabled()) {
+ HdmiLogger.debug(
+ "Cannot turn on" + "system audio mode "
+ + "because the System Audio Control feature is disabled.");
+ mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
+ return;
+ }
+ // Wake up device if it is still on standby
+ if (mService.isPowerStandbyOrTransient()) {
+ mService.wakeUp();
+ }
+ // Check if TV supports System Audio Control.
+ // Handle broadcasting setSystemAudioMode on or aborting message on callback.
+ queryTvSystemAudioModeSupport(new TvSystemAudioModeSupportedCallback() {
+ public void onResult(boolean supported) {
+ if (supported) {
+ setSystemAudioMode(true);
+ mService.sendCecCommand(
+ HdmiCecMessageBuilder.buildSetSystemAudioMode(
+ mAddress, Constants.ADDR_BROADCAST, true));
+ } else {
+ mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
+ }
+ }
+ });
+ }
+
void setTvSystemAudioModeSupport(boolean supported) {
mTvSystemAudioModeSupport = supported;
}
@@ -486,4 +593,148 @@
return mArcEstablished;
}
}
+
+ @ServiceThreadOnly
+ protected void setAutoTvOff(boolean autoTvOff) {
+ assertRunOnServiceThread();
+ mAutoTvOff = autoTvOff;
+ }
+
+ @Override
+ @ServiceThreadOnly
+ void setAutoDeviceOff(boolean autoDeviceOff) {
+ assertRunOnServiceThread();
+ mAutoDeviceOff = autoDeviceOff;
+ }
+
+ @Override
+ protected void switchInputOnReceivingNewActivePath(int physicalAddress) {
+ int port = getLocalPortFromPhysicalAddress(physicalAddress);
+ // Wake up if the new Active Source is the current device or under it
+ // or if System Audio Control is enabled.
+ if ((isSystemAudioActivated() || port >= 0) && mService.isPowerStandbyOrTransient()) {
+ mService.wakeUp();
+ }
+
+ if (isSystemAudioActivated() && port < 0) {
+ // If system audio mode is on and the new active source is not under the current device,
+ // Will switch to ARC input.
+ // TODO(b/115637145): handle system aduio without ARC
+ routeToInputFromPortId(Constants.CEC_SWITCH_ARC);
+ } else if (mIsSwitchDevice && port >= 0) {
+ // If current device is a switch and the new active source is under it,
+ // will switch to the corresponding active path.
+ routeToInputFromPortId(port);
+ }
+ }
+
+ protected void routeToInputFromPortId(int portId) {
+ if (mArcIntentUsed) {
+ routeToTvInputFromPortId(portId);
+ } else {
+ // TODO(): implement input switching for devices not using TvInput.
+ }
+ }
+
+ protected void routeToTvInputFromPortId(int portId) {
+ if (portId < 0 || portId >= Constants.CEC_SWITCH_PORT_MAX) {
+ HdmiLogger.debug("Invalid port number for Tv Input switching.");
+ return;
+ }
+ // TODO(amyjojo): handle if switching to the current input
+ if (portId == Constants.CEC_SWITCH_HOME && mService.isPlaybackDevice()) {
+ switchToHomeTvInput();
+ } else if (portId == Constants.CEC_SWITCH_ARC) {
+ switchToTvInput(SystemProperties.get(Constants.PROPERTY_SYSTEM_AUDIO_DEVICE_ARC_PORT));
+ return;
+ } else {
+ String uri = mTvInputs.get(portId);
+ if (uri != null) {
+ switchToTvInput(mTvInputs.get(portId));
+ } else {
+ HdmiLogger.debug("Port number does not match any Tv Input.");
+ return;
+ }
+ }
+
+ setLocalActivePort(portId);
+ }
+
+ // For device to switch to specific TvInput with corresponding URI.
+ private void switchToTvInput(String uri) {
+ mService.getContext().startActivity(new Intent(Intent.ACTION_VIEW,
+ TvContract.buildChannelUriForPassthroughInput(uri))
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ }
+
+ // For device using TvInput to switch to Home.
+ private void switchToHomeTvInput() {
+ Intent activityIntent = new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_HOME)
+ .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
+ | Intent.FLAG_ACTIVITY_SINGLE_TOP
+ | Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_NO_ANIMATION);
+ mService.getContext().startActivity(activityIntent);
+ }
+
+ @Override
+ protected void handleRoutingChangeAndInformation(int physicalAddress, HdmiCecMessage message) {
+ int port = getLocalPortFromPhysicalAddress(physicalAddress);
+ // Routing change or information sent from switches under the current device can be ignored.
+ if (port > 0) {
+ return;
+ }
+ // When other switches route to some other devices not under the current device,
+ // check system audio mode status and do ARC switch if needed.
+ if (port < 0 && isSystemAudioActivated()) {
+ handleRoutingChangeAndInformationForSystemAudio();
+ return;
+ }
+ // When other switches route to the current device
+ // and the current device is also a switch.
+ if (port == 0) {
+ handleRoutingChangeAndInformationForSwitch(message);
+ }
+ }
+
+ // Handle the system audio(ARC) part of the logic on receiving routing change or information.
+ private void handleRoutingChangeAndInformationForSystemAudio() {
+ if (mService.isPowerStandbyOrTransient()) {
+ mService.wakeUp();
+ }
+ // TODO(b/115637145): handle system aduio without ARC
+ routeToInputFromPortId(Constants.CEC_SWITCH_ARC);
+ }
+
+ // Handle the routing control part of the logic on receiving routing change or information.
+ private void handleRoutingChangeAndInformationForSwitch(HdmiCecMessage message) {
+ if (mService.isPowerStandbyOrTransient()) {
+ mService.wakeUp();
+ }
+ if (getLocalActivePort() == Constants.CEC_SWITCH_HOME && mService.isPlaybackDevice()) {
+ routeToInputFromPortId(Constants.CEC_SWITCH_HOME);
+ if (mService.playback() != null) {
+ mService.playback().setAndBroadcastActiveSource(
+ message, mService.getPhysicalAddress());
+ } else {
+ setAndBroadcastActiveSource(message, mService.getPhysicalAddress());
+ }
+ return;
+ }
+
+ int routingInformationPath =
+ getActivePathOnSwitchFromActivePortId(getLocalActivePort());
+ // If current device is already the leaf of the whole HDMI system, will do nothing.
+ if (routingInformationPath == mService.getPhysicalAddress()) {
+ HdmiLogger.debug("Current device can't assign valid physical address"
+ + "to devices under it any more. "
+ + "It's physical address is " + routingInformationPath);
+ return;
+ }
+ // Otherwise will switch to the current active port and broadcast routing information.
+ mService.sendCecCommand(HdmiCecMessageBuilder.buildRoutingInformation(
+ mAddress, routingInformationPath));
+ routeToInputFromPortId(getLocalActivePort());
+ }
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index d45b00b..379cc16 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -26,6 +26,7 @@
import android.provider.Settings.Global;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.LocalePicker;
import com.android.internal.app.LocalePicker.LocaleInfo;
import com.android.internal.util.IndentingPrintWriter;
@@ -35,12 +36,10 @@
import java.util.List;
import java.util.Locale;
-import java.util.List;
-
/**
* Represent a logical device of type Playback residing in Android system.
*/
-final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice {
+final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
private static final String TAG = "HdmiCecLocalDevicePlayback";
private static final boolean WAKE_ON_HOTPLUG =
@@ -62,6 +61,11 @@
// If true, turn off TV upon standby. False by default.
private boolean mAutoTvOff;
+ // Local active port number used for Routing Control.
+ // Default 0 means HOME is the current active path. Temp solution only.
+ // TODO(amyjojo): adding system constants for input ports to TIF mapping.
+ private int mLocalActivePath = 0;
+
HdmiCecLocalDevicePlayback(HdmiControlService service) {
super(service, HdmiDeviceInfo.DEVICE_PLAYBACK);
@@ -100,25 +104,6 @@
}
@ServiceThreadOnly
- void oneTouchPlay(IHdmiControlCallback callback) {
- assertRunOnServiceThread();
- List<OneTouchPlayAction> actions = getActions(OneTouchPlayAction.class);
- if (!actions.isEmpty()) {
- Slog.i(TAG, "oneTouchPlay already in progress");
- actions.get(0).addCallback(callback);
- return;
- }
- OneTouchPlayAction action = OneTouchPlayAction.create(this, Constants.ADDR_TV,
- callback);
- if (action == null) {
- Slog.w(TAG, "Cannot initiate oneTouchPlay");
- invokeCallback(callback, HdmiControlManager.RESULT_EXCEPTION);
- return;
- }
- addAndStartAction(action);
- }
-
- @ServiceThreadOnly
void queryDisplayStatus(IHdmiControlCallback callback) {
assertRunOnServiceThread();
List<DevicePowerStatusAction> actions = getActions(DevicePowerStatusAction.class);
@@ -189,7 +174,7 @@
}
@ServiceThreadOnly
- void setActiveSource(boolean on) {
+ void setIsActiveSource(boolean on) {
assertRunOnServiceThread();
mIsActiveSource = on;
if (on) {
@@ -227,21 +212,6 @@
return !getWakeLock().isHeld();
}
- @Override
- @ServiceThreadOnly
- protected boolean handleActiveSource(HdmiCecMessage message) {
- assertRunOnServiceThread();
- int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
- mayResetActiveSource(physicalAddress);
- return true; // Broadcast message.
- }
-
- private void mayResetActiveSource(int physicalAddress) {
- if (physicalAddress != mService.getPhysicalAddress()) {
- setActiveSource(false);
- }
- }
-
@ServiceThreadOnly
protected boolean handleUserControlPressed(HdmiCecMessage message) {
assertRunOnServiceThread();
@@ -254,10 +224,21 @@
protected boolean handleSetStreamPath(HdmiCecMessage message) {
assertRunOnServiceThread();
int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
- maySetActiveSource(physicalAddress);
- maySendActiveSource(message.getSource());
- wakeUpIfActiveSource();
- return true; // Broadcast message.
+ // If current device is the target path, set to Active Source.
+ // If the path is under the current device, should switch
+ int port = getLocalPortFromPhysicalAddress(physicalAddress);
+ if (port == 0) {
+ setIsActiveSource(true);
+ maySendActiveSource(message.getSource());
+ wakeUpIfActiveSource();
+ } else if (port > 0) {
+ // Wake up the device if the power is in standby mode for routing
+ if (mService.isPowerStandbyOrTransient()) {
+ mService.wakeUp();
+ }
+ routeToPort(port);
+ }
+ return true;
}
// Samsung model we tested sends <Routing Change> and <Request Active Source>
@@ -282,10 +263,11 @@
}
private void maySetActiveSource(int physicalAddress) {
- setActiveSource(physicalAddress == mService.getPhysicalAddress());
+ setIsActiveSource(physicalAddress == mService.getPhysicalAddress());
}
- private void wakeUpIfActiveSource() {
+ @Override
+ protected void wakeUpIfActiveSource() {
if (!mIsActiveSource) {
return;
}
@@ -296,7 +278,8 @@
}
}
- private void maySendActiveSource(int dest) {
+ @Override
+ protected void maySendActiveSource(int dest) {
if (mIsActiveSource) {
mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(
mAddress, mService.getPhysicalAddress()));
@@ -306,14 +289,6 @@
}
}
- @Override
- @ServiceThreadOnly
- protected boolean handleRequestActiveSource(HdmiCecMessage message) {
- assertRunOnServiceThread();
- maySendActiveSource(message.getSource());
- return true; // Broadcast message.
- }
-
@ServiceThreadOnly
protected boolean handleSetMenuLanguage(HdmiCecMessage message) {
assertRunOnServiceThread();
@@ -361,16 +336,6 @@
@Override
@ServiceThreadOnly
- protected void sendStandby(int deviceId) {
- assertRunOnServiceThread();
-
- // Playback device can send <Standby> to TV only. Ignore the parameter.
- int targetAddress = Constants.ADDR_TV;
- mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, targetAddress));
- }
-
- @Override
- @ServiceThreadOnly
protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
super.disableDevice(initiatedByCec, callback);
@@ -379,10 +344,20 @@
mService.sendCecCommand(HdmiCecMessageBuilder.buildInactiveSource(
mAddress, mService.getPhysicalAddress()));
}
- setActiveSource(false);
+ setIsActiveSource(false);
checkIfPendingActionsCleared();
}
+ private void routeToPort(int portId) {
+ // TODO(AMYJOJO): route to specific input of the port
+ mLocalActivePath = portId;
+ }
+
+ @VisibleForTesting
+ protected int getLocalActivePath() {
+ return mLocalActivePath;
+ }
+
@Override
protected void dump(final IndentingPrintWriter pw) {
super.dump(pw);
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
new file mode 100644
index 0000000..8d55299
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
@@ -0,0 +1,254 @@
+/*
+ * 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.
+ */
+
+package com.android.server.hdmi;
+
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.IHdmiControlCallback;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.hdmi.Constants.LocalActivePort;
+import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
+
+import java.util.List;
+
+/**
+ * Represent a logical source device residing in Android system.
+ */
+abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice {
+
+ private static final String TAG = "HdmiCecLocalDeviceSource";
+
+ // Indicate if current device is Active Source or not
+ private boolean mIsActiveSource = false;
+
+ // Device has cec switch functionality or not.
+ // Default is false.
+ protected boolean mIsSwitchDevice = SystemProperties.getBoolean(
+ Constants.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH, false);
+
+ // Local active port number used for Routing Control.
+ // This records the default active port or the previous valid active port.
+ // Default is HOME input.
+ // Note that we don't save active path here because for source device,
+ // new Active Source physical address might not match the local active path
+ @GuardedBy("mLock")
+ @LocalActivePort
+ private int mLocalActivePort = Constants.CEC_SWITCH_HOME;
+
+ protected HdmiCecLocalDeviceSource(HdmiControlService service, int deviceType) {
+ super(service, deviceType);
+ }
+
+ @Override
+ @ServiceThreadOnly
+ void onHotplug(int portId, boolean connected) {
+ assertRunOnServiceThread();
+ mCecMessageCache.flushAll();
+ // We'll not clear mIsActiveSource on the hotplug event to pass CETC 11.2.2-2 ~ 3.
+ if (mService.isPowerStandbyOrTransient()) {
+ mService.wakeUp();
+ }
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected void sendStandby(int deviceId) {
+ assertRunOnServiceThread();
+
+ // Send standby to TV only for now
+ int targetAddress = Constants.ADDR_TV;
+ mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, targetAddress));
+ }
+
+ @ServiceThreadOnly
+ void oneTouchPlay(IHdmiControlCallback callback) {
+ assertRunOnServiceThread();
+ List<OneTouchPlayAction> actions = getActions(OneTouchPlayAction.class);
+ if (!actions.isEmpty()) {
+ Slog.i(TAG, "oneTouchPlay already in progress");
+ actions.get(0).addCallback(callback);
+ return;
+ }
+ OneTouchPlayAction action = OneTouchPlayAction.create(this, Constants.ADDR_TV,
+ callback);
+ if (action == null) {
+ Slog.w(TAG, "Cannot initiate oneTouchPlay");
+ invokeCallback(callback, HdmiControlManager.RESULT_EXCEPTION);
+ return;
+ }
+ addAndStartAction(action);
+ }
+
+ @ServiceThreadOnly
+ private void invokeCallback(IHdmiControlCallback callback, int result) {
+ assertRunOnServiceThread();
+ try {
+ callback.onComplete(result);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Invoking callback failed:" + e);
+ }
+ }
+
+ @ServiceThreadOnly
+ protected boolean handleActiveSource(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ int logicalAddress = message.getSource();
+ int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
+ ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress);
+ if (!mActiveSource.equals(activeSource)) {
+ setActiveSource(activeSource);
+ }
+ setIsActiveSource(physicalAddress == mService.getPhysicalAddress());
+ switchInputOnReceivingNewActivePath(physicalAddress);
+ return true;
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected boolean handleRequestActiveSource(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ maySendActiveSource(message.getSource());
+ return true;
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected boolean handleSetStreamPath(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
+ // If current device is the target path, set to Active Source.
+ // If the path is under the current device, should switch
+ if (physicalAddress == mService.getPhysicalAddress() && mService.isPlaybackDevice()) {
+ setAndBroadcastActiveSource(message, physicalAddress);
+ }
+ switchInputOnReceivingNewActivePath(physicalAddress);
+ return true;
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected boolean handleRoutingChange(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ int newPath = HdmiUtils.twoBytesToInt(message.getParams(), 2);
+ // if the current device is a pure playback device
+ if (!mIsSwitchDevice
+ && newPath == mService.getPhysicalAddress()
+ && mService.isPlaybackDevice()) {
+ setAndBroadcastActiveSource(message, newPath);
+ }
+ handleRoutingChangeAndInformation(newPath, message);
+ return true;
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected boolean handleRoutingInformation(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
+ // if the current device is a pure playback device
+ if (!mIsSwitchDevice
+ && physicalAddress == mService.getPhysicalAddress()
+ && mService.isPlaybackDevice()) {
+ setAndBroadcastActiveSource(message, physicalAddress);
+ }
+ handleRoutingChangeAndInformation(physicalAddress, message);
+ return true;
+ }
+
+ // Method to switch Input with the new Active Path.
+ // All the devices with Switch functionality should implement this.
+ protected void switchInputOnReceivingNewActivePath(int physicalAddress) {
+ // do nothing
+ }
+
+ // Source device with Switch functionality should implement this method.
+ // TODO(): decide which type will handle the routing when multi device type is supported
+ protected void handleRoutingChangeAndInformation(int physicalAddress, HdmiCecMessage message) {
+ // do nothing
+ }
+
+ // Active source claiming needs to be handled in the parent class
+ // since we decide who will be the active source when the device supports
+ // multiple device types in this method.
+ // This method should only be called when the device can be the active source.
+ protected void setAndBroadcastActiveSource(HdmiCecMessage message, int physicalAddress) {
+ // If the device has both playback and audio system logical addresses,
+ // playback will claim active source. Otherwise audio system will.
+ HdmiCecLocalDevice deviceToBeActiveSource = mService.playback();
+ if (deviceToBeActiveSource == null) {
+ deviceToBeActiveSource = mService.audioSystem();
+ }
+ if (this == deviceToBeActiveSource) {
+ ActiveSource activeSource = ActiveSource.of(mAddress, physicalAddress);
+ setIsActiveSource(true);
+ setActiveSource(activeSource);
+ wakeUpIfActiveSource();
+ maySendActiveSource(message.getSource());
+ }
+ }
+
+ @ServiceThreadOnly
+ void setIsActiveSource(boolean on) {
+ assertRunOnServiceThread();
+ mIsActiveSource = on;
+ }
+
+ @ServiceThreadOnly
+ // Check if current device is the Active Source
+ boolean isActiveSource() {
+ assertRunOnServiceThread();
+ return mIsActiveSource;
+ }
+
+ protected void wakeUpIfActiveSource() {
+ if (!mIsActiveSource) {
+ return;
+ }
+ // Wake up the device if the power is in standby mode
+ if (mService.isPowerStandbyOrTransient()) {
+ mService.wakeUp();
+ }
+ return;
+ }
+
+ protected void maySendActiveSource(int dest) {
+ if (mIsActiveSource) {
+ mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(
+ mAddress, mService.getPhysicalAddress()));
+ }
+ }
+
+ @VisibleForTesting
+ protected void setLocalActivePort(@LocalActivePort int portId) {
+ synchronized (mLock) {
+ mLocalActivePort = portId;
+ }
+ }
+
+ // To get the local active port to switch to
+ // when receivng routing change or information.
+ @LocalActivePort
+ protected int getLocalActivePort() {
+ synchronized (mLock) {
+ return mLocalActivePort;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
index 941c321..3f949ba 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
@@ -365,6 +365,20 @@
}
/**
+ * Build <Routing Information> command.
+ *
+ * <p>This is a broadcast message sent to all devices on the bus.
+ *
+ * @param src source address of command
+ * @param physicalAddress physical address of the new active routing path
+ * @return newly created {@link HdmiCecMessage}
+ */
+ static HdmiCecMessage buildRoutingInformation(int src, int physicalAddress) {
+ return buildCommand(src, Constants.ADDR_BROADCAST,
+ Constants.MESSAGE_ROUTING_INFORMATION, physicalAddressToParam(physicalAddress));
+ }
+
+ /**
* Build <Give Device Power Status> command.
*
* @param src source address of command
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index f468c0bf..903045d 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -18,6 +18,7 @@
import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_ADD_DEVICE;
import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE;
+
import static com.android.server.hdmi.Constants.DISABLED;
import static com.android.server.hdmi.Constants.ENABLED;
import static com.android.server.hdmi.Constants.OPTION_MHL_ENABLE;
@@ -67,6 +68,7 @@
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
+
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
@@ -76,6 +78,9 @@
import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback;
import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource;
import com.android.server.hdmi.HdmiCecLocalDevice.PendingActionClearedCallback;
+
+import libcore.util.EmptyArray;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -85,7 +90,6 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
-import libcore.util.EmptyArray;
/**
* Provides a service for sending and processing HDMI control messages,
@@ -136,6 +140,10 @@
static final int STANDBY_SCREEN_OFF = 0;
static final int STANDBY_SHUTDOWN = 1;
+ private static final boolean isHdmiCecNeverClaimPlaybackLogicAddr =
+ SystemProperties.getBoolean(
+ Constants.PROPERTY_HDMI_CEC_NEVER_CLAIM_PLAYBACK_LOGICAL_ADDRESS, false);
+
/**
* Interface to report send result.
*/
@@ -409,7 +417,7 @@
mSettingsObserver = new SettingsObserver(mHandler);
}
- private static List<Integer> getIntList(String string) {
+ protected static List<Integer> getIntList(String string) {
ArrayList<Integer> list = new ArrayList<>();
TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(',');
splitter.setString(string);
@@ -588,6 +596,11 @@
}
// No need to propagate to HAL.
break;
+ case Global.HDMI_CONTROL_AUTO_TV_OFF_ENABLED:
+ if (isAudioSystemDevice()) {
+ audioSystem().setAutoTvOff(enabled);
+ }
+ break;
case Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED:
if (isTvDeviceEnabled()) {
tv().setSystemAudioControlFeatureEnabled(enabled);
@@ -630,6 +643,10 @@
// A container for [Device type, Local device info].
ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
for (int type : mLocalDevices) {
+ if (type == HdmiDeviceInfo.DEVICE_PLAYBACK
+ && isHdmiCecNeverClaimPlaybackLogicAddr) {
+ continue;
+ }
HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
if (localDevice == null) {
localDevice = HdmiCecLocalDevice.create(this, type);
@@ -994,6 +1011,10 @@
if (connected && !isTvDevice()) {
ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
for (int type : mLocalDevices) {
+ if (type == HdmiDeviceInfo.DEVICE_PLAYBACK
+ && isHdmiCecNeverClaimPlaybackLogicAddr) {
+ continue;
+ }
HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
if (localDevice == null) {
localDevice = HdmiCecLocalDevice.create(this, type);
@@ -1469,12 +1490,12 @@
@Override
public boolean getSystemAudioMode() {
+ // TODO(shubang): handle getSystemAudioMode() for all device types
enforceAccessPermission();
HdmiCecLocalDeviceTv tv = tv();
- if (tv == null) {
- return false;
- }
- return tv.isSystemAudioActivated();
+ HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
+ return (tv != null && tv.isSystemAudioActivated())
+ || (audioSystem != null && audioSystem.isSystemAudioActivated());
}
@Override
@@ -1642,6 +1663,9 @@
}
HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
if (device == null) {
+ device = audioSystem();
+ }
+ if (device == null) {
Slog.w(TAG, "Local device not available");
return;
}
@@ -1821,9 +1845,13 @@
@ServiceThreadOnly
private void oneTouchPlay(final IHdmiControlCallback callback) {
assertRunOnServiceThread();
- HdmiCecLocalDevicePlayback source = playback();
+ HdmiCecLocalDeviceSource source = playback();
if (source == null) {
- Slog.w(TAG, "Local playback device not available");
+ source = audioSystem();
+ }
+
+ if (source == null) {
+ Slog.w(TAG, "Local source device not available");
invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
return;
}
@@ -2086,11 +2114,15 @@
return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
}
+ boolean isPlaybackDevice() {
+ return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_PLAYBACK);
+ }
+
boolean isTvDeviceEnabled() {
return isTvDevice() && tv() != null;
}
- private HdmiCecLocalDevicePlayback playback() {
+ protected HdmiCecLocalDevicePlayback playback() {
return (HdmiCecLocalDevicePlayback)
mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK);
}
diff --git a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
index 5c66316..46611dd 100644
--- a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
+++ b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
@@ -16,8 +16,8 @@
package com.android.server.hdmi;
import android.hardware.hdmi.HdmiControlManager;
-import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.hdmi.HdmiPlaybackClient.OneTouchPlayCallback;
+import android.hardware.hdmi.IHdmiControlCallback;
import android.os.RemoteException;
import android.util.Slog;
@@ -55,7 +55,7 @@
private int mPowerStatusCounter = 0;
// Factory method. Ensures arguments are valid.
- static OneTouchPlayAction create(HdmiCecLocalDevicePlayback source,
+ static OneTouchPlayAction create(HdmiCecLocalDeviceSource source,
int targetAddress, IHdmiControlCallback callback) {
if (source == null || callback == null) {
Slog.e(TAG, "Wrong arguments");
@@ -84,8 +84,12 @@
private void broadcastActiveSource() {
sendCommand(HdmiCecMessageBuilder.buildActiveSource(getSourceAddress(), getSourcePath()));
- // Because only playback device can create this action, it's safe to cast.
- playback().setActiveSource(true);
+ // Because only source device can create this action, it's safe to cast.
+ HdmiCecLocalDeviceSource source = source();
+ source.setIsActiveSource(true);
+ source.setActiveSource(getSourceAddress(), getSourcePath());
+ // Set local active port to HOME when One Touch Play.
+ source.setLocalActivePort(Constants.CEC_SWITCH_HOME);
}
private void queryDevicePowerStatus() {
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java b/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java
index 2fdcb51..a6e6965 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java
@@ -16,6 +16,7 @@
package com.android.server.hdmi;
import android.hardware.tv.cec.V1_0.SendMessageResult;
+
import com.android.internal.annotations.VisibleForTesting;
/**
@@ -91,7 +92,7 @@
mSendRequestActiveSourceRetryCount++;
sendRequestActiveSource();
} else {
- audioSystem().setSystemAudioMode(false);
+ audioSystem().checkSupportAndSetSystemAudioMode(false);
finish();
}
}
@@ -106,7 +107,7 @@
mSendSetSystemAudioModeRetryCount++;
sendSetSystemAudioMode(on, dest);
} else {
- audioSystem().setSystemAudioMode(false);
+ audioSystem().checkSupportAndSetSystemAudioMode(false);
finish();
}
}
@@ -115,7 +116,7 @@
private void handleActiveSourceTimeout() {
HdmiLogger.debug("Cannot get active source.");
- audioSystem().setSystemAudioMode(false);
+ audioSystem().checkSupportAndSetSystemAudioMode(false);
finish();
}
@@ -123,12 +124,12 @@
audioSystem().queryTvSystemAudioModeSupport(
supported -> {
if (supported) {
- if (audioSystem().setSystemAudioMode(true)) {
+ if (audioSystem().checkSupportAndSetSystemAudioMode(true)) {
sendSetSystemAudioMode(true, Constants.ADDR_BROADCAST);
}
finish();
} else {
- audioSystem().setSystemAudioMode(false);
+ audioSystem().checkSupportAndSetSystemAudioMode(false);
finish();
}
});
diff --git a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
index a3ebe24..8e26097 100644
--- a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
+++ b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
@@ -274,10 +274,14 @@
}
synchronized (mLock) {
- final S service = getServiceForUserLocked(userId);
- if (service != null) {
- service.setTemporaryServiceLocked(componentName, durationMs);
+ final S oldService = peekServiceForUserLocked(userId);
+ if (oldService != null) {
+ oldService.removeSelfFromCacheLocked();
}
+ mServiceNameResolver.setTemporaryService(userId, componentName, durationMs);
+
+ // Must update the service on cache so its initialization code is triggered
+ updateCachedServiceLocked(userId);
}
}
@@ -500,6 +504,7 @@
protected void dumpLocked(@NonNull String prefix, @NonNull PrintWriter pw) {
boolean realDebug = debug;
boolean realVerbose = verbose;
+ final String prefix2 = " ";
try {
// Temporarily turn on full logging;
@@ -510,6 +515,13 @@
if (mServiceNameResolver != null) {
pw.print(prefix); pw.print("Name resolver: ");
mServiceNameResolver.dumpShort(pw); pw.println();
+ final UserManager um = getContext().getSystemService(UserManager.class);
+ final List<UserInfo> users = um.getUsers();
+ for (int i = 0; i < users.size(); i++) {
+ final int userId = users.get(i).id;
+ pw.print(prefix2); pw.print(userId); pw.print(": ");
+ mServiceNameResolver.dumpShort(pw, userId); pw.println();
+ }
}
pw.print(prefix); pw.print("Disabled users: "); pw.println(mDisabledUsers);
pw.print(prefix); pw.print("Allow instant service: "); pw.println(mAllowInstantService);
@@ -522,7 +534,6 @@
pw.println("none");
} else {
pw.println(size);
- final String prefix2 = " ";
for (int i = 0; i < size; i++) {
pw.print(prefix); pw.print("Service at "); pw.print(i); pw.println(": ");
final S service = mServicesCache.valueAt(i);
diff --git a/services/core/java/com/android/server/infra/AbstractPerUserSystemService.java b/services/core/java/com/android/server/infra/AbstractPerUserSystemService.java
index 556e489..cd9ebcd 100644
--- a/services/core/java/com/android/server/infra/AbstractPerUserSystemService.java
+++ b/services/core/java/com/android/server/infra/AbstractPerUserSystemService.java
@@ -214,7 +214,7 @@
/**
* Gets the current name of the service, which is either the default service or the
- * {@link #setTemporaryServiceLocked(String, int) temporary one}.
+ * {@link AbstractMasterSystemService#setTemporaryService(int, String, int) temporary one}.
*/
protected final @Nullable String getComponentNameLocked() {
return mMaster.mServiceNameResolver.getServiceName(mUserId);
@@ -228,17 +228,6 @@
}
/**
- * Temporary sets the service implementation.
- *
- * @param componentName name of the new component
- * @param durationMs how long the change will be valid (the service will be automatically reset
- * to the default component after this timeout expires).
- */
- protected final void setTemporaryServiceLocked(@NonNull String componentName, int durationMs) {
- mMaster.mServiceNameResolver.setTemporaryService(mUserId, componentName, durationMs);
- }
-
- /**
* Resets the temporary service implementation to the default component.
*/
protected final void resetTemporaryServiceLocked() {
diff --git a/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java b/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java
index 7027246..7f198ac 100644
--- a/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java
+++ b/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java
@@ -159,7 +159,7 @@
@Override
public String toString() {
- return "FrameworkResourcesServiceNamer: temps=" + mTemporaryServiceNames;
+ return "FrameworkResourcesServiceNamer[temps=" + mTemporaryServiceNames + "]";
}
// TODO(b/117779333): support proto
diff --git a/services/core/java/com/android/server/infra/SecureSettingsServiceNameResolver.java b/services/core/java/com/android/server/infra/SecureSettingsServiceNameResolver.java
index bcaa918..cac7f53 100644
--- a/services/core/java/com/android/server/infra/SecureSettingsServiceNameResolver.java
+++ b/services/core/java/com/android/server/infra/SecureSettingsServiceNameResolver.java
@@ -56,4 +56,9 @@
public void dumpShort(@NonNull PrintWriter pw, @UserIdInt int userId) {
pw.print("defaultService="); pw.print(getDefaultServiceName(userId));
}
+
+ @Override
+ public String toString() {
+ return "SecureSettingsServiceNameResolver[" + mProperty + "]";
+ }
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index d96b6cb..45a9bc0 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -213,8 +213,6 @@
private static native void nativeSetFocusedApplication(long ptr,
int displayId, InputApplicationHandle application);
private static native void nativeSetFocusedDisplay(long ptr, int displayId);
- private static native boolean nativeTransferTouchFocus(long ptr,
- InputChannel fromChannel, InputChannel toChannel);
private static native void nativeSetPointerSpeed(long ptr, int speed);
private static native void nativeSetShowTouches(long ptr, boolean enabled);
private static native void nativeSetInteractive(long ptr, boolean interactive);
@@ -1485,29 +1483,6 @@
nativeSetSystemUiVisibility(mPtr, visibility);
}
- /**
- * Atomically transfers touch focus from one window to another as identified by
- * their input channels. It is possible for multiple windows to have
- * touch focus if they support split touch dispatch
- * {@link android.view.WindowManager.LayoutParams#FLAG_SPLIT_TOUCH} but this
- * method only transfers touch focus of the specified window without affecting
- * other windows that may also have touch focus at the same time.
- * @param fromChannel The channel of a window that currently has touch focus.
- * @param toChannel The channel of the window that should receive touch focus in
- * place of the first.
- * @return True if the transfer was successful. False if the window with the
- * specified channel did not actually have touch focus at the time of the request.
- */
- public boolean transferTouchFocus(InputChannel fromChannel, InputChannel toChannel) {
- if (fromChannel == null) {
- throw new IllegalArgumentException("fromChannel must not be null.");
- }
- if (toChannel == null) {
- throw new IllegalArgumentException("toChannel must not be null.");
- }
- return nativeTransferTouchFocus(mPtr, fromChannel, toChannel);
- }
-
@Override // Binder call
public void tryPointerSpeed(int speed) {
if (!checkCallingPermission(android.Manifest.permission.SET_POINTER_SPEED,
@@ -1786,14 +1761,13 @@
// Native callback
private void notifyFocusChanged(IBinder token) {
- if (mFocusedWindow != token) {
- if (mFocusedWindowHasCapture) {
- setPointerCapture(false);
- }
- if (token instanceof IWindow) {
- mFocusedWindow = (IWindow) token;
- }
+ if (mFocusedWindow.asBinder() == token) {
+ Log.w(TAG, "notifyFocusChanged called with unchanged mFocusedWindow=" + mFocusedWindow);
+ return;
}
+
+ setPointerCapture(false);
+ mFocusedWindow = IWindow.Stub.asInterface(token);
}
// Native callback.
@@ -1951,6 +1925,11 @@
}
// Native callback.
+ private int getPointerDisplayId() {
+ return mWindowManagerCallbacks.getPointerDisplayId();
+ }
+
+ // Native callback.
private String[] getKeyboardLayoutOverlay(InputDeviceIdentifier identifier) {
if (!mSystemReady) {
return null;
@@ -2017,6 +1996,8 @@
KeyEvent event, int policyFlags);
public int getPointerLayer();
+
+ public int getPointerDisplayId();
}
/**
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index 3264790..d4b8eb2 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -16,7 +16,15 @@
package com.android.server.inputmethod;
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
import android.content.ComponentName;
+import android.view.inputmethod.InputMethodInfo;
+
+import com.android.server.LocalServices;
+
+import java.util.Collections;
+import java.util.List;
/**
* Input method manager local system service interface.
@@ -39,9 +47,25 @@
public abstract void startVrInputMethodNoCheck(ComponentName componentName);
/**
+ * Returns the list of installed input methods for the specified user.
+ *
+ * @param userId The user ID to be queried.
+ * @return A list of {@link InputMethodInfo}. VR-only IMEs are already excluded.
+ */
+ public abstract List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId);
+
+ /**
+ * Returns the list of installed input methods that are enabled for the specified user.
+ *
+ * @param userId The user ID to be queried.
+ * @return A list of {@link InputMethodInfo} that are enabled for {@code userId}.
+ */
+ public abstract List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId);
+
+ /**
* Fake implementation of {@link InputMethodManagerInternal}. All the methods do nothing.
*/
- public static final InputMethodManagerInternal NOP =
+ private static final InputMethodManagerInternal NOP =
new InputMethodManagerInternal() {
@Override
public void setInteractive(boolean interactive) {
@@ -54,5 +78,25 @@
@Override
public void startVrInputMethodNoCheck(ComponentName componentName) {
}
+
+ @Override
+ public List<InputMethodInfo> getInputMethodListAsUser(int userId) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List<InputMethodInfo> getEnabledInputMethodListAsUser(int userId) {
+ return Collections.emptyList();
+ }
};
+
+ /**
+ * @return Global instance if exists. Otherwise, a dummy no-op instance.
+ */
+ @NonNull
+ public static InputMethodManagerInternal get() {
+ final InputMethodManagerInternal instance =
+ LocalServices.getService(InputMethodManagerInternal.class);
+ return instance != null ? instance : NOP;
+ }
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 5a7739c..5fa3f52 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -20,6 +20,7 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
+import static android.view.inputmethod.InputMethodSystemProperty.PER_PROFILE_IME_ENABLED;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -291,6 +292,8 @@
new DebugFlag("debug.optimize_startinput", false);
}
+ @UserIdInt
+ private int mLastSwitchUserId;
final Context mContext;
final Resources mRes;
@@ -1436,6 +1439,8 @@
Slog.w(TAG, "Couldn't get current user ID; guessing it's 0", e);
}
+ mLastSwitchUserId = userId;
+
// mSettings should be created before buildInputMethodListLocked
mSettings = new InputMethodSettings(
mRes, context.getContentResolver(), mMethodMap, mMethodList, userId, !mSystemReady);
@@ -1523,6 +1528,8 @@
if (DEBUG) Slog.d(TAG, "Switching user stage 3/3. newUserId=" + newUserId
+ " selectedIme=" + mSettings.getSelectedInputMethod());
+
+ mLastSwitchUserId = newUserId;
}
void updateCurrentProfileIds() {
@@ -1597,7 +1604,7 @@
// 1) it comes from the system process
// 2) the calling process' user id is identical to the current user id IMMS thinks.
@GuardedBy("mMethodMap")
- private boolean calledFromValidUserLocked() {
+ private boolean calledFromValidUserLocked(boolean allowCrossProfileAccess) {
final int uid = Binder.getCallingUid();
final int userId = UserHandle.getUserId(uid);
if (DEBUG) {
@@ -1607,7 +1614,13 @@
+ mSettings.getCurrentUserId() + ", calling pid = " + Binder.getCallingPid()
+ InputMethodUtils.getApiCallStack());
}
- if (uid == Process.SYSTEM_UID || mSettings.isCurrentProfile(userId)) {
+ if (uid == Process.SYSTEM_UID) {
+ return true;
+ }
+ if (userId == mSettings.getCurrentUserId()) {
+ return true;
+ }
+ if (allowCrossProfileAccess && mSettings.isCurrentProfile(userId)) {
return true;
}
@@ -1668,40 +1681,93 @@
@Override
public List<InputMethodInfo> getInputMethodList() {
- return getInputMethodList(false /* isVrOnly */);
+ final int callingUserId = UserHandle.getCallingUserId();
+ synchronized (mMethodMap) {
+ final int[] resolvedUserIds = InputMethodUtils.resolveUserId(callingUserId,
+ mSettings.getCurrentUserId(), null);
+ if (resolvedUserIds.length != 1) {
+ return Collections.emptyList();
+ }
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return getInputMethodListLocked(false /* isVrOnly */, resolvedUserIds[0]);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
}
@Override
public List<InputMethodInfo> getVrInputMethodList() {
- return getInputMethodList(true /* isVrOnly */);
- }
-
- private List<InputMethodInfo> getInputMethodList(final boolean isVrOnly) {
+ final int callingUserId = UserHandle.getCallingUserId();
synchronized (mMethodMap) {
- // TODO: Make this work even for non-current users?
- if (!calledFromValidUserLocked()) {
+ final int[] resolvedUserIds = InputMethodUtils.resolveUserId(callingUserId,
+ mSettings.getCurrentUserId(), null);
+ if (resolvedUserIds.length != 1) {
return Collections.emptyList();
}
- ArrayList<InputMethodInfo> methodList = new ArrayList<>();
- for (InputMethodInfo info : mMethodList) {
-
- if (info.isVrOnly() == isVrOnly) {
- methodList.add(info);
- }
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return getInputMethodListLocked(true /* isVrOnly */, resolvedUserIds[0]);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
- return methodList;
}
}
@Override
public List<InputMethodInfo> getEnabledInputMethodList() {
+ final int callingUserId = UserHandle.getCallingUserId();
synchronized (mMethodMap) {
- // TODO: Make this work even for non-current users?
- if (!calledFromValidUserLocked()) {
+ final int[] resolvedUserIds = InputMethodUtils.resolveUserId(callingUserId,
+ mSettings.getCurrentUserId(), null);
+ if (resolvedUserIds.length != 1) {
return Collections.emptyList();
}
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return getEnabledInputMethodListLocked(resolvedUserIds[0]);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+
+ @GuardedBy("mMethodMap")
+ private List<InputMethodInfo> getInputMethodListLocked(boolean isVrOnly,
+ @UserIdInt int userId) {
+ final ArrayList<InputMethodInfo> methodList;
+ if (userId == mSettings.getCurrentUserId()) {
+ // Create a copy.
+ methodList = new ArrayList<>(mMethodList);
+ } else {
+ final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+ methodList = new ArrayList<>();
+ final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
+ new ArrayMap<>();
+ AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
+ queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap,
+ methodList);
+ }
+ methodList.removeIf(imi -> imi.isVrOnly() != isVrOnly);
+ return methodList;
+ }
+
+ @GuardedBy("mMethodMap")
+ private List<InputMethodInfo> getEnabledInputMethodListLocked(@UserIdInt int userId) {
+ if (userId == mSettings.getCurrentUserId()) {
return mSettings.getEnabledInputMethodListLocked();
}
+ final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+ final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
+ final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
+ new ArrayMap<>();
+ AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
+ queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap,
+ methodList);
+ final InputMethodSettings settings = new InputMethodSettings(mContext.getResources(),
+ mContext.getContentResolver(), methodMap, methodList, userId, true);
+ return settings.getEnabledInputMethodListLocked();
}
/**
@@ -1711,11 +1777,27 @@
@Override
public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId,
boolean allowsImplicitlySelectedSubtypes) {
+ final int callingUserId = UserHandle.getCallingUserId();
synchronized (mMethodMap) {
- // TODO: Make this work even for non-current users?
- if (!calledFromValidUserLocked()) {
+ final int[] resolvedUserIds = InputMethodUtils.resolveUserId(callingUserId,
+ mSettings.getCurrentUserId(), null);
+ if (resolvedUserIds.length != 1) {
return Collections.emptyList();
}
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return getEnabledInputMethodSubtypeListLocked(imiId,
+ allowsImplicitlySelectedSubtypes, resolvedUserIds[0]);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+
+ @GuardedBy("mMethodMap")
+ private List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(String imiId,
+ boolean allowsImplicitlySelectedSubtypes, @UserIdInt int userId) {
+ if (userId == mSettings.getCurrentUserId()) {
final InputMethodInfo imi;
if (imiId == null && mCurMethodId != null) {
imi = mMethodMap.get(mCurMethodId);
@@ -1728,6 +1810,21 @@
return mSettings.getEnabledInputMethodSubtypeListLocked(
mContext, imi, allowsImplicitlySelectedSubtypes);
}
+ final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+ final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
+ final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
+ new ArrayMap<>();
+ AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
+ queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap,
+ methodList);
+ final InputMethodInfo imi = methodMap.get(imiId);
+ if (imi == null) {
+ return Collections.emptyList();
+ }
+ final InputMethodSettings settings = new InputMethodSettings(mContext.getResources(),
+ mContext.getContentResolver(), methodMap, methodList, userId, true);
+ return settings.getEnabledInputMethodSubtypeListLocked(
+ mContext, imi, allowsImplicitlySelectedSubtypes);
}
/**
@@ -2428,7 +2525,7 @@
@Override
public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) {
synchronized (mMethodMap) {
- if (!calledFromValidUserLocked()) {
+ if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
return;
}
final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
@@ -2444,7 +2541,7 @@
@Override
public boolean notifySuggestionPicked(SuggestionSpan span, String originalString, int index) {
synchronized (mMethodMap) {
- if (!calledFromValidUserLocked()) {
+ if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
return false;
}
final InputMethodInfo targetImi = mSecureSuggestionSpans.get(span);
@@ -2611,7 +2708,7 @@
ResultReceiver resultReceiver) {
int uid = Binder.getCallingUid();
synchronized (mMethodMap) {
- if (!calledFromValidUserLocked()) {
+ if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
return false;
}
final long ident = Binder.clearCallingIdentity();
@@ -2696,7 +2793,7 @@
ResultReceiver resultReceiver) {
int uid = Binder.getCallingUid();
synchronized (mMethodMap) {
- if (!calledFromValidUserLocked()) {
+ if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
return false;
}
final long ident = Binder.clearCallingIdentity();
@@ -2803,10 +2900,12 @@
@SoftInputModeFlags int softInputMode, int windowFlags, EditorInfo attribute,
IInputContext inputContext, @MissingMethodFlags int missingMethods,
int unverifiedTargetSdkVersion) {
+ final int userId = UserHandle.getUserId(Binder.getCallingUid());
InputBindResult res = null;
synchronized (mMethodMap) {
// Needs to check the validity before clearing calling identity
- final boolean calledFromValidUser = calledFromValidUserLocked();
+ // Note that cross-profile access is always allowed here to allow profile-switching.
+ final boolean calledFromValidUser = calledFromValidUserLocked(true);
final int windowDisplayId =
mWindowManagerInternal.getDisplayIdForWindow(windowToken);
final long ident = Binder.clearCallingIdentity();
@@ -2858,6 +2957,10 @@
return InputBindResult.INVALID_USER;
}
+ if (PER_PROFILE_IME_ENABLED && userId != mSettings.getCurrentUserId()) {
+ switchUserLocked(userId);
+ }
+
if (mCurFocusedWindow == windowToken) {
if (DEBUG) {
Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client
@@ -3026,7 +3129,7 @@
public void showInputMethodPickerFromClient(
IInputMethodClient client, int auxiliarySubtypeMode) {
synchronized (mMethodMap) {
- if (!calledFromValidUserLocked()) {
+ if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
return;
}
if(!canShowInputMethodPickerLocked(client)) {
@@ -3097,7 +3200,7 @@
IInputMethodClient client, String inputMethodId) {
synchronized (mMethodMap) {
// TODO(yukawa): Should we verify the display ID?
- if (!calledFromValidUserLocked()) {
+ if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
return;
}
executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
@@ -3212,7 +3315,7 @@
@Override
public InputMethodSubtype getLastInputMethodSubtype() {
synchronized (mMethodMap) {
- if (!calledFromValidUserLocked()) {
+ if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
return null;
}
final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
@@ -3250,7 +3353,7 @@
}
}
synchronized (mMethodMap) {
- if (!calledFromValidUserLocked()) {
+ if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
return;
}
if (!mSystemReady) {
@@ -4096,7 +4199,7 @@
public InputMethodSubtype getCurrentInputMethodSubtype() {
synchronized (mMethodMap) {
// TODO: Make this work even for non-current users?
- if (!calledFromValidUserLocked()) {
+ if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
return null;
}
return getCurrentInputMethodSubtypeLocked();
@@ -4146,7 +4249,7 @@
public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
synchronized (mMethodMap) {
// TODO: Make this work even for non-current users?
- if (!calledFromValidUserLocked()) {
+ if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
return false;
}
if (subtype != null && mCurMethodId != null) {
@@ -4161,6 +4264,18 @@
}
}
+ private List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
+ synchronized (mMethodMap) {
+ return getInputMethodListLocked(false, userId);
+ }
+ }
+
+ private List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId) {
+ synchronized (mMethodMap) {
+ return getEnabledInputMethodListLocked(userId);
+ }
+ }
+
private static final class LocalServiceImpl extends InputMethodManagerInternal {
@NonNull
private final InputMethodManagerService mService;
@@ -4186,6 +4301,16 @@
public void startVrInputMethodNoCheck(@Nullable ComponentName componentName) {
mService.mHandler.obtainMessage(MSG_START_VR_INPUT, componentName).sendToTarget();
}
+
+ @Override
+ public List<InputMethodInfo> getInputMethodListAsUser(int userId) {
+ return mService.getInputMethodListAsUser(userId);
+ }
+
+ @Override
+ public List<InputMethodInfo> getEnabledInputMethodListAsUser(int userId) {
+ return mService.getEnabledInputMethodListAsUser(userId);
+ }
}
@BinderThread
@@ -4424,6 +4549,10 @@
return refreshDebugProperties();
}
+ if ("get-last-switch-user-id".equals(cmd)) {
+ return mService.getLastSwitchUserId(this);
+ }
+
// For existing "adb shell ime <command>".
if ("ime".equals(cmd)) {
final String imeCommand = getNextArg();
@@ -4516,6 +4645,15 @@
// ----------------------------------------------------------------------
// Shell command handlers:
+ @BinderThread
+ @ShellCommandResult
+ private int getLastSwitchUserId(@NonNull ShellCommand shellCommand) {
+ synchronized (mMethodMap) {
+ shellCommand.getOutPrintWriter().println(mLastSwitchUserId);
+ return ShellCommandResult.SUCCESS;
+ }
+ }
+
/**
* Handles {@code adb shell ime list}.
* @param shellCommand {@link ShellCommand} object that is handling this command.
@@ -4526,6 +4664,7 @@
private int handleShellCommandListInputMethods(@NonNull ShellCommand shellCommand) {
boolean all = false;
boolean brief = false;
+ int userIdToBeResolved = UserHandle.USER_CURRENT;
while (true) {
final String nextOption = shellCommand.getNextOption();
if (nextOption == null) {
@@ -4538,19 +4677,34 @@
case "-s":
brief = true;
break;
+ case "-u":
+ case "--user":
+ userIdToBeResolved = UserHandle.parseUserArg(shellCommand.getNextArgRequired());
+ break;
}
}
- final List<InputMethodInfo> methods = all ?
- getInputMethodList() : getEnabledInputMethodList();
- final PrintWriter pr = shellCommand.getOutPrintWriter();
- final Printer printer = x -> pr.println(x);
- final int N = methods.size();
- for (int i = 0; i < N; ++i) {
- if (brief) {
- pr.println(methods.get(i).getId());
- } else {
- pr.print(methods.get(i).getId()); pr.println(":");
- methods.get(i).dump(printer, " ");
+ synchronized (mMethodMap) {
+ final PrintWriter pr = shellCommand.getOutPrintWriter();
+ final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
+ mSettings.getCurrentUserId(), shellCommand.getErrPrintWriter());
+ for (int userId : userIds) {
+ final List<InputMethodInfo> methods = all
+ ? getInputMethodListLocked(false, userId)
+ : getEnabledInputMethodListLocked(userId);
+ if (userIds.length > 1) {
+ pr.print("User #");
+ pr.print(userId);
+ pr.println(":");
+ }
+ for (InputMethodInfo info : methods) {
+ if (brief) {
+ pr.println(info.getId());
+ } else {
+ pr.print(info.getId());
+ pr.println(":");
+ info.dump(pr::println, " ");
+ }
+ }
}
}
return ShellCommandResult.SUCCESS;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index 1137bf9..88d1a9c 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -29,21 +29,27 @@
import android.os.Build;
import android.os.LocaleList;
import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManagerInternal;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.IntArray;
import android.util.Pair;
import android.util.Printer;
import android.util.Slog;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
+import android.view.inputmethod.InputMethodSystemProperty;
import android.view.textservice.SpellCheckerInfo;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.inputmethod.StartInputFlags;
+import com.android.server.LocalServices;
import com.android.server.textservices.TextServicesManagerInternal;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
@@ -1286,4 +1292,62 @@
return true;
}
+ /**
+ * Converts a user ID, which can be a pseudo user ID such as {@link UserHandle#USER_ALL} to a
+ * list of real user IDs.
+ *
+ * <p>This method also converts profile user ID to profile parent user ID unless
+ * {@link InputMethodSystemProperty#PER_PROFILE_IME_ENABLED} is {@code true}.</p>
+ *
+ * @param userIdToBeResolved A user ID. Two pseudo user ID {@link UserHandle#USER_CURRENT} and
+ * {@link UserHandle#USER_ALL} are also supported
+ * @param currentUserId A real user ID, which will be used when {@link UserHandle#USER_CURRENT}
+ * is specified in {@code userIdToBeResolved}.
+ * @param warningWriter A {@link PrintWriter} to output some debug messages. {@code null} if
+ * no debug message is required.
+ * @return An integer array that contain user IDs.
+ */
+ static int[] resolveUserId(@UserIdInt int userIdToBeResolved,
+ @UserIdInt int currentUserId, @Nullable PrintWriter warningWriter) {
+ final UserManagerInternal userManagerInternal =
+ LocalServices.getService(UserManagerInternal.class);
+
+ if (userIdToBeResolved == UserHandle.USER_ALL) {
+ if (InputMethodSystemProperty.PER_PROFILE_IME_ENABLED) {
+ return userManagerInternal.getUserIds();
+ }
+ final IntArray result = new IntArray();
+ for (int userId : userManagerInternal.getUserIds()) {
+ final int parentUserId = userManagerInternal.getProfileParentId(userId);
+ if (result.indexOf(parentUserId) < 0) {
+ result.add(parentUserId);
+ }
+ }
+ return result.toArray();
+ }
+
+ final int sourceUserId;
+ if (userIdToBeResolved == UserHandle.USER_CURRENT) {
+ sourceUserId = currentUserId;
+ } else if (userIdToBeResolved < 0) {
+ if (warningWriter != null) {
+ warningWriter.print("Pseudo user ID ");
+ warningWriter.print(userIdToBeResolved);
+ warningWriter.println(" is not supported.");
+ }
+ return new int[]{};
+ } else if (userManagerInternal.exists(userIdToBeResolved)) {
+ sourceUserId = userIdToBeResolved;
+ } else {
+ if (warningWriter != null) {
+ warningWriter.print("User #");
+ warningWriter.print(userIdToBeResolved);
+ warningWriter.println(" does not exit.");
+ }
+ return new int[]{};
+ }
+ final int resolvedUserId = InputMethodSystemProperty.PER_PROFILE_IME_ENABLED
+ ? sourceUserId : userManagerInternal.getProfileParentId(sourceUserId);
+ return new int[]{resolvedUserId};
+ }
}
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index 98ed3ea..f304ceb 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -163,6 +163,18 @@
public void startVrInputMethodNoCheck(ComponentName componentName) {
reportNotSupported();
}
+
+ @Override
+ public List<InputMethodInfo> getInputMethodListAsUser(
+ @UserIdInt int userId) {
+ return userIdToInputMethodInfoMapper.getAsList(userId);
+ }
+
+ @Override
+ public List<InputMethodInfo> getEnabledInputMethodListAsUser(
+ @UserIdInt int userId) {
+ return userIdToInputMethodInfoMapper.getAsList(userId);
+ }
});
}
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 3f9d928..2464ca7 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -332,6 +332,44 @@
}
}
+ private static class MaxJobCounts {
+ private final KeyValueListParser.IntValue mTotal;
+ private final KeyValueListParser.IntValue mBg;
+
+ private MaxJobCounts(int totalDefault, String totalKey, int bgDefault, String bgKey) {
+ mTotal = new KeyValueListParser.IntValue(totalKey, totalDefault);
+ mBg = new KeyValueListParser.IntValue(bgKey, bgDefault);
+ }
+
+ public void parse(KeyValueListParser parser) {
+ mTotal.parse(parser);
+ mBg.parse(parser);
+
+ if (mBg.getValue() > mTotal.getValue()) {
+ mBg.setValue(mTotal.getValue());
+ }
+
+ }
+
+ public int getTotalMax() {
+ return mTotal.getValue();
+ }
+
+ public int getBgMax() {
+ return mBg.getValue();
+ }
+
+ public void dump(PrintWriter pw, String prefix) {
+ mTotal.dump(pw, prefix);
+ mBg.dump(pw, prefix);
+ }
+
+ public void dumpProto(ProtoOutputStream proto, long tagTotal, long tagBg) {
+ mTotal.dumpProto(proto, tagTotal);
+ mBg.dumpProto(proto, tagBg);
+ }
+ }
+
/**
* All times are in milliseconds. These constants are kept synchronized with the system
* global Settings. Any access to this class or its fields should be done while
@@ -492,6 +530,43 @@
* memory state.
*/
int BG_CRITICAL_JOB_COUNT = DEFAULT_BG_CRITICAL_JOB_COUNT;
+
+ // Max job counts for screen on / off, for each memory trim level.
+ // TODO Remove the old configs such as FG_JOB_COUNT and BG_*_COUNT, once the code switches
+ // to the below configs.
+
+ final MaxJobCounts MAX_JOB_COUNTS_ON_NORMAL = new MaxJobCounts(
+ 4, "max_job_total_on_normal",
+ 2, "max_job_bg_on_normal");
+
+ final MaxJobCounts MAX_JOB_COUNTS_ON_MODERATE = new MaxJobCounts(
+ 4, "max_job_total_on_moderate",
+ 1, "max_job_bg_on_moderate");
+
+ final MaxJobCounts MAX_JOB_COUNTS_ON_LOW = new MaxJobCounts(
+ 4, "max_job_total_on_low",
+ 1, "max_job_bg_on_low");
+
+ final MaxJobCounts MAX_JOB_COUNTS_ON_CRITICAL = new MaxJobCounts(
+ 2, "max_job_total_on_critical",
+ 1, "max_job_bg_on_critical");
+
+ final MaxJobCounts MAX_JOB_COUNTS_OFF_NORMAL = new MaxJobCounts(
+ 8, "max_job_total_off_normal",
+ 4, "max_job_bg_off_normal");
+
+ final MaxJobCounts MAX_JOB_COUNTS_OFF_MODERATE = new MaxJobCounts(
+ 6, "max_job_total_off_moderate",
+ 4, "max_job_bg_off_moderate");
+
+ final MaxJobCounts MAX_JOB_COUNTS_OFF_LOW = new MaxJobCounts(
+ 4, "max_job_total_off_low",
+ 1, "max_job_bg_off_low");
+
+ final MaxJobCounts MAX_JOB_COUNTS_OFF_CRITICAL = new MaxJobCounts(
+ 2, "max_job_total_off_critical",
+ 1, "max_job_bg_off_critical");
+
/**
* The maximum number of times we allow a job to have itself rescheduled before
* giving up on it, for standard jobs.
@@ -566,7 +641,7 @@
/**
* The quota window size of the particular standby bucket. Apps in this standby bucket are
- * expected to run only {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS} within the past
+ * expected to run only {@link #QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS} within the past
* WINDOW_SIZE_MS.
*/
public long QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS =
@@ -574,7 +649,7 @@
/**
* The quota window size of the particular standby bucket. Apps in this standby bucket are
- * expected to run only {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS} within the past
+ * expected to run only {@link #QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS} within the past
* WINDOW_SIZE_MS.
*/
public long QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS =
@@ -582,7 +657,7 @@
/**
* The quota window size of the particular standby bucket. Apps in this standby bucket are
- * expected to run only {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS} within the past
+ * expected to run only {@link #QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS} within the past
* WINDOW_SIZE_MS.
*/
public long QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS =
@@ -590,7 +665,7 @@
/**
* The quota window size of the particular standby bucket. Apps in this standby bucket are
- * expected to run only {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS} within the past
+ * expected to run only {@link #QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS} within the past
* WINDOW_SIZE_MS.
*/
public long QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS =
@@ -653,6 +728,17 @@
if ((FG_JOB_COUNT+BG_CRITICAL_JOB_COUNT) > MAX_JOB_CONTEXTS_COUNT) {
BG_CRITICAL_JOB_COUNT = MAX_JOB_CONTEXTS_COUNT - FG_JOB_COUNT;
}
+
+ MAX_JOB_COUNTS_ON_NORMAL.parse(mParser);
+ MAX_JOB_COUNTS_ON_MODERATE.parse(mParser);
+ MAX_JOB_COUNTS_ON_LOW.parse(mParser);
+ MAX_JOB_COUNTS_ON_CRITICAL.parse(mParser);
+
+ MAX_JOB_COUNTS_OFF_NORMAL.parse(mParser);
+ MAX_JOB_COUNTS_OFF_MODERATE.parse(mParser);
+ MAX_JOB_COUNTS_OFF_LOW.parse(mParser);
+ MAX_JOB_COUNTS_OFF_CRITICAL.parse(mParser);
+
MAX_STANDARD_RESCHEDULE_COUNT = mParser.getInt(KEY_MAX_STANDARD_RESCHEDULE_COUNT,
DEFAULT_MAX_STANDARD_RESCHEDULE_COUNT);
MAX_WORK_RESCHEDULE_COUNT = mParser.getInt(KEY_MAX_WORK_RESCHEDULE_COUNT,
@@ -717,6 +803,17 @@
pw.printPair(KEY_BG_MODERATE_JOB_COUNT, BG_MODERATE_JOB_COUNT).println();
pw.printPair(KEY_BG_LOW_JOB_COUNT, BG_LOW_JOB_COUNT).println();
pw.printPair(KEY_BG_CRITICAL_JOB_COUNT, BG_CRITICAL_JOB_COUNT).println();
+
+ MAX_JOB_COUNTS_ON_NORMAL.dump(pw, "");
+ MAX_JOB_COUNTS_ON_MODERATE.dump(pw, "");
+ MAX_JOB_COUNTS_ON_LOW.dump(pw, "");
+ MAX_JOB_COUNTS_ON_CRITICAL.dump(pw, "");
+
+ MAX_JOB_COUNTS_OFF_NORMAL.dump(pw, "");
+ MAX_JOB_COUNTS_OFF_MODERATE.dump(pw, "");
+ MAX_JOB_COUNTS_OFF_LOW.dump(pw, "");
+ MAX_JOB_COUNTS_OFF_CRITICAL.dump(pw, "");
+
pw.printPair(KEY_MAX_STANDARD_RESCHEDULE_COUNT, MAX_STANDARD_RESCHEDULE_COUNT).println();
pw.printPair(KEY_MAX_WORK_RESCHEDULE_COUNT, MAX_WORK_RESCHEDULE_COUNT).println();
pw.printPair(KEY_MIN_LINEAR_BACKOFF_TIME, MIN_LINEAR_BACKOFF_TIME).println();
@@ -767,6 +864,9 @@
proto.write(ConstantsProto.BG_MODERATE_JOB_COUNT, BG_MODERATE_JOB_COUNT);
proto.write(ConstantsProto.BG_LOW_JOB_COUNT, BG_LOW_JOB_COUNT);
proto.write(ConstantsProto.BG_CRITICAL_JOB_COUNT, BG_CRITICAL_JOB_COUNT);
+
+ // TODO Dump max job counts.
+
proto.write(ConstantsProto.MAX_STANDARD_RESCHEDULE_COUNT, MAX_STANDARD_RESCHEDULE_COUNT);
proto.write(ConstantsProto.MAX_WORK_RESCHEDULE_COUNT, MAX_WORK_RESCHEDULE_COUNT);
proto.write(ConstantsProto.MIN_LINEAR_BACKOFF_TIME_MS, MIN_LINEAR_BACKOFF_TIME);
diff --git a/services/core/java/com/android/server/location/AbstractLocationProvider.java b/services/core/java/com/android/server/location/AbstractLocationProvider.java
index 4c7c420..b3f1018 100644
--- a/services/core/java/com/android/server/location/AbstractLocationProvider.java
+++ b/services/core/java/com/android/server/location/AbstractLocationProvider.java
@@ -29,7 +29,7 @@
import java.util.List;
/**
- * Location Manager's interface for location providers.
+ * Location Manager's interface for location providers. Always starts as disabled.
*
* @hide
*/
@@ -41,12 +41,6 @@
public interface LocationProviderManager {
/**
- * Called on location provider construction to make the location service aware of this
- * provider and what it's initial enabled/disabled state should be.
- */
- void onAttachProvider(AbstractLocationProvider locationProvider, boolean initiallyEnabled);
-
- /**
* May be called to inform the location service of a change in this location provider's
* enabled/disabled state.
*/
@@ -74,13 +68,7 @@
private final LocationProviderManager mLocationProviderManager;
protected AbstractLocationProvider(LocationProviderManager locationProviderManager) {
- this(locationProviderManager, true);
- }
-
- protected AbstractLocationProvider(LocationProviderManager locationProviderManager,
- boolean initiallyEnabled) {
mLocationProviderManager = locationProviderManager;
- mLocationProviderManager.onAttachProvider(this, initiallyEnabled);
}
/**
diff --git a/services/core/java/com/android/server/location/GnssConfiguration.java b/services/core/java/com/android/server/location/GnssConfiguration.java
new file mode 100644
index 0000000..29465ad
--- /dev/null
+++ b/services/core/java/com/android/server/location/GnssConfiguration.java
@@ -0,0 +1,365 @@
+/*
+ * 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.location;
+
+import android.content.Context;
+import android.os.PersistableBundle;
+import android.os.SystemProperties;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import libcore.io.IoUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+
+/**
+ * A utility class to hold GNSS configuration properties.
+ *
+ * The trigger to load/reload the configuration parameters should be managed by the class
+ * that owns an instance of this class.
+ *
+ * Instances of this class are not thread-safe and should either be used from a single thread
+ * or with external synchronization when used by multiple threads.
+ */
+class GnssConfiguration {
+ private static final String TAG = "GnssConfiguration";
+
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ //TODO(b/33112647): Create gps_debug.conf with commented career parameters.
+ private static final String DEBUG_PROPERTIES_FILE = "/etc/gps_debug.conf";
+
+ // config.xml properties
+ private static final String CONFIG_SUPL_HOST = "SUPL_HOST";
+ private static final String CONFIG_SUPL_PORT = "SUPL_PORT";
+ private static final String CONFIG_C2K_HOST = "C2K_HOST";
+ private static final String CONFIG_C2K_PORT = "C2K_PORT";
+ private static final String CONFIG_SUPL_VER = "SUPL_VER";
+ private static final String CONFIG_SUPL_MODE = "SUPL_MODE";
+ private static final String CONFIG_SUPL_ES = "SUPL_ES";
+ private static final String CONFIG_LPP_PROFILE = "LPP_PROFILE";
+ private static final String CONFIG_A_GLONASS_POS_PROTOCOL_SELECT =
+ "A_GLONASS_POS_PROTOCOL_SELECT";
+ private static final String CONFIG_USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL =
+ "USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL";
+ private static final String CONFIG_GPS_LOCK = "GPS_LOCK";
+ private static final String CONFIG_ES_EXTENSION_SEC = "ES_EXTENSION_SEC";
+
+ // Limit on NI emergency mode time extension after emergency sessions ends
+ private static final int MAX_EMERGENCY_MODE_EXTENSION_SECONDS = 300; // 5 minute maximum
+
+ // Persist property for LPP_PROFILE
+ static final String LPP_PROFILE = "persist.sys.gps.lpp";
+
+ // Represents an HAL interface version. Instances of this class are created in the JNI layer
+ // and returned through native methods.
+ private static class HalInterfaceVersion {
+ final int mMajor;
+ final int mMinor;
+
+ HalInterfaceVersion(int major, int minor) {
+ mMajor = major;
+ mMinor = minor;
+ }
+ }
+
+ /**
+ * Properties loaded from PROPERTIES_FILE.
+ */
+ private Properties mProperties;
+
+ private int mEsExtensionSec = 0;
+
+ private final Context mContext;
+
+ GnssConfiguration(Context context) {
+ mContext = context;
+ mProperties = new Properties();
+ }
+
+ /**
+ * Returns the full set of properties loaded.
+ */
+ Properties getProperties() {
+ return mProperties;
+ }
+
+ /**
+ * Returns the value of config parameter ES_EXTENSION_SEC. The value is range checked
+ * and constrained to min/max limits.
+ */
+ int getEsExtensionSec() {
+ return mEsExtensionSec;
+ }
+
+ /**
+ * Returns the value of config parameter SUPL_HOST or {@code null} if no value is
+ * provided.
+ */
+ String getSuplHost() {
+ return mProperties.getProperty(CONFIG_SUPL_HOST);
+ }
+
+ /**
+ * Returns the value of config parameter SUPL_PORT or {@code defaultPort} if no value is
+ * provided or if there is an error parsing the configured value.
+ */
+ int getSuplPort(int defaultPort) {
+ return getIntConfig(CONFIG_SUPL_PORT, defaultPort);
+ }
+
+ /**
+ * Returns the value of config parameter C2K_HOST or {@code null} if no value is
+ * provided.
+ */
+ String getC2KHost() {
+ return mProperties.getProperty(CONFIG_C2K_HOST);
+ }
+
+ /**
+ * Returns the value of config parameter C2K_PORT or {@code defaultPort} if no value is
+ * provided or if there is an error parsing the configured value.
+ */
+ int getC2KPort(int defaultPort) {
+ return getIntConfig(CONFIG_C2K_PORT, defaultPort);
+ }
+
+ /**
+ * Returns the value of config parameter SUPL_MODE or {@code defaultMode} if no value is
+ * provided or if there is an error parsing the configured value.
+ */
+ int getSuplMode(int defaultMode) {
+ return getIntConfig(CONFIG_SUPL_MODE, defaultMode);
+ }
+
+ /**
+ * Returns the value of config parameter SUPL_ES or {@code defaultSuplEs} if no value is
+ * provided or if there is an error parsing the configured value.
+ */
+ int getSuplEs(int defaulSuplEs) {
+ return getIntConfig(CONFIG_SUPL_ES, defaulSuplEs);
+ }
+
+ /**
+ * Returns the value of config parameter LPP_PROFILE or {@code null} if no value is
+ * provided.
+ */
+ String getLppProfile() {
+ return mProperties.getProperty(CONFIG_LPP_PROFILE);
+ }
+
+ /**
+ * Updates the GNSS HAL satellite blacklist.
+ */
+ void setSatelliteBlacklist(int[] constellations, int[] svids) {
+ native_set_satellite_blacklist(constellations, svids);
+ }
+
+ interface SetCarrierProperty {
+ boolean set(int value);
+ }
+
+ /**
+ * Loads the GNSS properties from carrier config file followed by the properties from
+ * gps debug config file.
+ */
+ void reloadGpsProperties() {
+ if (DEBUG) Log.d(TAG, "Reset GPS properties, previous size = " + mProperties.size());
+ loadPropertiesFromCarrierConfig();
+
+ String lpp_prof = SystemProperties.get(LPP_PROFILE);
+ if (!TextUtils.isEmpty(lpp_prof)) {
+ // override default value of this if lpp_prof is not empty
+ mProperties.setProperty(CONFIG_LPP_PROFILE, lpp_prof);
+ }
+ /*
+ * Overlay carrier properties from a debug configuration file.
+ */
+ loadPropertiesFromGpsDebugConfig(mProperties);
+
+ mEsExtensionSec = getRangeCheckedConfigEsExtensionSec();
+
+ final HalInterfaceVersion gnssConfigurationIfaceVersion =
+ native_get_gnss_configuration_version();
+ if (gnssConfigurationIfaceVersion != null) {
+ // Set to a range checked value.
+ if (isConfigEsExtensionSecSupported(gnssConfigurationIfaceVersion)
+ && !native_set_es_extension_sec(mEsExtensionSec)) {
+ Log.e(TAG, "Unable to set " + CONFIG_ES_EXTENSION_SEC + ": " + mEsExtensionSec);
+ }
+
+ Map<String, SetCarrierProperty> map = new HashMap<String, SetCarrierProperty>() {
+ {
+ put(CONFIG_SUPL_VER, GnssConfiguration::native_set_supl_version);
+ put(CONFIG_SUPL_MODE, GnssConfiguration::native_set_supl_mode);
+
+ if (isConfigSuplEsSupported(gnssConfigurationIfaceVersion)) {
+ put(CONFIG_SUPL_ES, GnssConfiguration::native_set_supl_es);
+ }
+
+ put(CONFIG_LPP_PROFILE, GnssConfiguration::native_set_lpp_profile);
+ put(CONFIG_A_GLONASS_POS_PROTOCOL_SELECT,
+ GnssConfiguration::native_set_gnss_pos_protocol_select);
+ put(CONFIG_USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL,
+ GnssConfiguration::native_set_emergency_supl_pdn);
+
+ if (isConfigGpsLockSupported(gnssConfigurationIfaceVersion)) {
+ put(CONFIG_GPS_LOCK, GnssConfiguration::native_set_gps_lock);
+ }
+ }
+ };
+
+ for (Entry<String, SetCarrierProperty> entry : map.entrySet()) {
+ String propertyName = entry.getKey();
+ String propertyValueString = mProperties.getProperty(propertyName);
+ if (propertyValueString != null) {
+ try {
+ int propertyValueInt = Integer.decode(propertyValueString);
+ boolean result = entry.getValue().set(propertyValueInt);
+ if (!result) {
+ Log.e(TAG, "Unable to set " + propertyName);
+ }
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "Unable to parse propertyName: " + propertyValueString);
+ }
+ }
+ }
+ } else if (DEBUG) {
+ Log.d(TAG, "Skipped configuration update because GNSS configuration in GPS HAL is not"
+ + " supported");
+ }
+ }
+
+ /**
+ * Loads GNSS properties from carrier config file.
+ */
+ void loadPropertiesFromCarrierConfig() {
+ CarrierConfigManager configManager = (CarrierConfigManager)
+ mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+ if (configManager == null) {
+ return;
+ }
+ PersistableBundle configs = configManager.getConfigForSubId(
+ SubscriptionManager.getDefaultDataSubscriptionId());
+ if (configs == null) {
+ if (DEBUG) Log.d(TAG, "SIM not ready, use default carrier config.");
+ configs = CarrierConfigManager.getDefaultConfig();
+ }
+ for (String configKey : configs.keySet()) {
+ if (configKey.startsWith(CarrierConfigManager.Gps.KEY_PREFIX)) {
+ String key = configKey
+ .substring(CarrierConfigManager.Gps.KEY_PREFIX.length())
+ .toUpperCase();
+ Object value = configs.get(configKey);
+ if (value instanceof String) {
+ // All GPS properties are of String type; convert so.
+ if (DEBUG) Log.d(TAG, "Gps config: " + key + " = " + value);
+ mProperties.setProperty(key, (String) value);
+ }
+ }
+ }
+ }
+
+ private void loadPropertiesFromGpsDebugConfig(Properties properties) {
+ try {
+ File file = new File(DEBUG_PROPERTIES_FILE);
+ FileInputStream stream = null;
+ try {
+ stream = new FileInputStream(file);
+ properties.load(stream);
+ } finally {
+ IoUtils.closeQuietly(stream);
+ }
+ } catch (IOException e) {
+ if (DEBUG) Log.d(TAG, "Could not open GPS configuration file " + DEBUG_PROPERTIES_FILE);
+ }
+ }
+
+ private int getRangeCheckedConfigEsExtensionSec() {
+ int emergencyExtensionSeconds = getIntConfig(CONFIG_ES_EXTENSION_SEC, 0);
+ if (emergencyExtensionSeconds > MAX_EMERGENCY_MODE_EXTENSION_SECONDS) {
+ Log.w(TAG, CONFIG_ES_EXTENSION_SEC + ": " + emergencyExtensionSeconds
+ + " too high, reset to " + MAX_EMERGENCY_MODE_EXTENSION_SECONDS);
+ emergencyExtensionSeconds = MAX_EMERGENCY_MODE_EXTENSION_SECONDS;
+ } else if (emergencyExtensionSeconds < 0) {
+ Log.w(TAG, CONFIG_ES_EXTENSION_SEC + ": " + emergencyExtensionSeconds
+ + " is negative, reset to zero.");
+ emergencyExtensionSeconds = 0;
+ }
+ return emergencyExtensionSeconds;
+ }
+
+ private int getIntConfig(String configParameter, int defaultValue) {
+ String valueString = mProperties.getProperty(configParameter);
+ if (TextUtils.isEmpty(valueString)) {
+ return defaultValue;
+ }
+ try {
+ return Integer.parseInt(valueString);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "Unable to parse config parameter " + configParameter + " value: "
+ + valueString + ". Using default value: " + defaultValue);
+ return defaultValue;
+ }
+ }
+
+ private static boolean isConfigEsExtensionSecSupported(
+ HalInterfaceVersion gnssConfiguartionIfaceVersion) {
+ // ES_EXTENSION_SEC is introduced in @2.0::IGnssConfiguration.hal
+ return gnssConfiguartionIfaceVersion.mMajor >= 2;
+ }
+
+ private static boolean isConfigSuplEsSupported(
+ HalInterfaceVersion gnssConfiguartionIfaceVersion) {
+ // SUPL_ES is deprecated in @2.0::IGnssConfiguration.hal
+ return gnssConfiguartionIfaceVersion.mMajor < 2;
+ }
+
+ private static boolean isConfigGpsLockSupported(
+ HalInterfaceVersion gnssConfiguartionIfaceVersion) {
+ // GPS_LOCK is deprecated in @2.0::IGnssConfiguration.hal
+ return gnssConfiguartionIfaceVersion.mMajor < 2;
+ }
+
+ private static native HalInterfaceVersion native_get_gnss_configuration_version();
+
+ private static native boolean native_set_supl_version(int version);
+
+ private static native boolean native_set_supl_mode(int mode);
+
+ private static native boolean native_set_supl_es(int es);
+
+ private static native boolean native_set_lpp_profile(int lppProfile);
+
+ private static native boolean native_set_gnss_pos_protocol_select(int gnssPosProtocolSelect);
+
+ private static native boolean native_set_gps_lock(int gpsLock);
+
+ private static native boolean native_set_emergency_supl_pdn(int emergencySuplPdn);
+
+ private static native boolean native_set_satellite_blacklist(int[] constellations, int[] svIds);
+
+ private static native boolean native_set_es_extension_sec(int emergencyExtensionSeconds);
+}
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index 05d77ab..269767a 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -74,12 +74,7 @@
import com.android.server.location.GnssSatelliteBlacklistHelper.GnssSatelliteBlacklistCallback;
import com.android.server.location.NtpTimeHelper.InjectNtpTimeCallback;
-import libcore.io.IoUtils;
-
-import java.io.File;
import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.IOException;
import java.io.PrintWriter;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -87,11 +82,7 @@
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Properties;
/**
* A GNSS implementation of LocationProvider used by LocationManager.
@@ -147,7 +138,6 @@
private static final int LOCATION_HAS_SPEED_ACCURACY = 64;
private static final int LOCATION_HAS_BEARING_ACCURACY = 128;
-
// IMPORTANT - the GPS_DELETE_* symbols here must match GnssAidingData enum in IGnss.hal
private static final int GPS_DELETE_EPHEMERIS = 0x0001;
private static final int GPS_DELETE_ALMANAC = 0x0002;
@@ -200,9 +190,6 @@
private static final int AGPS_RIL_REQUEST_SETID_IMSI = 1;
private static final int AGPS_RIL_REQUEST_SETID_MSISDN = 2;
- //TODO(b/33112647): Create gps_debug.conf with commented career parameters.
- private static final String DEBUG_PROPERTIES_FILE = "/etc/gps_debug.conf";
-
// ref. location info
private static final int AGPS_REF_LOCATION_TYPE_GSM_CELLID = 1;
private static final int AGPS_REF_LOCATION_TYPE_UMTS_CELLID = 2;
@@ -375,7 +362,7 @@
* Properties loaded from PROPERTIES_FILE.
* It must be accessed only inside {@link #mHandler}.
*/
- private Properties mProperties;
+ private GnssConfiguration mGnssConfiguration;
private String mSuplServerHost;
private int mSuplServerPort = TCP_MIN_PORT;
@@ -411,10 +398,6 @@
private final static String ALARM_WAKEUP = "com.android.internal.location.ALARM_WAKEUP";
private final static String ALARM_TIMEOUT = "com.android.internal.location.ALARM_TIMEOUT";
- // Persist property for LPP_PROFILE
- private final static String LPP_PROFILE = "persist.sys.gps.lpp";
-
-
private final PowerManager mPowerManager;
private final AlarmManager mAlarmManager;
private final PendingIntent mWakeupIntent;
@@ -502,7 +485,7 @@
*/
@Override
public void onUpdateSatelliteBlacklist(int[] constellations, int[] svids) {
- mHandler.post(() -> native_set_satellite_blacklist(constellations, svids));
+ mHandler.post(() -> mGnssConfiguration.setSatelliteBlacklist(constellations, svids));
}
private void subscriptionOrCarrierConfigChanged(Context context) {
@@ -525,17 +508,17 @@
}
if (isKeepLppProfile) {
// load current properties for the carrier
- loadPropertiesFromCarrierConfig(context, mProperties);
- String lpp_profile = mProperties.getProperty("LPP_PROFILE");
+ mGnssConfiguration.loadPropertiesFromCarrierConfig();
+ String lpp_profile = mGnssConfiguration.getLppProfile();
// set the persist property LPP_PROFILE for the value
if (lpp_profile != null) {
- SystemProperties.set(LPP_PROFILE, lpp_profile);
+ SystemProperties.set(GnssConfiguration.LPP_PROFILE, lpp_profile);
}
} else {
// reset the persist property
- SystemProperties.set(LPP_PROFILE, "");
+ SystemProperties.set(GnssConfiguration.LPP_PROFILE, "");
}
- reloadGpsProperties(context, mProperties);
+ reloadGpsProperties();
mNIHandler.setSuplEsEnabled(mSuplEsEnabled);
}
} else {
@@ -564,138 +547,19 @@
return native_is_supported();
}
- interface SetCarrierProperty {
- boolean set(int value);
- }
-
- private void reloadGpsProperties(Context context, Properties properties) {
- if (DEBUG) Log.d(TAG, "Reset GPS properties, previous size = " + properties.size());
- loadPropertiesFromCarrierConfig(context, properties);
-
- String lpp_prof = SystemProperties.get(LPP_PROFILE);
- if (!TextUtils.isEmpty(lpp_prof)) {
- // override default value of this if lpp_prof is not empty
- properties.setProperty("LPP_PROFILE", lpp_prof);
- }
- /*
- * Overlay carrier properties from a debug configuration file.
- */
- loadPropertiesFromGpsDebugConfig(properties);
+ private void reloadGpsProperties() {
+ mGnssConfiguration.reloadGpsProperties();
+ setSuplHostPort();
// TODO: we should get rid of C2K specific setting.
- setSuplHostPort(properties.getProperty("SUPL_HOST"),
- properties.getProperty("SUPL_PORT"));
- mC2KServerHost = properties.getProperty("C2K_HOST");
- String portString = properties.getProperty("C2K_PORT");
- if (mC2KServerHost != null && portString != null) {
- try {
- mC2KServerPort = Integer.parseInt(portString);
- } catch (NumberFormatException e) {
- Log.e(TAG, "unable to parse C2K_PORT: " + portString);
- }
- }
- if (native_is_gnss_configuration_supported()) {
- Map<String, SetCarrierProperty> map = new HashMap<String, SetCarrierProperty>() {
- {
- put("SUPL_VER", GnssLocationProvider::native_set_supl_version);
- put("SUPL_MODE", GnssLocationProvider::native_set_supl_mode);
- put("SUPL_ES", GnssLocationProvider::native_set_supl_es);
- put("LPP_PROFILE", GnssLocationProvider::native_set_lpp_profile);
- put("A_GLONASS_POS_PROTOCOL_SELECT",
- GnssLocationProvider::native_set_gnss_pos_protocol_select);
- put("USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL",
- GnssLocationProvider::native_set_emergency_supl_pdn);
- put("GPS_LOCK", GnssLocationProvider::native_set_gps_lock);
- }
- };
-
- for (Entry<String, SetCarrierProperty> entry : map.entrySet()) {
- String propertyName = entry.getKey();
- String propertyValueString = properties.getProperty(propertyName);
- if (propertyValueString != null) {
- try {
- int propertyValueInt = Integer.decode(propertyValueString);
- boolean result = entry.getValue().set(propertyValueInt);
- if (!result) {
- Log.e(TAG, "Unable to set " + propertyName);
- }
- } catch (NumberFormatException e) {
- Log.e(TAG, "unable to parse propertyName: " + propertyValueString);
- }
- }
- }
- } else if (DEBUG) {
- Log.d(TAG, "Skipped configuration update because GNSS configuration in GPS HAL is not"
- + " supported");
- }
-
- // SUPL_ES configuration.
- String suplESProperty = mProperties.getProperty("SUPL_ES");
- if (suplESProperty != null) {
- try {
- mSuplEsEnabled = (Integer.parseInt(suplESProperty) == 1);
- } catch (NumberFormatException e) {
- Log.e(TAG, "unable to parse SUPL_ES: " + suplESProperty);
- }
- }
-
- String emergencyExtensionSecondsString
- = properties.getProperty("ES_EXTENSION_SEC", "0");
- try {
- int emergencyExtensionSeconds =
- Integer.parseInt(emergencyExtensionSecondsString);
- mNIHandler.setEmergencyExtensionSeconds(emergencyExtensionSeconds);
- } catch (NumberFormatException e) {
- Log.e(TAG, "unable to parse ES_EXTENSION_SEC: "
- + emergencyExtensionSecondsString);
- }
- }
-
- private void loadPropertiesFromCarrierConfig(Context context, Properties properties) {
- CarrierConfigManager configManager = (CarrierConfigManager)
- mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
- if (configManager == null) {
- return;
- }
- PersistableBundle configs = configManager.getConfigForSubId(
- SubscriptionManager.getDefaultDataSubscriptionId());
- if (configs == null) {
- if (DEBUG) Log.d(TAG, "SIM not ready, use default carrier config.");
- configs = CarrierConfigManager.getDefaultConfig();
- }
- for (String configKey : configs.keySet()) {
- if (configKey.startsWith(CarrierConfigManager.Gps.KEY_PREFIX)) {
- String key = configKey
- .substring(CarrierConfigManager.Gps.KEY_PREFIX.length())
- .toUpperCase();
- Object value = configs.get(configKey);
- if (value instanceof String) {
- // All GPS properties are of String type; convert so.
- if (DEBUG) Log.d(TAG, "Gps config: " + key + " = " + value);
- properties.setProperty(key, (String) value);
- }
- }
- }
- }
-
- private void loadPropertiesFromGpsDebugConfig(Properties properties) {
- try {
- File file = new File(DEBUG_PROPERTIES_FILE);
- FileInputStream stream = null;
- try {
- stream = new FileInputStream(file);
- properties.load(stream);
- } finally {
- IoUtils.closeQuietly(stream);
- }
-
- } catch (IOException e) {
- if (DEBUG) Log.d(TAG, "Could not open GPS configuration file " + DEBUG_PROPERTIES_FILE);
- }
+ mC2KServerHost = mGnssConfiguration.getC2KHost();
+ mC2KServerPort = mGnssConfiguration.getC2KPort(TCP_MIN_PORT);
+ mNIHandler.setEmergencyExtensionSeconds(mGnssConfiguration.getEsExtensionSec());
+ mSuplEsEnabled = mGnssConfiguration.getSuplEs(0) == 1;
}
public GnssLocationProvider(Context context, LocationProviderManager locationProviderManager,
Looper looper) {
- super(locationProviderManager, true);
+ super(locationProviderManager);
mContext = context;
@@ -733,7 +597,7 @@
// relative long time, so the ctor() is kept to create objects needed by this instance,
// while IO initialization and registration is delegated to our internal handler
// this approach is just fine because events are posted to our handler anyway
- mProperties = new Properties();
+ mGnssConfiguration = new GnssConfiguration(mContext);
sendMessage(INITIALIZE_HANDLER, 0, null);
// Create a GPS net-initiated handler.
@@ -788,6 +652,7 @@
}, UserHandle.ALL, intentFilter, null, mHandler);
setProperties(PROPERTIES);
+ setEnabled(true);
}
/**
@@ -918,7 +783,8 @@
mDownloadXtraWakeLock.acquire(DOWNLOAD_XTRA_DATA_TIMEOUT_MS);
Log.i(TAG, "WakeLock acquired by handleDownloadXtraData()");
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
- GpsXtraDownloader xtraDownloader = new GpsXtraDownloader(mProperties);
+ GpsXtraDownloader xtraDownloader = new GpsXtraDownloader(
+ mGnssConfiguration.getProperties());
byte[] data = xtraDownloader.downloadXtraData();
if (data != null) {
if (DEBUG) Log.d(TAG, "calling native_inject_xtra_data");
@@ -963,17 +829,9 @@
}
}
- private void setSuplHostPort(String hostString, String portString) {
- if (hostString != null) {
- mSuplServerHost = hostString;
- }
- if (portString != null) {
- try {
- mSuplServerPort = Integer.parseInt(portString);
- } catch (NumberFormatException e) {
- Log.e(TAG, "unable to parse SUPL_PORT: " + portString);
- }
- }
+ private void setSuplHostPort() {
+ mSuplServerHost = mGnssConfiguration.getSuplHost();
+ mSuplServerPort = mGnssConfiguration.getSuplPort(TCP_MIN_PORT);
if (mSuplServerHost != null
&& mSuplServerPort > TCP_MIN_PORT
&& mSuplServerPort <= TCP_MAX_PORT) {
@@ -986,23 +844,17 @@
* Checks what SUPL mode to use, according to the AGPS mode as well as the
* allowed mode from properties.
*
- * @param properties GPS properties
* @param agpsEnabled whether AGPS is enabled by settings value
* @param singleShot whether "singleshot" is needed
* @return SUPL mode (MSA vs MSB vs STANDALONE)
*/
- private int getSuplMode(Properties properties, boolean agpsEnabled, boolean singleShot) {
+ private int getSuplMode(boolean agpsEnabled, boolean singleShot) {
if (agpsEnabled) {
- String modeString = properties.getProperty("SUPL_MODE");
- int suplMode = 0;
- if (!TextUtils.isEmpty(modeString)) {
- try {
- suplMode = Integer.parseInt(modeString);
- } catch (NumberFormatException e) {
- Log.e(TAG, "unable to parse SUPL_MODE: " + modeString);
- return GPS_POSITION_MODE_STANDALONE;
- }
+ int suplMode = mGnssConfiguration.getSuplMode(0);
+ if (suplMode == 0) {
+ return GPS_POSITION_MODE_STANDALONE;
}
+
// MS-Based is the preferred mode for Assisted-GPS position computation, so we favor
// such mode when it is available
if (hasCapability(GPS_CAPABILITY_MSB) && (suplMode & AGPS_SUPL_MODE_MSB) != 0) {
@@ -1307,7 +1159,7 @@
boolean agpsEnabled =
(Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.ASSISTED_GPS_ENABLED, 1) != 0);
- mPositionMode = getSuplMode(mProperties, agpsEnabled, singleShot);
+ mPositionMode = getSuplMode(agpsEnabled, singleShot);
if (DEBUG) {
String mode;
@@ -1670,7 +1522,7 @@
// re-calls native_init() and other setup.
handleEnable();
// resend configuration into the restarted HAL service.
- reloadGpsProperties(mContext, mProperties);
+ reloadGpsProperties();
}
});
}
@@ -2072,7 +1924,7 @@
// load default GPS configuration
// (this configuration might change in the future based on SIM changes)
- reloadGpsProperties(mContext, mProperties);
+ reloadGpsProperties();
// TODO: When this object "finishes" we should unregister by invoking
// SubscriptionManager.getInstance(mContext).unregister
@@ -2227,8 +2079,6 @@
private static native boolean native_is_supported();
- private static native boolean native_is_gnss_configuration_supported();
-
private static native void native_init_once();
private native boolean native_init();
@@ -2284,21 +2134,4 @@
int lac, int cid);
private native void native_agps_set_id(int type, String setid);
-
- // GNSS Configuration
- private static native boolean native_set_supl_version(int version);
-
- private static native boolean native_set_supl_mode(int mode);
-
- private static native boolean native_set_supl_es(int es);
-
- private static native boolean native_set_lpp_profile(int lppProfile);
-
- private static native boolean native_set_gnss_pos_protocol_select(int gnssPosProtocolSelect);
-
- private static native boolean native_set_gps_lock(int gpsLock);
-
- private static native boolean native_set_emergency_supl_pdn(int emergencySuplPdn);
-
- private static native boolean native_set_satellite_blacklist(int[] constellations, int[] svIds);
-}
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/location/LocationProviderProxy.java b/services/core/java/com/android/server/location/LocationProviderProxy.java
index dfcef70..a6da8c5 100644
--- a/services/core/java/com/android/server/location/LocationProviderProxy.java
+++ b/services/core/java/com/android/server/location/LocationProviderProxy.java
@@ -101,7 +101,7 @@
private LocationProviderProxy(Context context, LocationProviderManager locationProviderManager,
String action, int overlaySwitchResId, int defaultServicePackageNameResId,
int initialPackageNamesResId) {
- super(locationProviderManager, false);
+ super(locationProviderManager);
mServiceWatcher = new ServiceWatcher(context, TAG, action, overlaySwitchResId,
defaultServicePackageNameResId, initialPackageNamesResId,
diff --git a/services/core/java/com/android/server/location/MockProvider.java b/services/core/java/com/android/server/location/MockProvider.java
index bfbebf7..86bc9f3 100644
--- a/services/core/java/com/android/server/location/MockProvider.java
+++ b/services/core/java/com/android/server/location/MockProvider.java
@@ -43,7 +43,7 @@
public MockProvider(
LocationProviderManager locationProviderManager, ProviderProperties properties) {
- super(locationProviderManager, true);
+ super(locationProviderManager);
mEnabled = true;
mLocation = null;
@@ -52,6 +52,7 @@
mExtras = null;
setProperties(properties);
+ setEnabled(true);
}
/** Sets the enabled state of this mock provider. */
@@ -63,8 +64,11 @@
/** Sets the location to report for this mock provider. */
public void setLocation(Location l) {
mLocation = new Location(l);
+ if (!mLocation.isFromMockProvider()) {
+ mLocation.setIsFromMockProvider(true);
+ }
if (mEnabled) {
- reportLocation(l);
+ reportLocation(mLocation);
}
}
diff --git a/services/core/java/com/android/server/location/PassiveProvider.java b/services/core/java/com/android/server/location/PassiveProvider.java
index 70d64b0..30260b2 100644
--- a/services/core/java/com/android/server/location/PassiveProvider.java
+++ b/services/core/java/com/android/server/location/PassiveProvider.java
@@ -43,11 +43,12 @@
private boolean mReportLocation;
public PassiveProvider(LocationProviderManager locationProviderManager) {
- super(locationProviderManager, true);
+ super(locationProviderManager);
mReportLocation = false;
setProperties(PROPERTIES);
+ setEnabled(true);
}
@Override
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 139d8ac..d611a17 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -19,7 +19,6 @@
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ParceledListSlice;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioManagerInternal;
@@ -27,15 +26,15 @@
import android.media.MediaMetadata;
import android.media.Rating;
import android.media.VolumeProvider;
+import android.media.session.ControllerCallbackLink;
import android.media.session.ISession;
-import android.media.session.ISessionCallback;
import android.media.session.ISessionController;
-import android.media.session.ISessionControllerCallback;
import android.media.session.MediaController;
import android.media.session.MediaController.PlaybackInfo;
import android.media.session.MediaSession;
-import android.media.session.ParcelableVolumeInfo;
+import android.media.session.MediaSession.QueueItem;
import android.media.session.PlaybackState;
+import android.media.session.SessionCallbackLink;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
@@ -45,7 +44,6 @@
import android.os.Looper;
import android.os.Message;
import android.os.Process;
-import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.SystemClock;
import android.util.Log;
@@ -56,6 +54,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.List;
/**
* This is the system implementation of a Session. Apps will interact with the
@@ -85,7 +84,7 @@
private final Context mContext;
private final Object mLock = new Object();
- private final ArrayList<ISessionControllerCallbackHolder> mControllerCallbackHolders =
+ private final ArrayList<ControllerCallbackLinkHolder> mControllerCallbackHolders =
new ArrayList<>();
private long mFlags;
@@ -98,7 +97,7 @@
// may result in throwing an exception.
private MediaMetadata mMetadata;
private PlaybackState mPlaybackState;
- private ParceledListSlice mQueue;
+ private List<QueueItem> mQueue;
private CharSequence mQueueTitle;
private int mRatingType;
// End TransportPerformer fields
@@ -117,11 +116,11 @@
private boolean mIsActive = false;
private boolean mDestroyed = false;
- private long mDuration;
+ private long mDuration = -1;
private String mMetadataDescription;
public MediaSessionRecord(int ownerPid, int ownerUid, int userId, String ownerPackageName,
- ISessionCallback cb, String tag, MediaSessionService service, Looper handlerLooper) {
+ SessionCallbackLink cb, String tag, MediaSessionService service, Looper handlerLooper) {
mOwnerPid = ownerPid;
mOwnerUid = ownerUid;
mUserId = userId;
@@ -250,7 +249,7 @@
* @param useSuggested True to use adjustSuggestedStreamVolume instead of
*/
public void adjustVolume(String packageName, String opPackageName, int pid, int uid,
- ISessionControllerCallback caller, boolean asSystemService, int direction, int flags,
+ ControllerCallbackLink caller, boolean asSystemService, int direction, int flags,
boolean useSuggested) {
int previousFlagPlaySound = flags & AudioManager.FLAG_PLAY_SOUND;
if (isPlaybackActive() || hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
@@ -292,7 +291,7 @@
}
private void setVolumeTo(String packageName, String opPackageName, int pid, int uid,
- ISessionControllerCallback caller, int value, int flags) {
+ ControllerCallbackLink caller, int value, int flags) {
if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL) {
int stream = AudioAttributes.toLegacyStreamType(mAudioAttrs);
final int volumeValue = value;
@@ -441,7 +440,7 @@
}
}
- public ISessionCallback getCallback() {
+ public SessionCallbackLink getCallback() {
return mSessionCb.mCb;
}
@@ -469,7 +468,7 @@
+ ", max=" + mMaxVolume + ", current=" + mCurrentVolume);
pw.println(indent + "metadata: " + mMetadataDescription);
pw.println(indent + "queueTitle=" + mQueueTitle + ", size="
- + (mQueue == null ? 0 : mQueue.getList().size()));
+ + (mQueue == null ? 0 : mQueue.size()));
}
@Override
@@ -519,7 +518,7 @@
}
private void logCallbackException(
- String msg, ISessionControllerCallbackHolder holder, Exception e) {
+ String msg, ControllerCallbackLinkHolder holder, Exception e) {
Log.v(TAG, msg + ", this=" + this + ", callback package=" + holder.mPackageName
+ ", exception=" + e);
}
@@ -530,16 +529,18 @@
return;
}
for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
- ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
+ ControllerCallbackLinkHolder holder = mControllerCallbackHolders.get(i);
try {
- holder.mCallback.onPlaybackStateChanged(mPlaybackState);
- } catch (DeadObjectException e) {
- mControllerCallbackHolders.remove(i);
- logCallbackException("Removed dead callback in pushPlaybackStateUpdate",
- holder, e);
- } catch (RemoteException e) {
- logCallbackException("unexpected exception in pushPlaybackStateUpdate",
- holder, e);
+ holder.mCallback.notifyPlaybackStateChanged(mPlaybackState);
+ } catch (RuntimeException e) {
+ if (e.getCause() instanceof DeadObjectException) {
+ mControllerCallbackHolders.remove(i);
+ logCallbackException("Removing dead callback in pushPlaybackStateUpdate",
+ holder, e);
+ } else {
+ logCallbackException("unexpected exception in pushPlaybackStateUpdate",
+ holder, e);
+ }
}
}
}
@@ -551,14 +552,18 @@
return;
}
for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
- ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
+ ControllerCallbackLinkHolder holder = mControllerCallbackHolders.get(i);
try {
- holder.mCallback.onMetadataChanged(mMetadata);
- } catch (DeadObjectException e) {
- logCallbackException("Removing dead callback in pushMetadataUpdate", holder, e);
- mControllerCallbackHolders.remove(i);
- } catch (RemoteException e) {
- logCallbackException("unexpected exception in pushMetadataUpdate", holder, e);
+ holder.mCallback.notifyMetadataChanged(mMetadata);
+ } catch (RuntimeException e) {
+ if (e.getCause() instanceof DeadObjectException) {
+ mControllerCallbackHolders.remove(i);
+ logCallbackException("Removing dead callback in pushMetadataUpdate",
+ holder, e);
+ } else {
+ logCallbackException("unexpected exception in pushMetadataUpdate",
+ holder, e);
+ }
}
}
}
@@ -570,14 +575,17 @@
return;
}
for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
- ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
+ ControllerCallbackLinkHolder holder = mControllerCallbackHolders.get(i);
try {
- holder.mCallback.onQueueChanged(mQueue);
- } catch (DeadObjectException e) {
- mControllerCallbackHolders.remove(i);
- logCallbackException("Removed dead callback in pushQueueUpdate", holder, e);
- } catch (RemoteException e) {
- logCallbackException("unexpected exception in pushQueueUpdate", holder, e);
+ holder.mCallback.notifyQueueChanged(mQueue);
+ } catch (RuntimeException e) {
+ if (e.getCause() instanceof DeadObjectException) {
+ mControllerCallbackHolders.remove(i);
+ logCallbackException("Removing dead callback in pushQueueUpdate",
+ holder, e);
+ } else {
+ logCallbackException("unexpected exception in pushQueueUpdate", holder, e);
+ }
}
}
}
@@ -589,16 +597,18 @@
return;
}
for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
- ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
+ ControllerCallbackLinkHolder holder = mControllerCallbackHolders.get(i);
try {
- holder.mCallback.onQueueTitleChanged(mQueueTitle);
- } catch (DeadObjectException e) {
- mControllerCallbackHolders.remove(i);
- logCallbackException("Removed dead callback in pushQueueTitleUpdate",
- holder, e);
- } catch (RemoteException e) {
- logCallbackException("unexpected exception in pushQueueTitleUpdate",
- holder, e);
+ holder.mCallback.notifyQueueTitleChanged(mQueueTitle);
+ } catch (RuntimeException e) {
+ if (e.getCause() instanceof DeadObjectException) {
+ mControllerCallbackHolders.remove(i);
+ logCallbackException("Removing dead callback in pushQueueTitleUpdate",
+ holder, e);
+ } else {
+ logCallbackException("unexpected exception in pushQueueTitleUpdate",
+ holder, e);
+ }
}
}
}
@@ -610,14 +620,17 @@
return;
}
for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
- ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
+ ControllerCallbackLinkHolder holder = mControllerCallbackHolders.get(i);
try {
- holder.mCallback.onExtrasChanged(mExtras);
- } catch (DeadObjectException e) {
- mControllerCallbackHolders.remove(i);
- logCallbackException("Removed dead callback in pushExtrasUpdate", holder, e);
- } catch (RemoteException e) {
- logCallbackException("unexpected exception in pushExtrasUpdate", holder, e);
+ holder.mCallback.notifyExtrasChanged(mExtras);
+ } catch (RuntimeException e) {
+ if (e.getCause() instanceof DeadObjectException) {
+ mControllerCallbackHolders.remove(i);
+ logCallbackException("Removing dead callback in pushExtrasUpdate",
+ holder, e);
+ } else {
+ logCallbackException("unexpected exception in pushExtrasUpdate", holder, e);
+ }
}
}
}
@@ -628,16 +641,19 @@
if (mDestroyed) {
return;
}
- ParcelableVolumeInfo info = mController.getVolumeAttributes();
+ PlaybackInfo info = mController.getVolumeAttributes();
for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
- ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
+ ControllerCallbackLinkHolder holder = mControllerCallbackHolders.get(i);
try {
- holder.mCallback.onVolumeInfoChanged(info);
- } catch (DeadObjectException e) {
- mControllerCallbackHolders.remove(i);
- logCallbackException("Removing dead callback in pushVolumeUpdate", holder, e);
- } catch (RemoteException e) {
- logCallbackException("Unexpected exception in pushVolumeUpdate", holder, e);
+ holder.mCallback.notifyVolumeInfoChanged(info);
+ } catch (RuntimeException e) {
+ if (e.getCause() instanceof DeadObjectException) {
+ mControllerCallbackHolders.remove(i);
+ logCallbackException("Removing dead callback in pushVolumeUpdate",
+ holder, e);
+ } else {
+ logCallbackException("unexpected exception in pushVolumeUpdate", holder, e);
+ }
}
}
}
@@ -649,14 +665,16 @@
return;
}
for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
- ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
+ ControllerCallbackLinkHolder holder = mControllerCallbackHolders.get(i);
try {
- holder.mCallback.onEvent(event, data);
- } catch (DeadObjectException e) {
- mControllerCallbackHolders.remove(i);
- logCallbackException("Removing dead callback in pushEvent", holder, e);
- } catch (RemoteException e) {
- logCallbackException("unexpected exception in pushEvent", holder, e);
+ holder.mCallback.notifyEvent(event, data);
+ } catch (RuntimeException e) {
+ if (e.getCause() instanceof DeadObjectException) {
+ mControllerCallbackHolders.remove(i);
+ logCallbackException("Removing dead callback in pushEvent", holder, e);
+ } else {
+ logCallbackException("unexpected exception in pushEvent", holder, e);
+ }
}
}
}
@@ -670,14 +688,18 @@
return;
}
for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
- ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
+ ControllerCallbackLinkHolder holder = mControllerCallbackHolders.get(i);
try {
- holder.mCallback.onSessionDestroyed();
- } catch (DeadObjectException e) {
- logCallbackException("Removing dead callback in pushEvent", holder, e);
- mControllerCallbackHolders.remove(i);
- } catch (RemoteException e) {
- logCallbackException("unexpected exception in pushEvent", holder, e);
+ holder.mCallback.notifySessionDestroyed();
+ } catch (RuntimeException e) {
+ if (e.getCause() instanceof DeadObjectException) {
+ mControllerCallbackHolders.remove(i);
+ logCallbackException("Removing dead callback in pushSessionDestroyed",
+ holder, e);
+ } else {
+ logCallbackException("unexpected exception in pushSessionDestroyed",
+ holder, e);
+ }
}
}
// After notifying clear all listeners
@@ -717,10 +739,10 @@
return result == null ? state : result;
}
- private int getControllerHolderIndexForCb(ISessionControllerCallback cb) {
- IBinder binder = cb.asBinder();
+ private int getControllerHolderIndexForCb(ControllerCallbackLink cb) {
+ IBinder binder = cb.getBinder();
for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
- if (binder.equals(mControllerCallbackHolders.get(i).mCallback.asBinder())) {
+ if (binder.equals(mControllerCallbackHolders.get(i).mCallback.getBinder())) {
return i;
}
}
@@ -844,7 +866,7 @@
}
@Override
- public void setQueue(ParceledListSlice queue) {
+ public void setQueue(List<QueueItem> queue) {
synchronized (mLock) {
mQueue = queue;
}
@@ -921,9 +943,9 @@
}
class SessionCb {
- private final ISessionCallback mCb;
+ private final SessionCallbackLink mCb;
- public SessionCb(ISessionCallback cb) {
+ SessionCb(SessionCallbackLink cb) {
mCb = cb;
}
@@ -931,224 +953,224 @@
boolean asSystemService, KeyEvent keyEvent, int sequenceId, ResultReceiver cb) {
try {
if (asSystemService) {
- mCb.onMediaButton(mContext.getPackageName(), Process.myPid(),
+ mCb.notifyMediaButton(mContext.getPackageName(), Process.myPid(),
Process.SYSTEM_UID, createMediaButtonIntent(keyEvent), sequenceId, cb);
} else {
- mCb.onMediaButton(packageName, pid, uid,
+ mCb.notifyMediaButton(packageName, pid, uid,
createMediaButtonIntent(keyEvent), sequenceId, cb);
}
return true;
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in sendMediaRequest.", e);
}
return false;
}
public boolean sendMediaButton(String packageName, int pid, int uid,
- ISessionControllerCallback caller, boolean asSystemService,
+ ControllerCallbackLink caller, boolean asSystemService,
KeyEvent keyEvent) {
try {
if (asSystemService) {
- mCb.onMediaButton(mContext.getPackageName(), Process.myPid(),
+ mCb.notifyMediaButton(mContext.getPackageName(), Process.myPid(),
Process.SYSTEM_UID, createMediaButtonIntent(keyEvent), 0, null);
} else {
- mCb.onMediaButtonFromController(packageName, pid, uid, caller,
+ mCb.notifyMediaButtonFromController(packageName, pid, uid, caller,
createMediaButtonIntent(keyEvent));
}
return true;
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in sendMediaRequest.", e);
}
return false;
}
public void sendCommand(String packageName, int pid, int uid,
- ISessionControllerCallback caller, String command, Bundle args, ResultReceiver cb) {
+ ControllerCallbackLink caller, String command, Bundle args, ResultReceiver cb) {
try {
- mCb.onCommand(packageName, pid, uid, caller, command, args, cb);
- } catch (RemoteException e) {
+ mCb.notifyCommand(packageName, pid, uid, caller, command, args, cb);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in sendCommand.", e);
}
}
public void sendCustomAction(String packageName, int pid, int uid,
- ISessionControllerCallback caller, String action,
+ ControllerCallbackLink caller, String action,
Bundle args) {
try {
- mCb.onCustomAction(packageName, pid, uid, caller, action, args);
- } catch (RemoteException e) {
+ mCb.notifyCustomAction(packageName, pid, uid, caller, action, args);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in sendCustomAction.", e);
}
}
public void prepare(String packageName, int pid, int uid,
- ISessionControllerCallback caller) {
+ ControllerCallbackLink caller) {
try {
- mCb.onPrepare(packageName, pid, uid, caller);
- } catch (RemoteException e) {
+ mCb.notifyPrepare(packageName, pid, uid, caller);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in prepare.", e);
}
}
public void prepareFromMediaId(String packageName, int pid, int uid,
- ISessionControllerCallback caller, String mediaId, Bundle extras) {
+ ControllerCallbackLink caller, String mediaId, Bundle extras) {
try {
- mCb.onPrepareFromMediaId(packageName, pid, uid, caller, mediaId, extras);
- } catch (RemoteException e) {
+ mCb.notifyPrepareFromMediaId(packageName, pid, uid, caller, mediaId, extras);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in prepareFromMediaId.", e);
}
}
public void prepareFromSearch(String packageName, int pid, int uid,
- ISessionControllerCallback caller, String query, Bundle extras) {
+ ControllerCallbackLink caller, String query, Bundle extras) {
try {
- mCb.onPrepareFromSearch(packageName, pid, uid, caller, query, extras);
- } catch (RemoteException e) {
+ mCb.notifyPrepareFromSearch(packageName, pid, uid, caller, query, extras);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in prepareFromSearch.", e);
}
}
public void prepareFromUri(String packageName, int pid, int uid,
- ISessionControllerCallback caller, Uri uri, Bundle extras) {
+ ControllerCallbackLink caller, Uri uri, Bundle extras) {
try {
- mCb.onPrepareFromUri(packageName, pid, uid, caller, uri, extras);
- } catch (RemoteException e) {
+ mCb.notifyPrepareFromUri(packageName, pid, uid, caller, uri, extras);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in prepareFromUri.", e);
}
}
- public void play(String packageName, int pid, int uid, ISessionControllerCallback caller) {
+ public void play(String packageName, int pid, int uid, ControllerCallbackLink caller) {
try {
- mCb.onPlay(packageName, pid, uid, caller);
- } catch (RemoteException e) {
+ mCb.notifyPlay(packageName, pid, uid, caller);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in play.", e);
}
}
public void playFromMediaId(String packageName, int pid, int uid,
- ISessionControllerCallback caller, String mediaId, Bundle extras) {
+ ControllerCallbackLink caller, String mediaId, Bundle extras) {
try {
- mCb.onPlayFromMediaId(packageName, pid, uid, caller, mediaId, extras);
- } catch (RemoteException e) {
+ mCb.notifyPlayFromMediaId(packageName, pid, uid, caller, mediaId, extras);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in playFromMediaId.", e);
}
}
public void playFromSearch(String packageName, int pid, int uid,
- ISessionControllerCallback caller, String query, Bundle extras) {
+ ControllerCallbackLink caller, String query, Bundle extras) {
try {
- mCb.onPlayFromSearch(packageName, pid, uid, caller, query, extras);
- } catch (RemoteException e) {
+ mCb.notifyPlayFromSearch(packageName, pid, uid, caller, query, extras);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in playFromSearch.", e);
}
}
public void playFromUri(String packageName, int pid, int uid,
- ISessionControllerCallback caller, Uri uri, Bundle extras) {
+ ControllerCallbackLink caller, Uri uri, Bundle extras) {
try {
- mCb.onPlayFromUri(packageName, pid, uid, caller, uri, extras);
- } catch (RemoteException e) {
+ mCb.notifyPlayFromUri(packageName, pid, uid, caller, uri, extras);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in playFromUri.", e);
}
}
public void skipToTrack(String packageName, int pid, int uid,
- ISessionControllerCallback caller, long id) {
+ ControllerCallbackLink caller, long id) {
try {
- mCb.onSkipToTrack(packageName, pid, uid, caller, id);
- } catch (RemoteException e) {
+ mCb.notifySkipToTrack(packageName, pid, uid, caller, id);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in skipToTrack", e);
}
}
- public void pause(String packageName, int pid, int uid, ISessionControllerCallback caller) {
+ public void pause(String packageName, int pid, int uid, ControllerCallbackLink caller) {
try {
- mCb.onPause(packageName, pid, uid, caller);
- } catch (RemoteException e) {
+ mCb.notifyPause(packageName, pid, uid, caller);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in pause.", e);
}
}
- public void stop(String packageName, int pid, int uid, ISessionControllerCallback caller) {
+ public void stop(String packageName, int pid, int uid, ControllerCallbackLink caller) {
try {
- mCb.onStop(packageName, pid, uid, caller);
- } catch (RemoteException e) {
+ mCb.notifyStop(packageName, pid, uid, caller);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in stop.", e);
}
}
- public void next(String packageName, int pid, int uid, ISessionControllerCallback caller) {
+ public void next(String packageName, int pid, int uid, ControllerCallbackLink caller) {
try {
- mCb.onNext(packageName, pid, uid, caller);
- } catch (RemoteException e) {
+ mCb.notifyNext(packageName, pid, uid, caller);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in next.", e);
}
}
public void previous(String packageName, int pid, int uid,
- ISessionControllerCallback caller) {
+ ControllerCallbackLink caller) {
try {
- mCb.onPrevious(packageName, pid, uid, caller);
- } catch (RemoteException e) {
+ mCb.notifyPrevious(packageName, pid, uid, caller);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in previous.", e);
}
}
public void fastForward(String packageName, int pid, int uid,
- ISessionControllerCallback caller) {
+ ControllerCallbackLink caller) {
try {
- mCb.onFastForward(packageName, pid, uid, caller);
- } catch (RemoteException e) {
+ mCb.notifyFastForward(packageName, pid, uid, caller);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in fastForward.", e);
}
}
public void rewind(String packageName, int pid, int uid,
- ISessionControllerCallback caller) {
+ ControllerCallbackLink caller) {
try {
- mCb.onRewind(packageName, pid, uid, caller);
- } catch (RemoteException e) {
+ mCb.notifyRewind(packageName, pid, uid, caller);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in rewind.", e);
}
}
- public void seekTo(String packageName, int pid, int uid, ISessionControllerCallback caller,
+ public void seekTo(String packageName, int pid, int uid, ControllerCallbackLink caller,
long pos) {
try {
- mCb.onSeekTo(packageName, pid, uid, caller, pos);
- } catch (RemoteException e) {
+ mCb.notifySeekTo(packageName, pid, uid, caller, pos);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in seekTo.", e);
}
}
- public void rate(String packageName, int pid, int uid, ISessionControllerCallback caller,
+ public void rate(String packageName, int pid, int uid, ControllerCallbackLink caller,
Rating rating) {
try {
- mCb.onRate(packageName, pid, uid, caller, rating);
- } catch (RemoteException e) {
+ mCb.notifyRate(packageName, pid, uid, caller, rating);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in rate.", e);
}
}
public void adjustVolume(String packageName, int pid, int uid,
- ISessionControllerCallback caller, boolean asSystemService, int direction) {
+ ControllerCallbackLink caller, boolean asSystemService, int direction) {
try {
if (asSystemService) {
- mCb.onAdjustVolume(mContext.getPackageName(), Process.myPid(),
+ mCb.notifyAdjustVolume(mContext.getPackageName(), Process.myPid(),
Process.SYSTEM_UID, null, direction);
} else {
- mCb.onAdjustVolume(packageName, pid, uid, caller, direction);
+ mCb.notifyAdjustVolume(packageName, pid, uid, caller, direction);
}
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in adjustVolume.", e);
}
}
public void setVolumeTo(String packageName, int pid, int uid,
- ISessionControllerCallback caller, int value) {
+ ControllerCallbackLink caller, int value) {
try {
- mCb.onSetVolumeTo(packageName, pid, uid, caller, value);
- } catch (RemoteException e) {
+ mCb.notifySetVolumeTo(packageName, pid, uid, caller, value);
+ } catch (RuntimeException e) {
Slog.e(TAG, "Remote failure in setVolumeTo.", e);
}
}
@@ -1162,34 +1184,34 @@
class ControllerStub extends ISessionController.Stub {
@Override
- public void sendCommand(String packageName, ISessionControllerCallback caller,
+ public void sendCommand(String packageName, ControllerCallbackLink caller,
String command, Bundle args, ResultReceiver cb) {
mSessionCb.sendCommand(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
caller, command, args, cb);
}
@Override
- public boolean sendMediaButton(String packageName, ISessionControllerCallback cb,
+ public boolean sendMediaButton(String packageName, ControllerCallbackLink cb,
boolean asSystemService, KeyEvent keyEvent) {
return mSessionCb.sendMediaButton(packageName, Binder.getCallingPid(),
Binder.getCallingUid(), cb, asSystemService, keyEvent);
}
@Override
- public void registerCallbackListener(String packageName, ISessionControllerCallback cb) {
+ public void registerCallbackListener(String packageName, ControllerCallbackLink cb) {
synchronized (mLock) {
// If this session is already destroyed tell the caller and
// don't add them.
if (mDestroyed) {
try {
- cb.onSessionDestroyed();
+ cb.notifySessionDestroyed();
} catch (Exception e) {
// ignored
}
return;
}
if (getControllerHolderIndexForCb(cb) < 0) {
- mControllerCallbackHolders.add(new ISessionControllerCallbackHolder(cb,
+ mControllerCallbackHolders.add(new ControllerCallbackLinkHolder(cb,
packageName, Binder.getCallingUid()));
if (DEBUG) {
Log.d(TAG, "registering controller callback " + cb + " from controller"
@@ -1200,14 +1222,14 @@
}
@Override
- public void unregisterCallbackListener(ISessionControllerCallback cb) {
+ public void unregisterCallbackListener(ControllerCallbackLink cb) {
synchronized (mLock) {
int index = getControllerHolderIndexForCb(cb);
if (index != -1) {
mControllerCallbackHolders.remove(index);
}
if (DEBUG) {
- Log.d(TAG, "unregistering callback " + cb.asBinder());
+ Log.d(TAG, "unregistering callback " + cb.getBinder());
}
}
}
@@ -1233,14 +1255,14 @@
}
@Override
- public ParcelableVolumeInfo getVolumeAttributes() {
+ public PlaybackInfo getVolumeAttributes() {
int volumeType;
AudioAttributes attributes;
synchronized (mLock) {
if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
int current = mOptimisticVolume != -1 ? mOptimisticVolume : mCurrentVolume;
- return new ParcelableVolumeInfo(
- mVolumeType, mAudioAttrs, mVolumeControlType, mMaxVolume, current);
+ return new PlaybackInfo(mVolumeType, mVolumeControlType, mMaxVolume, current,
+ mAudioAttrs);
}
volumeType = mVolumeType;
attributes = mAudioAttrs;
@@ -1248,13 +1270,13 @@
int stream = AudioAttributes.toLegacyStreamType(attributes);
int max = mAudioManager.getStreamMaxVolume(stream);
int current = mAudioManager.getStreamVolume(stream);
- return new ParcelableVolumeInfo(
- volumeType, attributes, VolumeProvider.VOLUME_CONTROL_ABSOLUTE, max, current);
+ return new PlaybackInfo(volumeType, VolumeProvider.VOLUME_CONTROL_ABSOLUTE, max,
+ current, attributes);
}
@Override
public void adjustVolume(String packageName, String opPackageName,
- ISessionControllerCallback caller, boolean asSystemService, int direction,
+ ControllerCallbackLink caller, boolean asSystemService, int direction,
int flags) {
int pid = Binder.getCallingPid();
int uid = Binder.getCallingUid();
@@ -1269,7 +1291,7 @@
@Override
public void setVolumeTo(String packageName, String opPackageName,
- ISessionControllerCallback caller, int value, int flags) {
+ ControllerCallbackLink caller, int value, int flags) {
int pid = Binder.getCallingPid();
int uid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
@@ -1282,110 +1304,110 @@
}
@Override
- public void prepare(String packageName, ISessionControllerCallback caller) {
+ public void prepare(String packageName, ControllerCallbackLink caller) {
mSessionCb.prepare(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller);
}
@Override
- public void prepareFromMediaId(String packageName, ISessionControllerCallback caller,
+ public void prepareFromMediaId(String packageName, ControllerCallbackLink caller,
String mediaId, Bundle extras) {
mSessionCb.prepareFromMediaId(packageName, Binder.getCallingPid(),
Binder.getCallingUid(), caller, mediaId, extras);
}
@Override
- public void prepareFromSearch(String packageName, ISessionControllerCallback caller,
+ public void prepareFromSearch(String packageName, ControllerCallbackLink caller,
String query, Bundle extras) {
mSessionCb.prepareFromSearch(packageName, Binder.getCallingPid(),
Binder.getCallingUid(), caller, query, extras);
}
@Override
- public void prepareFromUri(String packageName, ISessionControllerCallback caller,
+ public void prepareFromUri(String packageName, ControllerCallbackLink caller,
Uri uri, Bundle extras) {
mSessionCb.prepareFromUri(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
caller, uri, extras);
}
@Override
- public void play(String packageName, ISessionControllerCallback caller) {
+ public void play(String packageName, ControllerCallbackLink caller) {
mSessionCb.play(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller);
}
@Override
- public void playFromMediaId(String packageName, ISessionControllerCallback caller,
+ public void playFromMediaId(String packageName, ControllerCallbackLink caller,
String mediaId, Bundle extras) {
mSessionCb.playFromMediaId(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
caller, mediaId, extras);
}
@Override
- public void playFromSearch(String packageName, ISessionControllerCallback caller,
+ public void playFromSearch(String packageName, ControllerCallbackLink caller,
String query, Bundle extras) {
mSessionCb.playFromSearch(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
caller, query, extras);
}
@Override
- public void playFromUri(String packageName, ISessionControllerCallback caller,
+ public void playFromUri(String packageName, ControllerCallbackLink caller,
Uri uri, Bundle extras) {
mSessionCb.playFromUri(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
caller, uri, extras);
}
@Override
- public void skipToQueueItem(String packageName, ISessionControllerCallback caller,
+ public void skipToQueueItem(String packageName, ControllerCallbackLink caller,
long id) {
mSessionCb.skipToTrack(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
caller, id);
}
@Override
- public void pause(String packageName, ISessionControllerCallback caller) {
+ public void pause(String packageName, ControllerCallbackLink caller) {
mSessionCb.pause(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller);
}
@Override
- public void stop(String packageName, ISessionControllerCallback caller) {
+ public void stop(String packageName, ControllerCallbackLink caller) {
mSessionCb.stop(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller);
}
@Override
- public void next(String packageName, ISessionControllerCallback caller) {
+ public void next(String packageName, ControllerCallbackLink caller) {
mSessionCb.next(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller);
}
@Override
- public void previous(String packageName, ISessionControllerCallback caller) {
+ public void previous(String packageName, ControllerCallbackLink caller) {
mSessionCb.previous(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
caller);
}
@Override
- public void fastForward(String packageName, ISessionControllerCallback caller) {
+ public void fastForward(String packageName, ControllerCallbackLink caller) {
mSessionCb.fastForward(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
caller);
}
@Override
- public void rewind(String packageName, ISessionControllerCallback caller) {
+ public void rewind(String packageName, ControllerCallbackLink caller) {
mSessionCb.rewind(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller);
}
@Override
- public void seekTo(String packageName, ISessionControllerCallback caller, long pos) {
+ public void seekTo(String packageName, ControllerCallbackLink caller, long pos) {
mSessionCb.seekTo(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller,
pos);
}
@Override
- public void rate(String packageName, ISessionControllerCallback caller, Rating rating) {
+ public void rate(String packageName, ControllerCallbackLink caller, Rating rating) {
mSessionCb.rate(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller,
rating);
}
@Override
- public void sendCustomAction(String packageName, ISessionControllerCallback caller,
+ public void sendCustomAction(String packageName, ControllerCallbackLink caller,
String action, Bundle args) {
mSessionCb.sendCustomAction(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
caller, action, args);
@@ -1404,7 +1426,7 @@
}
@Override
- public ParceledListSlice getQueue() {
+ public List<QueueItem> getQueue() {
synchronized (mLock) {
return mQueue;
}
@@ -1433,12 +1455,12 @@
}
}
- private class ISessionControllerCallbackHolder {
- private final ISessionControllerCallback mCallback;
+ private class ControllerCallbackLinkHolder {
+ private final ControllerCallbackLink mCallback;
private final String mPackageName;
private final int mUid;
- ISessionControllerCallbackHolder(ISessionControllerCallback callback, String packageName,
+ ControllerCallbackLinkHolder(ControllerCallbackLink callback, String packageName,
int uid) {
mCallback = callback;
mPackageName = packageName;
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index d51da99..ce0e72b 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -40,19 +40,23 @@
import android.media.AudioSystem;
import android.media.IAudioService;
import android.media.IRemoteVolumeController;
+import android.media.MediaController2;
+import android.media.Session2CommandGroup;
+import android.media.Session2Token;
import android.media.session.IActiveSessionsListener;
import android.media.session.ICallback;
import android.media.session.IOnMediaKeyListener;
import android.media.session.IOnVolumeKeyLongPressListener;
import android.media.session.ISession;
-import android.media.session.ISessionCallback;
import android.media.session.ISessionManager;
import android.media.session.MediaSession;
import android.media.session.MediaSessionManager;
+import android.media.session.SessionCallbackLink;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.Message;
import android.os.PowerManager;
@@ -72,6 +76,7 @@
import android.view.KeyEvent;
import android.view.ViewConfiguration;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.DumpUtils;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -96,17 +101,24 @@
private static final int MEDIA_KEY_LISTENER_TIMEOUT = 1000;
private final SessionManagerImpl mSessionManagerImpl;
-
- // Keeps the full user id for each user.
- private final SparseIntArray mFullUserIds = new SparseIntArray();
- private final SparseArray<FullUserRecord> mUserRecords = new SparseArray<FullUserRecord>();
- private final ArrayList<SessionsListenerRecord> mSessionsListeners
- = new ArrayList<SessionsListenerRecord>();
- private final Object mLock = new Object();
private final MessageHandler mHandler = new MessageHandler();
private final PowerManager.WakeLock mMediaEventWakeLock;
private final int mLongPressTimeout;
private final INotificationManager mNotificationManager;
+ private final Object mLock = new Object();
+ // Keeps the full user id for each user.
+ @GuardedBy("mLock")
+ private final SparseIntArray mFullUserIds = new SparseIntArray();
+ @GuardedBy("mLock")
+ private final SparseArray<FullUserRecord> mUserRecords = new SparseArray<FullUserRecord>();
+ @GuardedBy("mLock")
+ private final ArrayList<SessionsListenerRecord> mSessionsListeners
+ = new ArrayList<SessionsListenerRecord>();
+ // Map user id as index to list of Session2Tokens
+ // TODO: Keep session2 info in MediaSessionStack for prioritizing both session1 and session2 in
+ // one place.
+ @GuardedBy("mLock")
+ private final SparseArray<List<Session2Token>> mSession2TokensPerUser = new SparseArray<>();
private KeyguardManager mKeyguardManager;
private IAudioService mAudioService;
@@ -294,10 +306,13 @@
updateUser();
}
+ // Called when the user with the userId is removed.
@Override
public void onStopUser(int userId) {
if (DEBUG) Log.d(TAG, "onStopUser: " + userId);
synchronized (mLock) {
+ // TODO: Also handle removing user in updateUser() because adding/switching user is
+ // handled in updateUser().
FullUserRecord user = getFullUserRecordLocked(userId);
if (user != null) {
if (user.mFullUserId == userId) {
@@ -307,6 +322,7 @@
user.destroySessionsForUserLocked(userId);
}
}
+ mSession2TokensPerUser.remove(userId);
updateUser();
}
}
@@ -352,6 +368,9 @@
mUserRecords.put(userInfo.id, new FullUserRecord(userInfo.id));
}
}
+ if (mSession2TokensPerUser.get(userInfo.id) == null) {
+ mSession2TokensPerUser.put(userInfo.id, new ArrayList<>());
+ }
}
}
// Ensure that the current full user exists.
@@ -361,6 +380,9 @@
Log.w(TAG, "Cannot find FullUserInfo for the current user " + currentFullUserId);
mCurrentFullUserRecord = new FullUserRecord(currentFullUserId);
mUserRecords.put(currentFullUserId, mCurrentFullUserRecord);
+ if (mSession2TokensPerUser.get(currentFullUserId) == null) {
+ mSession2TokensPerUser.put(currentFullUserId, new ArrayList<>());
+ }
}
mFullUserIds.put(currentFullUserId, currentFullUserId);
}
@@ -414,7 +436,7 @@
}
try {
- session.getCallback().asBinder().unlinkToDeath(session, 0);
+ session.getCallback().getBinder().unlinkToDeath(session, 0);
} catch (Exception e) {
// ignore exceptions while destroying a session.
}
@@ -500,7 +522,7 @@
}
private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
- String callerPackageName, ISessionCallback cb, String tag) throws RemoteException {
+ String callerPackageName, SessionCallbackLink cb, String tag) throws RemoteException {
synchronized (mLock) {
return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag);
}
@@ -514,7 +536,7 @@
* 4. It needs to be added to the relevant user record.
*/
private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
- String callerPackageName, ISessionCallback cb, String tag) {
+ String callerPackageName, SessionCallbackLink cb, String tag) {
FullUserRecord user = getFullUserRecordLocked(userId);
if (user == null) {
Log.wtf(TAG, "Request from invalid user: " + userId);
@@ -524,7 +546,7 @@
final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
callerPackageName, cb, tag, this, mHandler.getLooper());
try {
- cb.asBinder().linkToDeath(session, 0);
+ cb.getBinder().linkToDeath(session, 0);
} catch (RemoteException e) {
throw new RuntimeException("Media Session owner died prematurely.", e);
}
@@ -721,6 +743,16 @@
pw.println(indent + "Restored MediaButtonReceiverComponentType: "
+ mRestoredMediaButtonReceiverComponentType);
mPriorityStack.dump(pw, indent);
+ pw.println(indent + "Session2Tokens:");
+ for (int i = 0; i < mSession2TokensPerUser.size(); i++) {
+ List<Session2Token> list = mSession2TokensPerUser.valueAt(i);
+ if (list == null || list.size() == 0) {
+ continue;
+ }
+ for (Session2Token token : list) {
+ pw.println(indent + " " + token);
+ }
+ }
}
@Override
@@ -875,7 +907,7 @@
private boolean mVoiceButtonHandled = false;
@Override
- public ISession createSession(String packageName, ISessionCallback cb, String tag,
+ public ISession createSession(String packageName, SessionCallbackLink cb, String tag,
int userId) throws RemoteException {
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
@@ -895,6 +927,31 @@
}
@Override
+ public void notifySession2Created(Session2Token sessionToken) throws RemoteException {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "Session2 is created " + sessionToken);
+ }
+ if (uid != sessionToken.getUid()) {
+ throw new SecurityException("Unexpected Session2Token's UID, expected=" + uid
+ + " but actually=" + sessionToken.getUid());
+ }
+ Controller2Callback callback = new Controller2Callback(sessionToken);
+ // Note: It's safe not to keep controller here because it wouldn't be GC'ed until
+ // it's closed.
+ // TODO: Keep controller as well for better readability
+ // because the GC behavior isn't straightforward.
+ MediaController2 controller = new MediaController2(getContext(), sessionToken,
+ new HandlerExecutor(mHandler), callback);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
public List<IBinder> getSessions(ComponentName componentName, int userId) {
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
@@ -916,6 +973,34 @@
}
@Override
+ public List<Session2Token> getSession2Tokens(int userId) {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+
+ try {
+ // Check that they can make calls on behalf of the user and
+ // get the final user id
+ int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
+ true /* allowAll */, true /* requireFull */, "getSession2Tokens",
+ null /* optional packageName */);
+ List<Session2Token> result = new ArrayList<>();
+ synchronized (mLock) {
+ if (resolvedUserId == UserHandle.USER_ALL) {
+ for (int i = 0; i < mSession2TokensPerUser.size(); i++) {
+ result.addAll(mSession2TokensPerUser.valueAt(i));
+ }
+ } else {
+ result.addAll(mSession2TokensPerUser.get(userId));
+ }
+ }
+ return result;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
public void addSessionsListener(IActiveSessionsListener listener,
ComponentName componentName, int userId) throws RemoteException {
final int pid = Binder.getCallingPid();
@@ -1914,4 +1999,28 @@
obtainMessage(MSG_SESSIONS_CHANGED, userIdInteger).sendToTarget();
}
}
+
+ private class Controller2Callback extends MediaController2.ControllerCallback {
+ private final Session2Token mToken;
+
+ Controller2Callback(Session2Token token) {
+ mToken = token;
+ }
+
+ @Override
+ public void onConnected(MediaController2 controller, Session2CommandGroup allowedCommands) {
+ synchronized (mLock) {
+ int userId = UserHandle.getUserId(mToken.getUid());
+ mSession2TokensPerUser.get(userId).add(mToken);
+ }
+ }
+
+ @Override
+ public void onDisconnected(MediaController2 controller) {
+ synchronized (mLock) {
+ int userId = UserHandle.getUserId(mToken.getUid());
+ mSession2TokensPerUser.get(userId).remove(mToken);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 0d6dadf..af55605 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -270,14 +270,12 @@
* enforcement.
*
* <p>
- * This class uses 2-3 locks to synchronize state:
+ * This class uses 2 locks to synchronize state:
* <ul>
* <li>{@code mUidRulesFirstLock}: used to guard state related to individual UIDs (such as firewall
* rules).
* <li>{@code mNetworkPoliciesSecondLock}: used to guard state related to network interfaces (such
* as network policies).
- * <li>{@code allLocks}: not a "real" lock, but an indication (through @GuardedBy) that all locks
- * must be held.
* </ul>
*
* <p>
@@ -419,7 +417,8 @@
final Object mUidRulesFirstLock = new Object();
final Object mNetworkPoliciesSecondLock = new Object();
- @GuardedBy("allLocks") volatile boolean mSystemReady;
+ @GuardedBy({"mUidRulesFirstLock", "mNetworkPoliciesSecondLock"})
+ volatile boolean mSystemReady;
@GuardedBy("mUidRulesFirstLock") volatile boolean mRestrictBackground;
@GuardedBy("mUidRulesFirstLock") volatile boolean mRestrictPower;
@@ -558,7 +557,7 @@
private final ServiceThread mUidEventThread;
- @GuardedBy("allLocks")
+ @GuardedBy({"mUidRulesFirstLock", "mNetworkPoliciesSecondLock"})
private final AtomicFile mPolicyFile;
private final AppOpsManager mAppOps;
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index dc3bfbc..8c274e4 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -368,7 +368,7 @@
mStatsObservers = checkNotNull(statsObservers, "missing NetworkStatsObservers");
mSystemDir = checkNotNull(systemDir, "missing systemDir");
mBaseDir = checkNotNull(baseDir, "missing baseDir");
- mUseBpfTrafficStats = new File("/sys/fs/bpf/traffic_uid_stats_map").exists();
+ mUseBpfTrafficStats = new File("/sys/fs/bpf/map_netd_app_uid_stats_map").exists();
}
private void registerLocalService() {
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 88d73fb..c222e69 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -767,6 +767,23 @@
return installed;
}
+ protected Set<String> getAllowedPackages() {
+ final Set<String> allowedPackages = new ArraySet<>();
+ for (int k = 0; k < mApproved.size(); k++) {
+ ArrayMap<Boolean, ArraySet<String>> allowedByType = mApproved.valueAt(k);
+ for (int i = 0; i < allowedByType.size(); i++) {
+ final ArraySet<String> allowed = allowedByType.valueAt(i);
+ for (int j = 0; j < allowed.size(); j++) {
+ String pkgName = getPackageName(allowed.valueAt(j));
+ if (!TextUtils.isEmpty(pkgName)) {
+ allowedPackages.add(pkgName);
+ }
+ }
+ }
+ }
+ return allowedPackages;
+ }
+
private void trimApprovedListsAccordingToInstalledServices() {
int N = mApproved.size();
for (int i = 0 ; i < N; i++) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index c2c8825..c68e0f9 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -765,8 +765,7 @@
.addTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX, nv.rank)
.addTaggedData(MetricsEvent.NOTIFICATION_SHADE_COUNT, nv.count)
.addTaggedData(MetricsEvent.NOTIFICATION_ACTION_IS_SMART,
- (Notification.Action.SEMANTIC_ACTION_CONTEXTUAL_SUGGESTION
- == action.getSemanticAction()) ? 1 : 0)
+ action.isContextual() ? 1 : 0)
.addTaggedData(
MetricsEvent.NOTIFICATION_SMART_SUGGESTION_ASSISTANT_GENERATED,
generatedByAssistant ? 1 : 0));
@@ -970,7 +969,7 @@
r.getNumSmartActionsAdded())
.addTaggedData(
MetricsEvent.NOTIFICATION_SMART_SUGGESTION_ASSISTANT_GENERATED,
- r.getSuggestionsGeneratedByAssistant());
+ r.getSuggestionsGeneratedByAssistant() ? 1 : 0);
mMetricsLogger.write(logMaker);
}
}
@@ -1702,8 +1701,16 @@
}
private void sendRegisteredOnlyBroadcast(String action) {
- getContext().sendBroadcastAsUser(new Intent(action)
- .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), UserHandle.ALL, null);
+ Intent intent = new Intent(action);
+ getContext().sendBroadcastAsUser(intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY),
+ UserHandle.ALL, null);
+ // explicitly send the broadcast to all DND packages, even if they aren't currently running
+ intent.setFlags(0);
+ final Set<String> dndApprovedPackages = mConditionProviders.getAllowedPackages();
+ for (String pkg : dndApprovedPackages) {
+ intent.setPackage(pkg);
+ getContext().sendBroadcastAsUser(intent, UserHandle.ALL);
+ }
}
@Override
@@ -2642,6 +2649,9 @@
// Zen
mConditionProviders.onPackagesChanged(true, packages, uids);
+ // Snoozing
+ mSnoozeHelper.clearData(UserHandle.getUserId(uid), packageName);
+
// Reset notification preferences
if (!fromApp) {
mPreferencesHelper.onPackagesChanged(
@@ -5698,19 +5708,17 @@
}
int indexBefore = findNotificationRecordIndexLocked(record);
boolean interceptBefore = record.isIntercepted();
- float contactAffinityBefore = record.getContactAffinity();
int visibilityBefore = record.getPackageVisibilityOverride();
recon.applyChangesLocked(record);
applyZenModeLocked(record);
mRankingHelper.sort(mNotificationList);
int indexAfter = findNotificationRecordIndexLocked(record);
boolean interceptAfter = record.isIntercepted();
- float contactAffinityAfter = record.getContactAffinity();
int visibilityAfter = record.getPackageVisibilityOverride();
changed = indexBefore != indexAfter || interceptBefore != interceptAfter
|| visibilityBefore != visibilityAfter;
if (interceptBefore && !interceptAfter
- && Float.compare(contactAffinityBefore, contactAffinityAfter) != 0) {
+ && record.isNewEnoughForAlerting(System.currentTimeMillis())) {
buzzBeepBlinkLocked(record);
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index e2c64ca..9942f59 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -92,6 +92,8 @@
static final String TAG = "NotificationRecord";
static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
private static final int MAX_LOGTAG_LENGTH = 35;
+ // the period after which a notification is updated where it can make sound
+ private static final int MAX_SOUND_DELAY_MS = 2000;
final StatusBarNotification sbn;
IActivityManager mAm;
UriGrantsManagerInternal mUgmInternal;
@@ -125,7 +127,8 @@
private long mVisibleSinceMs;
// The most recent update time, or the creation time if no updates.
- private long mUpdateTimeMs;
+ @VisibleForTesting
+ final long mUpdateTimeMs;
// The most recent interruption time, or the creation time if no updates. Differs from the
// above value because updates are filtered based on whether they actually interrupted the
@@ -165,7 +168,7 @@
private String mChannelIdLogTag;
/**
* This list contains system generated smart actions from NAS, app-generated smart actions are
- * stored in Notification.actions marked as SEMANTIC_ACTION_CONTEXTUAL_SUGGESTION.
+ * stored in Notification.actions with isContextual() set to true.
*/
private ArrayList<Notification.Action> mSystemGeneratedSmartActions;
private ArrayList<CharSequence> mSmartReplies;
@@ -826,6 +829,10 @@
return mIntercept;
}
+ public boolean isNewEnoughForAlerting(long now) {
+ return getFreshnessMs(now) <= MAX_SOUND_DELAY_MS;
+ }
+
public void setHidden(boolean hidden) {
mHidden = hidden;
}
diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java
index 2b581d6..abc9841 100644
--- a/services/core/java/com/android/server/notification/SnoozeHelper.java
+++ b/services/core/java/com/android/server/notification/SnoozeHelper.java
@@ -17,6 +17,7 @@
import android.annotation.NonNull;
import android.app.AlarmManager;
+import android.app.Notification;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -163,7 +164,6 @@
mSnoozedNotifications.get(userId).get(pkg);
if (recordsForPkg != null) {
final Set<Map.Entry<String, NotificationRecord>> records = recordsForPkg.entrySet();
- String key = null;
for (Map.Entry<String, NotificationRecord> record : records) {
final StatusBarNotification sbn = record.getValue().sbn;
if (Objects.equals(sbn.getTag(), tag) && sbn.getId() == id) {
@@ -305,6 +305,30 @@
}
}
+ protected void clearData(int userId, String pkg) {
+ ArrayMap<String, ArrayMap<String, NotificationRecord>> records =
+ mSnoozedNotifications.get(userId);
+ if (records == null) {
+ return;
+ }
+ ArrayMap<String, NotificationRecord> pkgRecords = records.get(pkg);
+ if (pkgRecords == null) {
+ return;
+ }
+ for (int i = pkgRecords.size() - 1; i >= 0; i--) {
+ final NotificationRecord r = pkgRecords.removeAt(i);
+ if (r != null) {
+ mPackages.remove(r.getKey());
+ mUsers.remove(r.getKey());
+ final PendingIntent pi = createPendingIntent(pkg, r.getKey(), userId);
+ mAm.cancel(pi);
+ MetricsLogger.action(r.getLogMaker()
+ .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED)
+ .setType(MetricsProto.MetricsEvent.TYPE_DISMISS));
+ }
+ }
+ }
+
private PendingIntent createPendingIntent(String pkg, String key, int userId) {
return PendingIntent.getBroadcast(mContext,
REQUEST_CODE_REPOST,
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index e5b1878..afc0b72 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -375,8 +375,7 @@
newConfig = mConfig.copy();
for (int i = newConfig.automaticRules.size() - 1; i >= 0; i--) {
ZenRule rule = newConfig.automaticRules.get(newConfig.automaticRules.keyAt(i));
- if (rule.component.getPackageName().equals(packageName)
- && canManageAutomaticZenRule(rule)) {
+ if (rule.pkg.equals(packageName) && canManageAutomaticZenRule(rule)) {
newConfig.automaticRules.removeAt(i);
}
}
diff --git a/services/core/java/com/android/server/os/BugreportManagerService.java b/services/core/java/com/android/server/os/BugreportManagerService.java
new file mode 100644
index 0000000..e241591
--- /dev/null
+++ b/services/core/java/com/android/server/os/BugreportManagerService.java
@@ -0,0 +1,43 @@
+/*
+ * 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.os;
+
+import android.content.Context;
+
+import com.android.server.SystemService;
+
+/**
+ * Service that provides a privileged API to capture and consume bugreports.
+ *
+ * @hide
+ */
+public class BugreportManagerService extends SystemService {
+ private static final String TAG = "BugreportManagerService";
+
+ private BugreportManagerServiceImpl mService;
+
+ public BugreportManagerService(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ mService = new BugreportManagerServiceImpl(getContext());
+ // TODO(b/111441001): Needs sepolicy to be submitted first.
+ // publishBinderService(Context.BUGREPORT_SERVICE, mService);
+ }
+}
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
new file mode 100644
index 0000000..faa4714
--- /dev/null
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -0,0 +1,130 @@
+/*
+ * 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.os;
+
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.os.BugreportParams;
+import android.os.IDumpstate;
+import android.os.IDumpstateListener;
+import android.os.IDumpstateToken;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.util.Slog;
+
+import java.io.FileDescriptor;
+
+// TODO(b/111441001):
+// 1. Handle the case where another bugreport is in progress
+// 2. Make everything threadsafe
+// 3. Pass validation & other errors on listener
+
+/**
+ * Implementation of the service that provides a privileged API to capture and consume bugreports.
+ *
+ * <p>Delegates the actualy generation to a native implementation of {@code Dumpstate}.
+ */
+class BugreportManagerServiceImpl extends IDumpstate.Stub {
+ private static final String TAG = "BugreportManagerService";
+ private static final long DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS = 30 * 1000;
+
+ private IDumpstate mDs = null;
+ private final Context mContext;
+
+ BugreportManagerServiceImpl(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ @RequiresPermission(android.Manifest.permission.DUMP)
+ public IDumpstateToken setListener(String name, IDumpstateListener listener,
+ boolean getSectionDetails) throws RemoteException {
+ // TODO(b/111441001): Figure out if lazy setting of listener should be allowed
+ // and if so how to handle it.
+ throw new UnsupportedOperationException("setListener is not allowed on this service");
+ }
+
+
+ @Override
+ @RequiresPermission(android.Manifest.permission.DUMP)
+ public void startBugreport(FileDescriptor bugreportFd, FileDescriptor screenshotFd,
+ int bugreportMode, IDumpstateListener listener) throws RemoteException {
+
+ validate(bugreportMode);
+
+ mDs = getDumpstateService();
+ if (mDs == null) {
+ Slog.w(TAG, "Unable to get bugreport service");
+ // TODO(b/111441001): pass error on listener
+ return;
+ }
+ mDs.startBugreport(bugreportFd, screenshotFd, bugreportMode, listener);
+ }
+
+ private boolean validate(@BugreportParams.BugreportMode int mode) {
+ if (mode != BugreportParams.BUGREPORT_MODE_FULL
+ && mode != BugreportParams.BUGREPORT_MODE_INTERACTIVE
+ && mode != BugreportParams.BUGREPORT_MODE_REMOTE
+ && mode != BugreportParams.BUGREPORT_MODE_WEAR
+ && mode != BugreportParams.BUGREPORT_MODE_TELEPHONY
+ && mode != BugreportParams.BUGREPORT_MODE_WIFI) {
+ Slog.w(TAG, "Unknown bugreport mode: " + mode);
+ return false;
+ }
+ return true;
+ }
+
+ /*
+ * Start and get a handle to the native implementation of {@code IDumpstate} which does the
+ * actual bugreport generation.
+ *
+ * <p>Generating bugreports requires root privileges. To limit the footprint
+ * of the root access, the actual generation in Dumpstate binary is accessed as a
+ * oneshot service 'bugreport'.
+ */
+ private IDumpstate getDumpstateService() {
+ // Start bugreport service.
+ SystemProperties.set("ctl.start", "bugreport");
+
+ IDumpstate ds = null;
+ boolean timedOut = false;
+ int totalTimeWaitedMillis = 0;
+ int seedWaitTimeMillis = 500;
+ while (!timedOut) {
+ // Note that the binder service on the native side is "dumpstate".
+ ds = IDumpstate.Stub.asInterface(ServiceManager.getService("dumpstate"));
+ if (ds != null) {
+ Slog.i(TAG, "Got bugreport service handle.");
+ break;
+ }
+ SystemClock.sleep(seedWaitTimeMillis);
+ Slog.i(TAG,
+ "Waiting to get dumpstate service handle (" + totalTimeWaitedMillis + "ms)");
+ totalTimeWaitedMillis += seedWaitTimeMillis;
+ seedWaitTimeMillis *= 2;
+ timedOut = totalTimeWaitedMillis > DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS;
+ }
+ if (timedOut) {
+ Slog.w(TAG,
+ "Timed out waiting to get dumpstate service handle ("
+ + totalTimeWaitedMillis + "ms)");
+ }
+ return ds;
+ }
+}
diff --git a/services/core/java/com/android/server/pm/ComponentResolver.java b/services/core/java/com/android/server/pm/ComponentResolver.java
index 3b11525..8facce1 100644
--- a/services/core/java/com/android/server/pm/ComponentResolver.java
+++ b/services/core/java/com/android/server/pm/ComponentResolver.java
@@ -42,6 +42,7 @@
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.DebugUtils;
import android.util.Log;
import android.util.LogPrinter;
import android.util.Pair;
@@ -60,6 +61,7 @@
/** Resolves all Android component types [activities, services, providers and receivers]. */
public class ComponentResolver {
+ private static final boolean DEBUG = false;
private static final String TAG = "PackageManager";
private static final boolean DEBUG_FILTERS = false;
private static final boolean DEBUG_SHOW_INFO = false;
@@ -1198,22 +1200,48 @@
return packageName.equals(info.activity.owner.packageName);
}
+ private void log(String reason, ActivityIntentInfo info, int match,
+ int userId) {
+ Slog.w(TAG, reason
+ + "; match: "
+ + DebugUtils.flagsToString(IntentFilter.class, "MATCH_", match)
+ + "; userId: " + userId
+ + "; intent info: " + info);
+ }
+
@Override
protected ResolveInfo newResult(PackageParser.ActivityIntentInfo info,
int match, int userId) {
- if (!sUserManager.exists(userId)) return null;
+ if (!sUserManager.exists(userId)) {
+ if (DEBUG) {
+ log("User doesn't exist", info, match, userId);
+ }
+ return null;
+ }
if (!sPackageManagerInternal.isEnabledAndMatches(info.activity.info, mFlags, userId)) {
+ if (DEBUG) {
+ log("!PackageManagerInternal.isEnabledAndMatches; mFlags="
+ + DebugUtils.flagsToString(PackageManager.class, "MATCH_", mFlags),
+ info, match, userId);
+ }
return null;
}
final PackageParser.Activity activity = info.activity;
PackageSetting ps = (PackageSetting) activity.owner.mExtras;
if (ps == null) {
+ if (DEBUG) {
+ log("info.activity.owner.mExtras == null", info, match, userId);
+ }
return null;
}
final PackageUserState userState = ps.readUserState(userId);
ActivityInfo ai =
PackageParser.generateActivityInfo(activity, mFlags, userState, userId);
if (ai == null) {
+ if (DEBUG) {
+ log("Failed to create ActivityInfo based on " + info.activity, info, match,
+ userId);
+ }
return null;
}
final boolean matchExplicitlyVisibleOnly =
@@ -1227,15 +1255,31 @@
final boolean matchInstantApp = (mFlags & PackageManager.MATCH_INSTANT) != 0;
// throw out filters that aren't visible to ephemeral apps
if (matchVisibleToInstantApp && !(componentVisible || userState.instantApp)) {
+ if (DEBUG) {
+ log("Filter(s) not visible to ephemeral apps"
+ + "; matchVisibleToInstantApp=" + matchVisibleToInstantApp
+ + "; matchInstantApp=" + matchInstantApp
+ + "; info.isVisibleToInstantApp()=" + info.isVisibleToInstantApp()
+ + "; matchExplicitlyVisibleOnly=" + matchExplicitlyVisibleOnly
+ + "; info.isExplicitlyVisibleToInstantApp()="
+ + info.isExplicitlyVisibleToInstantApp(),
+ info, match, userId);
+ }
return null;
}
// throw out instant app filters if we're not explicitly requesting them
if (!matchInstantApp && userState.instantApp) {
+ if (DEBUG) {
+ log("Instant app filter is not explicitly requested", info, match, userId);
+ }
return null;
}
// throw out instant app filters if updates are available; will trigger
// instant app resolution
if (userState.instantApp && ps.isUpdateAvailable()) {
+ if (DEBUG) {
+ log("Instant app update is available", info, match, userId);
+ }
return null;
}
final ResolveInfo res = new ResolveInfo();
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index f9e31ae..5412e94 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -85,9 +85,6 @@
// One minute over PM WATCHDOG_TIMEOUT
private static final long WAKELOCK_TIMEOUT_MS = WATCHDOG_TIMEOUT + 1000 * 60;
- /** Special library name that skips shared libraries check during compilation. */
- public static final String SKIP_SHARED_LIBRARY_CHECK = "&";
-
@GuardedBy("mInstallLock")
private final Installer mInstaller;
private final Object mInstallLock;
@@ -397,23 +394,23 @@
Slog.e(TAG, "Could not infer CE/DE storage for package " + info.packageName);
return DEX_OPT_FAILED;
}
- Log.d(TAG, "Running dexopt on: " + path
- + " pkg=" + info.packageName + " isa=" + dexUseInfo.getLoaderIsas()
- + " dexoptFlags=" + printDexoptFlags(dexoptFlags)
- + " target-filter=" + compilerFilter);
-
- String classLoaderContext;
+ String classLoaderContext = null;
if (dexUseInfo.isUnknownClassLoaderContext() || dexUseInfo.isVariableClassLoaderContext()) {
- // If we have an unknown (not yet set), or a variable class loader chain, compile
- // without a context and mark the oat file with SKIP_SHARED_LIBRARY_CHECK. Note that
- // this might lead to a incorrect compilation.
- // TODO(calin): We should just extract in this case.
- classLoaderContext = SKIP_SHARED_LIBRARY_CHECK;
+ // If we have an unknown (not yet set), or a variable class loader chain. Just extract
+ // the dex file.
+ compilerFilter = "extract";
} else {
classLoaderContext = dexUseInfo.getClassLoaderContext();
}
int reason = options.getCompilationReason();
+ Log.d(TAG, "Running dexopt on: " + path
+ + " pkg=" + info.packageName + " isa=" + dexUseInfo.getLoaderIsas()
+ + " reason=" + getReasonName(reason)
+ + " dexoptFlags=" + printDexoptFlags(dexoptFlags)
+ + " target-filter=" + compilerFilter
+ + " class-loader-context=" + classLoaderContext);
+
try {
for (String isa : dexUseInfo.getLoaderIsas()) {
// Reuse the same dexopt path as for the primary apks. We don't need all the
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 7ca39df..d0b20e8 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -329,6 +329,9 @@
if (valid) {
mSessions.put(session.sessionId, session);
+ if (session.isStaged()) {
+ mStagingManager.restoreSession(session);
+ }
} else {
// Since this is early during boot we don't send
// any observer events about the session, but we
@@ -533,7 +536,7 @@
session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this,
mInstallThread.getLooper(), mStagingManager, sessionId, userId,
installerPackageName, callingUid, params, createdMillis, stageDir, stageCid, false,
- false, null, SessionInfo.INVALID_ID);
+ false, null, SessionInfo.INVALID_ID, false, false, false, SessionInfo.NO_ERROR);
synchronized (mSessions) {
mSessions.put(sessionId, session);
@@ -1131,7 +1134,9 @@
}
}
synchronized (mSessions) {
- mSessions.remove(session.sessionId);
+ if (!session.isStaged() || !success) {
+ mSessions.remove(session.sessionId);
+ }
addHistoricalSessionLocked(session);
final File appIconFile = buildAppIconFile(session.sessionId);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 982daa5..516927f 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -57,6 +57,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionInfo;
+import android.content.pm.PackageInstaller.SessionInfo.StagedSessionErrorCode;
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
@@ -150,6 +151,10 @@
private static final String ATTR_MULTI_PACKAGE = "multiPackage";
private static final String ATTR_PARENT_SESSION_ID = "parentSessionId";
private static final String ATTR_STAGED_SESSION = "stagedSession";
+ private static final String ATTR_IS_READY = "isReady";
+ private static final String ATTR_IS_FAILED = "isFailed";
+ private static final String ATTR_IS_APPLIED = "isApplied";
+ private static final String ATTR_STAGED_SESSION_ERROR_CODE = "errorCode";
private static final String ATTR_MODE = "mode";
private static final String ATTR_INSTALL_FLAGS = "installFlags";
private static final String ATTR_INSTALL_LOCATION = "installLocation";
@@ -407,7 +412,8 @@
int sessionId, int userId,
String installerPackageName, int installerUid, SessionParams params, long createdMillis,
File stageDir, String stageCid, boolean prepared, boolean sealed,
- @Nullable int[] childSessionIds, int parentSessionId) {
+ @Nullable int[] childSessionIds, int parentSessionId, boolean isReady,
+ boolean isFailed, boolean isApplied, int stagedSessionErrorCode) {
mCallback = callback;
mContext = context;
mPm = pm;
@@ -437,7 +443,10 @@
}
mPrepared = prepared;
-
+ mStagedSessionReady = isReady;
+ mStagedSessionFailed = isFailed;
+ mStagedSessionApplied = isApplied;
+ mStagedSessionErrorCode = stagedSessionErrorCode;
if (sealed) {
synchronized (mLock) {
try {
@@ -982,9 +991,9 @@
mSealed = true;
// Read transfers from the original owner stay open, but as the session's data
- // cannot be modified anymore, there is no leak of information.
- // For staged sessions, the validation is performed by StagingManager.
- if (!params.isMultiPackage && !params.isStaged) {
+ // cannot be modified anymore, there is no leak of information. For staged sessions,
+ // further validation may be performed by the staging manager.
+ if (!params.isMultiPackage) {
final PackageInfo pkgInfo = mPm.getPackageInfo(
params.appPackageName, PackageManager.GET_SIGNATURES
| PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId);
@@ -1328,18 +1337,15 @@
mSigningDetails = apk.signingDetails;
mResolvedBaseFile = addedFile;
- assertApkConsistentLocked(String.valueOf(addedFile), apk);
-
- if (mSigningDetails == PackageParser.SigningDetails.UNKNOWN) {
- try {
- // STOPSHIP: For APEX we should also implement proper APK Signature verification.
- mSigningDetails = ApkSignatureVerifier.plsCertsNoVerifyOnlyCerts(
- pkgInfo.applicationInfo.sourceDir,
- PackageParser.SigningDetails.SignatureSchemeVersion.JAR);
- } catch (PackageParserException e) {
- throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
- "Couldn't obtain signatures from base APK");
- }
+ // STOPSHIP: Ensure that we remove the non-staged version of APEX installs in production
+ // because we currently do not verify that signatures are consistent with the previously
+ // installed version in that case.
+ //
+ // When that happens, this hack can be reverted and we can rely on APEXd to map between
+ // APEX files and their package names instead of parsing it out of the AndroidManifest
+ // such as here.
+ if (params.appPackageName == null) {
+ params.appPackageName = mPackageName;
}
}
@@ -1374,7 +1380,8 @@
"Missing existing base package");
}
// Default to require only if existing base has fs-verity.
- mVerityFound = params.mode == SessionParams.MODE_INHERIT_EXISTING
+ mVerityFound = PackageManagerServiceUtils.isApkVerityEnabled()
+ && params.mode == SessionParams.MODE_INHERIT_EXISTING
&& VerityUtils.hasFsverity(pkgInfo.applicationInfo.getBaseCodePath());
try {
@@ -2011,11 +2018,22 @@
}
}
+ /** {@hide} */
+ void setStagedSessionFailed(@StagedSessionErrorCode int errorCode) {
+ synchronized (mLock) {
+ mStagedSessionReady = false;
+ mStagedSessionApplied = false;
+ mStagedSessionFailed = true;
+ mStagedSessionErrorCode = errorCode;
+ }
+ }
+
private void destroyInternal() {
synchronized (mLock) {
mSealed = true;
- mDestroyed = true;
-
+ if (!params.isStaged) {
+ mDestroyed = true;
+ }
// Force shut down all bridges
for (RevocableFileDescriptor fd : mFds) {
fd.revoke();
@@ -2122,6 +2140,10 @@
writeBooleanAttribute(out, ATTR_MULTI_PACKAGE, params.isMultiPackage);
writeBooleanAttribute(out, ATTR_STAGED_SESSION, params.isStaged);
+ writeBooleanAttribute(out, ATTR_IS_READY, mStagedSessionReady);
+ writeBooleanAttribute(out, ATTR_IS_FAILED, mStagedSessionFailed);
+ writeBooleanAttribute(out, ATTR_IS_APPLIED, mStagedSessionApplied);
+ writeIntAttribute(out, ATTR_STAGED_SESSION_ERROR_CODE, mStagedSessionErrorCode);
// TODO(patb,109941548): avoid writing to xml and instead infer / validate this after
// we've read all sessions.
writeIntAttribute(out, ATTR_PARENT_SESSION_ID, mParentSessionId);
@@ -2260,10 +2282,16 @@
params.appIcon = BitmapFactory.decodeFile(appIconFile.getAbsolutePath());
params.appIconLastModified = appIconFile.lastModified();
}
+ final boolean isReady = readBooleanAttribute(in, ATTR_IS_READY);
+ final boolean isFailed = readBooleanAttribute(in, ATTR_IS_FAILED);
+ final boolean isApplied = readBooleanAttribute(in, ATTR_IS_APPLIED);
+ final int stagedSessionErrorCode = readIntAttribute(in, ATTR_STAGED_SESSION_ERROR_CODE);
+
return new PackageInstallerSession(callback, context, pm, sessionProvider,
installerThread, stagingManager, sessionId, userId, installerPackageName,
installerUid, params, createdMillis, stageDir, stageCid, prepared, sealed,
- EMPTY_CHILD_SESSION_ARRAY, parentSessionId);
+ EMPTY_CHILD_SESSION_ARRAY, parentSessionId, isReady, isFailed, isApplied,
+ stagedSessionErrorCode);
}
/**
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 5c4c1ba..2dc5437 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -8549,16 +8549,16 @@
}
/**
- * Returns if full apk verification can be skipped for the whole package, including the splits.
+ * Returns if forced apk verification can be skipped for the whole package, including splits.
*/
- private boolean canSkipFullPackageVerification(PackageParser.Package pkg) {
- if (!canSkipFullApkVerification(pkg.baseCodePath)) {
+ private boolean canSkipForcedPackageVerification(PackageParser.Package pkg) {
+ if (!canSkipForcedApkVerification(pkg.baseCodePath)) {
return false;
}
// TODO: Allow base and splits to be verified individually.
if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
for (int i = 0; i < pkg.splitCodePaths.length; i++) {
- if (!canSkipFullApkVerification(pkg.splitCodePaths[i])) {
+ if (!canSkipForcedApkVerification(pkg.splitCodePaths[i])) {
return false;
}
}
@@ -8567,14 +8567,17 @@
}
/**
- * Returns if full apk verification can be skipped, depending on current FSVerity setup and
+ * Returns if forced apk verification can be skipped, depending on current FSVerity setup and
* whether the apk contains signed root hash. Note that the signer's certificate still needs to
* match one in a trusted source, and should be done separately.
*/
- private boolean canSkipFullApkVerification(String apkPath) {
- final byte[] rootHashObserved;
+ private boolean canSkipForcedApkVerification(String apkPath) {
+ if (!PackageManagerServiceUtils.isLegacyApkVerityEnabled()) {
+ return VerityUtils.hasFsverity(apkPath);
+ }
+
try {
- rootHashObserved = VerityUtils.generateApkVerityRootHash(apkPath);
+ final byte[] rootHashObserved = VerityUtils.generateApkVerityRootHash(apkPath);
if (rootHashObserved == null) {
return false; // APK does not contain Merkle tree root hash.
}
@@ -8746,7 +8749,7 @@
// in verified partition, or can be verified on access (when apk verity is enabled). In both
// cases, only data in Signing Block is verified instead of the whole file.
final boolean skipVerify = scanSystemPartition
- || (forceCollect && canSkipFullPackageVerification(pkg));
+ || (forceCollect && canSkipForcedPackageVerification(pkg));
collectCertificatesLI(pkgSetting, pkg, forceCollect, skipVerify);
// Reset profile if the application version is changed
@@ -13559,7 +13562,8 @@
}
Signature[] callerSignature;
- Object obj = mSettings.getSettingLPr(callingUid);
+ final int appId = UserHandle.getAppId(callingUid);
+ final Object obj = mSettings.getSettingLPr(appId);
if (obj != null) {
if (obj instanceof SharedUserSetting) {
callerSignature =
@@ -15615,13 +15619,25 @@
if (!hasDynamicLibraries) {
return null;
}
+ final boolean isUpdatedSystemApp = pkg.isUpdatedSystemApp();
+ // We may not yet have disabled the updated package yet, so be sure to grab the
+ // current setting if that's the case.
+ final PackageSetting updatedSystemPs = isUpdatedSystemApp
+ ? scanResult.request.disabledPkgSetting == null
+ ? scanResult.request.oldPkgSetting
+ : scanResult.request.disabledPkgSetting
+ : null;
+ if (isUpdatedSystemApp && (updatedSystemPs.pkg == null
+ || updatedSystemPs.pkg.libraryNames == null)) {
+ Slog.w(TAG, "Package " + pkg.packageName + " declares libraries that are not "
+ + "declared on the system image; skipping");
+ return null;
+ }
final ArrayList<SharedLibraryInfo> infos =
new ArrayList<>(scanResult.dynamicSharedLibraryInfos.size());
- final boolean updatedSystemApp = pkg.isUpdatedSystemApp();
for (SharedLibraryInfo info : scanResult.dynamicSharedLibraryInfos) {
- String name = info.getName();
- boolean allowed = false;
- if (updatedSystemApp) {
+ final String name = info.getName();
+ if (isUpdatedSystemApp) {
// New library entries can only be added through the
// system image. This is important to get rid of a lot
// of nasty edge cases: for example if we allowed a non-
@@ -15631,36 +15647,20 @@
// have allowed apps on the device which aren't compatible
// with it. Better to just have the restriction here, be
// conservative, and create many fewer cases that can negatively
- // impact the user experience. We may not yet have disabled the
- // updated package yet, so be sure to grab the current setting if
- // that's the case.
- final PackageSetting sysPs = scanResult.request.disabledPkgSetting == null
- ? scanResult.request.oldPkgSetting
- : scanResult.request.disabledPkgSetting;
- if (sysPs.pkg != null && sysPs.pkg.libraryNames != null) {
- for (int j = 0; j < sysPs.pkg.libraryNames.size(); j++) {
- if (name.equals(sysPs.pkg.libraryNames.get(j))) {
- allowed = true;
- break;
- }
- }
- }
- } else {
- allowed = true;
- }
- if (allowed) {
- if (sharedLibExists(
- name, SharedLibraryInfo.VERSION_UNDEFINED, existingSharedLibraries)) {
- Slog.w(TAG, "Package " + pkg.packageName + " library "
- + name + " already exists; skipping");
+ // impact the user experience.
+ if (!updatedSystemPs.pkg.libraryNames.contains(name)) {
+ Slog.w(TAG, "Package " + pkg.packageName + " declares library " + name
+ + " that is not declared on system image; skipping");
continue;
}
- infos.add(info);
- } else {
- Slog.w(TAG, "Package " + pkg.packageName + " declares lib "
- + name + " that is not declared on system image; skipping");
+ }
+ if (sharedLibExists(
+ name, SharedLibraryInfo.VERSION_UNDEFINED, existingSharedLibraries)) {
+ Slog.w(TAG, "Package " + pkg.packageName + " declares library " + name
+ + " that already exists; skipping");
continue;
}
+ infos.add(info);
}
return infos;
}
@@ -16863,10 +16863,11 @@
*/
private void setUpFsVerityIfPossible(PackageParser.Package pkg) throws InstallerException,
PrepareFailure, IOException, DigestException, NoSuchAlgorithmException {
- if (!PackageManagerServiceUtils.isApkVerityEnabled()) {
+ final boolean standardMode = PackageManagerServiceUtils.isApkVerityEnabled();
+ final boolean legacyMode = PackageManagerServiceUtils.isLegacyApkVerityEnabled();
+ if (!standardMode && !legacyMode) {
return;
}
- final boolean legacyMode = PackageManagerServiceUtils.isLegacyApkVerityMode();
// Collect files we care for fs-verity setup.
ArrayMap<String, String> fsverityCandidates = new ArrayMap<>();
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 25169a2..6134d30 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -555,19 +555,19 @@
/** Standard fs-verity. */
private static final int FSVERITY_ENABLED = 2;
- /** Returns true if APK Verity is enabled. */
+ /** Returns true if standard APK Verity is enabled. */
static boolean isApkVerityEnabled() {
- int mode = SystemProperties.getInt("ro.apk_verity.mode", FSVERITY_DISABLED);
- return mode == FSVERITY_LEGACY || mode == FSVERITY_ENABLED;
+ return SystemProperties.getInt("ro.apk_verity.mode", FSVERITY_DISABLED) == FSVERITY_ENABLED;
}
- static boolean isLegacyApkVerityMode() {
+ static boolean isLegacyApkVerityEnabled() {
return SystemProperties.getInt("ro.apk_verity.mode", FSVERITY_DISABLED) == FSVERITY_LEGACY;
}
/** Returns true to force apk verification if the updated package (in /data) is a priv app. */
static boolean isApkVerificationForced(@Nullable PackageSetting disabledPs) {
- return disabledPs != null && disabledPs.isPrivileged() && isApkVerityEnabled();
+ return disabledPs != null && disabledPs.isPrivileged() && (
+ isApkVerityEnabled() || isLegacyApkVerityEnabled());
}
/**
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 2e9d26a..c9d298c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -2293,9 +2293,7 @@
break;
case "--apex":
sessionParams.installFlags |= PackageManager.INSTALL_APEX;
- // TODO(b/118865310): APEX packages should always imply
- // sessionParams.isStaged(). Enforce this when the staged
- // install workflow is complete.
+ sessionParams.setStaged();
break;
case "--multi-package":
sessionParams.setMultiPackage();
diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java
index b47d966..b4154c7 100644
--- a/services/core/java/com/android/server/pm/SELinuxMMAC.java
+++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java
@@ -17,8 +17,8 @@
package com.android.server.pm;
import android.content.pm.PackageParser;
-import android.content.pm.Signature;
import android.content.pm.PackageParser.SigningDetails;
+import android.content.pm.Signature;
import android.os.Environment;
import android.util.Slog;
import android.util.Xml;
@@ -81,6 +81,13 @@
sMacPermissions.add(new File(
Environment.getRootDirectory(), "/etc/selinux/plat_mac_permissions.xml"));
+ // Product mac permissions (optional).
+ final File productMacPermission = new File(
+ Environment.getProductDirectory(), "/etc/selinux/product_mac_permissions.xml");
+ if (productMacPermission.exists()) {
+ sMacPermissions.add(productMacPermission);
+ }
+
// Vendor mac permissions.
// The filename has been renamed from nonplat_mac_permissions to
// vendor_mac_permissions. Either of them should exist.
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 3beda6a..c297c62 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -17,10 +17,24 @@
package com.android.server.pm;
import android.annotation.NonNull;
+import android.apex.ApexInfo;
+import android.apex.ApexInfoList;
+import android.apex.IApexService;
import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageParser.PackageParserException;
+import android.content.pm.PackageParser.SigningDetails;
+import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
import android.content.pm.ParceledListSlice;
+import android.content.pm.Signature;
import android.os.Handler;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.text.TextUtils;
+import android.util.Slog;
import android.util.SparseArray;
+import android.util.apk.ApkSignatureVerifier;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.BackgroundThread;
@@ -39,9 +53,6 @@
private final PackageManagerService mPm;
private final Handler mBgHandler;
- // STOPSHIP: This is a temporary mock implementation of staged sessions. This variable
- // shouldn't be needed at all.
- // TODO(b/118865310): Implement staged sessions logic.
@GuardedBy("mStagedSessions")
private final SparseArray<PackageInstallerSession> mStagedSessions = new SparseArray<>();
@@ -71,15 +82,98 @@
return new ParceledListSlice<>(result);
}
- void commitSession(@NonNull PackageInstallerSession sessionInfo) {
- updateStoredSession(sessionInfo);
+ private static boolean validateApexSignatureLocked(String apexPath, String packageName) {
+ final SigningDetails signingDetails;
+ try {
+ signingDetails = ApkSignatureVerifier.verify(apexPath, SignatureSchemeVersion.JAR);
+ } catch (PackageParserException e) {
+ Slog.e(TAG, "Unable to parse APEX package: " + apexPath, e);
+ return false;
+ }
- mBgHandler.post(() -> {
- // TODO(b/118865310): Dispatch the session to apexd/PackageManager for verification. For
- // now we directly mark it as ready.
- sessionInfo.setStagedSessionReady();
- mPm.sendSessionUpdatedBroadcast(sessionInfo.generateInfo(), sessionInfo.userId);
- });
+ final IApexService apex = IApexService.Stub.asInterface(
+ ServiceManager.getService("apexservice"));
+ final ApexInfo apexInfo;
+ try {
+ apexInfo = apex.getActivePackage(packageName);
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Unable to contact APEXD", re);
+ return false;
+ }
+
+ if (apexInfo == null || TextUtils.isEmpty(apexInfo.packageName)) {
+ // TODO: What is the right thing to do here ? This implies there's no active package
+ // with the given name. This should never be the case in production (where we only
+ // accept updates to existing APEXes) but may be required for testing.
+ return true;
+ }
+
+ final SigningDetails existingSigningDetails;
+ try {
+ existingSigningDetails = ApkSignatureVerifier.verify(
+ apexInfo.packagePath, SignatureSchemeVersion.JAR);
+ } catch (PackageParserException e) {
+ Slog.e(TAG, "Unable to parse APEX package: " + apexInfo.packagePath, e);
+ return false;
+ }
+
+ // Now that we have both sets of signatures, demand that they're an exact match.
+ if (Signature.areExactMatch(existingSigningDetails.signatures, signingDetails.signatures)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ private static boolean submitSessionToApexService(int sessionId, ApexInfoList apexInfoList) {
+ final IApexService apex = IApexService.Stub.asInterface(
+ ServiceManager.getService("apexservice"));
+ boolean success;
+ try {
+ success = apex.submitStagedSession(sessionId, apexInfoList);
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Unable to contact apexservice", re);
+ return false;
+ }
+ return success;
+ }
+
+ void preRebootVerification(@NonNull PackageInstallerSession session) {
+ boolean success = true;
+ if ((session.params.installFlags & PackageManager.INSTALL_APEX) != 0) {
+
+ final ApexInfoList apexInfoList = new ApexInfoList();
+
+ if (!submitSessionToApexService(session.sessionId, apexInfoList)) {
+ success = false;
+ } else {
+ // For APEXes, we validate the signature here before we mark the session as ready,
+ // so we fail the session early if there is a signature mismatch. For APKs, the
+ // signature verification will be done by the package manager at the point at which
+ // it applies the staged install.
+ //
+ // TODO: Decide whether we want to fail fast by detecting signature mismatches right
+ // away.
+ for (ApexInfo apexPackage : apexInfoList.apexInfos) {
+ if (!validateApexSignatureLocked(apexPackage.packagePath,
+ apexPackage.packageName)) {
+ success = false;
+ break;
+ }
+ }
+ }
+ }
+ if (success) {
+ session.setStagedSessionReady();
+ } else {
+ session.setStagedSessionFailed(SessionInfo.VERIFICATION_FAILED);
+ }
+ mPm.sendSessionUpdatedBroadcast(session.generateInfo(false), session.userId);
+ }
+
+ void commitSession(@NonNull PackageInstallerSession session) {
+ updateStoredSession(session);
+ mBgHandler.post(() -> preRebootVerification(session));
}
void createSession(@NonNull PackageInstallerSession sessionInfo) {
@@ -94,4 +188,11 @@
mStagedSessions.remove(sessionInfo.sessionId);
}
}
+
+ void restoreSession(@NonNull PackageInstallerSession session) {
+ updateStoredSession(session);
+ // TODO(b/118865310): This method is called when PackageInstaller is re-instantiated, e.g.
+ // at reboot. Staging manager should at this point recover state from apexd and decide what
+ // to do with the session.
+ }
}
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index dd04652..aaa1874 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -620,9 +620,6 @@
&& callingUid != Process.SYSTEM_UID) {
return true;
} else if (String.valueOf(Settings.Secure.LOCATION_MODE_OFF).equals(value)) {
- // Note LOCATION_MODE will be converted into LOCATION_PROVIDERS_ALLOWED
- // in android.provider.Settings.Secure.putStringForUser(), so we shouldn't come
- // here normally, but we still protect it here from a direct provider write.
return false;
}
restriction = UserManager.DISALLOW_SHARE_LOCATION;
diff --git a/services/core/java/com/android/server/pm/dex/DexoptUtils.java b/services/core/java/com/android/server/pm/dex/DexoptUtils.java
index 93ee44c..91ad11e 100644
--- a/services/core/java/com/android/server/pm/dex/DexoptUtils.java
+++ b/services/core/java/com/android/server/pm/dex/DexoptUtils.java
@@ -22,7 +22,6 @@
import android.util.SparseArray;
import com.android.internal.os.ClassLoaderFactory;
-import com.android.server.pm.PackageDexOptimizer;
import java.io.File;
import java.util.List;
@@ -275,15 +274,11 @@
/**
* Encodes a single class loader dependency starting from {@param path} and
* {@param classLoaderName}.
- * When classpath is {@link PackageDexOptimizer#SKIP_SHARED_LIBRARY_CHECK}, the method returns
- * the same. This special property is used only during OTA.
* NOTE: Keep this in sync with the dexopt expectations! Right now that is either "PCL[path]"
* for a PathClassLoader or "DLC[path]" for a DelegateLastClassLoader.
*/
/*package*/ static String encodeClassLoader(String classpath, String classLoaderName) {
- if (classpath.equals(PackageDexOptimizer.SKIP_SHARED_LIBRARY_CHECK)) {
- return classpath;
- }
+ classpath.getClass(); // Throw NPE if classpath is null
String classLoaderDexoptEncoding = classLoaderName;
if (ClassLoaderFactory.isPathClassLoaderName(classLoaderName)) {
classLoaderDexoptEncoding = "PCL";
@@ -306,16 +301,10 @@
/**
* Links to dependencies together in a format accepted by dexopt.
* For the special case when either of cl1 or cl2 equals
- * {@link PackageDexOptimizer#SKIP_SHARED_LIBRARY_CHECK}, the method returns the same. This
- * property is used only during OTA.
* NOTE: Keep this in sync with the dexopt expectations! Right now that is a list of split
* dependencies {@see encodeClassLoader} separated by ';'.
*/
/*package*/ static String encodeClassLoaderChain(String cl1, String cl2) {
- if (cl1.equals(PackageDexOptimizer.SKIP_SHARED_LIBRARY_CHECK) ||
- cl2.equals(PackageDexOptimizer.SKIP_SHARED_LIBRARY_CHECK)) {
- return PackageDexOptimizer.SKIP_SHARED_LIBRARY_CHECK;
- }
if (cl1.isEmpty()) return cl2;
if (cl2.isEmpty()) return cl1;
return cl1 + ";" + cl2;
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index c792863..2060aef 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -24,6 +24,7 @@
import static android.content.Context.CONTEXT_RESTRICTED;
import static android.content.Context.DISPLAY_SERVICE;
import static android.content.Context.WINDOW_SERVICE;
+import static android.content.pm.PackageManager.FEATURE_HDMI_CEC;
import static android.content.pm.PackageManager.FEATURE_LEANBACK;
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
import static android.content.pm.PackageManager.FEATURE_WATCH;
@@ -126,6 +127,7 @@
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
import android.hardware.display.DisplayManager;
+import android.hardware.hdmi.HdmiAudioSystemClient;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiPlaybackClient;
import android.hardware.hdmi.HdmiPlaybackClient.OneTouchPlayCallback;
@@ -195,6 +197,7 @@
import com.android.internal.accessibility.AccessibilityShortcutController;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.os.RoSystemProperties;
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.internal.policy.IShortcutService;
import com.android.internal.policy.PhoneWindow;
@@ -373,6 +376,7 @@
private ScreenshotHelper mScreenshotHelper;
private boolean mHasFeatureWatch;
private boolean mHasFeatureLeanback;
+ private boolean mHasFeatureHdmiCec;
// Assigned on main thread, accessed on UI thread
volatile VrManagerInternal mVrManagerInternal;
@@ -1453,7 +1457,7 @@
*/
private HdmiControl getHdmiControl() {
if (null == mHdmiControl) {
- if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_HDMI_CEC)) {
+ if (!mHasFeatureHdmiCec) {
return null;
}
HdmiControlManager manager = (HdmiControlManager) mContext.getSystemService(
@@ -1689,6 +1693,7 @@
mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
mHasFeatureWatch = mContext.getPackageManager().hasSystemFeature(FEATURE_WATCH);
mHasFeatureLeanback = mContext.getPackageManager().hasSystemFeature(FEATURE_LEANBACK);
+ mHasFeatureHdmiCec = mContext.getPackageManager().hasSystemFeature(FEATURE_HDMI_CEC);
mAccessibilityShortcutController =
new AccessibilityShortcutController(mContext, new Handler(), mCurrentUserId);
mLogger = new MetricsLogger();
@@ -4116,6 +4121,19 @@
}
private void dispatchDirectAudioEvent(KeyEvent event) {
+ // When System Audio Mode is off, volume keys received by AVR can be either consumed by AVR
+ // or forwarded to the TV. It's up to Amplifier manufacturer’s implementation.
+ HdmiControlManager hdmiControlManager = getHdmiControlManager();
+ if (null != hdmiControlManager
+ && !hdmiControlManager.getSystemAudioMode()
+ && shouldCecAudioDeviceForwardVolumeKeysSystemAudioModeOff()) {
+ HdmiAudioSystemClient audioSystemClient = hdmiControlManager.getAudioSystemClient();
+ if (audioSystemClient != null) {
+ audioSystemClient.sendKeyEvent(
+ event.getKeyCode(), event.getAction() == KeyEvent.ACTION_DOWN);
+ return;
+ }
+ }
if (event.getAction() != KeyEvent.ACTION_DOWN) {
return;
}
@@ -4123,6 +4141,7 @@
int flags = AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_PLAY_SOUND
| AudioManager.FLAG_FROM_KEY;
String pkgName = mContext.getOpPackageName();
+
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP:
try {
@@ -4154,6 +4173,18 @@
}
}
+ @Nullable
+ private HdmiControlManager getHdmiControlManager() {
+ if (!mHasFeatureHdmiCec) {
+ return null;
+ }
+ return (HdmiControlManager) mContext.getSystemService(HdmiControlManager.class);
+ }
+
+ private boolean shouldCecAudioDeviceForwardVolumeKeysSystemAudioModeOff() {
+ return RoSystemProperties.CEC_AUDIO_DEVICE_FORWARD_VOLUME_KEYS_SYSTEM_AUDIO_MODE_OFF;
+ }
+
void dispatchMediaKeyWithWakeLock(KeyEvent event) {
if (DEBUG_INPUT) {
Slog.d(TAG, "dispatchMediaKeyWithWakeLock: " + event);
diff --git a/services/core/java/com/android/server/policy/TEST_MAPPING b/services/core/java/com/android/server/policy/TEST_MAPPING
index e212b04..437ef73 100644
--- a/services/core/java/com/android/server/policy/TEST_MAPPING
+++ b/services/core/java/com/android/server/policy/TEST_MAPPING
@@ -10,7 +10,7 @@
"include-annotation": "android.platform.test.annotations.Presubmit"
},
{
- "exclude-annotation": "android.support.test.filters.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
},
diff --git a/services/core/java/com/android/server/policy/role/LegacyRoleResolutionPolicy.java b/services/core/java/com/android/server/policy/role/LegacyRoleResolutionPolicy.java
new file mode 100644
index 0000000..45c975b
--- /dev/null
+++ b/services/core/java/com/android/server/policy/role/LegacyRoleResolutionPolicy.java
@@ -0,0 +1,97 @@
+/*
+ * 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.
+ */
+
+package com.android.server.policy.role;
+
+import android.annotation.NonNull;
+import android.app.role.RoleManager;
+import android.content.Context;
+import android.os.Debug;
+import android.provider.Settings;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.telephony.SmsApplication;
+import com.android.internal.util.CollectionUtils;
+import com.android.server.role.RoleManagerService;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Logic to retrieve the various legacy(pre-Q) equivalents of role holders.
+ *
+ * Unlike {@link RoleManagerService} this is meant to be pretty high-level to allow for depending
+ * on all kinds of various systems that are historically involved in legacy role resolution,
+ * e.g. {@link SmsApplication}
+ *
+ * @see RoleManagerService#migrateRoleIfNecessary
+ */
+public class LegacyRoleResolutionPolicy implements RoleManagerService.RoleHoldersResolver {
+
+ private static final boolean DEBUG = false;
+ private static final String LOG_TAG = "LegacyRoleResolutionPol";
+
+ @NonNull
+ private final Context mContext;
+
+ public LegacyRoleResolutionPolicy(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public List<String> getRoleHolders(String roleName, int userId) {
+ switch (roleName) {
+ case RoleManager.ROLE_SMS: {
+ // Moved over from SmsApplication#getApplication
+ String result = Settings.Secure.getStringForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.SMS_DEFAULT_APPLICATION, userId);
+
+ if (result == null) {
+ Collection<SmsApplication.SmsApplicationData> applications =
+ SmsApplication.getApplicationCollectionAsUser(mContext, userId);
+ SmsApplication.SmsApplicationData applicationData;
+ String defaultPackage = mContext.getResources()
+ .getString(com.android.internal.R.string.default_sms_application);
+ applicationData =
+ SmsApplication.getApplicationForPackage(applications, defaultPackage);
+
+ if (applicationData == null) {
+ // Are there any applications?
+ if (applications.size() != 0) {
+ applicationData =
+ (SmsApplication.SmsApplicationData) applications.toArray()[0];
+ }
+ }
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Found default sms app: " + applicationData
+ + " among: " + applications + " from " + Debug.getCallers(4));
+ }
+ SmsApplication.SmsApplicationData app = applicationData;
+ result = app == null ? null : app.mPackageName;
+ }
+
+ return CollectionUtils.singletonOrEmpty(result);
+ }
+ default: {
+ Slog.e(LOG_TAG, "Don't know how to find legacy role holders for " + roleName);
+ return Collections.emptyList();
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 5adc248..c3f20aa 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -755,9 +755,10 @@
};
/**
- * Plays the wireless charging sound for both wireless and non-wireless charging
+ * If enabled, plays a sound and/or vibration when wireless or non-wireless charging has started
*/
- private void playChargingStartedSound(@UserIdInt int userId) {
+ private void playChargingStartedFeedback(@UserIdInt int userId) {
+ playChargingStartedVibration(userId);
final String soundPath = Settings.Global.getString(mContext.getContentResolver(),
Settings.Global.CHARGING_STARTED_SOUND);
if (isChargingFeedbackEnabled(userId) && soundPath != null) {
@@ -773,8 +774,7 @@
}
private void showWirelessChargingStarted(int batteryLevel, @UserIdInt int userId) {
- playWirelessChargingVibration(userId);
- playChargingStartedSound(userId);
+ playChargingStartedFeedback(userId);
if (mStatusBarManagerInternal != null) {
mStatusBarManagerInternal.showChargingAnimation(batteryLevel);
}
@@ -782,7 +782,7 @@
}
private void showWiredChargingStarted(@UserIdInt int userId) {
- playChargingStartedSound(userId);
+ playChargingStartedFeedback(userId);
mSuspendBlocker.release();
}
@@ -790,7 +790,7 @@
mTrustManager.setDeviceLockedForUser(userId, true /*locked*/);
}
- private void playWirelessChargingVibration(@UserIdInt int userId) {
+ private void playChargingStartedVibration(@UserIdInt int userId) {
final boolean vibrateEnabled = Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.CHARGING_VIBRATION_ENABLED, 1, userId) != 0;
if (vibrateEnabled && isChargingFeedbackEnabled(userId)) {
diff --git a/services/core/java/com/android/server/power/OWNERS b/services/core/java/com/android/server/power/OWNERS
index 244ccb6..5cbe74c 100644
--- a/services/core/java/com/android/server/power/OWNERS
+++ b/services/core/java/com/android/server/power/OWNERS
@@ -1,6 +1,4 @@
michaelwr@google.com
santoscordon@google.com
-per-file BatterySaverPolicy.java=omakoto@google.com
-per-file ShutdownThread.java=fkupolov@google.com
-per-file ThermalManagerService.java=wvw@google.com
\ No newline at end of file
+per-file ThermalManagerService.java=wvw@google.com
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 565bb706..a027873 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -96,6 +96,7 @@
import com.android.server.lights.LightsManager;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.power.batterysaver.BatterySaverController;
+import com.android.server.power.batterysaver.BatterySaverPolicy;
import com.android.server.power.batterysaver.BatterySaverStateMachine;
import com.android.server.power.batterysaver.BatterySavingStats;
diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java
index 02689a9..7d03d82 100644
--- a/services/core/java/com/android/server/power/ThermalManagerService.java
+++ b/services/core/java/com/android/server/power/ThermalManagerService.java
@@ -134,10 +134,14 @@
if (!halConnected) {
mHalWrapper = new ThermalHal20Wrapper();
halConnected = mHalWrapper.connectToHal();
- if (!halConnected) {
- mHalWrapper = new ThermalHal11Wrapper();
- halConnected = mHalWrapper.connectToHal();
- }
+ }
+ if (!halConnected) {
+ mHalWrapper = new ThermalHal11Wrapper();
+ halConnected = mHalWrapper.connectToHal();
+ }
+ if (!halConnected) {
+ mHalWrapper = new ThermalHal10Wrapper();
+ halConnected = mHalWrapper.connectToHal();
}
mHalWrapper.setCallback(this::onTemperatureChangedCallback);
if (!halConnected) {
@@ -616,6 +620,81 @@
}
}
+
+ static class ThermalHal10Wrapper extends ThermalHalWrapper {
+ /** Proxy object for the Thermal HAL 1.0 service. */
+ @GuardedBy("mHalLock")
+ private android.hardware.thermal.V1_0.IThermal mThermalHal10 = null;
+
+ @Override
+ protected List<Temperature> getCurrentTemperatures(boolean shouldFilter,
+ int type) {
+ synchronized (mHalLock) {
+ List<Temperature> ret = new ArrayList<>();
+ if (mThermalHal10 == null) {
+ return ret;
+ }
+ try {
+ mThermalHal10.getTemperatures(
+ (ThermalStatus status,
+ ArrayList<android.hardware.thermal.V1_0.Temperature>
+ temperatures) -> {
+ if (ThermalStatusCode.SUCCESS == status.code) {
+ for (android.hardware.thermal.V1_0.Temperature
+ temperature : temperatures) {
+ if (shouldFilter && type != temperature.type) {
+ continue;
+ }
+ // Thermal HAL 1.0 doesn't report current throttling status
+ ret.add(new Temperature(
+ temperature.currentValue, temperature.type,
+ temperature.name,
+ Temperature.THROTTLING_NONE));
+ }
+ } else {
+ Slog.e(TAG,
+ "Couldn't get temperatures because of HAL error: "
+ + status.debugMessage);
+ }
+
+ });
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Couldn't getCurrentTemperatures, reconnecting...", e);
+ connectToHal();
+ }
+ return ret;
+ }
+ }
+
+ @Override
+ protected boolean connectToHal() {
+ synchronized (mHalLock) {
+ try {
+ mThermalHal10 = android.hardware.thermal.V1_0.IThermal.getService();
+ mThermalHal10.linkToDeath(new DeathRecipient(),
+ THERMAL_HAL_DEATH_COOKIE);
+ Slog.i(TAG,
+ "Thermal HAL 1.0 service connected, no thermal call back will be "
+ + "called due to legacy API.");
+ } catch (NoSuchElementException | RemoteException e) {
+ Slog.e(TAG,
+ "Thermal HAL 1.0 service not connected.");
+ mThermalHal10 = null;
+ }
+ return (mThermalHal10 != null);
+ }
+ }
+
+ @Override
+ protected void dump(PrintWriter pw, String prefix) {
+ synchronized (mHalLock) {
+ pw.print(prefix);
+ pw.println("ThermalHAL 1.0 connected: " + (mThermalHal10 != null ? "yes"
+ : "no"));
+ }
+ }
+ }
+
static class ThermalHal11Wrapper extends ThermalHalWrapper {
/** Proxy object for the Thermal HAL 1.1 service. */
@GuardedBy("mHalLock")
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
index 6400c88..1d74350 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
@@ -35,13 +35,13 @@
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
-import com.android.server.power.BatterySaverPolicy;
-import com.android.server.power.BatterySaverPolicy.BatterySaverPolicyListener;
import com.android.server.power.PowerManagerService;
+import com.android.server.power.batterysaver.BatterySaverPolicy.BatterySaverPolicyListener;
import com.android.server.power.batterysaver.BatterySavingStats.BatterySaverState;
import com.android.server.power.batterysaver.BatterySavingStats.DozeState;
import com.android.server.power.batterysaver.BatterySavingStats.InteractiveState;
@@ -158,10 +158,9 @@
mBatterySavingStats = batterySavingStats;
// Initialize plugins.
- final ArrayList<Plugin> plugins = new ArrayList<>();
- plugins.add(new BatterySaverLocationPlugin(mContext));
-
- mPlugins = plugins.toArray(new Plugin[plugins.size()]);
+ mPlugins = new Plugin[] {
+ new BatterySaverLocationPlugin(mContext)
+ };
}
/**
@@ -217,7 +216,7 @@
super(looper);
}
- public void postStateChanged(boolean sendBroadcast, int reason) {
+ void postStateChanged(boolean sendBroadcast, int reason) {
obtainMessage(MSG_STATE_CHANGED, sendBroadcast ?
ARG_SEND_BROADCAST : ARG_DONT_SEND_BROADCAST, reason).sendToTarget();
}
@@ -244,9 +243,8 @@
}
}
- /**
- * Called by {@link PowerManagerService} to update the battery saver state.
- */
+ /** Enable or disable full battery saver. */
+ @VisibleForTesting
public void enableBatterySaver(boolean enable, int reason) {
synchronized (mLock) {
if (mEnabled == enable) {
@@ -311,7 +309,7 @@
reason);
mPreviouslyEnabled = mEnabled;
- listeners = mListeners.toArray(new LowPowerModeListener[mListeners.size()]);
+ listeners = mListeners.toArray(new LowPowerModeListener[0]);
enabled = mEnabled;
mIsInteractive = isInteractive;
diff --git a/services/core/java/com/android/server/power/BatterySaverPolicy.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
similarity index 98%
rename from services/core/java/com/android/server/power/BatterySaverPolicy.java
rename to services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
index cedb548..48a041e 100644
--- a/services/core/java/com/android/server/power/BatterySaverPolicy.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.server.power;
+package com.android.server.power.batterysaver;
import android.content.ContentResolver;
import android.content.Context;
@@ -36,21 +36,19 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ConcurrentUtils;
-import com.android.server.power.batterysaver.BatterySavingStats;
-import com.android.server.power.batterysaver.CpuFrequencies;
+import com.android.server.power.PowerManagerService;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
/**
- * Class to decide whether to turn on battery saver mode for specific service
+ * Class to decide whether to turn on battery saver mode for specific services.
*
* IMPORTANT: This class shares the power manager lock, which is very low in the lock hierarchy.
* Do not call out with the lock held, such as AccessibilityManager. (Settings provider is okay.)
*
- * Test:
- atest ${ANDROID_BUILD_TOP}/frameworks/base/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
+ * Test: atest com.android.server.power.batterysaver.BatterySaverPolicyTest.java
*/
public class BatterySaverPolicy extends ContentObserver {
private static final String TAG = "BatterySaverPolicy";
@@ -200,8 +198,10 @@
onChange(true, null);
}
+ @VisibleForTesting
public void addListener(BatterySaverPolicyListener listener) {
synchronized (mLock) {
+ // TODO: set this in the constructor instead
mListeners.add(listener);
}
}
@@ -244,7 +244,7 @@
// Update.
updateConstantsLocked(setting, deviceSpecificSetting);
- listeners = mListeners.toArray(new BatterySaverPolicyListener[mListeners.size()]);
+ listeners = mListeners.toArray(new BatterySaverPolicyListener[0]);
}
// Notify the listeners.
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
index f262f6d..b7f28da 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
@@ -15,8 +15,14 @@
*/
package com.android.server.power.batterysaver;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
import android.database.ContentObserver;
import android.os.Handler;
import android.os.PowerManager;
@@ -26,11 +32,11 @@
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
import com.android.server.EventLogTags;
-import com.android.server.power.BatterySaverPolicy;
import com.android.server.power.BatterySaverStateMachineProto;
import java.io.PrintWriter;
@@ -46,6 +52,8 @@
*/
public class BatterySaverStateMachine {
private static final String TAG = "BatterySaverStateMachine";
+ private static final String DYNAMIC_MODE_NOTIF_CHANNEL_ID = "dynamic_mode_notification";
+ private static final int DYNAMIC_MODE_NOTIFICATION_ID = 1992;
private final Object mLock;
private static final boolean DEBUG = BatterySaverPolicy.DEBUG;
@@ -220,8 +228,8 @@
}
/**
- * Run a {@link Runnable} on a background handler, but lazily. If the same {@link Runnable},
- * it'll be first removed before a new one is posted.
+ * Run a {@link Runnable} on a background handler, but lazily. If the same {@link Runnable} is
+ * already registered, it'll be first removed before being re-posted.
*/
@VisibleForTesting
void runOnBgThreadLazy(Runnable r, int delayMillis) {
@@ -484,6 +492,13 @@
}
mBatterySaverController.enableBatterySaver(enable, intReason);
+ // Handle triggering the notification to show/hide when appropriate
+ if (intReason == BatterySaverController.REASON_DYNAMIC_POWER_SAVINGS_AUTOMATIC_ON) {
+ runOnBgThread(this::triggerDynamicModeNotification);
+ } else if (!enable) {
+ runOnBgThread(this::hideDynamicModeNotification);
+ }
+
if (DEBUG) {
Slog.d(TAG, "Battery saver: Enabled=" + enable
+ " manual=" + manual
@@ -491,6 +506,44 @@
}
}
+ private void triggerDynamicModeNotification() {
+ NotificationManager manager = mContext.getSystemService(NotificationManager.class);
+ ensureNotificationChannelExists(manager);
+
+ manager.notify(DYNAMIC_MODE_NOTIFICATION_ID, buildNotification());
+ }
+
+ private void ensureNotificationChannelExists(NotificationManager manager) {
+ NotificationChannel channel = new NotificationChannel(
+ DYNAMIC_MODE_NOTIF_CHANNEL_ID,
+ mContext.getText(
+ R.string.dynamic_mode_notification_channel_name),
+ NotificationManager.IMPORTANCE_DEFAULT);
+ channel.setSound(null, null);
+ manager.createNotificationChannel(channel);
+ }
+
+ private Notification buildNotification() {
+ Resources res = mContext.getResources();
+ Intent intent = new Intent(Intent.ACTION_POWER_USAGE_SUMMARY);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ PendingIntent batterySaverIntent = PendingIntent.getActivity(
+ mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+
+ return new Notification.Builder(mContext, DYNAMIC_MODE_NOTIF_CHANNEL_ID)
+ .setSmallIcon(R.drawable.ic_battery)
+ .setContentTitle(res.getString(R.string.dynamic_mode_notification_title))
+ .setContentText(res.getString(R.string.dynamic_mode_notification_summary))
+ .setContentIntent(batterySaverIntent)
+ .setOnlyAlertOnce(true)
+ .build();
+ }
+
+ private void hideDynamicModeNotification() {
+ NotificationManager manager = mContext.getSystemService(NotificationManager.class);
+ manager.cancel(DYNAMIC_MODE_NOTIFICATION_ID);
+ }
+
@GuardedBy("mLock")
private void updateSnoozingLocked(boolean snoozing, String reason) {
if (mBatterySaverSnoozing == snoozing) {
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java b/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java
index 8213205..79b44eb 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java
@@ -29,7 +29,6 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
-import com.android.server.power.BatterySaverPolicy;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
diff --git a/services/core/java/com/android/server/role/RoleManagerService.java b/services/core/java/com/android/server/role/RoleManagerService.java
index f37ca12..b488337 100644
--- a/services/core/java/com/android/server/role/RoleManagerService.java
+++ b/services/core/java/com/android/server/role/RoleManagerService.java
@@ -35,6 +35,7 @@
import android.content.IntentFilter;
import android.content.pm.PackageManagerInternal;
import android.content.pm.Signature;
+import android.os.Binder;
import android.os.Handler;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
@@ -92,6 +93,15 @@
@NonNull
private final Object mLock = new Object();
+ @NonNull
+ private final RoleHoldersResolver mLegacyRoleResolver;
+
+ /** @see #getRoleHolders(String, int) */
+ public interface RoleHoldersResolver {
+ /** @return a list of packages that hold a given role for a given user */
+ List<String> getRoleHolders(String roleName, int userId);
+ }
+
/**
* Maps user id to its state.
*/
@@ -118,9 +128,12 @@
@NonNull
private final Handler mListenerHandler = FgThread.getHandler();
- public RoleManagerService(@NonNull Context context) {
+ public RoleManagerService(@NonNull Context context,
+ @NonNull RoleHoldersResolver legacyRoleResolver) {
super(context);
+ mLegacyRoleResolver = legacyRoleResolver;
+
mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
mAppOpsManager = context.getSystemService(AppOpsManager.class);
@@ -175,10 +188,17 @@
private void performInitialGrantsIfNecessary(@UserIdInt int userId) {
RoleUserState userState;
userState = getOrCreateUserState(userId);
+
String packagesHash = computeComponentStateHash(userId);
String oldPackagesHash = userState.getPackagesHash();
boolean needGrant = !Objects.equals(packagesHash, oldPackagesHash);
if (needGrant) {
+
+ //TODO gradually add more role migrations statements here for remaining roles
+ // Make sure to implement LegacyRoleResolutionPolicy#getRoleHolders
+ // for a given role before adding a migration statement for it here
+ migrateRoleIfNecessary(RoleManager.ROLE_SMS, userId);
+
// Some vital packages state has changed since last role grant
// Run grants again
Slog.i(LOG_TAG, "Granting default permissions...");
@@ -205,6 +225,20 @@
}
}
+ private void migrateRoleIfNecessary(String role, @UserIdInt int userId) {
+ // Any role for which we have a record are already migrated
+ RoleUserState userState = getOrCreateUserState(userId);
+ if (!userState.isRoleAvailable(role)) {
+ userState.addRoleName(role);
+ List<String> roleHolders = mLegacyRoleResolver.getRoleHolders(role, userId);
+ Slog.i(LOG_TAG, "Migrating " + role + ", legacy holders: " + roleHolders);
+ int size = roleHolders.size();
+ for (int i = 0; i < size; i++) {
+ userState.addRoleHolder(role, roleHolders.get(i));
+ }
+ }
+ }
+
@Nullable
private static String computeComponentStateHash(@UserIdInt int userId) {
PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
@@ -372,6 +406,7 @@
@Nullable
private ArraySet<String> getRoleHoldersInternal(@NonNull String roleName,
@UserIdInt int userId) {
+ migrateRoleIfNecessary(roleName, userId);
RoleUserState userState = getOrCreateUserState(userId);
return userState.getRoleHolders(roleName);
}
@@ -530,6 +565,17 @@
}
@Override
+ public String getDefaultSmsPackage(int userId) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ return CollectionUtils.firstOrNull(
+ getRoleHoldersAsUser(RoleManager.ROLE_SMS, userId));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
@Nullable String[] args) {
if (!DumpUtils.checkDumpPermission(getContext(), LOG_TAG, fout)) {
diff --git a/services/core/java/com/android/server/role/RoleUserState.java b/services/core/java/com/android/server/role/RoleUserState.java
index 630a39c..69e1449 100644
--- a/services/core/java/com/android/server/role/RoleUserState.java
+++ b/services/core/java/com/android/server/role/RoleUserState.java
@@ -205,6 +205,28 @@
}
/**
+ * Adds the given role, effectively marking it as {@link #isRoleAvailable available}
+ *
+ * @param roleName the name of the role
+ *
+ * @return whether any changes were made
+ */
+ public boolean addRoleName(@NonNull String roleName) {
+ synchronized (mLock) {
+ throwIfDestroyedLocked();
+
+ if (!mRoles.containsKey(roleName)) {
+ mRoles.put(roleName, new ArraySet<>());
+ Slog.i(LOG_TAG, "Added new role: " + roleName);
+ scheduleWriteFileLocked();
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ /**
* Set the names of all available roles.
*
* @param roleNames the names of all the available roles
@@ -231,13 +253,7 @@
int roleNamesSize = roleNames.size();
for (int i = 0; i < roleNamesSize; i++) {
- String roleName = roleNames.get(i);
-
- if (!mRoles.containsKey(roleName)) {
- mRoles.put(roleName, new ArraySet<>());
- Slog.i(LOG_TAG, "Added new role: " + roleName);
- changed = true;
- }
+ changed |= addRoleName(roleNames.get(i));
}
if (changed) {
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerService.java b/services/core/java/com/android/server/rollback/RollbackManagerService.java
index 8c3f04f..4b5e764 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerService.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerService.java
@@ -17,8 +17,6 @@
package com.android.server.rollback;
import android.content.Context;
-import android.os.SELinux;
-import android.util.Log;
import com.android.server.SystemService;
@@ -30,8 +28,6 @@
*/
public final class RollbackManagerService extends SystemService {
- private static final String TAG = "RollbackManager";
-
private RollbackManagerServiceImpl mService;
public RollbackManagerService(Context context) {
@@ -41,12 +37,6 @@
@Override
public void onStart() {
mService = new RollbackManagerServiceImpl(getContext());
-
- // TODO: Set up sepolicy to allow publishing the service.
- if (SELinux.isSELinuxEnforced()) {
- Log.w(TAG, "RollbackManager disabled pending selinux policy updates");
- } else {
- publishBinderService(Context.ROLLBACK_SERVICE, mService);
- }
+ publishBinderService(Context.ROLLBACK_SERVICE, mService);
}
}
diff --git a/services/core/java/com/android/server/rollback/TEST_MAPPING b/services/core/java/com/android/server/rollback/TEST_MAPPING
new file mode 100644
index 0000000..c1d95ac
--- /dev/null
+++ b/services/core/java/com/android/server/rollback/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "RollbackTest"
+ }
+ ]
+}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index fc21adb..7c1e619 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -598,11 +598,11 @@
}
@Override
- public void onBiometricAuthenticated() {
+ public void onBiometricAuthenticated(boolean authenticated) {
enforceBiometricDialog();
if (mBar != null) {
try {
- mBar.onBiometricAuthenticated();
+ mBar.onBiometricAuthenticated(authenticated);
} catch (RemoteException ex) {
}
}
@@ -641,17 +641,6 @@
}
}
- @Override
- public void showBiometricTryAgain() {
- enforceBiometricDialog();
- if (mBar != null) {
- try {
- mBar.showBiometricTryAgain();
- } catch (RemoteException ex) {
- }
- }
- }
-
// TODO(b/117478341): make it aware of multi-display if needed.
@Override
public void disable(int what, IBinder token, String pkg) {
diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
index 2cab63a..ef77140 100644
--- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
+++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
@@ -510,7 +510,8 @@
Slog.d(LOG_TAG, "Binding to " + serviceIntent.getComponent());
willBind = mContext.bindServiceAsUser(
serviceIntent, mConnection,
- Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
+ Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
+ | Context.BIND_RESTRICT_ASSOCIATIONS,
UserHandle.of(mUserId));
mBinding = willBind;
} finally {
diff --git a/services/core/java/com/android/server/textservices/TextServicesManagerService.java b/services/core/java/com/android/server/textservices/TextServicesManagerService.java
index 65d5b10..7236d79 100644
--- a/services/core/java/com/android/server/textservices/TextServicesManagerService.java
+++ b/services/core/java/com/android/server/textservices/TextServicesManagerService.java
@@ -16,8 +16,6 @@
package com.android.server.textservices;
-import static android.view.textservice.TextServicesManager.DISABLE_PER_PROFILE_SPELL_CHECKER;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -43,6 +41,7 @@
import android.text.TextUtils;
import android.util.Slog;
import android.util.SparseArray;
+import android.view.inputmethod.InputMethodSystemProperty;
import android.view.textservice.SpellCheckerInfo;
import android.view.textservice.SpellCheckerSubtype;
@@ -334,7 +333,7 @@
mContext = context;
mUserManager = mContext.getSystemService(UserManager.class);
mSpellCheckerOwnerUserIdMap = new LazyIntToIntMap(callingUserId -> {
- if (DISABLE_PER_PROFILE_SPELL_CHECKER) {
+ if (!InputMethodSystemProperty.PER_PROFILE_IME_ENABLED) {
final long token = Binder.clearCallingIdentity();
try {
final UserInfo parent = mUserManager.getProfileParent(callingUserId);
@@ -355,7 +354,7 @@
private void initializeInternalStateLocked(@UserIdInt int userId) {
// When DISABLE_PER_PROFILE_SPELL_CHECKER is true, we make sure here that work profile users
// will never have non-null TextServicesData for their user ID.
- if (DISABLE_PER_PROFILE_SPELL_CHECKER
+ if (!InputMethodSystemProperty.PER_PROFILE_IME_ENABLED
&& userId != mSpellCheckerOwnerUserIdMap.get(userId)) {
return;
}
@@ -780,7 +779,7 @@
private TextServicesData getDataFromCallingUserIdLocked(@UserIdInt int callingUserId) {
final int spellCheckerOwnerUserId = mSpellCheckerOwnerUserIdMap.get(callingUserId);
final TextServicesData data = mUserData.get(spellCheckerOwnerUserId);
- if (DISABLE_PER_PROFILE_SPELL_CHECKER) {
+ if (!InputMethodSystemProperty.PER_PROFILE_IME_ENABLED) {
if (spellCheckerOwnerUserId != callingUserId) {
// Calling process is running under child profile.
if (data == null) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index f008770..fcc8284 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -84,11 +84,13 @@
import android.system.ErrnoException;
import android.system.Os;
import android.util.EventLog;
+import android.util.FeatureFlagUtils;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.Xml;
import android.view.Display;
+import android.view.DisplayInfo;
import android.view.IWindowManager;
import com.android.internal.R;
@@ -350,12 +352,34 @@
}
private void notifyWallpaperColorsChanged(@NonNull WallpaperData wallpaper, int which) {
+ if (wallpaper.connection != null) {
+ wallpaper.connection.forEachDisplayConnector(connector -> {
+ notifyWallpaperColorsChangedOnDisplay(wallpaper, which, connector.mDisplayId);
+ });
+ } else { // Lock wallpaper does not have WallpaperConnection.
+ notifyWallpaperColorsChangedOnDisplay(wallpaper, which, DEFAULT_DISPLAY);
+ }
+ }
+
+ private RemoteCallbackList<IWallpaperManagerCallback> getWallpaperCallbacks(int userId,
+ int displayId) {
+ RemoteCallbackList<IWallpaperManagerCallback> listeners = null;
+ final SparseArray<RemoteCallbackList<IWallpaperManagerCallback>> displayListeners =
+ mColorsChangedListeners.get(userId);
+ if (displayListeners != null) {
+ listeners = displayListeners.get(displayId);
+ }
+ return listeners;
+ }
+
+ private void notifyWallpaperColorsChangedOnDisplay(@NonNull WallpaperData wallpaper, int which,
+ int displayId) {
boolean needsExtraction;
synchronized (mLock) {
final RemoteCallbackList<IWallpaperManagerCallback> currentUserColorListeners =
- mColorsChangedListeners.get(wallpaper.userId);
+ getWallpaperCallbacks(wallpaper.userId, displayId);
final RemoteCallbackList<IWallpaperManagerCallback> userAllColorListeners =
- mColorsChangedListeners.get(UserHandle.USER_ALL);
+ getWallpaperCallbacks(UserHandle.USER_ALL, displayId);
// No-op until someone is listening to it.
if (emptyCallbackList(currentUserColorListeners) &&
emptyCallbackList(userAllColorListeners)) {
@@ -363,7 +387,7 @@
}
if (DEBUG) {
- Slog.v(TAG, "notifyWallpaperColorsChanged " + which);
+ Slog.v(TAG, "notifyWallpaperColorsChangedOnDisplay " + which);
}
needsExtraction = wallpaper.primaryColors == null;
@@ -371,7 +395,7 @@
// Let's notify the current values, it's fine if it's null, it just means
// that we don't know yet.
- notifyColorListeners(wallpaper.primaryColors, which, wallpaper.userId);
+ notifyColorListeners(wallpaper.primaryColors, which, wallpaper.userId, displayId);
if (needsExtraction) {
extractColors(wallpaper);
@@ -381,7 +405,7 @@
return;
}
}
- notifyColorListeners(wallpaper.primaryColors, which, wallpaper.userId);
+ notifyColorListeners(wallpaper.primaryColors, which, wallpaper.userId, displayId);
}
}
@@ -390,14 +414,14 @@
}
private void notifyColorListeners(@NonNull WallpaperColors wallpaperColors, int which,
- int userId) {
+ int userId, int displayId) {
final IWallpaperManagerCallback keyguardListener;
final ArrayList<IWallpaperManagerCallback> colorListeners = new ArrayList<>();
synchronized (mLock) {
final RemoteCallbackList<IWallpaperManagerCallback> currentUserColorListeners =
- mColorsChangedListeners.get(userId);
+ getWallpaperCallbacks(userId, displayId);
final RemoteCallbackList<IWallpaperManagerCallback> userAllColorListeners =
- mColorsChangedListeners.get(UserHandle.USER_ALL);
+ getWallpaperCallbacks(UserHandle.USER_ALL, displayId);
keyguardListener = mKeyguardListener;
if (currentUserColorListeners != null) {
@@ -427,7 +451,8 @@
}
}
- if (keyguardListener != null) {
+ // Only shows Keyguard on default display
+ if (keyguardListener != null && displayId == DEFAULT_DISPLAY) {
try {
keyguardListener.onWallpaperColorsChanged(wallpaperColors, which, userId);
} catch (RemoteException e) {
@@ -446,6 +471,11 @@
String cropFile = null;
int wallpaperId;
+ if (wallpaper.equals(mFallbackWallpaper)) {
+ extractDefaultImageWallpaperColors();
+ return;
+ }
+
synchronized (mLock) {
// Not having a wallpaperComponent means it's a lock screen wallpaper.
final boolean imageWallpaper = mImageWallpaper.equals(wallpaper.wallpaperComponent)
@@ -482,6 +512,39 @@
}
}
+ private void extractDefaultImageWallpaperColors() {
+ synchronized (mLock) {
+ if (mFallbackWallpaper.primaryColors != null) return;
+ }
+
+ if (DEBUG) Slog.d(TAG, "Extract default image wallpaper colors");
+ WallpaperColors colors = null;
+ final InputStream is = WallpaperManager.openDefaultWallpaper(mContext, FLAG_SYSTEM);
+ if (is != null) {
+ try {
+ final BitmapFactory.Options options = new BitmapFactory.Options();
+ final Bitmap bitmap = BitmapFactory.decodeStream(is, null, options);
+ if (bitmap != null) {
+ colors = WallpaperColors.fromBitmap(bitmap);
+ bitmap.recycle();
+ }
+ } catch (OutOfMemoryError e) {
+ Slog.w(TAG, "Can't decode default wallpaper stream", e);
+ } finally {
+ IoUtils.closeQuietly(is);
+ }
+ }
+
+ if (colors == null) {
+ Slog.e(TAG, "Extract default image wallpaper colors failed");
+ return;
+ }
+
+ synchronized (mLock) {
+ mFallbackWallpaper.primaryColors = colors;
+ }
+ }
+
/**
* Once a new wallpaper has been written via setWallpaper(...), it needs to be cropped
* for display.
@@ -539,6 +602,20 @@
// scale if the crop height winds up not matching the recommended metrics
needScale = (wpData.mHeight != cropHint.height());
+ //make sure screen aspect ratio is preserved if width is scaled under screen size
+ if (needScale) {
+ final DisplayInfo displayInfo = new DisplayInfo();
+ mDisplayManager.getDisplay(DEFAULT_DISPLAY).getDisplayInfo(displayInfo);
+ final float scaleByHeight = (float) wpData.mHeight / (float) cropHint.height();
+ final int newWidth = (int) (cropHint.width() * scaleByHeight);
+ if (newWidth < displayInfo.logicalWidth) {
+ final float screenAspectRatio =
+ (float) displayInfo.logicalHeight / (float) displayInfo.logicalWidth;
+ cropHint.bottom = (int) (cropHint.width() * screenAspectRatio);
+ needCrop = true;
+ }
+ }
+
if (DEBUG) {
Slog.v(TAG, "crop: w=" + cropHint.width() + " h=" + cropHint.height());
Slog.v(TAG, "dims: w=" + wpData.mWidth + " h=" + wpData.mHeight);
@@ -696,6 +773,11 @@
targetWallpaper.connection.removeDisplayConnector(displayId);
removeDisplayData(displayId);
}
+ for (int i = mColorsChangedListeners.size() - 1; i >= 0; i--) {
+ final SparseArray<RemoteCallbackList<IWallpaperManagerCallback>> callbacks =
+ mColorsChangedListeners.valueAt(i);
+ callbacks.delete(displayId);
+ }
}
}
@@ -706,9 +788,11 @@
/**
* Map of color listeners per user id.
- * The key will be the id of a user or UserHandle.USER_ALL - for wildcard listeners.
+ * The first key will be the id of a user or UserHandle.USER_ALL - for wildcard listeners.
+ * The secondary key will be the display id, which means which display the listener is
+ * interested in.
*/
- private final SparseArray<RemoteCallbackList<IWallpaperManagerCallback>>
+ private final SparseArray<SparseArray<RemoteCallbackList<IWallpaperManagerCallback>>>
mColorsChangedListeners;
private WallpaperData mLastWallpaper;
private IWallpaperManagerCallback mKeyguardListener;
@@ -1228,9 +1312,10 @@
/**
* Called by a live wallpaper if its colors have changed.
* @param primaryColors representation of wallpaper primary colors
+ * @param displayId for which display
*/
@Override
- public void onWallpaperColorsChanged(WallpaperColors primaryColors) {
+ public void onWallpaperColorsChanged(WallpaperColors primaryColors, int displayId) {
int which;
synchronized (mLock) {
// Do not broadcast changes on ImageWallpaper since it's handled
@@ -1243,14 +1328,16 @@
// Live wallpapers always are system wallpapers.
which = FLAG_SYSTEM;
- // It's also the lock screen wallpaper when we don't have a bitmap in there
- WallpaperData lockedWallpaper = mLockWallpaperMap.get(mWallpaper.userId);
- if (lockedWallpaper == null) {
- which |= FLAG_LOCK;
+ // It's also the lock screen wallpaper when we don't have a bitmap in there.
+ if (displayId == DEFAULT_DISPLAY) {
+ final WallpaperData lockedWallpaper = mLockWallpaperMap.get(mWallpaper.userId);
+ if (lockedWallpaper == null) {
+ which |= FLAG_LOCK;
+ }
}
}
if (which != 0) {
- notifyWallpaperColorsChanged(mWallpaper, which);
+ notifyWallpaperColorsChangedOnDisplay(mWallpaper, which, displayId);
}
}
@@ -1277,16 +1364,12 @@
Slog.w(TAG, "Failed to set ambient mode state", e);
}
}
- // TODO(multi-display) So far, we have shared the same wallpaper on each display.
- // Once we have multiple wallpapers on multiple displays, please complete here.
- if (displayId == DEFAULT_DISPLAY) {
- try {
- // This will trigger onComputeColors in the wallpaper engine.
- // It's fine to be locked in here since the binder is oneway.
- connector.mEngine.requestWallpaperColors();
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to request wallpaper colors", e);
- }
+ try {
+ // This will trigger onComputeColors in the wallpaper engine.
+ // It's fine to be locked in here since the binder is oneway.
+ connector.mEngine.requestWallpaperColors();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to request wallpaper colors", e);
}
}
}
@@ -1681,6 +1764,7 @@
FgThread.getHandler().post(() -> {
notifyWallpaperColorsChanged(systemWallpaper, FLAG_SYSTEM);
notifyWallpaperColorsChanged(lockWallpaper, FLAG_LOCK);
+ notifyWallpaperColorsChanged(mFallbackWallpaper, FLAG_SYSTEM);
});
}
@@ -1743,6 +1827,7 @@
// When clearing a wallpaper, broadcast new valid colors
if (data != null) {
notifyWallpaperColorsChanged(data, which);
+ notifyWallpaperColorsChanged(mFallbackWallpaper, FLAG_SYSTEM);
}
}
@@ -2084,35 +2169,47 @@
}
@Override
- public void registerWallpaperColorsCallback(IWallpaperManagerCallback cb, int userId) {
+ public void registerWallpaperColorsCallback(IWallpaperManagerCallback cb, int userId,
+ int displayId) {
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
userId, true, true, "registerWallpaperColorsCallback", null);
synchronized (mLock) {
- RemoteCallbackList<IWallpaperManagerCallback> userColorsChangedListeners =
- mColorsChangedListeners.get(userId);
- if (userColorsChangedListeners == null) {
- userColorsChangedListeners = new RemoteCallbackList<>();
- mColorsChangedListeners.put(userId, userColorsChangedListeners);
+ SparseArray<RemoteCallbackList<IWallpaperManagerCallback>>
+ userDisplayColorsChangedListeners = mColorsChangedListeners.get(userId);
+ if (userDisplayColorsChangedListeners == null) {
+ userDisplayColorsChangedListeners = new SparseArray<>();
+ mColorsChangedListeners.put(userId, userDisplayColorsChangedListeners);
}
- userColorsChangedListeners.register(cb);
+ RemoteCallbackList<IWallpaperManagerCallback> displayChangedListeners =
+ userDisplayColorsChangedListeners.get(displayId);
+ if (displayChangedListeners == null) {
+ displayChangedListeners = new RemoteCallbackList<>();
+ userDisplayColorsChangedListeners.put(displayId, displayChangedListeners);
+ }
+ displayChangedListeners.register(cb);
}
}
@Override
- public void unregisterWallpaperColorsCallback(IWallpaperManagerCallback cb, int userId) {
+ public void unregisterWallpaperColorsCallback(IWallpaperManagerCallback cb, int userId,
+ int displayId) {
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
userId, true, true, "unregisterWallpaperColorsCallback", null);
synchronized (mLock) {
- final RemoteCallbackList<IWallpaperManagerCallback> userColorsChangedListeners =
- mColorsChangedListeners.get(userId);
- if (userColorsChangedListeners != null) {
- userColorsChangedListeners.unregister(cb);
+ SparseArray<RemoteCallbackList<IWallpaperManagerCallback>>
+ userDisplayColorsChangedListeners = mColorsChangedListeners.get(userId);
+ if (userDisplayColorsChangedListeners != null) {
+ RemoteCallbackList<IWallpaperManagerCallback> displayChangedListeners =
+ userDisplayColorsChangedListeners.get(displayId);
+ if (displayChangedListeners != null) {
+ displayChangedListeners.unregister(cb);
+ }
}
}
}
/**
- * TODO(b/115486823) Extends this method with specific display.
+ * TODO(multi-display) Extends this method with specific display.
* Propagate ambient state to wallpaper engine.
*
* @param inAmbientMode {@code true} when in ambient mode, {@code false} otherwise.
@@ -2123,9 +2220,13 @@
synchronized (mLock) {
mInAmbientMode = inAmbientMode;
final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
- if (data != null && data.connection != null && data.connection.mInfo != null
- && data.connection.mInfo.supportsAmbientMode()) {
- // TODO(b/115486823) Extends this method with specific display.
+ final boolean hasConnection = data != null && data.connection != null;
+ final WallpaperInfo info = hasConnection ? data.connection.mInfo : null;
+
+ // The wallpaper info is null for image wallpaper, also use the engine in this case.
+ if (hasConnection && (info == null && isAodImageWallpaperEnabled()
+ || info != null && info.supportsAmbientMode())) {
+ // TODO(multi-display) Extends this method with specific display.
engine = data.connection.getDisplayConnectorOrCreate(DEFAULT_DISPLAY).mEngine;
} else {
engine = null;
@@ -2141,6 +2242,10 @@
}
}
+ private boolean isAodImageWallpaperEnabled() {
+ return FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.AOD_IMAGEWALLPAPER_ENABLED);
+ }
+
@Override
public boolean setLockWallpaperCallback(IWallpaperManagerCallback cb) {
checkPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW);
@@ -2151,7 +2256,8 @@
}
@Override
- public WallpaperColors getWallpaperColors(int which, int userId) throws RemoteException {
+ public WallpaperColors getWallpaperColors(int which, int userId, int displayId)
+ throws RemoteException {
if (which != FLAG_LOCK && which != FLAG_SYSTEM) {
throw new IllegalArgumentException("which should be either FLAG_LOCK or FLAG_SYSTEM");
}
@@ -2169,7 +2275,7 @@
// Try to get the system wallpaper anyway since it might
// also be the lock screen wallpaper
if (wallpaperData == null) {
- wallpaperData = mWallpaperMap.get(userId);
+ wallpaperData = findWallpaperAtDisplay(userId, displayId);
}
if (wallpaperData == null) {
@@ -2187,6 +2293,15 @@
}
}
+ private WallpaperData findWallpaperAtDisplay(int userId, int displayId) {
+ if (mFallbackWallpaper != null && mFallbackWallpaper.connection != null
+ && mFallbackWallpaper.connection.containsDisplay(displayId)) {
+ return mFallbackWallpaper;
+ } else {
+ return mWallpaperMap.get(userId);
+ }
+ }
+
@Override
public ParcelFileDescriptor setWallpaper(String name, String callingPackage,
Rect cropHint, boolean allowBackup, Bundle extras, int which,
@@ -2382,6 +2497,7 @@
if (shouldNotifyColors) {
notifyWallpaperColorsChanged(wallpaper, which);
+ notifyWallpaperColorsChanged(mFallbackWallpaper, FLAG_SYSTEM);
}
}
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index fe0b5c2..caebf15 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -1066,93 +1066,19 @@
final int visibleWindowCount = visibleWindows.size();
HashSet<Integer> skipRemainingWindowsForTasks = new HashSet<>();
- for (int i = visibleWindowCount - 1; i >= 0; i--) {
+
+ // Iterate until we figure out what is touchable for the entire screen.
+ for (int i = visibleWindowCount - 1; i >= 0 && !unaccountedSpace.isEmpty(); i--) {
final WindowState windowState = visibleWindows.valueAt(i);
- final int flags = windowState.mAttrs.flags;
- final Task task = windowState.getTask();
- // If the window is part of a task that we're finished with - ignore.
- if (task != null && skipRemainingWindowsForTasks.contains(task.mTaskId)) {
- continue;
- }
-
- // Ignore non-touchable windows, except the split-screen divider, which is
- // occasionally non-touchable but still useful for identifying split-screen
- // mode.
- if (((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0)
- && (windowState.mAttrs.type != TYPE_DOCK_DIVIDER)) {
- continue;
- }
-
- // Compute the bounds in the screen.
final Rect boundsInScreen = mTempRect;
computeWindowBoundsInScreen(windowState, boundsInScreen);
- // If the window is completely covered by other windows - ignore.
- if (unaccountedSpace.quickReject(boundsInScreen)) {
- continue;
- }
-
- // Add windows of certain types not covered by modal windows.
- if (isReportedWindowType(windowState.mAttrs.type)) {
- // Add the window to the ones to be reported.
+ if (windowMattersToAccessibility(windowState, boundsInScreen, unaccountedSpace,
+ skipRemainingWindowsForTasks)) {
addPopulatedWindowInfo(windowState, boundsInScreen, windows, addedWindows);
- if (windowState.isFocused()) {
- focusedWindowAdded = true;
- }
- }
-
- if (windowState.mAttrs.type !=
- WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY) {
-
- // Account for the space this window takes if the window
- // is not an accessibility overlay which does not change
- // the reported windows.
- unaccountedSpace.op(boundsInScreen, unaccountedSpace,
- Region.Op.REVERSE_DIFFERENCE);
-
- // If a window is modal it prevents other windows from being touched
- if ((flags & (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)) == 0) {
- // Account for all space in the task, whether the windows in it are
- // touchable or not. The modal window blocks all touches from the task's
- // area.
- unaccountedSpace.op(windowState.getDisplayFrameLw(), unaccountedSpace,
- Region.Op.REVERSE_DIFFERENCE);
-
- if (task != null) {
- // If the window is associated with a particular task, we can skip the
- // rest of the windows for that task.
- skipRemainingWindowsForTasks.add(task.mTaskId);
- continue;
- } else {
- // If the window is not associated with a particular task, then it is
- // globally modal. In this case we can skip all remaining windows.
- break;
- }
- }
- }
-
- // We figured out what is touchable for the entire screen - done.
- if (unaccountedSpace.isEmpty()) {
- break;
- }
- }
-
- // Always report the focused window.
- if (!focusedWindowAdded) {
- for (int i = visibleWindowCount - 1; i >= 0; i--) {
- WindowState windowState = visibleWindows.valueAt(i);
- if (windowState.isFocused()) {
- // Compute the bounds in the screen.
- Rect boundsInScreen = mTempRect;
- computeWindowBoundsInScreen(windowState, boundsInScreen);
-
- // Add the window to the ones to be reported.
- addPopulatedWindowInfo(
- windowState, boundsInScreen, windows, addedWindows);
- break;
- }
+ updateUnaccountedSpace(windowState, boundsInScreen, unaccountedSpace,
+ skipRemainingWindowsForTasks);
}
}
@@ -1221,6 +1147,73 @@
clearAndRecycleWindows(windows);
}
+ private boolean windowMattersToAccessibility(WindowState windowState, Rect boundsInScreen,
+ Region unaccountedSpace, HashSet<Integer> skipRemainingWindowsForTasks) {
+ if (windowState.isFocused()) {
+ return true;
+ }
+
+ // If the window is part of a task that we're finished with - ignore.
+ final Task task = windowState.getTask();
+ if (task != null && skipRemainingWindowsForTasks.contains(task.mTaskId)) {
+ return false;
+ }
+
+ // Ignore non-touchable windows, except the split-screen divider, which is
+ // occasionally non-touchable but still useful for identifying split-screen
+ // mode.
+ if (((windowState.mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0)
+ && (windowState.mAttrs.type != TYPE_DOCK_DIVIDER)) {
+ return false;
+ }
+
+ // If the window is completely covered by other windows - ignore.
+ if (unaccountedSpace.quickReject(boundsInScreen)) {
+ return false;
+ }
+
+ // Add windows of certain types not covered by modal windows.
+ if (isReportedWindowType(windowState.mAttrs.type)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ private void updateUnaccountedSpace(WindowState windowState, Rect boundsInScreen,
+ Region unaccountedSpace, HashSet<Integer> skipRemainingWindowsForTasks) {
+ if (windowState.mAttrs.type
+ != WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY) {
+
+ // Account for the space this window takes if the window
+ // is not an accessibility overlay which does not change
+ // the reported windows.
+ unaccountedSpace.op(boundsInScreen, unaccountedSpace,
+ Region.Op.REVERSE_DIFFERENCE);
+
+ // If a window is modal it prevents other windows from being touched
+ if ((windowState.mAttrs.flags & (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)) == 0) {
+ // Account for all space in the task, whether the windows in it are
+ // touchable or not. The modal window blocks all touches from the task's
+ // area.
+ unaccountedSpace.op(windowState.getDisplayFrameLw(), unaccountedSpace,
+ Region.Op.REVERSE_DIFFERENCE);
+
+ final Task task = windowState.getTask();
+ if (task != null) {
+ // If the window is associated with a particular task, we can skip the
+ // rest of the windows for that task.
+ skipRemainingWindowsForTasks.add(task.mTaskId);
+ } else {
+ // If the window is not associated with a particular task, then it is
+ // globally modal. In this case we can skip all remaining windows.
+ unaccountedSpace.setEmpty();
+ }
+ }
+ }
+ }
+
private void computeWindowBoundsInScreen(WindowState windowState, Rect outBounds) {
// Get the touchable frame.
Region touchableRegion = mTempRegion1;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 8884615..f8f0d1c 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -390,6 +390,11 @@
private boolean mTurnScreenOn;
/**
+ * Current sequencing integer of the configuration, for skipping old activity configurations.
+ */
+ private int mConfigurationSeq;
+
+ /**
* Temp configs used in {@link #ensureActivityConfiguration(int, boolean)}
*/
private final Configuration mTmpConfig = new Configuration();
@@ -728,9 +733,10 @@
mLastReportedMultiWindowMode = inPictureInPictureMode;
final Configuration newConfig = new Configuration();
if (targetStackBounds != null && !targetStackBounds.isEmpty()) {
- task.computeResolvedOverrideConfiguration(newConfig,
- task.getParent().getConfiguration(),
- task.getRequestedOverrideConfiguration());
+ newConfig.setTo(task.getRequestedOverrideConfiguration());
+ Rect outBounds = newConfig.windowConfiguration.getBounds();
+ task.adjustForMinimalTaskDimensions(outBounds, outBounds);
+ task.computeConfigResourceOverrides(newConfig, task.getParent().getConfiguration());
}
schedulePictureInPictureModeChanged(newConfig);
scheduleMultiWindowModeChanged(newConfig);
@@ -2503,7 +2509,8 @@
return;
}
- final IBinder binder = freezeScreenIfNeeded ? appToken.asBinder() : null;
+ final IBinder binder =
+ (freezeScreenIfNeeded && appToken != null) ? appToken.asBinder() : null;
mAppWindowToken.setOrientation(requestedOrientation, binder, this);
}
@@ -2547,7 +2554,6 @@
// TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
private void updateOverrideConfiguration() {
- mTmpConfig.unset();
computeBounds(mTmpBounds);
if (mTmpBounds.equals(getRequestedOverrideBounds())) {
@@ -2558,13 +2564,54 @@
// Bounds changed...update configuration to match.
if (!matchParentBounds()) {
- task.computeResolvedOverrideConfiguration(mTmpConfig,
- task.getParent().getConfiguration(), getRequestedOverrideConfiguration());
+ mTmpConfig.setTo(getRequestedOverrideConfiguration());
+ task.computeConfigResourceOverrides(mTmpConfig, task.getParent().getConfiguration());
+ } else {
+ mTmpConfig.unset();
}
onRequestedOverrideConfigurationChanged(mTmpConfig);
}
+ @Override
+ void resolveOverrideConfiguration(Configuration newParentConfiguration) {
+ super.resolveOverrideConfiguration(newParentConfiguration);
+
+ // Assign configuration sequence number into hierarchy because there is a different way than
+ // ensureActivityConfiguration() in this class that uses configuration in WindowState during
+ // layout traversals.
+ mConfigurationSeq = Math.max(++mConfigurationSeq, 1);
+ getResolvedOverrideConfiguration().seq = mConfigurationSeq;
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newParentConfig) {
+ super.onConfigurationChanged(newParentConfig);
+
+ // Configuration's equality doesn't consider seq so if only seq number changes in resolved
+ // override configuration. Therefore ConfigurationContainer doesn't change merged override
+ // configuration, but it's used to push configuration changes so explicitly update that.
+ if (getMergedOverrideConfiguration().seq != getResolvedOverrideConfiguration().seq) {
+ onMergedOverrideConfigurationChanged();
+ }
+
+ // TODO(b/80414790): Remove code below after unification.
+ // Same as above it doesn't notify configuration listeners, and consequently AppWindowToken
+ // can't get updated seq number. However WindowState's merged override configuration needs
+ // to have this seq number because that's also used for activity config pushes during layout
+ // traversal. Therefore explicitly update them here.
+ if (mAppWindowToken == null) {
+ return;
+ }
+ final Configuration appWindowTokenRequestedOverrideConfig =
+ mAppWindowToken.getRequestedOverrideConfiguration();
+ if (appWindowTokenRequestedOverrideConfig.seq != getResolvedOverrideConfiguration().seq) {
+ appWindowTokenRequestedOverrideConfig.seq =
+ getResolvedOverrideConfiguration().seq;
+ mAppWindowToken.onMergedOverrideConfigurationChanged();
+ }
+ }
+
/** Returns true if the configuration is compatible with this activity. */
boolean isConfigurationCompatible(Configuration config) {
final int orientation = mAppWindowToken != null
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index d5286b9..a50ae84 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -947,6 +947,7 @@
final WindowProcessController wpc =
mService.getProcessController(r.processName, r.info.applicationInfo.uid);
+ boolean knownToBeDead = false;
if (wpc != null && wpc.hasThread()) {
try {
if ((r.info.flags & ActivityInfo.FLAG_MULTIPROCESS) == 0
@@ -965,6 +966,7 @@
// If a dead object exception was thrown -- fall through to
// restart the application.
+ knownToBeDead = true;
}
// Suppress transition until the new activity becomes ready, otherwise the keyguard can
@@ -978,7 +980,7 @@
// ATMS lock held.
final Message msg = PooledLambda.obtainMessage(
ActivityManagerInternal::startProcess, mService.mAmInternal, r.processName,
- r.info.applicationInfo, true, "activity", r.intent.getComponent());
+ r.info.applicationInfo, knownToBeDead, "activity", r.intent.getComponent());
mService.mH.sendMessage(msg);
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 0f286ce..d8644df 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.ActivityManager;
import android.app.AppProtoEnums;
import android.app.IActivityManager;
import android.app.IApplicationThread;
@@ -479,4 +480,10 @@
public abstract void setProfilerInfo(ProfilerInfo profilerInfo);
public abstract ActivityMetricsLaunchObserverRegistry getLaunchObserverRegistry();
+
+ /**
+ * Gets bitmap snapshot of the provided task id.
+ */
+ public abstract ActivityManager.TaskSnapshot getTaskSnapshot(int taskId,
+ boolean reducedResolution);
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index efce379..e5bf21a 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -245,7 +245,7 @@
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.internal.util.function.pooled.PooledLambda;
-import com.android.server.AppOpsService;
+import com.android.server.appop.AppOpsService;
import com.android.server.AttributeCache;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -639,7 +639,8 @@
}
}
- ActivityTaskManagerService(Context context) {
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public ActivityTaskManagerService(Context context) {
mContext = context;
mFactoryTest = FactoryTest.getMode();
mSystemThread = ActivityThread.currentActivityThread();
@@ -7011,5 +7012,12 @@
return mStackSupervisor.getActivityMetricsLogger().getLaunchObserverRegistry();
}
}
+
+ @Override
+ public ActivityManager.TaskSnapshot getTaskSnapshot(int taskId, boolean reducedResolution) {
+ synchronized (mGlobalLock) {
+ return ActivityTaskManagerService.this.getTaskSnapshot(taskId, reducedResolution);
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index a5341ca..801c1e7 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -1714,7 +1714,8 @@
if (mLetterbox == null) {
mLetterbox = new Letterbox(() -> makeChildSurface(null));
}
- mLetterbox.layout(getParent().getBounds(), w.getFrameLw());
+ getPosition(mTmpPoint);
+ mLetterbox.layout(getParent().getBounds(), w.getFrameLw(), mTmpPoint);
} else if (mLetterbox != null) {
mLetterbox.hide();
}
@@ -2355,7 +2356,7 @@
public void onAnimationLeashDestroyed(Transaction t) {
super.onAnimationLeashDestroyed(t);
if (mAnimationBoundsLayer != null) {
- t.destroy(mAnimationBoundsLayer);
+ t.reparent(mAnimationBoundsLayer, null);
mAnimationBoundsLayer = null;
}
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index ded45c9..650d0be 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -37,6 +37,7 @@
import android.annotation.CallSuper;
import android.app.WindowConfiguration;
import android.content.res.Configuration;
+import android.graphics.Point;
import android.graphics.Rect;
import android.util.proto.ProtoOutputStream;
@@ -243,6 +244,14 @@
}
/**
+ * Sets {@code out} to the top-left corner of the bounds as returned by {@link #getBounds()}.
+ */
+ public void getPosition(Point out) {
+ Rect bounds = getBounds();
+ out.set(bounds.left, bounds.top);
+ }
+
+ /**
* Returns the bounds requested on this container. These may not be the actual bounds the
* container ends up with due to policy constraints. The {@link Rect} handed back is
* shared for all calls to this method and should not be modified.
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index aea071f..c39060e 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -129,7 +129,7 @@
final DimAnimatable dimAnimatable = new DimAnimatable(dimLayer);
mSurfaceAnimator = new SurfaceAnimator(dimAnimatable, () -> {
if (!mDimming) {
- dimAnimatable.getPendingTransaction().destroy(mDimLayer);
+ dimAnimatable.getPendingTransaction().reparent(mDimLayer, null);
}
}, mHost.mWmService);
}
@@ -300,7 +300,7 @@
if (!mDimState.mDimming) {
if (!mDimState.mAnimateExit) {
- t.destroy(mDimState.mDimLayer);
+ t.reparent(mDimState.mDimLayer, null);
} else {
startDimExit(mLastRequestedDimContainer, mDimState.mSurfaceAnimator, t);
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index fecc8da..740d472 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3656,6 +3656,12 @@
}
}
+ /** @returns the orientation of the display when it's rotation is ROTATION_0. */
+ int getNaturalOrientation() {
+ return mBaseDisplayWidth < mBaseDisplayHeight
+ ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
+ }
+
void performLayout(boolean initial, boolean updateInputWindows) {
if (!isLayoutNeeded()) {
return;
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 607ee76..786a306 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -127,6 +127,7 @@
* {@code true} when {@link #closeLocked()} is called.
*/
private boolean mIsClosing;
+ IBinder mTransferTouchFromToken;
DragState(WindowManagerService service, DragDropController controller, IBinder token,
SurfaceControl surface, int flags, IBinder localWin) {
@@ -177,6 +178,8 @@
mTmpClipRect.set(0, 0, mDisplaySize.x, mDisplaySize.y);
t.setWindowCrop(mInputSurface, mTmpClipRect);
+ t.transferTouchFocus(mTransferTouchFromToken, h.token);
+ mTransferTouchFromToken = null;
}
/**
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 639ed02..f9c9d33 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -1,5 +1,6 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -9,7 +10,6 @@
import android.os.Debug;
import android.os.IBinder;
import android.util.Slog;
-import android.view.InputApplicationHandle;
import android.view.KeyEvent;
import android.view.WindowManager;
@@ -204,6 +204,37 @@
+ WindowManagerService.TYPE_LAYER_OFFSET;
}
+ /** Callback to get pointer display id. */
+ @Override
+ public int getPointerDisplayId() {
+ synchronized (mService.mGlobalLock) {
+ // If desktop mode is not enabled, show on the default display.
+ if (!mService.mForceDesktopModeOnExternalDisplays) {
+ return DEFAULT_DISPLAY;
+ }
+
+ // Look for the topmost freeform display.
+ int firstExternalDisplayId = DEFAULT_DISPLAY;
+ for (int i = mService.mRoot.mChildren.size() - 1; i >= 0; --i) {
+ final DisplayContent displayContent = mService.mRoot.mChildren.get(i);
+ // Heuristic solution here. Currently when "Freeform windows" developer option is
+ // enabled we automatically put secondary displays in freeform mode and emulating
+ // "desktop mode". It also makes sense to show the pointer on the same display.
+ if (displayContent.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+ return displayContent.getDisplayId();
+ }
+
+ if (firstExternalDisplayId == DEFAULT_DISPLAY
+ && displayContent.getDisplayId() != DEFAULT_DISPLAY) {
+ firstExternalDisplayId = displayContent.getDisplayId();
+ }
+ }
+
+ // Look for the topmost non-default display
+ return firstExternalDisplayId;
+ }
+ }
+
/** 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/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 49a3186..d6f1616 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.graphics.Point;
import android.graphics.Rect;
import android.util.proto.ProtoOutputStream;
import android.view.InsetsState;
@@ -26,8 +25,10 @@
import android.view.SurfaceControl.Transaction;
import android.view.InsetsSource;
import android.view.InsetsSourceControl;
+import android.view.ViewRootImpl;
import com.android.internal.util.function.TriConsumer;
+import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
import java.io.PrintWriter;
@@ -106,8 +107,8 @@
mTmpRect.inset(mWin.mGivenContentInsets);
}
mSource.setFrame(mTmpRect);
- setServerVisible(mWin.isVisible() && !mWin.mGivenInsetsPending);
-
+ setServerVisible(mWin.wouldBeVisibleIfPolicyIgnored() && mWin.mPolicyVisibility
+ && !mWin.mGivenInsetsPending);
}
void updateControlForTarget(@Nullable WindowState target) {
@@ -115,15 +116,15 @@
return;
}
if (target == null) {
- revokeControl();
+ // Cancelling the animation will invoke onAnimationCancelled, resetting all the fields.
+ mWin.cancelAnimation();
return;
}
mAdapter = new ControlAdapter();
mWin.startAnimation(mDisplayContent.getPendingTransaction(), mAdapter,
- false /* TODO hidden */);
+ !mClientVisible /* hidden */);
mControllingWin = target;
mControl = new InsetsSourceControl(mSource.getType(), mAdapter.mCapturedLeash);
- setClientVisible(InsetsState.getDefaultVisibly(mSource.getType()));
}
boolean onInsetsModified(WindowState caller, InsetsSource modifiedSource) {
@@ -135,7 +136,12 @@
}
private void setClientVisible(boolean clientVisible) {
+ if (mClientVisible == clientVisible) {
+ return;
+ }
mClientVisible = clientVisible;
+ mDisplayContent.mWmService.mH.sendMessage(PooledLambda.obtainMessage(
+ DisplayContent::layoutAndAssignWindowLayersIfNeeded, mDisplayContent));
updateVisibility();
}
@@ -152,13 +158,8 @@
return mControl;
}
- void revokeControl() {
- if (mControllingWin != null) {
-
- // Cancelling the animation will invoke onAnimationCancelled, resetting all the fields.
- mWin.cancelAnimation();
- }
- setClientVisible(InsetsState.getDefaultVisibly(mSource.getType()));
+ boolean isClientVisible() {
+ return !ViewRootImpl.USE_NEW_INSETS || mClientVisible;
}
private class ControlAdapter implements AnimationAdapter {
@@ -186,6 +187,7 @@
public void onAnimationCancelled(SurfaceControl animationLeash) {
if (mAdapter == this) {
mStateController.notifyControlRevoked(mControllingWin, InsetsSourceProvider.this);
+ setClientVisible(InsetsState.getDefaultVisibly(mSource.getType()));
mControl = null;
mControllingWin = null;
mAdapter = null;
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 8e119bb..bc01f7c 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -202,6 +202,9 @@
}
private void notifyPendingInsetsControlChanged() {
+ if (mPendingControlChanged.isEmpty()) {
+ return;
+ }
mDisplayContent.mWmService.mAnimator.addAfterPrepareSurfacesRunnable(() -> {
for (int i = mPendingControlChanged.size() - 1; i >= 0; i--) {
final WindowState controllingWin = mPendingControlChanged.valueAt(i);
@@ -218,5 +221,11 @@
void dump(String prefix, PrintWriter pw) {
pw.println(prefix + "WindowInsetsStateController");
mState.dump(prefix + " ", pw);
+ pw.println(prefix + " " + "Control map:");
+ for (int i = mTypeWinControlMap.size() - 1; i >= 0; i--) {
+ pw.print(prefix + " ");
+ pw.println(InsetsState.typeToString(mTypeWinControlMap.keyAt(i)) + " -> "
+ + mTypeWinControlMap.valueAt(i));
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index 1a2aa2f..33ff194 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -18,6 +18,7 @@
import static android.view.SurfaceControl.HIDDEN;
+import android.graphics.Point;
import android.graphics.Rect;
import android.view.SurfaceControl;
@@ -30,6 +31,7 @@
public class Letterbox {
private static final Rect EMPTY_RECT = new Rect();
+ private static final Point ZERO_POINT = new Point(0, 0);
private final Supplier<SurfaceControl.Builder> mFactory;
private final Rect mOuter = new Rect();
@@ -53,14 +55,19 @@
* frames will be covered by black color surfaces.
*
* The caller must use {@link #applySurfaceChanges} to apply the new layout to the surface.
- *
* @param outer the outer frame of the letterbox (this frame will be black, except the area
- * that intersects with the {code inner} frame).
- * @param inner the inner frame of the letterbox (this frame will be clear)
+ * that intersects with the {code inner} frame), in global coordinates
+ * @param inner the inner frame of the letterbox (this frame will be clear), in global
+ * coordinates
+ * @param surfaceOrigin the origin of the surface factory in global coordinates
*/
- public void layout(Rect outer, Rect inner) {
+ public void layout(Rect outer, Rect inner, Point surfaceOrigin) {
mOuter.set(outer);
mInner.set(inner);
+ mOuter.offset(-surfaceOrigin.x, -surfaceOrigin.y);
+ mInner.offset(-surfaceOrigin.x, -surfaceOrigin.y);
+ outer = mOuter;
+ inner = mInner;
mTop.layout(outer.left, outer.top, inner.right, inner.top);
mLeft.layout(outer.left, inner.top, inner.left, outer.bottom);
@@ -94,7 +101,7 @@
* The caller must use {@link #applySurfaceChanges} to apply the new layout to the surface.
*/
public void hide() {
- layout(EMPTY_RECT, EMPTY_RECT);
+ layout(EMPTY_RECT, EMPTY_RECT, ZERO_POINT);
}
/**
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index d85fdb0..937c9d9 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -187,7 +187,7 @@
Rect outFrame, Rect outOverscanInsets, Rect outContentInsets, Rect outVisibleInsets,
Rect outStableInsets, Rect outsets, Rect outBackdropFrame,
DisplayCutout.ParcelableWrapper cutout, MergedConfiguration mergedConfiguration,
- Surface outSurface, InsetsState outInsetsState) {
+ SurfaceControl outSurfaceControl, InsetsState outInsetsState) {
if (false) Slog.d(TAG_WM, ">>>>>> ENTERED relayout from "
+ Binder.getCallingPid());
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, mRelayoutTag);
@@ -195,7 +195,7 @@
requestedWidth, requestedHeight, viewFlags, flags, frameNumber,
outFrame, outOverscanInsets, outContentInsets, outVisibleInsets,
outStableInsets, outsets, outBackdropFrame, cutout,
- mergedConfiguration, outSurface, outInsetsState);
+ mergedConfiguration, outSurfaceControl, outInsetsState);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
if (false) Slog.d(TAG_WM, "<<<<<< EXITING relayout to "
+ Binder.getCallingPid());
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 11068ce..9d9b48a 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -280,7 +280,7 @@
}
mService.mAnimationTransferMap.remove(mAnimation);
if (mLeash != null && destroyLeash) {
- t.destroy(mLeash);
+ t.reparent(mLeash, null);
scheduleAnim = true;
}
mLeash = null;
diff --git a/services/core/java/com/android/server/wm/TaskPositioningController.java b/services/core/java/com/android/server/wm/TaskPositioningController.java
index e15bf5b..9163165 100644
--- a/services/core/java/com/android/server/wm/TaskPositioningController.java
+++ b/services/core/java/com/android/server/wm/TaskPositioningController.java
@@ -24,6 +24,7 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Slog;
@@ -50,6 +51,7 @@
private @Nullable TaskPositioner mTaskPositioner;
private final Rect mTmpClipRect = new Rect();
+ private IBinder mTransferTouchFromToken;
boolean isPositioningLocked() {
return mTaskPositioner != null;
@@ -102,6 +104,8 @@
mTmpClipRect.set(0, 0, p.x, p.y);
t.setWindowCrop(mInputSurface, mTmpClipRect);
+ t.transferTouchFocus(mTransferTouchFromToken, h.token);
+ mTransferTouchFromToken = null;
}
boolean startMovingTask(IWindow window, float startX, float startY) {
@@ -170,7 +174,6 @@
mPositioningDisplay = displayContent;
mTaskPositioner = TaskPositioner.create(mService);
- mTaskPositioner.register(displayContent);
// We need to grab the touch focus so that the touch events during the
// resizing/scrolling are not sent to the app. 'win' is the main window
@@ -181,12 +184,8 @@
&& displayContent.mCurrentFocus.mAppToken == win.mAppToken) {
transferFocusFromWin = displayContent.mCurrentFocus;
}
- if (!mInputManager.transferTouchFocus(
- transferFocusFromWin.mInputChannel, mTaskPositioner.mServerChannel)) {
- Slog.e(TAG_WM, "startPositioningLocked: Unable to transfer touch focus");
- cleanUpTaskPositioner();
- return false;
- }
+ mTransferTouchFromToken = transferFocusFromWin.mInputChannel.getToken();
+ mTaskPositioner.register(displayContent);
mTaskPositioner.startDrag(win, resize, preserveOrientation, startX, startY);
return true;
diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java
index e343322..4a553cf 100644
--- a/services/core/java/com/android/server/wm/TaskRecord.java
+++ b/services/core/java/com/android/server/wm/TaskRecord.java
@@ -44,6 +44,9 @@
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE_DEPRECATED;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
+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.provider.Settings.Secure.USER_SETUP_COMPLETE;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -971,10 +974,11 @@
*/
boolean isSameIntentFilter(ActivityRecord r) {
final Intent intent = new Intent(r.intent);
- // Correct the activity intent for aliasing. The task record intent will always be based on
- // the real activity that will be launched not the alias, so we need to use an intent with
- // the component name pointing to the real activity not the alias in the activity record.
- intent.setComponent(r.mActivityComponent);
+ // Make sure the component are the same if the input activity has the same real activity
+ // as the one in the task because either one of them could be the alias activity.
+ if (Objects.equals(realActivity, r.mActivityComponent) && this.intent != null) {
+ intent.setComponent(this.intent.getComponent());
+ }
return intent.filterEquals(this.intent);
}
@@ -1259,10 +1263,6 @@
setFrontOfTask();
}
- void addActivityAtBottom(ActivityRecord r) {
- addActivityAtIndex(0, r);
- }
-
void addActivityToTop(ActivityRecord r) {
addActivityAtIndex(mActivities.size(), r);
}
@@ -1278,6 +1278,34 @@
}
/**
+ * Checks if the root activity requires a particular orientation (either by override or
+ * activityInfo) and returns that. Otherwise, this returns ORIENTATION_UNDEFINED.
+ */
+ private int getRootActivityRequestedOrientation() {
+ ActivityRecord root = getRootActivity();
+ if (getRequestedOverrideConfiguration().orientation != ORIENTATION_UNDEFINED
+ || root == null) {
+ return getRequestedOverrideConfiguration().orientation;
+ }
+ int rootScreenOrientation = root.getOrientation();
+ if (rootScreenOrientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR) {
+ // NOSENSOR means the display's "natural" orientation, so return that.
+ ActivityDisplay display = mStack != null ? mStack.getDisplay() : null;
+ if (display != null && display.mDisplayContent != null) {
+ return mStack.getDisplay().mDisplayContent.getNaturalOrientation();
+ }
+ } else if (rootScreenOrientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED) {
+ // LOCKED means the activity's orientation remains unchanged, so return existing value.
+ return root.getConfiguration().orientation;
+ } else if (ActivityInfo.isFixedOrientationLandscape(rootScreenOrientation)) {
+ return ORIENTATION_LANDSCAPE;
+ } else if (ActivityInfo.isFixedOrientationPortrait(rootScreenOrientation)) {
+ return ORIENTATION_PORTRAIT;
+ }
+ return ORIENTATION_UNDEFINED;
+ }
+
+ /**
* Adds an activity {@param r} at the given {@param index}. The activity {@param r} must either
* be in the current task or unparented to any task.
*/
@@ -1741,7 +1769,7 @@
updateTaskDescription();
}
- private void adjustForMinimalTaskDimensions(Rect bounds, Rect previousBounds) {
+ void adjustForMinimalTaskDimensions(Rect bounds, Rect previousBounds) {
if (bounds == null) {
return;
}
@@ -1853,11 +1881,27 @@
@Override
public void onConfigurationChanged(Configuration newParentConfig) {
+ // Check if the new configuration supports persistent bounds (eg. is Freeform) and if so
+ // restore the last recorded non-fullscreen bounds.
+ final boolean prevPersistTaskBounds = getWindowConfiguration().persistTaskBounds();
+ final boolean nextPersistTaskBounds =
+ getRequestedOverrideConfiguration().windowConfiguration.persistTaskBounds()
+ || newParentConfig.windowConfiguration.persistTaskBounds();
+ if (!prevPersistTaskBounds && nextPersistTaskBounds
+ && mLastNonFullscreenBounds != null && !mLastNonFullscreenBounds.isEmpty()) {
+ // Bypass onRequestedOverrideConfigurationChanged here to avoid infinite loop.
+ getRequestedOverrideConfiguration().windowConfiguration
+ .setBounds(mLastNonFullscreenBounds);
+ }
+
final boolean wasInMultiWindowMode = inMultiWindowMode();
super.onConfigurationChanged(newParentConfig);
if (wasInMultiWindowMode != inMultiWindowMode()) {
mService.mStackSupervisor.scheduleUpdateMultiWindowMode(this);
}
+
+ // If the configuration supports persistent bounds (eg. Freeform), keep track of the
+ // current (non-fullscreen) bounds for persistence.
if (getWindowConfiguration().persistTaskBounds()) {
final Rect currentBounds = getRequestedOverrideBounds();
if (!currentBounds.isEmpty()) {
@@ -2047,7 +2091,7 @@
* configuring an "inherit-bounds" window which means that all configuration settings would
* just be inherited from the parent configuration.
**/
- void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, @NonNull Rect bounds,
+ void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
@NonNull Configuration parentConfig) {
int windowingMode = inOutConfig.windowConfiguration.getWindowingMode();
if (windowingMode == WINDOWING_MODE_UNDEFINED) {
@@ -2060,6 +2104,7 @@
}
density *= DisplayMetrics.DENSITY_DEFAULT_SCALE;
+ final Rect bounds = inOutConfig.windowConfiguration.getBounds();
Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
if (outAppBounds == null || outAppBounds.isEmpty()) {
inOutConfig.windowConfiguration.setAppBounds(bounds);
@@ -2107,13 +2152,14 @@
// Iterating across all screen orientations, and return the minimum of the task
// width taking into account that the bounds might change because the snap
// algorithm snaps to a different value
- getSmallestScreenWidthDpForDockedBounds(bounds);
+ inOutConfig.smallestScreenWidthDp =
+ getSmallestScreenWidthDpForDockedBounds(bounds);
}
// otherwise, it will just inherit
}
}
- if (inOutConfig.orientation == Configuration.ORIENTATION_UNDEFINED) {
+ if (inOutConfig.orientation == ORIENTATION_UNDEFINED) {
inOutConfig.orientation = (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp)
? Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
}
@@ -2134,36 +2180,56 @@
}
}
- // TODO(b/113900640): remove this once ActivityRecord is changed to not need it anymore.
- void computeResolvedOverrideConfiguration(Configuration inOutConfig, Configuration parentConfig,
- Configuration overrideConfig) {
- // Save previous bounds because adjustForMinimalTaskDimensions uses that to determine if it
- // changes left bound vs. right bound, or top bound vs. bottom bound.
- mTmpBounds.set(inOutConfig.windowConfiguration.getBounds());
-
- inOutConfig.setTo(overrideConfig);
-
- Rect outOverrideBounds = inOutConfig.windowConfiguration.getBounds();
- if (outOverrideBounds != null && !outOverrideBounds.isEmpty()) {
- adjustForMinimalTaskDimensions(outOverrideBounds, mTmpBounds);
-
- int windowingMode = overrideConfig.windowConfiguration.getWindowingMode();
- if (windowingMode == WINDOWING_MODE_UNDEFINED) {
- windowingMode = parentConfig.windowConfiguration.getWindowingMode();
- }
- if (windowingMode == WINDOWING_MODE_FREEFORM) {
- // by policy, make sure the window remains within parent
- fitWithinBounds(outOverrideBounds, parentConfig.windowConfiguration.getBounds());
- }
-
- computeConfigResourceOverrides(inOutConfig, outOverrideBounds, parentConfig);
- }
- }
-
@Override
void resolveOverrideConfiguration(Configuration newParentConfig) {
- computeResolvedOverrideConfiguration(getResolvedOverrideConfiguration(), newParentConfig,
- getRequestedOverrideConfiguration());
+ mTmpBounds.set(getResolvedOverrideConfiguration().windowConfiguration.getBounds());
+ super.resolveOverrideConfiguration(newParentConfig);
+ int windowingMode =
+ getRequestedOverrideConfiguration().windowConfiguration.getWindowingMode();
+ if (windowingMode == WINDOWING_MODE_UNDEFINED) {
+ windowingMode = newParentConfig.windowConfiguration.getWindowingMode();
+ }
+ Rect outOverrideBounds =
+ getResolvedOverrideConfiguration().windowConfiguration.getBounds();
+
+ if (windowingMode == WINDOWING_MODE_FULLSCREEN) {
+ // In FULLSCREEN mode, always start with empty bounds to indicate "fill parent"
+ outOverrideBounds.setEmpty();
+
+ // If the task or its root activity require a different orientation, make it fit the
+ // available bounds by scaling down its bounds.
+ int forcedOrientation = getRootActivityRequestedOrientation();
+ if (forcedOrientation != ORIENTATION_UNDEFINED
+ && forcedOrientation != newParentConfig.orientation) {
+ final Rect parentBounds = newParentConfig.windowConfiguration.getBounds();
+ final int parentWidth = parentBounds.width();
+ final int parentHeight = parentBounds.height();
+ final float aspect = ((float) parentHeight) / parentWidth;
+ if (forcedOrientation == ORIENTATION_LANDSCAPE) {
+ final int height = (int) (parentWidth / aspect);
+ final int top = parentBounds.centerY() - height / 2;
+ outOverrideBounds.set(
+ parentBounds.left, top, parentBounds.right, top + height);
+ } else {
+ final int width = (int) (parentHeight * aspect);
+ final int left = parentBounds.centerX() - width / 2;
+ outOverrideBounds.set(
+ left, parentBounds.top, left + width, parentBounds.bottom);
+ }
+ }
+ }
+
+ if (outOverrideBounds.isEmpty()) {
+ // If the task fills the parent, just inherit all the other configs from parent.
+ return;
+ }
+
+ adjustForMinimalTaskDimensions(outOverrideBounds, mTmpBounds);
+ if (windowingMode == WINDOWING_MODE_FREEFORM) {
+ // by policy, make sure the window remains within parent somewhere
+ fitWithinBounds(outOverrideBounds, newParentConfig.windowConfiguration.getBounds());
+ }
+ computeConfigResourceOverrides(getResolvedOverrideConfiguration(), newParentConfig);
}
Rect updateOverrideConfigurationFromLaunchBounds() {
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
index 9a56606..2d3e3ae 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -111,6 +111,7 @@
private static final String TITLE_FORMAT = "SnapshotStartingWindow for taskId=%s";
private final Window mWindow;
private final Surface mSurface;
+ private SurfaceControl mSurfaceControl;
private SurfaceControl mChildSurfaceControl;
private final IWindowSession mSession;
private final WindowManagerService mService;
@@ -136,7 +137,7 @@
final Window window = new Window();
final IWindowSession session = WindowManagerGlobal.getWindowSession();
window.setSession(session);
- final Surface surface = new Surface();
+ final SurfaceControl surfaceControl = new SurfaceControl();
final Rect tmpRect = new Rect();
final DisplayCutout.ParcelableWrapper tmpCutout = new DisplayCutout.ParcelableWrapper();
final Rect tmpFrame = new Rect();
@@ -213,14 +214,14 @@
// Local call.
}
final TaskSnapshotSurface snapshotSurface = new TaskSnapshotSurface(service, window,
- surface, snapshot, layoutParams.getTitle(), backgroundColor, statusBarColor,
+ surfaceControl, snapshot, layoutParams.getTitle(), backgroundColor, statusBarColor,
navigationBarColor, sysUiVis, windowFlags, windowPrivateFlags, taskBounds,
currentOrientation);
window.setOuter(snapshotSurface);
try {
session.relayout(window, window.mSeq, layoutParams, -1, -1, View.VISIBLE, 0, -1,
tmpFrame, tmpRect, tmpContentInsets, tmpRect, tmpStableInsets, tmpRect, tmpRect,
- tmpCutout, tmpMergedConfiguration, surface, mTmpInsetsState);
+ tmpCutout, tmpMergedConfiguration, surfaceControl, mTmpInsetsState);
} catch (RemoteException e) {
// Local call.
}
@@ -230,15 +231,16 @@
}
@VisibleForTesting
- TaskSnapshotSurface(WindowManagerService service, Window window, Surface surface,
+ TaskSnapshotSurface(WindowManagerService service, Window window, SurfaceControl surfaceControl,
TaskSnapshot snapshot, CharSequence title, int backgroundColor, int statusBarColor,
int navigationBarColor, int sysUiVis, int windowFlags, int windowPrivateFlags,
Rect taskBounds, int currentOrientation) {
mService = service;
+ mSurface = new Surface();
mHandler = new Handler(mService.mH.getLooper());
mSession = WindowManagerGlobal.getWindowSession();
mWindow = window;
- mSurface = surface;
+ mSurfaceControl = surfaceControl;
mSnapshot = snapshot;
mTitle = title;
mBackgroundPaint.setColor(backgroundColor != 0 ? backgroundColor : WHITE);
@@ -281,6 +283,8 @@
private void drawSnapshot() {
final GraphicBuffer buffer = mSnapshot.getSnapshot();
+ mSurface.copyFrom(mSurfaceControl);
+
if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Drawing snapshot surface sizeMismatch="
+ mSizeMismatch);
if (mSizeMismatch) {
@@ -310,13 +314,14 @@
if (!mSurface.isValid()) {
throw new IllegalStateException("mSurface does not hold a valid surface.");
}
- final SurfaceSession session = new SurfaceSession(mSurface);
+ final SurfaceSession session = new SurfaceSession();
// Keep a reference to it such that it doesn't get destroyed when finalized.
mChildSurfaceControl = new SurfaceControl.Builder(session)
.setName(mTitle + " - task-snapshot-surface")
.setBufferSize(buffer.getWidth(), buffer.getHeight())
.setFormat(buffer.getFormat())
+ .setParent(mSurfaceControl)
.build();
Surface surface = new Surface();
surface.copyFrom(mChildSurfaceControl);
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 32c5a3b..25e61f8 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -326,7 +326,7 @@
}
if (mSurfaceControl != null) {
- mPendingTransaction.destroy(mSurfaceControl);
+ mPendingTransaction.reparent(mSurfaceControl, null);
// Merge to parent transaction to ensure the transactions on this WindowContainer are
// applied in native even if WindowContainer is removed.
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 646fdd9..1691dc0 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -158,8 +158,9 @@
default boolean registerInputChannel(
DragState state, Display display, InputManagerService service,
InputChannel source) {
+ state.mTransferTouchFromToken = source.getToken();
state.register(display);
- return service.transferTouchFocus(source, state.getInputChannel());
+ return true;
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index f5f55e2..91aac7e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1874,7 +1874,7 @@
long frameNumber, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,
Rect outVisibleInsets, Rect outStableInsets, Rect outOutsets, Rect outBackdropFrame,
DisplayCutout.ParcelableWrapper outCutout, MergedConfiguration mergedConfiguration,
- Surface outSurface, InsetsState outInsetsState) {
+ SurfaceControl outSurfaceControl, InsetsState outInsetsState) {
int result = 0;
boolean configChanged;
final boolean hasStatusBarPermission =
@@ -2047,7 +2047,7 @@
result = win.relayoutVisibleWindow(result, attrChanges, oldVisibility);
try {
- result = createSurfaceControl(outSurface, result, win, winAnimator);
+ result = createSurfaceControl(outSurfaceControl, result, win, winAnimator);
} catch (Exception e) {
displayContent.getInputMonitor().updateInputWindowsLw(true /*force*/);
@@ -2078,7 +2078,7 @@
// handled yet, or it might want to draw a last frame. If we already have a
// surface, let the client use that, but don't create new surface at this point.
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: getSurface");
- winAnimator.mSurfaceController.getSurface(outSurface);
+ winAnimator.mSurfaceController.getSurfaceControl(outSurfaceControl);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
} else {
if (DEBUG_VISIBILITY) Slog.i(TAG_WM, "Releasing surface in: " + win);
@@ -2086,7 +2086,7 @@
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wmReleaseOutSurface_"
+ win.mAttrs.getTitle());
- outSurface.release();
+ outSurfaceControl.release();
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
@@ -2182,7 +2182,7 @@
+ ", requestedHeight=" + requestedHeight
+ ", viewVisibility=" + viewVisibility
+ "\nRelayout returning frame=" + outFrame
- + ", surface=" + outSurface);
+ + ", surface=" + outSurfaceControl);
if (localLOGV || DEBUG_FOCUS) Slog.v(
TAG_WM, "Relayout of " + win + ": focusMayChange=" + focusMayChange);
@@ -2252,7 +2252,7 @@
return focusMayChange;
}
- private int createSurfaceControl(Surface outSurface, int result, WindowState win,
+ private int createSurfaceControl(SurfaceControl outSurfaceControl, int result, WindowState win,
WindowStateAnimator winAnimator) {
if (!win.mHasSurface) {
result |= RELAYOUT_RES_SURFACE_CHANGED;
@@ -2266,13 +2266,13 @@
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
if (surfaceController != null) {
- surfaceController.getSurface(outSurface);
- if (SHOW_TRANSACTIONS) Slog.i(TAG_WM, " OUT SURFACE " + outSurface + ": copied");
+ surfaceController.getSurfaceControl(outSurfaceControl);
+ if (SHOW_TRANSACTIONS) Slog.i(TAG_WM, " OUT SURFACE " + outSurfaceControl + ": copied");
} else {
// For some reason there isn't a surface. Clear the
// caller's object so they see the same state.
Slog.w(TAG_WM, "Failed to create surface control for " + win);
- outSurface.release();
+ outSurfaceControl.release();
}
return result;
@@ -6166,7 +6166,7 @@
dumpWindowsLocked(pw, true, null);
}
return;
- } else if ("all".equals(cmd) || "a".equals(cmd)) {
+ } else if ("all".equals(cmd)) {
synchronized (mGlobalLock) {
dumpWindowsLocked(pw, true, null);
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index e78c12c..cd29b3c 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1373,12 +1373,15 @@
@Override
boolean isVisible() {
- return wouldBeVisibleIfPolicyIgnored() && mPolicyVisibility;
+ return wouldBeVisibleIfPolicyIgnored() && mPolicyVisibility
+ // If we don't have a provider, this window isn't used as a window generating
+ // insets, so nobody can hide it over the inset APIs.
+ && (mInsetProvider == null || mInsetProvider.isClientVisible());
}
/**
- * @return True if the window would be visible if we'd ignore policy visibility, false
- * otherwise.
+ * @return {@code true} if the window would be visible if we'd ignore policy visibility,
+ * {@code false} otherwise.
*/
boolean wouldBeVisibleIfPolicyIgnored() {
return mHasSurface && !isParentWindowHidden()
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index ce627e2..c2a8e7e 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -478,8 +478,8 @@
return mSurfaceControl.getHandle();
}
- void getSurface(Surface outSurface) {
- outSurface.copyFrom(mSurfaceControl);
+ void getSurfaceControl(SurfaceControl outSurfaceControl) {
+ outSurfaceControl.copyFrom(mSurfaceControl);
}
int getLayer() {
diff --git a/services/core/java/com/android/server/wm/WindowTraceBuffer.java b/services/core/java/com/android/server/wm/WindowTraceBuffer.java
new file mode 100644
index 0000000..936ee85
--- /dev/null
+++ b/services/core/java/com/android/server/wm/WindowTraceBuffer.java
@@ -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.android.server.wm;
+
+import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER;
+import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_H;
+import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_L;
+
+import android.os.Trace;
+import android.util.proto.ProtoOutputStream;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * Buffer used for window tracing.
+ */
+abstract class WindowTraceBuffer {
+ private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
+
+ final Object mBufferSizeLock = new Object();
+ final BlockingQueue<byte[]> mBuffer;
+ int mBufferSize;
+ private final int mBufferCapacity;
+ private final File mTraceFile;
+
+ WindowTraceBuffer(int size, File traceFile) throws IOException {
+ mBufferCapacity = size;
+ mTraceFile = traceFile;
+ mBuffer = new LinkedBlockingQueue<>();
+
+ initTraceFile();
+ }
+
+ int getAvailableSpace() {
+ return mBufferCapacity - mBufferSize;
+ }
+
+ /**
+ * Inserts the specified element into this buffer.
+ *
+ * This method is synchronized with {@code #take()} and {@code #clear()}
+ * for consistency.
+ *
+ * @param proto the element to add
+ * @return {@code true} if the inserted item was inserted into the buffer
+ * @throws IllegalStateException if the element cannot be added because it is larger
+ * than the buffer size.
+ */
+ boolean add(ProtoOutputStream proto) throws InterruptedException {
+ byte[] protoBytes = proto.getBytes();
+ int protoLength = protoBytes.length;
+ if (protoLength > mBufferCapacity) {
+ throw new IllegalStateException("Trace object too large for the buffer. Buffer size:"
+ + mBufferCapacity + " Object size: " + protoLength);
+ }
+ synchronized (mBufferSizeLock) {
+ boolean canAdd = canAdd(protoBytes);
+ if (canAdd) {
+ mBuffer.offer(protoBytes);
+ mBufferSize += protoLength;
+ }
+ return canAdd;
+ }
+ }
+
+ void writeNextBufferElementToFile() throws IOException {
+ byte[] proto;
+ try {
+ proto = take();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return;
+ }
+
+ try {
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeToFile");
+ try (OutputStream os = new FileOutputStream(mTraceFile, true)) {
+ os.write(proto);
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ /**
+ * Retrieves and removes the head of this queue, waiting if necessary
+ * until an element becomes available.
+ *
+ * This method is synchronized with {@code #add(ProtoOutputStream)} and {@code #clear()}
+ * for consistency.
+ *
+ * @return the head of this buffer, or {@code null} if this buffer is empty
+ */
+ private byte[] take() throws InterruptedException {
+ byte[] item = mBuffer.take();
+ synchronized (mBufferSizeLock) {
+ mBufferSize -= item.length;
+ return item;
+ }
+ }
+
+ private void initTraceFile() throws IOException {
+ mTraceFile.delete();
+ try (OutputStream os = new FileOutputStream(mTraceFile)) {
+ mTraceFile.setReadable(true, false);
+ ProtoOutputStream proto = new ProtoOutputStream(os);
+ proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
+ proto.flush();
+ }
+ }
+
+ /**
+ * Checks if the element can be added to the buffer. The element is already certain to be
+ * smaller than the overall buffer size.
+ *
+ * @param protoBytes byte array representation of the Proto object to add
+ * @return <tt>true<</tt> if the element can be added to the buffer or not
+ */
+ abstract boolean canAdd(byte[] protoBytes) throws InterruptedException;
+
+ /**
+ * Flush all buffer content to the disk.
+ *
+ * @throws IOException if the buffer cannot write its contents to the {@link #mTraceFile}
+ */
+ abstract void writeToDisk() throws IOException, InterruptedException;
+
+ /**
+ * Builder for a {@code WindowTraceBuffer} which creates a {@link WindowTraceQueueBuffer}
+ */
+ static class Builder {
+ private File mTraceFile;
+ private int mBufferCapacity;
+
+
+ Builder setTraceFile(File traceFile) {
+ mTraceFile = traceFile;
+ return this;
+ }
+
+ Builder setBufferCapacity(int size) {
+ mBufferCapacity = size;
+ return this;
+ }
+
+ File getFile() {
+ return mTraceFile;
+ }
+
+ WindowTraceBuffer build() throws IOException {
+ if (mBufferCapacity <= 0) {
+ throw new IllegalStateException("Buffer capacity must be greater than 0.");
+ }
+
+ if (mTraceFile == null) {
+ throw new IllegalArgumentException("A valid trace file must be specified.");
+ }
+
+ return new WindowTraceQueueBuffer(mBufferCapacity, mTraceFile);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/WindowTraceQueueBuffer.java b/services/core/java/com/android/server/wm/WindowTraceQueueBuffer.java
new file mode 100644
index 0000000..b7fc7ac
--- /dev/null
+++ b/services/core/java/com/android/server/wm/WindowTraceQueueBuffer.java
@@ -0,0 +1,88 @@
+/*
+ * 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.wm;
+
+import static android.os.Build.IS_USER;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * A buffer structure backed by a {@link java.util.concurrent.BlockingQueue} to store the first
+ * {@code #size size} bytes of window trace elements.
+ * Once the buffer is full it will no longer accepts new elements.
+ */
+class WindowTraceQueueBuffer extends WindowTraceBuffer {
+ private Thread mWriterThread;
+ private boolean mCancel;
+
+ @VisibleForTesting
+ WindowTraceQueueBuffer(int size, File traceFile, boolean startWriterThread) throws IOException {
+ super(size, traceFile);
+ if (startWriterThread) {
+ initializeWriterThread();
+ }
+ }
+
+ WindowTraceQueueBuffer(int size, File traceFile) throws IOException {
+ this(size, traceFile, !IS_USER);
+ }
+
+ private void initializeWriterThread() {
+ mCancel = false;
+ mWriterThread = new Thread(() -> {
+ try {
+ loop();
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to execute trace write loop thread", e);
+ }
+ }, "window_tracing");
+ mWriterThread.start();
+ }
+
+ private void loop() throws IOException {
+ while (!mCancel) {
+ writeNextBufferElementToFile();
+ }
+ }
+
+ private void restartWriterThread() throws InterruptedException {
+ if (mWriterThread != null) {
+ mCancel = true;
+ mWriterThread.interrupt();
+ mWriterThread.join();
+ initializeWriterThread();
+ }
+ }
+
+ @Override
+ boolean canAdd(byte[] protoBytes) {
+ long availableSpace = getAvailableSpace();
+ return availableSpace >= protoBytes.length;
+ }
+
+ @Override
+ void writeToDisk() throws InterruptedException {
+ while (!mBuffer.isEmpty()) {
+ mBufferSizeLock.wait();
+ mBufferSizeLock.notify();
+ }
+ restartWriterThread();
+ }
+}
diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java
index 8fa56bb..63539c4 100644
--- a/services/core/java/com/android/server/wm/WindowTracing.java
+++ b/services/core/java/com/android/server/wm/WindowTracing.java
@@ -17,31 +17,23 @@
package com.android.server.wm;
import static android.os.Build.IS_USER;
+
import static com.android.server.wm.WindowManagerTraceFileProto.ENTRY;
-import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER;
-import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_H;
-import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_L;
import static com.android.server.wm.WindowManagerTraceProto.ELAPSED_REALTIME_NANOS;
import static com.android.server.wm.WindowManagerTraceProto.WHERE;
import static com.android.server.wm.WindowManagerTraceProto.WINDOW_MANAGER_SERVICE;
+import android.annotation.Nullable;
import android.content.Context;
import android.os.ShellCommand;
import android.os.SystemClock;
import android.os.Trace;
-import android.annotation.Nullable;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
-import com.android.internal.annotations.VisibleForTesting;
-
import java.io.File;
-import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.OutputStream;
import java.io.PrintWriter;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.BlockingQueue;
/**
* A class that allows window manager to dump its state continuously to a trace file, such that a
@@ -49,35 +41,42 @@
*/
class WindowTracing {
+ /**
+ * Maximum buffer size, currently defined as 512 KB
+ * Size was experimentally defined to fit between 100 to 150 elements.
+ */
+ private static final int WINDOW_TRACE_BUFFER_SIZE = 512 * 1024;
private static final String TAG = "WindowTracing";
- private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
private final Object mLock = new Object();
- private final File mTraceFile;
- private final BlockingQueue<ProtoOutputStream> mWriteQueue = new ArrayBlockingQueue<>(200);
+ private final WindowTraceBuffer.Builder mBufferBuilder;
+
+ private WindowTraceBuffer mTraceBuffer;
private boolean mEnabled;
private volatile boolean mEnabledLockFree;
WindowTracing(File file) {
- mTraceFile = file;
+ mBufferBuilder = new WindowTraceBuffer.Builder()
+ .setTraceFile(file)
+ .setBufferCapacity(WINDOW_TRACE_BUFFER_SIZE);
}
void startTrace(@Nullable PrintWriter pw) throws IOException {
- if (IS_USER){
+ if (IS_USER) {
logAndPrintln(pw, "Error: Tracing is not supported on user builds.");
return;
}
synchronized (mLock) {
- logAndPrintln(pw, "Start tracing to " + mTraceFile + ".");
- mWriteQueue.clear();
- mTraceFile.delete();
- try (OutputStream os = new FileOutputStream(mTraceFile)) {
- mTraceFile.setReadable(true, false);
- ProtoOutputStream proto = new ProtoOutputStream(os);
- proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
- proto.flush();
+ logAndPrintln(pw, "Start tracing to " + mBufferBuilder.getFile() + ".");
+ if (mTraceBuffer != null) {
+ try {
+ mTraceBuffer.writeToDisk();
+ } catch (InterruptedException e) {
+ logAndPrintln(pw, "Error: Unable to flush the previous buffer.");
+ }
}
+ mTraceBuffer = mBufferBuilder.build();
mEnabled = mEnabledLockFree = true;
}
}
@@ -91,67 +90,42 @@
}
void stopTrace(@Nullable PrintWriter pw) {
- if (IS_USER){
+ if (IS_USER) {
logAndPrintln(pw, "Error: Tracing is not supported on user builds.");
return;
}
synchronized (mLock) {
- logAndPrintln(pw, "Stop tracing to " + mTraceFile + ". Waiting for traces to flush.");
+ logAndPrintln(pw, "Stop tracing to " + mBufferBuilder.getFile()
+ + ". Waiting for traces to flush.");
mEnabled = mEnabledLockFree = false;
- while (!mWriteQueue.isEmpty()) {
+
+ synchronized (mLock) {
if (mEnabled) {
logAndPrintln(pw, "ERROR: tracing was re-enabled while waiting for flush.");
throw new IllegalStateException("tracing enabled while waiting for flush.");
}
try {
- mLock.wait();
- mLock.notify();
+ mTraceBuffer.writeToDisk();
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to write buffer to file", e);
} catch (InterruptedException e) {
- Thread.currentThread().interrupt();
+ Log.e(TAG, "Unable to interrupt window tracing file write thread", e);
}
}
- logAndPrintln(pw, "Trace written to " + mTraceFile + ".");
+ logAndPrintln(pw, "Trace written to " + mBufferBuilder.getFile() + ".");
}
}
- void appendTraceEntry(ProtoOutputStream proto) {
+ private void appendTraceEntry(ProtoOutputStream proto) {
if (!mEnabledLockFree) {
return;
}
- if (!mWriteQueue.offer(proto)) {
- Log.e(TAG, "Dropping window trace entry, queue full");
- }
- }
-
- void loop() {
- for (;;) {
- loopOnce();
- }
- }
-
- @VisibleForTesting
- void loopOnce() {
- ProtoOutputStream proto;
try {
- proto = mWriteQueue.take();
+ mTraceBuffer.add(proto);
} catch (InterruptedException e) {
+ Log.e(TAG, "Unable to add element to trace", e);
Thread.currentThread().interrupt();
- return;
- }
-
- synchronized (mLock) {
- try {
- Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeToFile");
- try (OutputStream os = new FileOutputStream(mTraceFile, true /* append */)) {
- os.write(proto.getBytes());
- }
- } catch (IOException e) {
- Log.e(TAG, "Failed to write file " + mTraceFile, e);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
- }
- mLock.notify();
}
}
@@ -161,11 +135,7 @@
static WindowTracing createDefaultAndStartLooper(Context context) {
File file = new File("/data/misc/wmtrace/wm_trace.pb");
- WindowTracing windowTracing = new WindowTracing(file);
- if (!IS_USER){
- new Thread(windowTracing::loop, "window_tracing").start();
- }
- return windowTracing;
+ return new WindowTracing(file);
}
int onShellCommand(ShellCommand shell, String cmd) {
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index b4fe837..c8c5e8f 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -107,6 +107,7 @@
"android.hardware.gnss@1.0",
"android.hardware.gnss@1.1",
"android.hardware.gnss@2.0",
+ "android.hardware.input.classifier@1.0",
"android.hardware.ir@1.0",
"android.hardware.light@2.0",
"android.hardware.power@1.0",
@@ -119,6 +120,7 @@
"android.hardware.vibrator@1.0",
"android.hardware.vibrator@1.1",
"android.hardware.vibrator@1.2",
+ "android.hardware.vibrator@1.3",
"android.hardware.vr@1.0",
"android.frameworks.schedulerservice@1.0",
"android.frameworks.sensorservice@1.0",
diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp
index defcfd9..63dca62 100644
--- a/services/core/jni/com_android_server_VibratorService.cpp
+++ b/services/core/jni/com_android_server_VibratorService.cpp
@@ -22,6 +22,7 @@
#include <android/hardware/vibrator/1.1/types.h>
#include <android/hardware/vibrator/1.2/IVibrator.h>
#include <android/hardware/vibrator/1.2/types.h>
+#include <android/hardware/vibrator/1.3/IVibrator.h>
#include "jni.h"
#include <nativehelper/JNIHelp.h>
@@ -42,6 +43,7 @@
namespace V1_0 = android::hardware::vibrator::V1_0;
namespace V1_1 = android::hardware::vibrator::V1_1;
namespace V1_2 = android::hardware::vibrator::V1_2;
+namespace V1_3 = android::hardware::vibrator::V1_3;
namespace android {
@@ -136,6 +138,19 @@
}
}
+static jboolean vibratorSupportsExternalControl(JNIEnv*, jobject) {
+ return halCall(&V1_3::IVibrator::supportsExternalControl).withDefault(false);
+}
+
+static void vibratorSetExternalControl(JNIEnv*, jobject, jboolean enabled) {
+ Status status = halCall(&V1_3::IVibrator::setExternalControl, static_cast<uint32_t>(enabled))
+ .withDefault(Status::UNKNOWN_ERROR);
+ if (status != Status::OK) {
+ ALOGE("Failed to set vibrator external control (%" PRIu32 ").",
+ static_cast<uint32_t>(status));
+ }
+}
+
static jlong vibratorPerformEffect(JNIEnv*, jobject, jlong effect, jint strength) {
Status status;
uint32_t lengthMs;
@@ -187,7 +202,9 @@
{ "vibratorOff", "()V", (void*)vibratorOff },
{ "vibratorSupportsAmplitudeControl", "()Z", (void*)vibratorSupportsAmplitudeControl},
{ "vibratorSetAmplitude", "(I)V", (void*)vibratorSetAmplitude},
- { "vibratorPerformEffect", "(JJ)J", (void*)vibratorPerformEffect}
+ { "vibratorPerformEffect", "(JJ)J", (void*)vibratorPerformEffect},
+ { "vibratorSupportsExternalControl", "()Z", (void*)vibratorSupportsExternalControl},
+ { "vibratorSetExternalControl", "(Z)V", (void*)vibratorSetExternalControl},
};
int register_android_server_VibratorService(JNIEnv *env)
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 43d2dcf..6f105ec 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -107,6 +107,7 @@
jmethodID getLongPressTimeout;
jmethodID getPointerLayer;
jmethodID getPointerIcon;
+ jmethodID getPointerDisplayId;
jmethodID getKeyboardLayoutOverlay;
jmethodID getDeviceAlias;
jmethodID getTouchCalibrationForInputDevice;
@@ -174,15 +175,6 @@
loadSystemIconAsSpriteWithPointerIcon(env, contextObj, style, &pointerIcon, outSpriteIcon);
}
-static void updatePointerControllerFromViewport(
- sp<PointerController> controller, const DisplayViewport* const viewport) {
- if (controller != nullptr && viewport != nullptr) {
- const int32_t width = viewport->logicalRight - viewport->logicalLeft;
- const int32_t height = viewport->logicalBottom - viewport->logicalTop;
- controller->setDisplayViewport(width, height, viewport->orientation);
- }
-}
-
enum {
WM_ACTION_PASS_TO_USER = 1,
};
@@ -310,14 +302,19 @@
// Input devices to be disabled
SortedVector<int32_t> disabledInputDevices;
+
+ // Associated Pointer controller display.
+ int32_t pointerDisplayId;
} mLocked GUARDED_BY(mLock);
std::atomic<bool> mInteractive;
- void updateInactivityTimeoutLocked(const sp<PointerController>& controller);
+ void updateInactivityTimeoutLocked();
void handleInterceptActions(jint wmActions, nsecs_t when, uint32_t& policyFlags);
void ensureSpriteControllerLocked();
-
+ const DisplayViewport* findDisplayViewportLocked(int32_t displayId);
+ int32_t getPointerDisplayId();
+ void updatePointerDisplayLocked();
static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName);
static inline JNIEnv* jniEnv() {
@@ -342,6 +339,7 @@
mLocked.pointerGesturesEnabled = true;
mLocked.showTouches = false;
mLocked.pointerCapture = false;
+ mLocked.pointerDisplayId = ADISPLAY_ID_DEFAULT;
}
mInteractive = true;
@@ -391,9 +389,10 @@
return false;
}
-static const DisplayViewport* findInternalViewport(const std::vector<DisplayViewport>& viewports) {
- for (const DisplayViewport& v : viewports) {
- if (v.type == ViewportType::VIEWPORT_INTERNAL) {
+const DisplayViewport* NativeInputManager::findDisplayViewportLocked(int32_t displayId)
+ REQUIRES(mLock) {
+ for (const DisplayViewport& v : mLocked.viewports) {
+ if (v.displayId == displayId) {
return &v;
}
}
@@ -420,20 +419,14 @@
}
}
- const DisplayViewport* newInternalViewport = findInternalViewport(viewports);
- {
+ // Get the preferred pointer controller displayId.
+ int32_t pointerDisplayId = getPointerDisplayId();
+
+ { // acquire lock
AutoMutex _l(mLock);
- const DisplayViewport* oldInternalViewport = findInternalViewport(mLocked.viewports);
- // Internal viewport has changed if there wasn't one earlier, and there is one now, or,
- // if they are different.
- const bool internalViewportChanged = (newInternalViewport != nullptr) &&
- (oldInternalViewport == nullptr || (*oldInternalViewport != *newInternalViewport));
- if (internalViewportChanged) {
- sp<PointerController> controller = mLocked.pointerController.promote();
- updatePointerControllerFromViewport(controller, newInternalViewport);
- }
mLocked.viewports = viewports;
- }
+ mLocked.pointerDisplayId = pointerDisplayId;
+ } // release lock
mInputManager->getReader()->requestRefreshConfiguration(
InputReaderConfiguration::CHANGE_DISPLAY_INFO);
@@ -556,15 +549,42 @@
controller = new PointerController(this, mLooper, mLocked.spriteController);
mLocked.pointerController = controller;
-
- const DisplayViewport* internalViewport = findInternalViewport(mLocked.viewports);
- updatePointerControllerFromViewport(controller, internalViewport);
-
- updateInactivityTimeoutLocked(controller);
+ updateInactivityTimeoutLocked();
}
+
+ updatePointerDisplayLocked();
+
return controller;
}
+int32_t NativeInputManager::getPointerDisplayId() {
+ JNIEnv* env = jniEnv();
+ jint pointerDisplayId = env->CallIntMethod(mServiceObj,
+ gServiceClassInfo.getPointerDisplayId);
+ if (checkAndClearExceptionFromCallback(env, "getPointerDisplayId")) {
+ pointerDisplayId = ADISPLAY_ID_DEFAULT;
+ }
+
+ return pointerDisplayId;
+}
+
+void NativeInputManager::updatePointerDisplayLocked() REQUIRES(mLock) {
+ ATRACE_CALL();
+
+ sp<PointerController> controller = mLocked.pointerController.promote();
+ if (controller != nullptr) {
+ const DisplayViewport* viewport = findDisplayViewportLocked(mLocked.pointerDisplayId);
+ if (viewport == nullptr) {
+ ALOGW("Can't find pointer display viewport, fallback to default display.");
+ viewport = findDisplayViewportLocked(ADISPLAY_ID_DEFAULT);
+ }
+
+ if (viewport != nullptr) {
+ controller->setDisplayViewport(*viewport);
+ }
+ }
+}
+
void NativeInputManager::ensureSpriteControllerLocked() REQUIRES(mLock) {
if (mLocked.spriteController == nullptr) {
JNIEnv* env = jniEnv();
@@ -821,16 +841,16 @@
if (mLocked.systemUiVisibility != visibility) {
mLocked.systemUiVisibility = visibility;
-
- sp<PointerController> controller = mLocked.pointerController.promote();
- if (controller != nullptr) {
- updateInactivityTimeoutLocked(controller);
- }
+ updateInactivityTimeoutLocked();
}
}
-void NativeInputManager::updateInactivityTimeoutLocked(const sp<PointerController>& controller)
- REQUIRES(mLock) {
+void NativeInputManager::updateInactivityTimeoutLocked() REQUIRES(mLock) {
+ sp<PointerController> controller = mLocked.pointerController.promote();
+ if (controller == nullptr) {
+ return;
+ }
+
bool lightsOut = mLocked.systemUiVisibility & ASYSTEM_UI_VISIBILITY_STATUS_BAR_HIDDEN;
controller->setInactivityTimeout(lightsOut
? PointerController::INACTIVITY_TIMEOUT_SHORT
@@ -1480,27 +1500,6 @@
im->setSystemUiVisibility(visibility);
}
-static jboolean nativeTransferTouchFocus(JNIEnv* env,
- jclass /* clazz */, jlong ptr, jobject fromChannelObj, jobject toChannelObj) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
-
- sp<InputChannel> fromChannel =
- android_view_InputChannel_getInputChannel(env, fromChannelObj);
- sp<InputChannel> toChannel =
- android_view_InputChannel_getInputChannel(env, toChannelObj);
-
- if (fromChannel == nullptr || toChannel == nullptr) {
- return JNI_FALSE;
- }
-
- if (im->getInputManager()->getDispatcher()->
- transferTouchFocus(fromChannel, toChannel)) {
- return JNI_TRUE;
- } else {
- return JNI_FALSE;
- }
-}
-
static void nativeSetPointerSpeed(JNIEnv* /* env */,
jclass /* clazz */, jlong ptr, jint speed) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
@@ -1688,8 +1687,6 @@
(void*) nativeSetInputDispatchMode },
{ "nativeSetSystemUiVisibility", "(JI)V",
(void*) nativeSetSystemUiVisibility },
- { "nativeTransferTouchFocus", "(JLandroid/view/InputChannel;Landroid/view/InputChannel;)Z",
- (void*) nativeTransferTouchFocus },
{ "nativeSetPointerSpeed", "(JI)V",
(void*) nativeSetPointerSpeed },
{ "nativeSetShowTouches", "(JZ)V",
@@ -1824,6 +1821,9 @@
GET_METHOD_ID(gServiceClassInfo.getPointerIcon, clazz,
"getPointerIcon", "()Landroid/view/PointerIcon;");
+ GET_METHOD_ID(gServiceClassInfo.getPointerDisplayId, clazz,
+ "getPointerDisplayId", "()I");
+
GET_METHOD_ID(gServiceClassInfo.getKeyboardLayoutOverlay, clazz,
"getKeyboardLayoutOverlay",
"(Landroid/hardware/input/InputDeviceIdentifier;)[Ljava/lang/String;");
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 6d8fc1c..b290bc5 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -135,6 +135,7 @@
using IGnss_V2_0 = android::hardware::gnss::V2_0::IGnss;
using IGnssConfiguration_V1_0 = android::hardware::gnss::V1_0::IGnssConfiguration;
using IGnssConfiguration_V1_1 = android::hardware::gnss::V1_1::IGnssConfiguration;
+using IGnssConfiguration_V2_0 = android::hardware::gnss::V2_0::IGnssConfiguration;
using IGnssMeasurement_V1_0 = android::hardware::gnss::V1_0::IGnssMeasurement;
using IGnssMeasurement_V1_1 = android::hardware::gnss::V1_1::IGnssMeasurement;
using IGnssMeasurement_V2_0 = android::hardware::gnss::V2_0::IGnssMeasurement;
@@ -179,6 +180,7 @@
sp<IGnssDebug> gnssDebugIface = nullptr;
sp<IGnssConfiguration_V1_0> gnssConfigurationIface = nullptr;
sp<IGnssConfiguration_V1_1> gnssConfigurationIface_V1_1 = nullptr;
+sp<IGnssConfiguration_V2_0> gnssConfigurationIface_V2_0 = nullptr;
sp<IGnssNi> gnssNiIface = nullptr;
sp<IGnssMeasurement_V1_0> gnssMeasurementIface = nullptr;
sp<IGnssMeasurement_V1_1> gnssMeasurementIface_V1_1 = nullptr;
@@ -313,6 +315,15 @@
}
}
+static jobject createHalInterfaceVersionJavaObject(JNIEnv* env, jint major, jint minor) {
+ jclass versionClass =
+ env->FindClass("com/android/server/location/GnssConfiguration$HalInterfaceVersion");
+ jmethodID versionCtor = env->GetMethodID(versionClass, "<init>", "(II)V");
+ jobject version = env->NewObject(versionClass, versionCtor, major, minor);
+ env->DeleteLocalRef(versionClass);
+ return version;
+}
+
struct ScopedJniString {
ScopedJniString(JNIEnv* env, jstring javaString) : mEnv(env), mJavaString(javaString) {
mNativeString = mEnv->GetStringUTFChars(mJavaString, nullptr);
@@ -1421,10 +1432,19 @@
gnssNiIface = gnssNi;
}
- if (gnssHal_V1_1 != nullptr) {
+ if (gnssHal_V2_0 != nullptr) {
+ auto gnssConfiguration = gnssHal_V2_0->getExtensionGnssConfiguration_2_0();
+ if (!gnssConfiguration.isOk()) {
+ ALOGD("Unable to get a handle to GnssConfiguration_V2_0");
+ } else {
+ gnssConfigurationIface_V2_0 = gnssConfiguration;
+ gnssConfigurationIface_V1_1 = gnssConfigurationIface_V2_0;
+ gnssConfigurationIface = gnssConfigurationIface_V2_0;
+ }
+ } else if (gnssHal_V1_1 != nullptr) {
auto gnssConfiguration = gnssHal_V1_1->getExtensionGnssConfiguration_1_1();
if (!gnssConfiguration.isOk()) {
- ALOGD("Unable to get a handle to GnssConfiguration");
+ ALOGD("Unable to get a handle to GnssConfiguration_V1_1");
} else {
gnssConfigurationIface_V1_1 = gnssConfiguration;
gnssConfigurationIface = gnssConfigurationIface_V1_1;
@@ -1463,9 +1483,23 @@
return (agnssRilIface != nullptr) ? JNI_TRUE : JNI_FALSE;
}
-static jboolean android_location_gpsLocationProvider_is_gnss_configuration_supported(
- JNIEnv* /* env */, jclass /* jclazz */) {
- return (gnssConfigurationIface != nullptr) ? JNI_TRUE : JNI_FALSE;
+static jobject android_location_GnssConfiguration_get_gnss_configuration_version(
+ JNIEnv* env, jclass /* jclazz */) {
+ jint major, minor;
+ if (gnssConfigurationIface_V2_0 != nullptr) {
+ major = 2;
+ minor = 0;
+ } else if (gnssConfigurationIface_V1_1 != nullptr) {
+ major = 1;
+ minor = 1;
+ } else if (gnssConfigurationIface != nullptr) {
+ major = 1;
+ minor = 0;
+ } else {
+ return nullptr;
+ }
+
+ return createHalInterfaceVersionJavaObject(env, major, minor);
}
static jboolean android_location_GnssLocationProvider_init(JNIEnv* env, jobject obj) {
@@ -2278,9 +2312,9 @@
return boolToJbool(result.isOk());
}
-static jboolean android_location_GnssLocationProvider_set_emergency_supl_pdn(JNIEnv*,
- jobject,
- jint emergencySuplPdn) {
+static jboolean android_location_GnssConfiguration_set_emergency_supl_pdn(JNIEnv*,
+ jobject,
+ jint emergencySuplPdn) {
if (gnssConfigurationIface == nullptr) {
ALOGE("no GNSS configuration interface available");
return JNI_FALSE;
@@ -2294,7 +2328,7 @@
}
}
-static jboolean android_location_GnssLocationProvider_set_supl_version(JNIEnv*,
+static jboolean android_location_GnssConfiguration_set_supl_version(JNIEnv*,
jobject,
jint version) {
if (gnssConfigurationIface == nullptr) {
@@ -2309,9 +2343,14 @@
}
}
-static jboolean android_location_GnssLocationProvider_set_supl_es(JNIEnv*,
- jobject,
- jint suplEs) {
+static jboolean android_location_GnssConfiguration_set_supl_es(JNIEnv*,
+ jobject,
+ jint suplEs) {
+ if (gnssConfigurationIface_V2_0 != nullptr) {
+ ALOGI("Config parameter SUPL_ES is deprecated in IGnssConfiguration.hal version 2.0.");
+ return JNI_FALSE;
+ }
+
if (gnssConfigurationIface == nullptr) {
ALOGE("no GNSS configuration interface available");
return JNI_FALSE;
@@ -2325,9 +2364,9 @@
}
}
-static jboolean android_location_GnssLocationProvider_set_supl_mode(JNIEnv*,
- jobject,
- jint mode) {
+static jboolean android_location_GnssConfiguration_set_supl_mode(JNIEnv*,
+ jobject,
+ jint mode) {
if (gnssConfigurationIface == nullptr) {
ALOGE("no GNSS configuration interface available");
return JNI_FALSE;
@@ -2341,9 +2380,14 @@
}
}
-static jboolean android_location_GnssLocationProvider_set_gps_lock(JNIEnv*,
- jobject,
- jint gpsLock) {
+static jboolean android_location_GnssConfiguration_set_gps_lock(JNIEnv*,
+ jobject,
+ jint gpsLock) {
+ if (gnssConfigurationIface_V2_0 != nullptr) {
+ ALOGI("Config parameter GPS_LOCK is deprecated in IGnssConfiguration.hal version 2.0.");
+ return JNI_FALSE;
+ }
+
if (gnssConfigurationIface == nullptr) {
ALOGE("no GNSS configuration interface available");
return JNI_FALSE;
@@ -2357,7 +2401,7 @@
}
}
-static jboolean android_location_GnssLocationProvider_set_lpp_profile(JNIEnv*,
+static jboolean android_location_GnssConfiguration_set_lpp_profile(JNIEnv*,
jobject,
jint lppProfile) {
if (gnssConfigurationIface == nullptr) {
@@ -2374,9 +2418,9 @@
}
}
-static jboolean android_location_GnssLocationProvider_set_gnss_pos_protocol_select(JNIEnv*,
- jobject,
- jint gnssPosProtocol) {
+static jboolean android_location_GnssConfiguration_set_gnss_pos_protocol_select(JNIEnv*,
+ jobject,
+ jint gnssPosProtocol) {
if (gnssConfigurationIface == nullptr) {
ALOGE("no GNSS configuration interface available");
return JNI_FALSE;
@@ -2390,7 +2434,7 @@
}
}
-static jboolean android_location_GnssLocationProvider_set_satellite_blacklist(
+static jboolean android_location_GnssConfiguration_set_satellite_blacklist(
JNIEnv* env, jobject, jintArray constellations, jintArray sv_ids) {
if (gnssConfigurationIface_V1_1 == nullptr) {
ALOGI("No GNSS Satellite Blacklist interface available");
@@ -2431,6 +2475,27 @@
}
}
+static jboolean android_location_GnssConfiguration_set_es_extension_sec(
+ JNIEnv*, jobject, jint emergencyExtensionSeconds) {
+ if (gnssConfigurationIface == nullptr) {
+ ALOGE("no GNSS configuration interface available");
+ return JNI_FALSE;
+ }
+
+ if (gnssConfigurationIface_V2_0 == nullptr) {
+ ALOGI("Config parameter ES_EXTENSION_SEC is not supported in IGnssConfiguration.hal"
+ " versions earlier than 2.0.");
+ return JNI_FALSE;
+ }
+
+ auto result = gnssConfigurationIface_V2_0->setEsExtensionSec(emergencyExtensionSeconds);
+ if (result.isOk()) {
+ return result;
+ } else {
+ return JNI_FALSE;
+ }
+}
+
static jint android_location_GnssBatchingProvider_get_batch_size(JNIEnv*, jclass) {
if (gnssBatchingIface == nullptr) {
return 0; // batching not supported, size = 0
@@ -2498,17 +2563,14 @@
android_location_GnssLocationProvider_class_init_native)},
{"native_is_supported", "()Z", reinterpret_cast<void *>(
android_location_GnssLocationProvider_is_supported)},
- {"native_is_gnss_configuration_supported", "()Z",
- reinterpret_cast<void *>(
- android_location_gpsLocationProvider_is_gnss_configuration_supported)},
{"native_init_once", "()V", reinterpret_cast<void *>(
android_location_GnssLocationProvider_init_once)},
{"native_init", "()Z", reinterpret_cast<void *>(android_location_GnssLocationProvider_init)},
{"native_cleanup", "()V", reinterpret_cast<void *>(
android_location_GnssLocationProvider_cleanup)},
{"native_set_position_mode",
- "(IIIIIZ)Z",
- reinterpret_cast<void*>(android_location_GnssLocationProvider_set_position_mode)},
+ "(IIIIIZ)Z",
+ reinterpret_cast<void*>(android_location_GnssLocationProvider_set_position_mode)},
{"native_start", "()Z", reinterpret_cast<void*>(android_location_GnssLocationProvider_start)},
{"native_stop", "()Z", reinterpret_cast<void*>(android_location_GnssLocationProvider_stop)},
{"native_delete_aiding_data",
@@ -2545,31 +2607,6 @@
{"native_get_internal_state",
"()Ljava/lang/String;",
reinterpret_cast<void *>(android_location_GnssLocationProvider_get_internal_state)},
- {"native_set_supl_es",
- "(I)Z",
- reinterpret_cast<void *>(android_location_GnssLocationProvider_set_supl_es)},
- {"native_set_supl_version",
- "(I)Z",
- reinterpret_cast<void *>(android_location_GnssLocationProvider_set_supl_version)},
- {"native_set_supl_mode",
- "(I)Z",
- reinterpret_cast<void *>(android_location_GnssLocationProvider_set_supl_mode)},
- {"native_set_lpp_profile",
- "(I)Z",
- reinterpret_cast<void *>(android_location_GnssLocationProvider_set_lpp_profile)},
- {"native_set_gnss_pos_protocol_select",
- "(I)Z",
- reinterpret_cast<void *>(
- android_location_GnssLocationProvider_set_gnss_pos_protocol_select)},
- {"native_set_gps_lock",
- "(I)Z",
- reinterpret_cast<void *>(android_location_GnssLocationProvider_set_gps_lock)},
- {"native_set_emergency_supl_pdn",
- "(I)Z",
- reinterpret_cast<void *>(android_location_GnssLocationProvider_set_emergency_supl_pdn)},
- {"native_set_satellite_blacklist",
- "([I[I)Z",
- reinterpret_cast<void *>(android_location_GnssLocationProvider_set_satellite_blacklist)},
};
static const JNINativeMethod sMethodsBatching[] = {
@@ -2663,6 +2700,42 @@
reinterpret_cast<void *>(android_location_GnssNetworkConnectivityHandler_agps_data_conn_failed)},
};
+static const JNINativeMethod sConfigurationMethods[] = {
+ /* name, signature, funcPtr */
+ {"native_get_gnss_configuration_version",
+ "()Lcom/android/server/location/GnssConfiguration$HalInterfaceVersion;",
+ reinterpret_cast<void *>(
+ android_location_GnssConfiguration_get_gnss_configuration_version)},
+ {"native_set_supl_es",
+ "(I)Z",
+ reinterpret_cast<void *>(android_location_GnssConfiguration_set_supl_es)},
+ {"native_set_supl_version",
+ "(I)Z",
+ reinterpret_cast<void *>(android_location_GnssConfiguration_set_supl_version)},
+ {"native_set_supl_mode",
+ "(I)Z",
+ reinterpret_cast<void *>(android_location_GnssConfiguration_set_supl_mode)},
+ {"native_set_lpp_profile",
+ "(I)Z",
+ reinterpret_cast<void *>(android_location_GnssConfiguration_set_lpp_profile)},
+ {"native_set_gnss_pos_protocol_select",
+ "(I)Z",
+ reinterpret_cast<void *>(
+ android_location_GnssConfiguration_set_gnss_pos_protocol_select)},
+ {"native_set_gps_lock",
+ "(I)Z",
+ reinterpret_cast<void *>(android_location_GnssConfiguration_set_gps_lock)},
+ {"native_set_emergency_supl_pdn",
+ "(I)Z",
+ reinterpret_cast<void *>(android_location_GnssConfiguration_set_emergency_supl_pdn)},
+ {"native_set_satellite_blacklist",
+ "([I[I)Z",
+ reinterpret_cast<void *>(android_location_GnssConfiguration_set_satellite_blacklist)},
+ {"native_set_es_extension_sec",
+ "(I)Z",
+ reinterpret_cast<void *>(android_location_GnssConfiguration_set_es_extension_sec)},
+};
+
int register_android_server_location_GnssLocationProvider(JNIEnv* env) {
jniRegisterNativeMethods(
env,
@@ -2689,6 +2762,11 @@
"com/android/server/location/GnssNetworkConnectivityHandler",
sNetworkConnectivityMethods,
NELEM(sNetworkConnectivityMethods));
+ jniRegisterNativeMethods(
+ env,
+ "com/android/server/location/GnssConfiguration",
+ sConfigurationMethods,
+ NELEM(sConfigurationMethods));
return jniRegisterNativeMethods(
env,
"com/android/server/location/GnssLocationProvider",
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/AbUpdateInstaller.java b/services/devicepolicy/java/com/android/server/devicepolicy/AbUpdateInstaller.java
index 05912a5..de5dd17 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/AbUpdateInstaller.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/AbUpdateInstaller.java
@@ -66,6 +66,9 @@
map.put(
DOWNLOAD_STATE_INITIALIZATION_ERROR,
InstallUpdateCallback.UPDATE_ERROR_INCORRECT_OS_VERSION);
+ map.put(
+ UpdateEngine.ErrorCodeConstants.PAYLOAD_TIMESTAMP_ERROR,
+ InstallUpdateCallback.UPDATE_ERROR_INCORRECT_OS_VERSION);
// Error constants corresponding to errors related to bad update file.
map.put(
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 781e1c4..76ae5cc 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -221,7 +221,7 @@
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IAccessibilityManager;
import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSystemProperty;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -245,6 +245,7 @@
import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemService;
import com.android.server.devicepolicy.DevicePolicyManagerService.ActiveAdmin.TrustAgentInfo;
+import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.net.NetworkPolicyManagerInternal;
import com.android.server.pm.UserRestrictionsUtils;
import com.android.server.storage.DeviceStorageMonitorInternal;
@@ -4990,26 +4991,22 @@
private boolean resetPasswordInternal(String password, long tokenHandle, byte[] token,
int flags, int callingUid, int userHandle) {
int quality;
- final int realQuality;
synchronized (getLockObject()) {
quality = getPasswordQuality(null, userHandle, /* parent */ false);
if (quality == DevicePolicyManager.PASSWORD_QUALITY_MANAGED) {
quality = PASSWORD_QUALITY_UNSPECIFIED;
}
final PasswordMetrics metrics = PasswordMetrics.computeForPassword(password);
- realQuality = metrics.quality;
- if (quality != PASSWORD_QUALITY_UNSPECIFIED) {
-
- if (realQuality < quality
- && quality != DevicePolicyManager.PASSWORD_QUALITY_COMPLEX) {
- Slog.w(LOG_TAG, "resetPassword: password quality 0x"
- + Integer.toHexString(realQuality)
- + " does not meet required quality 0x"
- + Integer.toHexString(quality));
- return false;
- }
- quality = Math.max(realQuality, quality);
+ final int realQuality = metrics.quality;
+ if (realQuality < quality
+ && quality != DevicePolicyManager.PASSWORD_QUALITY_COMPLEX) {
+ Slog.w(LOG_TAG, "resetPassword: password quality 0x"
+ + Integer.toHexString(realQuality)
+ + " does not meet required quality 0x"
+ + Integer.toHexString(quality));
+ 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()
@@ -5086,7 +5083,7 @@
try {
if (token == null) {
if (!TextUtils.isEmpty(password)) {
- mLockPatternUtils.saveLockPassword(password, null, realQuality, userHandle);
+ mLockPatternUtils.saveLockPassword(password, null, quality, userHandle);
} else {
mLockPatternUtils.clearLock(null, userHandle);
}
@@ -5095,7 +5092,7 @@
result = mLockPatternUtils.setLockCredentialWithToken(password,
TextUtils.isEmpty(password) ? LockPatternUtils.CREDENTIAL_TYPE_NONE
: LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
- realQuality, tokenHandle, token, userHandle);
+ quality, tokenHandle, token, userHandle);
}
boolean requireEntry = (flags & DevicePolicyManager.RESET_PASSWORD_REQUIRE_ENTRY) != 0;
if (requireEntry) {
@@ -5326,6 +5323,7 @@
}
final int callingUserId = mInjector.userHandleGetCallingUserId();
+ ComponentName adminComponent = null;
synchronized (getLockObject()) {
// Make sure the caller has any active admin with the right policy or
// the required permission.
@@ -5336,8 +5334,7 @@
android.Manifest.permission.LOCK_DEVICE);
final long ident = mInjector.binderClearCallingIdentity();
try {
- final ComponentName adminComponent = admin == null ?
- null : admin.info.getComponent();
+ adminComponent = admin == null ? null : admin.info.getComponent();
if (adminComponent != null) {
// For Profile Owners only, callers with only permission not allowed.
if ((flags & DevicePolicyManager.FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY) != 0) {
@@ -5389,6 +5386,7 @@
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.LOCK_NOW)
+ .setAdmin(adminComponent)
.setInt(flags)
.write();
}
@@ -6105,7 +6103,9 @@
synchronized (getLockObject()) {
delegates = getDelegatePackagesInternalLocked(scope, userId);
}
- if (delegates.size() != 1) {
+ if (delegates.size() == 0) {
+ return null;
+ } else if (delegates.size() > 1) {
Slog.wtf(LOG_TAG, "More than one delegate holds " + scope);
return null;
}
@@ -9192,21 +9192,15 @@
}
Preconditions.checkNotNull(who, "ComponentName is null");
- // TODO When InputMethodManager supports per user calls remove
- // this restriction.
- if (!checkCallerIsCurrentUserOrProfile()) {
+ // TODO When InputMethodManager supports per user calls remove this restriction.
+ if (!InputMethodSystemProperty.PER_PROFILE_IME_ENABLED
+ && !checkCallerIsCurrentUserOrProfile()) {
return false;
}
-
final int callingUserId = mInjector.userHandleGetCallingUserId();
if (packageList != null) {
- // InputMethodManager fetches input methods for current user.
- // So this can only be set when calling user is the current user
- // or parent is current user in case of managed profiles.
- InputMethodManager inputMethodManager =
- mContext.getSystemService(InputMethodManager.class);
- List<InputMethodInfo> enabledImes = inputMethodManager.getEnabledInputMethodList();
-
+ List<InputMethodInfo> enabledImes = InputMethodManagerInternal.get()
+ .getEnabledInputMethodListAsUser(callingUserId);
if (enabledImes != null) {
List<String> enabledPackages = new ArrayList<String>();
for (InputMethodInfo ime : enabledImes) {
@@ -9254,22 +9248,16 @@
@Override
public List getPermittedInputMethodsForCurrentUser() {
enforceManageUsers();
- UserInfo currentUser;
- try {
- currentUser = mInjector.getIActivityManager().getCurrentUser();
- } catch (RemoteException e) {
- Slog.e(LOG_TAG, "Failed to make remote calls to get current user", e);
- // Activity managed is dead, just allow all IMEs
- return null;
- }
- int userId = currentUser.id;
+ final int callingUserId = mInjector.userHandleGetCallingUserId();
synchronized (getLockObject()) {
List<String> result = null;
// If we have multiple profiles we return the intersection of the
// permitted lists. This can happen in cases where we have a device
// and profile owner.
- int[] profileIds = mUserManager.getProfileIdsWithDisabled(userId);
+ int[] profileIds = InputMethodSystemProperty.PER_PROFILE_IME_ENABLED
+ ? new int[]{callingUserId}
+ : mUserManager.getProfileIdsWithDisabled(callingUserId);
for (int profileId : profileIds) {
// Just loop though all admins, only device or profiles
// owners can have permitted lists set.
@@ -9290,9 +9278,8 @@
// If we have a permitted list add all system input methods.
if (result != null) {
- InputMethodManager inputMethodManager =
- mContext.getSystemService(InputMethodManager.class);
- List<InputMethodInfo> imes = inputMethodManager.getInputMethodList();
+ List<InputMethodInfo> imes =
+ InputMethodManagerInternal.get().getInputMethodListAsUser(callingUserId);
if (imes != null) {
for (InputMethodInfo ime : imes) {
ServiceInfo serviceInfo = ime.getServiceInfo();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java b/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
index d0ec0ee..699bec2 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
@@ -23,6 +23,7 @@
import static com.android.internal.util.Preconditions.checkNotNull;
import android.annotation.NonNull;
+import android.annotation.UserIdInt;
import android.app.admin.DeviceAdminReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -30,15 +31,13 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.ServiceManager;
import android.util.ArraySet;
import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSystemProperty;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.view.IInputMethodManager;
+import com.android.server.inputmethod.InputMethodManagerInternal;
import java.util.Arrays;
import java.util.List;
@@ -53,18 +52,38 @@
protected static final String TAG = "OverlayPackagesProvider";
private final PackageManager mPm;
- private final IInputMethodManager mIInputMethodManager;
private final Context mContext;
+ private final Injector mInjector;
public OverlayPackagesProvider(Context context) {
- this(context, getIInputMethodManager());
+ this(context, new DefaultInjector());
}
@VisibleForTesting
- OverlayPackagesProvider(Context context, IInputMethodManager iInputMethodManager) {
+ interface Injector {
+ boolean isPerProfileImeEnabled();
+ @NonNull
+ List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId);
+ }
+
+ private static final class DefaultInjector implements Injector {
+ @Override
+ public boolean isPerProfileImeEnabled() {
+ return InputMethodSystemProperty.PER_PROFILE_IME_ENABLED;
+ }
+
+ @NonNull
+ @Override
+ public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
+ return InputMethodManagerInternal.get().getInputMethodListAsUser(userId);
+ }
+ }
+
+ @VisibleForTesting
+ OverlayPackagesProvider(Context context, Injector injector) {
mContext = context;
mPm = checkNotNull(context.getPackageManager());
- mIInputMethodManager = checkNotNull(iInputMethodManager);
+ mInjector = checkNotNull(injector);
}
/**
@@ -89,10 +108,12 @@
// Newly installed system apps are uninstalled when they are not required and are either
// disallowed or have a launcher icon.
nonRequiredApps.removeAll(getRequiredApps(provisioningAction, admin.getPackageName()));
- // Don't delete the system input method packages in case of Device owner provisioning.
- if (ACTION_PROVISION_MANAGED_DEVICE.equals(provisioningAction)
+ if (mInjector.isPerProfileImeEnabled()) {
+ nonRequiredApps.removeAll(getSystemInputMethods(userId));
+ } else if (ACTION_PROVISION_MANAGED_DEVICE.equals(provisioningAction)
|| ACTION_PROVISION_MANAGED_USER.equals(provisioningAction)) {
- nonRequiredApps.removeAll(getSystemInputMethods());
+ // Don't delete the system input method packages in case of Device owner provisioning.
+ nonRequiredApps.removeAll(getSystemInputMethods(userId));
}
nonRequiredApps.addAll(getDisallowedApps(provisioningAction));
return nonRequiredApps;
@@ -114,16 +135,8 @@
return apps;
}
- private Set<String> getSystemInputMethods() {
- // InputMethodManager is final so it cannot be mocked.
- // So, we're using IInputMethodManager directly because it can be mocked.
- final List<InputMethodInfo> inputMethods;
- try {
- inputMethods = mIInputMethodManager.getInputMethodList();
- } catch (RemoteException e) {
- // Should not happen
- return null;
- }
+ private Set<String> getSystemInputMethods(int userId) {
+ final List<InputMethodInfo> inputMethods = mInjector.getInputMethodListAsUser(userId);
final Set<String> systemInputMethods = new ArraySet<>();
for (InputMethodInfo inputMethodInfo : inputMethods) {
ApplicationInfo applicationInfo = inputMethodInfo.getServiceInfo().applicationInfo;
@@ -149,11 +162,6 @@
return disallowedApps;
}
- private static IInputMethodManager getIInputMethodManager() {
- final IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);
- return IInputMethodManager.Stub.asInterface(b);
- }
-
private Set<String> getRequiredAppsSet(String provisioningAction) {
final int resId;
switch (provisioningAction) {
diff --git a/services/ipmemorystore/Android.bp b/services/ipmemorystore/Android.bp
new file mode 100644
index 0000000..013cf56
--- /dev/null
+++ b/services/ipmemorystore/Android.bp
@@ -0,0 +1,4 @@
+java_library_static {
+ name: "services.ipmemorystore",
+ srcs: ["java/**/*.java"],
+}
diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
new file mode 100644
index 0000000..c9759bf
--- /dev/null
+++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
@@ -0,0 +1,151 @@
+/*
+ * 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.
+ */
+
+package com.android.server.net.ipmemorystore;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.net.IIpMemoryStore;
+import android.net.ipmemorystore.Blob;
+import android.net.ipmemorystore.IOnBlobRetrievedListener;
+import android.net.ipmemorystore.IOnL2KeyResponseListener;
+import android.net.ipmemorystore.IOnNetworkAttributesRetrieved;
+import android.net.ipmemorystore.IOnSameNetworkResponseListener;
+import android.net.ipmemorystore.IOnStatusListener;
+import android.net.ipmemorystore.NetworkAttributesParcelable;
+
+/**
+ * Implementation for the IP memory store.
+ * This component offers specialized services for network components to store and retrieve
+ * knowledge about networks, and provides intelligence that groups level 2 networks together
+ * into level 3 networks.
+ *
+ * @hide
+ */
+public class IpMemoryStoreService extends IIpMemoryStore.Stub {
+ final Context mContext;
+
+ public IpMemoryStoreService(@NonNull final Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Store network attributes for a given L2 key.
+ *
+ * @param l2Key The L2 key for the L2 network. Clients that don't know or care about the L2
+ * key and only care about grouping can pass a unique ID here like the ones
+ * generated by {@code java.util.UUID.randomUUID()}, but keep in mind the low
+ * relevance of such a network will lead to it being evicted soon if it's not
+ * refreshed. Use findL2Key to try and find a similar L2Key to these attributes.
+ * @param attributes The attributes for this network.
+ * @param listener A listener to inform of the completion of this call, or null if the client
+ * is not interested in learning about success/failure.
+ * Through the listener, returns the L2 key. This is useful if the L2 key was not specified.
+ * If the call failed, the L2 key will be null.
+ */
+ @Override
+ public void storeNetworkAttributes(@NonNull final String l2Key,
+ @NonNull final NetworkAttributesParcelable attributes,
+ @Nullable final IOnStatusListener listener) {
+ // TODO : implement this
+ }
+
+ /**
+ * Store a binary blob associated with an L2 key and a name.
+ *
+ * @param l2Key The L2 key for this network.
+ * @param clientId The ID of the client.
+ * @param name The name of this data.
+ * @param data The data to store.
+ * @param listener The listener that will be invoked to return the answer, or null if the
+ * is not interested in learning about success/failure.
+ * Through the listener, returns a status to indicate success or failure.
+ */
+ @Override
+ public void storeBlob(@NonNull final String l2Key, @NonNull final String clientId,
+ @NonNull final String name, @NonNull final Blob data,
+ @Nullable final IOnStatusListener listener) {
+ // TODO : implement this
+ }
+
+ /**
+ * Returns the best L2 key associated with the attributes.
+ *
+ * This will find a record that would be in the same group as the passed attributes. This is
+ * useful to choose the key for storing a sample or private data when the L2 key is not known.
+ * If multiple records are group-close to these attributes, the closest match is returned.
+ * If multiple records have the same closeness, the one with the smaller (unicode codepoint
+ * order) L2 key is returned.
+ * If no record matches these attributes, null is returned.
+ *
+ * @param attributes The attributes of the network to find.
+ * @param listener The listener that will be invoked to return the answer.
+ * Through the listener, returns the L2 key if one matched, or null.
+ */
+ @Override
+ public void findL2Key(@NonNull final NetworkAttributesParcelable attributes,
+ @NonNull final IOnL2KeyResponseListener listener) {
+ // TODO : implement this
+ }
+
+ /**
+ * Returns whether, to the best of the store's ability to tell, the two specified L2 keys point
+ * to the same L3 network. Group-closeness is used to determine this.
+ *
+ * @param l2Key1 The key for the first network.
+ * @param l2Key2 The key for the second network.
+ * @param listener The listener that will be invoked to return the answer.
+ * Through the listener, a SameL3NetworkResponse containing the answer and confidence.
+ */
+ @Override
+ public void isSameNetwork(@NonNull final String l2Key1, @NonNull final String l2Key2,
+ @NonNull final IOnSameNetworkResponseListener listener) {
+ // TODO : implement this
+ }
+
+ /**
+ * Retrieve the network attributes for a key.
+ * If no record is present for this key, this will return null attributes.
+ *
+ * @param l2Key The key of the network to query.
+ * @param listener The listener that will be invoked to return the answer.
+ * Through the listener, returns the network attributes and the L2 key associated with
+ * the query.
+ */
+ @Override
+ public void retrieveNetworkAttributes(@NonNull final String l2Key,
+ @NonNull final IOnNetworkAttributesRetrieved listener) {
+ // TODO : implement this.
+ }
+
+ /**
+ * Retrieve previously stored private data.
+ * If no data was stored for this L2 key and name this will return null.
+ *
+ * @param l2Key The L2 key.
+ * @param clientId The id of the client that stored this data.
+ * @param name The name of the data.
+ * @param listener The listener that will be invoked to return the answer.
+ * Through the listener, returns the private data if any or null if none, with the L2 key
+ * and the name of the data associated with the query.
+ */
+ @Override
+ public void retrieveBlob(@NonNull final String l2Key, @NonNull final String clientId,
+ @NonNull final String name, @NonNull final IOnBlobRetrievedListener listener) {
+ // TODO : implement this.
+ }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index be09aea..fef2db9 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -102,10 +102,12 @@
import com.android.server.media.projection.MediaProjectionManagerService;
import com.android.server.net.NetworkPolicyManagerService;
import com.android.server.net.NetworkStatsService;
+import com.android.server.net.ipmemorystore.IpMemoryStoreService;
import com.android.server.net.watchlist.NetworkWatchlistService;
import com.android.server.notification.NotificationManagerService;
import com.android.server.oemlock.OemLockService;
import com.android.server.om.OverlayManagerService;
+import com.android.server.os.BugreportManagerService;
import com.android.server.os.DeviceIdentifiersPolicyService;
import com.android.server.os.SchedulingPolicyService;
import com.android.server.pm.BackgroundDexOptService;
@@ -118,6 +120,7 @@
import com.android.server.pm.ShortcutService;
import com.android.server.pm.UserManagerService;
import com.android.server.policy.PhoneWindowManager;
+import com.android.server.policy.role.LegacyRoleResolutionPolicy;
import com.android.server.power.PowerManagerService;
import com.android.server.power.ShutdownThread;
import com.android.server.power.ThermalManagerService;
@@ -145,6 +148,7 @@
import com.android.server.wm.ActivityTaskManagerService;
import com.android.server.wm.WindowManagerGlobalLock;
import com.android.server.wm.WindowManagerService;
+import com.google.android.startop.iorap.IorapForwardingService;
import dalvik.system.VMRuntime;
@@ -259,6 +263,10 @@
"com.android.server.accessibility.AccessibilityManagerService$Lifecycle";
private static final String ADB_SERVICE_CLASS =
"com.android.server.adb.AdbService$Lifecycle";
+ private static final String APP_PREDICTION_MANAGER_SERVICE_CLASS =
+ "com.android.server.appprediction.AppPredictionManagerService";
+ private static final String CONTENT_SUGGESTIONS_SERVICE_CLASS =
+ "com.android.server.contentsuggestions.ContentSuggestionsManagerService";
private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
@@ -785,6 +793,11 @@
traceBeginAndSlog("StartRollbackManagerService");
mSystemServiceManager.startService(RollbackManagerService.class);
traceEnd();
+
+ // Service to capture bugreports.
+ traceBeginAndSlog("StartBugreportManagerService");
+ mSystemServiceManager.startService(BugreportManagerService.class);
+ traceEnd();
}
/**
@@ -1007,15 +1020,30 @@
mSystemServiceManager.startService(PinnerService.class);
traceEnd();
+ traceBeginAndSlog("IorapForwardingService");
+ mSystemServiceManager.startService(IorapForwardingService.class);
+ traceEnd();
+
traceBeginAndSlog("SignedConfigService");
SignedConfigService.registerUpdateReceiver(mSystemContext);
traceEnd();
-
} catch (RuntimeException e) {
Slog.e("System", "******************************************");
Slog.e("System", "************ Failure starting core service", e);
}
+ // Before things start rolling, be sure we have decided whether
+ // we are in safe mode.
+ final boolean safeMode = wm.detectSafeMode();
+ if (safeMode) {
+ // If yes, immediately turn on the global setting for airplane mode.
+ // Note that this does not send broadcasts at this stage because
+ // subsystems are not yet up. We will send broadcasts later to ensure
+ // all listeners have the chance to react with special handling.
+ Settings.Global.putInt(context.getContentResolver(),
+ Settings.Global.AIRPLANE_MODE_ON, 1);
+ }
+
StatusBarManagerService statusBar = null;
INotificationManager notification = null;
LocationManagerService location = null;
@@ -1149,6 +1177,16 @@
startContentCaptureService(context);
+ // App prediction manager service
+ traceBeginAndSlog("StartAppPredictionService");
+ mSystemServiceManager.startService(APP_PREDICTION_MANAGER_SERVICE_CLASS);
+ traceEnd();
+
+ // Content suggestions manager service
+ traceBeginAndSlog("StartContentSuggestionsService");
+ mSystemServiceManager.startService(CONTENT_SUGGESTIONS_SERVICE_CLASS);
+ traceEnd();
+
// NOTE: ClipboardService indirectly depends on IntelligenceService
traceBeginAndSlog("StartClipboardService");
mSystemServiceManager.startService(ClipboardService.class);
@@ -1163,6 +1201,15 @@
}
traceEnd();
+ traceBeginAndSlog("StartIpMemoryStoreService");
+ try {
+ ServiceManager.addService(Context.IP_MEMORY_STORE_SERVICE,
+ new IpMemoryStoreService(context));
+ } catch (Throwable e) {
+ reportWtf("starting IP Memory Store Service", e);
+ }
+ traceEnd();
+
traceBeginAndSlog("StartIpSecService");
try {
ipSecService = IpSecService.create(context);
@@ -1782,9 +1829,6 @@
mSystemServiceManager.startService(StatsCompanionService.Lifecycle.class);
traceEnd();
- // Before things start rolling, be sure we have decided whether
- // we are in safe mode.
- final boolean safeMode = wm.detectSafeMode();
if (safeMode) {
traceBeginAndSlog("EnterSafeModeAndDisableJitCompilation");
mActivityManagerService.enterSafeMode();
@@ -1948,7 +1992,8 @@
// Grants default permissions and defines roles
traceBeginAndSlog("StartRoleManagerService");
- mSystemServiceManager.startService(RoleManagerService.class);
+ mSystemServiceManager.startService(new RoleManagerService(
+ mSystemContext, new LegacyRoleResolutionPolicy(mSystemContext)));
traceEnd();
// No dependency on Webview preparation in system server. But this should
@@ -1981,6 +2026,20 @@
reportWtf("starting System UI", e);
}
traceEnd();
+ // Enable airplane mode in safe mode. setAirplaneMode() cannot be called
+ // earlier as it sends broadcasts to other services.
+ // TODO: This may actually be too late if radio firmware already started leaking
+ // RF before the respective services start. However, fixing this requires changes
+ // to radio firmware and interfaces.
+ if (safeMode) {
+ traceBeginAndSlog("EnableAirplaneModeInSafeMode");
+ try {
+ connectivityF.setAirplaneMode(true);
+ } catch (Throwable e) {
+ reportWtf("enabling Airplane Mode during Safe Mode bootup", e);
+ }
+ traceEnd();
+ }
traceBeginAndSlog("MakeNetworkManagementServiceReady");
try {
if (networkManagementF != null) {
@@ -2175,7 +2234,7 @@
windowManager.onSystemUiStarted();
}
- private static void traceBeginAndSlog(String name) {
+ private static void traceBeginAndSlog(@NonNull String name) {
Slog.i(TAG, name);
BOOT_TIMINGS_TRACE_LOG.traceBegin(name);
}
diff --git a/services/net/Android.bp b/services/net/Android.bp
index e0ae68f..3b4d6a7 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -2,3 +2,19 @@
name: "services.net",
srcs: ["java/**/*.java"],
}
+
+// TODO: move to networking module with DhcpClient and remove lib
+java_library {
+ name: "dhcp-packet-lib",
+ srcs: [
+ "java/android/net/dhcp/*Packet.java",
+ ]
+}
+
+filegroup {
+ name: "services-networkstack-shared-srcs",
+ srcs: [
+ "java/android/net/util/FdEventsReader.java", // TODO: move to NetworkStack with IpClient
+ "java/android/net/shared/*.java",
+ ]
+}
diff --git a/services/net/java/android/net/dhcp/DhcpPacket.java b/services/net/java/android/net/dhcp/DhcpPacket.java
index 6ba7d94..ce8b7e7 100644
--- a/services/net/java/android/net/dhcp/DhcpPacket.java
+++ b/services/net/java/android/net/dhcp/DhcpPacket.java
@@ -1,8 +1,5 @@
package android.net.dhcp;
-import static android.net.util.NetworkConstants.IPV4_MAX_MTU;
-import static android.net.util.NetworkConstants.IPV4_MIN_MTU;
-
import android.annotation.Nullable;
import android.net.DhcpResults;
import android.net.LinkAddress;
@@ -37,6 +34,9 @@
public abstract class DhcpPacket {
protected static final String TAG = "DhcpPacket";
+ // TODO: use NetworkStackConstants.IPV4_MIN_MTU once this class is moved to the network stack.
+ private static final int IPV4_MIN_MTU = 68;
+
// dhcpcd has a minimum lease of 20 seconds, but DhcpStateMachine would refuse to wake up the
// CPU for anything shorter than 5 minutes. For sanity's sake, this must be higher than the
// DHCP client timeout.
diff --git a/services/net/java/android/net/dhcp/DhcpServingParamsParcelExt.java b/services/net/java/android/net/dhcp/DhcpServingParamsParcelExt.java
new file mode 100644
index 0000000..f068c3a
--- /dev/null
+++ b/services/net/java/android/net/dhcp/DhcpServingParamsParcelExt.java
@@ -0,0 +1,172 @@
+/*
+ * 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.
+ */
+
+package android.net.dhcp;
+
+import static android.net.NetworkUtils.inet4AddressToIntHTH;
+
+import android.annotation.NonNull;
+import android.net.LinkAddress;
+
+import com.google.android.collect.Sets;
+
+import java.net.Inet4Address;
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * Subclass of {@link DhcpServingParamsParcel} with additional utility methods for building.
+ *
+ * <p>This utility class does not check for validity of the parameters: invalid parameters are
+ * reported by the receiving module when unparceling the parcel.
+ *
+ * @see DhcpServingParams
+ * @hide
+ */
+public class DhcpServingParamsParcelExt extends DhcpServingParamsParcel {
+ public static final int MTU_UNSET = 0;
+
+ /**
+ * Set the server address and served prefix for the DHCP server.
+ *
+ * <p>This parameter is required.
+ */
+ public DhcpServingParamsParcelExt setServerAddr(@NonNull LinkAddress serverAddr) {
+ this.serverAddr = inet4AddressToIntHTH((Inet4Address) serverAddr.getAddress());
+ this.serverAddrPrefixLength = serverAddr.getPrefixLength();
+ return this;
+ }
+
+ /**
+ * Set the default routers to be advertised to DHCP clients.
+ *
+ * <p>Each router must be inside the served prefix. This may be an empty set, but it must
+ * always be set explicitly.
+ */
+ public DhcpServingParamsParcelExt setDefaultRouters(@NonNull Set<Inet4Address> defaultRouters) {
+ this.defaultRouters = toIntArray(defaultRouters);
+ return this;
+ }
+
+ /**
+ * Set the default routers to be advertised to DHCP clients.
+ *
+ * <p>Each router must be inside the served prefix. This may be an empty list of routers,
+ * but it must always be set explicitly.
+ */
+ public DhcpServingParamsParcelExt setDefaultRouters(@NonNull Inet4Address... defaultRouters) {
+ return setDefaultRouters(Sets.newArraySet(defaultRouters));
+ }
+
+ /**
+ * Convenience method to build the parameters with no default router.
+ *
+ * <p>Equivalent to calling {@link #setDefaultRouters(Inet4Address...)} with no address.
+ */
+ public DhcpServingParamsParcelExt setNoDefaultRouter() {
+ return setDefaultRouters();
+ }
+
+ /**
+ * Set the DNS servers to be advertised to DHCP clients.
+ *
+ * <p>This may be an empty set, but it must always be set explicitly.
+ */
+ public DhcpServingParamsParcelExt setDnsServers(@NonNull Set<Inet4Address> dnsServers) {
+ this.dnsServers = toIntArray(dnsServers);
+ return this;
+ }
+
+ /**
+ * Set the DNS servers to be advertised to DHCP clients.
+ *
+ * <p>This may be an empty list of servers, but it must always be set explicitly.
+ */
+ public DhcpServingParamsParcelExt setDnsServers(@NonNull Inet4Address... dnsServers) {
+ return setDnsServers(Sets.newArraySet(dnsServers));
+ }
+
+ /**
+ * Convenience method to build the parameters with no DNS server.
+ *
+ * <p>Equivalent to calling {@link #setDnsServers(Inet4Address...)} with no address.
+ */
+ public DhcpServingParamsParcelExt setNoDnsServer() {
+ return setDnsServers();
+ }
+
+ /**
+ * Set excluded addresses that the DHCP server is not allowed to assign to clients.
+ *
+ * <p>This parameter is optional. DNS servers and default routers are always excluded
+ * and do not need to be set here.
+ */
+ public DhcpServingParamsParcelExt setExcludedAddrs(@NonNull Set<Inet4Address> excludedAddrs) {
+ this.excludedAddrs = toIntArray(excludedAddrs);
+ return this;
+ }
+
+ /**
+ * Set excluded addresses that the DHCP server is not allowed to assign to clients.
+ *
+ * <p>This parameter is optional. DNS servers and default routers are always excluded
+ * and do not need to be set here.
+ */
+ public DhcpServingParamsParcelExt setExcludedAddrs(@NonNull Inet4Address... excludedAddrs) {
+ return setExcludedAddrs(Sets.newArraySet(excludedAddrs));
+ }
+
+ /**
+ * Set the lease time for leases assigned by the DHCP server.
+ *
+ * <p>This parameter is required.
+ */
+ public DhcpServingParamsParcelExt setDhcpLeaseTimeSecs(long dhcpLeaseTimeSecs) {
+ this.dhcpLeaseTimeSecs = dhcpLeaseTimeSecs;
+ return this;
+ }
+
+ /**
+ * Set the link MTU to be advertised to DHCP clients.
+ *
+ * <p>If set to {@link #MTU_UNSET}, no MTU will be advertised to clients. This parameter
+ * is optional and defaults to {@link #MTU_UNSET}.
+ */
+ public DhcpServingParamsParcelExt setLinkMtu(int linkMtu) {
+ this.linkMtu = linkMtu;
+ return this;
+ }
+
+ /**
+ * Set whether the DHCP server should send the ANDROID_METERED vendor-specific option.
+ *
+ * <p>If not set, the default value is false.
+ */
+ public DhcpServingParamsParcelExt setMetered(boolean metered) {
+ this.metered = metered;
+ return this;
+ }
+
+ private static int[] toIntArray(@NonNull Collection<Inet4Address> addrs) {
+ int[] res = new int[addrs.size()];
+ int i = 0;
+ for (Inet4Address addr : addrs) {
+ res[i] = inet4AddressToIntHTH(addr);
+ i++;
+ }
+ return res;
+ }
+}
diff --git a/services/net/java/android/net/ip/IpServer.java b/services/net/java/android/net/ip/IpServer.java
index 493350d..8b22f68 100644
--- a/services/net/java/android/net/ip/IpServer.java
+++ b/services/net/java/android/net/ip/IpServer.java
@@ -17,20 +17,26 @@
package android.net.ip;
import static android.net.NetworkUtils.numericToInetAddress;
-import static android.net.util.NetworkConstants.asByte;
+import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
import static android.net.util.NetworkConstants.FF;
import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH;
+import static android.net.util.NetworkConstants.asByte;
+import android.content.Context;
import android.net.ConnectivityManager;
import android.net.INetd;
+import android.net.INetworkStackStatusCallback;
import android.net.INetworkStatsService;
import android.net.InterfaceConfiguration;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
+import android.net.NetworkStack;
import android.net.RouteInfo;
-import android.net.dhcp.DhcpServer;
-import android.net.dhcp.DhcpServingParams;
+import android.net.dhcp.DhcpServerCallbacks;
+import android.net.dhcp.DhcpServingParamsParcel;
+import android.net.dhcp.DhcpServingParamsParcelExt;
+import android.net.dhcp.IDhcpServer;
import android.net.ip.RouterAdvertisementDaemon.RaParams;
import android.net.util.InterfaceParams;
import android.net.util.InterfaceSet;
@@ -126,6 +132,10 @@
}
public static class Dependencies {
+ private final Context mContext;
+ public Dependencies(Context context) {
+ mContext = context;
+ }
public RouterAdvertisementDaemon getRouterAdvertisementDaemon(InterfaceParams ifParams) {
return new RouterAdvertisementDaemon(ifParams);
}
@@ -138,9 +148,12 @@
return NetdService.getInstance();
}
- public DhcpServer makeDhcpServer(Looper looper, String ifName,
- DhcpServingParams params, SharedLog log) {
- return new DhcpServer(looper, ifName, params, log);
+ /**
+ * Create a DhcpServer instance to be used by IpServer.
+ */
+ public void makeDhcpServer(String ifName, DhcpServingParamsParcel params,
+ DhcpServerCallbacks cb) {
+ mContext.getSystemService(NetworkStack.class).makeDhcpServer(ifName, params, cb);
}
}
@@ -197,7 +210,10 @@
// Advertisements (otherwise, we do not add them to mLinkProperties at all).
private LinkProperties mLastIPv6LinkProperties;
private RouterAdvertisementDaemon mRaDaemon;
- private DhcpServer mDhcpServer;
+
+ // To be accessed only on the handler thread
+ private int mDhcpServerStartIndex = 0;
+ private IDhcpServer mDhcpServer;
private RaParams mLastRaParams;
public IpServer(
@@ -252,35 +268,109 @@
private boolean startIPv4() { return configureIPv4(true); }
+ /**
+ * Convenience wrapper around INetworkStackStatusCallback to run callbacks on the IpServer
+ * handler.
+ *
+ * <p>Different instances of this class can be created for each call to IDhcpServer methods,
+ * with different implementations of the callback, to differentiate handling of success/error in
+ * each call.
+ */
+ private abstract class OnHandlerStatusCallback extends INetworkStackStatusCallback.Stub {
+ @Override
+ public void onStatusAvailable(int statusCode) {
+ getHandler().post(() -> callback(statusCode));
+ }
+
+ public abstract void callback(int statusCode);
+ }
+
+ private class DhcpServerCallbacksImpl extends DhcpServerCallbacks {
+ private final int mStartIndex;
+
+ private DhcpServerCallbacksImpl(int startIndex) {
+ mStartIndex = startIndex;
+ }
+
+ @Override
+ public void onDhcpServerCreated(int statusCode, IDhcpServer server) throws RemoteException {
+ getHandler().post(() -> {
+ // We are on the handler thread: mDhcpServerStartIndex can be read safely.
+ if (mStartIndex != mDhcpServerStartIndex) {
+ // This start request is obsolete. When the |server| binder token goes out of
+ // scope, the garbage collector will finalize it, which causes the network stack
+ // process garbage collector to collect the server itself.
+ return;
+ }
+
+ if (statusCode != STATUS_SUCCESS) {
+ mLog.e("Error obtaining DHCP server: " + statusCode);
+ handleError();
+ return;
+ }
+
+ mDhcpServer = server;
+ try {
+ mDhcpServer.start(new OnHandlerStatusCallback() {
+ @Override
+ public void callback(int startStatusCode) {
+ if (startStatusCode != STATUS_SUCCESS) {
+ mLog.e("Error starting DHCP server: " + startStatusCode);
+ handleError();
+ }
+ }
+ });
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ });
+ }
+
+ private void handleError() {
+ mLastError = ConnectivityManager.TETHER_ERROR_DHCPSERVER_ERROR;
+ transitionTo(mInitialState);
+ }
+ }
+
private boolean startDhcp(Inet4Address addr, int prefixLen) {
if (mUsingLegacyDhcp) {
return true;
}
- final DhcpServingParams params;
- try {
- params = new DhcpServingParams.Builder()
- .setDefaultRouters(addr)
- .setDhcpLeaseTimeSecs(DHCP_LEASE_TIME_SECS)
- .setDnsServers(addr)
- .setServerAddr(new LinkAddress(addr, prefixLen))
- .setMetered(true)
- .build();
- // TODO: also advertise link MTU
- } catch (DhcpServingParams.InvalidParameterException e) {
- Log.e(TAG, "Invalid DHCP parameters", e);
- return false;
- }
+ final DhcpServingParamsParcel params;
+ params = new DhcpServingParamsParcelExt()
+ .setDefaultRouters(addr)
+ .setDhcpLeaseTimeSecs(DHCP_LEASE_TIME_SECS)
+ .setDnsServers(addr)
+ .setServerAddr(new LinkAddress(addr, prefixLen))
+ .setMetered(true);
+ // TODO: also advertise link MTU
- mDhcpServer = mDeps.makeDhcpServer(getHandler().getLooper(), mIfaceName, params,
- mLog.forSubComponent("DHCP"));
- mDhcpServer.start();
+ mDhcpServerStartIndex++;
+ mDeps.makeDhcpServer(
+ mIfaceName, params, new DhcpServerCallbacksImpl(mDhcpServerStartIndex));
return true;
}
private void stopDhcp() {
+ // Make all previous start requests obsolete so servers are not started later
+ mDhcpServerStartIndex++;
+
if (mDhcpServer != null) {
- mDhcpServer.stop();
- mDhcpServer = null;
+ try {
+ mDhcpServer.stop(new OnHandlerStatusCallback() {
+ @Override
+ public void callback(int statusCode) {
+ if (statusCode != STATUS_SUCCESS) {
+ mLog.e("Error stopping DHCP server: " + statusCode);
+ mLastError = ConnectivityManager.TETHER_ERROR_DHCPSERVER_ERROR;
+ // Not much more we can do here
+ }
+ }
+ });
+ mDhcpServer = null;
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
}
}
diff --git a/services/net/java/android/net/shared/NetworkMonitorUtils.java b/services/net/java/android/net/shared/NetworkMonitorUtils.java
new file mode 100644
index 0000000..463cf2a
--- /dev/null
+++ b/services/net/java/android/net/shared/NetworkMonitorUtils.java
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+package android.net.shared;
+
+import android.content.Context;
+import android.net.NetworkCapabilities;
+import android.provider.Settings;
+
+/** @hide */
+public class NetworkMonitorUtils {
+
+ // Network conditions broadcast constants
+ public static final String ACTION_NETWORK_CONDITIONS_MEASURED =
+ "android.net.conn.NETWORK_CONDITIONS_MEASURED";
+ public static final String EXTRA_CONNECTIVITY_TYPE = "extra_connectivity_type";
+ public static final String EXTRA_NETWORK_TYPE = "extra_network_type";
+ public static final String EXTRA_RESPONSE_RECEIVED = "extra_response_received";
+ public static final String EXTRA_IS_CAPTIVE_PORTAL = "extra_is_captive_portal";
+ public static final String EXTRA_CELL_ID = "extra_cellid";
+ public static final String EXTRA_SSID = "extra_ssid";
+ public static final String EXTRA_BSSID = "extra_bssid";
+ /** real time since boot */
+ public static final String EXTRA_REQUEST_TIMESTAMP_MS = "extra_request_timestamp_ms";
+ public static final String EXTRA_RESPONSE_TIMESTAMP_MS = "extra_response_timestamp_ms";
+ public static final String PERMISSION_ACCESS_NETWORK_CONDITIONS =
+ "android.permission.ACCESS_NETWORK_CONDITIONS";
+
+ // TODO: once the URL is a resource overlay, remove and have the resource define the default
+ private static final String DEFAULT_HTTP_URL =
+ "http://connectivitycheck.gstatic.com/generate_204";
+
+ /**
+ * Get the captive portal server HTTP URL that is configured on the device.
+ */
+ public static String getCaptivePortalServerHttpUrl(Context context) {
+ final String settingUrl = Settings.Global.getString(
+ context.getContentResolver(),
+ Settings.Global.CAPTIVE_PORTAL_HTTP_URL);
+ return settingUrl != null ? settingUrl : DEFAULT_HTTP_URL;
+ }
+
+ /**
+ * Return whether validation is required for a network.
+ * @param dfltNetCap Default requested network capabilities.
+ * @param nc Network capabilities of the network to test.
+ */
+ public static boolean isValidationRequired(
+ NetworkCapabilities dfltNetCap, NetworkCapabilities nc) {
+ // TODO: Consider requiring validation for DUN networks.
+ return dfltNetCap.satisfiedByNetworkCapabilities(nc);
+ }
+}
diff --git a/services/net/java/android/net/shared/PrivateDnsConfig.java b/services/net/java/android/net/shared/PrivateDnsConfig.java
new file mode 100644
index 0000000..41e0bad
--- /dev/null
+++ b/services/net/java/android/net/shared/PrivateDnsConfig.java
@@ -0,0 +1,94 @@
+/*
+ * 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.
+ */
+
+package android.net.shared;
+
+import android.net.InetAddresses;
+import android.net.PrivateDnsConfigParcel;
+import android.text.TextUtils;
+
+import java.net.InetAddress;
+import java.util.Arrays;
+
+/** @hide */
+public class PrivateDnsConfig {
+ public final boolean useTls;
+ public final String hostname;
+ public final InetAddress[] ips;
+
+ public PrivateDnsConfig() {
+ this(false);
+ }
+
+ public PrivateDnsConfig(boolean useTls) {
+ this.useTls = useTls;
+ this.hostname = "";
+ this.ips = new InetAddress[0];
+ }
+
+ public PrivateDnsConfig(String hostname, InetAddress[] ips) {
+ this.useTls = !TextUtils.isEmpty(hostname);
+ this.hostname = useTls ? hostname : "";
+ this.ips = (ips != null) ? ips : new InetAddress[0];
+ }
+
+ public PrivateDnsConfig(PrivateDnsConfig cfg) {
+ useTls = cfg.useTls;
+ hostname = cfg.hostname;
+ ips = cfg.ips;
+ }
+
+ /**
+ * Indicates whether this is a strict mode private DNS configuration.
+ */
+ public boolean inStrictMode() {
+ return useTls && !TextUtils.isEmpty(hostname);
+ }
+
+ @Override
+ public String toString() {
+ return PrivateDnsConfig.class.getSimpleName()
+ + "{" + useTls + ":" + hostname + "/" + Arrays.toString(ips) + "}";
+ }
+
+ /**
+ * Create a stable AIDL-compatible parcel from the current instance.
+ */
+ public PrivateDnsConfigParcel toParcel() {
+ final PrivateDnsConfigParcel parcel = new PrivateDnsConfigParcel();
+ parcel.hostname = hostname;
+
+ final String[] parceledIps = new String[ips.length];
+ for (int i = 0; i < ips.length; i++) {
+ parceledIps[i] = ips[i].getHostAddress();
+ }
+ parcel.ips = parceledIps;
+
+ return parcel;
+ }
+
+ /**
+ * Build a configuration from a stable AIDL-compatible parcel.
+ */
+ public static PrivateDnsConfig fromParcel(PrivateDnsConfigParcel parcel) {
+ final InetAddress[] ips = new InetAddress[parcel.ips.length];
+ for (int i = 0; i < ips.length; i++) {
+ ips[i] = InetAddresses.parseNumericAddress(parcel.ips[i]);
+ }
+
+ return new PrivateDnsConfig(parcel.hostname, ips);
+ }
+}
diff --git a/services/net/java/android/net/util/NetworkConstants.java b/services/net/java/android/net/util/NetworkConstants.java
index 3defe56..c183b81 100644
--- a/services/net/java/android/net/util/NetworkConstants.java
+++ b/services/net/java/android/net/util/NetworkConstants.java
@@ -16,9 +16,6 @@
package android.net.util;
-import java.nio.ByteBuffer;
-
-
/**
* Networking protocol constants.
*
@@ -81,8 +78,6 @@
* - https://tools.ietf.org/html/rfc791
*/
public static final int IPV4_HEADER_MIN_LEN = 20;
- public static final int IPV4_MIN_MTU = 68;
- public static final int IPV4_MAX_MTU = 65_535;
public static final int IPV4_IHL_MASK = 0xf;
public static final int IPV4_FLAGS_OFFSET = 6;
public static final int IPV4_FRAGMENT_MASK = 0x1fff;
diff --git a/services/net/java/android/net/util/SharedLog.java b/services/net/java/android/net/util/SharedLog.java
index 5a73a4e..2cdb2b0 100644
--- a/services/net/java/android/net/util/SharedLog.java
+++ b/services/net/java/android/net/util/SharedLog.java
@@ -32,11 +32,12 @@
*
* All access to class methods other than dump() must be on the same thread.
*
+ * TODO: this is a copy of SharedLog in the NetworkStack. Remove after Tethering is migrated.
* @hide
*/
public class SharedLog {
- private final static int DEFAULT_MAX_RECORDS = 500;
- private final static String COMPONENT_DELIMITER = ".";
+ private static final int DEFAULT_MAX_RECORDS = 500;
+ private static final String COMPONENT_DELIMITER = ".";
private enum Category {
NONE,
@@ -69,6 +70,13 @@
mComponent = component;
}
+ public String getTag() {
+ return mTag;
+ }
+
+ /**
+ * Create a SharedLog based on this log with an additional component prefix on each logged line.
+ */
public SharedLog forSubComponent(String component) {
if (!isRootLogInstance()) {
component = mComponent + COMPONENT_DELIMITER + component;
@@ -76,6 +84,11 @@
return new SharedLog(mLocalLog, mTag, component);
}
+ /**
+ * Dump the contents of this log.
+ *
+ * <p>This method may be called on any thread.
+ */
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
mLocalLog.readOnlyLocalLog().dump(fd, writer, args);
}
@@ -84,10 +97,21 @@
// Methods that both log an entry and emit it to the system log.
//////
+ /**
+ * Log an error due to an exception. This does not include the exception stacktrace.
+ *
+ * <p>The log entry will be also added to the system log.
+ * @see #e(String, Throwable)
+ */
public void e(Exception e) {
Log.e(mTag, record(Category.ERROR, e.toString()));
}
+ /**
+ * Log an error message.
+ *
+ * <p>The log entry will be also added to the system log.
+ */
public void e(String msg) {
Log.e(mTag, record(Category.ERROR, msg));
}
@@ -96,7 +120,7 @@
* Log an error due to an exception, with the exception stacktrace if provided.
*
* <p>The error and exception message appear in the shared log, but the stacktrace is only
- * logged in general log output (logcat).
+ * logged in general log output (logcat). The log entry will be also added to the system log.
*/
public void e(@NonNull String msg, @Nullable Throwable exception) {
if (exception == null) {
@@ -106,10 +130,20 @@
Log.e(mTag, record(Category.ERROR, msg + ": " + exception.getMessage()), exception);
}
+ /**
+ * Log an informational message.
+ *
+ * <p>The log entry will be also added to the system log.
+ */
public void i(String msg) {
Log.i(mTag, record(Category.NONE, msg));
}
+ /**
+ * Log a warning message.
+ *
+ * <p>The log entry will be also added to the system log.
+ */
public void w(String msg) {
Log.w(mTag, record(Category.WARN, msg));
}
@@ -118,14 +152,30 @@
// Methods that only log an entry (and do NOT emit to the system log).
//////
+ /**
+ * Log a general message to be only included in the in-memory log.
+ *
+ * <p>The log entry will *not* be added to the system log.
+ */
public void log(String msg) {
record(Category.NONE, msg);
}
+ /**
+ * Log a general, formatted message to be only included in the in-memory log.
+ *
+ * <p>The log entry will *not* be added to the system log.
+ * @see String#format(String, Object...)
+ */
public void logf(String fmt, Object... args) {
log(String.format(fmt, args));
}
+ /**
+ * Log a message with MARK level.
+ *
+ * <p>The log entry will *not* be added to the system log.
+ */
public void mark(String msg) {
record(Category.MARK, msg);
}
diff --git a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceTest.java b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceTest.java
index b8723c5..769a9d4 100644
--- a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceTest.java
@@ -16,6 +16,7 @@
package com.android.server.backup;
+import static android.Manifest.permission.BACKUP;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static com.android.server.backup.testing.BackupManagerServiceTestUtils.startBackupThread;
@@ -192,6 +193,19 @@
/** Test that the service unregisters users when stopped. */
@Test
+ public void testStopServiceForUser_forRegisteredUser_tearsDownCorrectUser() throws Exception {
+ BackupManagerService backupManagerService =
+ createServiceAndRegisterUser(mUserOneId, mUserOneService);
+ backupManagerService.startServiceForUser(mUserTwoId, mUserTwoService);
+
+ backupManagerService.stopServiceForUser(mUserOneId);
+
+ verify(mUserOneService).tearDownService();
+ verify(mUserTwoService, never()).tearDownService();
+ }
+
+ /** Test that the service unregisters users when stopped. */
+ @Test
public void testStopServiceForUser_forUnknownUser_doesNothing() throws Exception {
BackupManagerService backupManagerService = createService();
@@ -1528,6 +1542,7 @@
}
private BackupManagerService createService() {
+ mShadowContext.grantPermissions(BACKUP);
return new BackupManagerService(
mContext, new Trampoline(mContext), startBackupThread(null));
}
diff --git a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
index bb60c27..8d5c301 100644
--- a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -829,7 +829,7 @@
public void testRequestBackup_whenNotProvisioned() throws Exception {
mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks();
- backupManagerService.setProvisioned(false);
+ backupManagerService.setSetupComplete(false);
int result = backupManagerService.requestBackup(new String[] {PACKAGE_1}, mObserver, 0);
@@ -848,7 +848,7 @@
setUpCurrentTransport(mTransportManager, mTransport.unregistered());
UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks();
backupManagerService.setEnabled(true);
- backupManagerService.setProvisioned(true);
+ backupManagerService.setSetupComplete(true);
int result = backupManagerService.requestBackup(new String[] {PACKAGE_1}, mObserver, 0);
@@ -868,7 +868,7 @@
setUpCurrentTransport(mTransportManager, mTransport);
UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks();
backupManagerService.setEnabled(true);
- backupManagerService.setProvisioned(true);
+ backupManagerService.setSetupComplete(true);
// Haven't set PACKAGE_1 as eligible
int result = backupManagerService.requestBackup(new String[] {PACKAGE_1}, mObserver, 0);
@@ -965,14 +965,13 @@
private UserBackupManagerService createBackupManagerServiceForRequestBackup() {
UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks();
backupManagerService.setEnabled(true);
- backupManagerService.setProvisioned(true);
+ backupManagerService.setSetupComplete(true);
return backupManagerService;
}
/**
- * Test verifying that {@link UserBackupManagerService#createAndInitializeService(Context,
- * Trampoline, HandlerThread, File, File, TransportManager)} posts a transport registration task
- * to the backup thread.
+ * Test verifying that creating a new instance posts a transport registration task to the backup
+ * thread.
*/
@Test
public void testCreateAndInitializeService_postRegisterTransports() {
@@ -992,9 +991,8 @@
}
/**
- * Test verifying that {@link UserBackupManagerService#createAndInitializeService(Context,
- * Trampoline, HandlerThread, File, File, TransportManager)} does not directly register
- * transports on the main thread.
+ * Test verifying that creating a new instance does not directly register transports on the main
+ * thread.
*/
@Test
public void testCreateAndInitializeService_doesNotRegisterTransportsSynchronously() {
@@ -1013,11 +1011,7 @@
verify(mTransportManager, never()).registerTransports();
}
- /**
- * Test checking non-null argument on {@link
- * UserBackupManagerService#createAndInitializeService(Context, Trampoline, HandlerThread, File,
- * File, TransportManager)}.
- */
+ /** Test checking non-null argument on instance creation. */
@Test
public void testCreateAndInitializeService_withNullContext_throws() {
expectThrows(
@@ -1033,11 +1027,7 @@
mTransportManager));
}
- /**
- * Test checking non-null argument on {@link
- * UserBackupManagerService#createAndInitializeService(Context, Trampoline, HandlerThread, File,
- * File, TransportManager)}.
- */
+ /** Test checking non-null argument on instance creation. */
@Test
public void testCreateAndInitializeService_withNullTrampoline_throws() {
expectThrows(
@@ -1053,11 +1043,7 @@
mTransportManager));
}
- /**
- * Test checking non-null argument on {@link
- * UserBackupManagerService#createAndInitializeService(Context, Trampoline, HandlerThread, File,
- * File, TransportManager)}.
- */
+ /** Test checking non-null argument on instance creation. */
@Test
public void testCreateAndInitializeService_withNullBackupThread_throws() {
expectThrows(
@@ -1073,11 +1059,7 @@
mTransportManager));
}
- /**
- * Test checking non-null argument on {@link
- * UserBackupManagerService#createAndInitializeService(Context, Trampoline, HandlerThread, File,
- * File, TransportManager)}.
- */
+ /** Test checking non-null argument on instance creation. */
@Test
public void testCreateAndInitializeService_withNullStateDir_throws() {
expectThrows(
diff --git a/services/robotests/backup/src/com/android/server/backup/fullbackup/AppMetadataBackupWriterTest.java b/services/robotests/backup/src/com/android/server/backup/fullbackup/AppMetadataBackupWriterTest.java
index 423512c..aca48b6 100644
--- a/services/robotests/backup/src/com/android/server/backup/fullbackup/AppMetadataBackupWriterTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/fullbackup/AppMetadataBackupWriterTest.java
@@ -12,6 +12,7 @@
import static org.testng.Assert.expectThrows;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.Application;
import android.app.backup.BackupDataInput;
import android.app.backup.FullBackupDataOutput;
@@ -62,6 +63,7 @@
private static final String TEST_PACKAGE_INSTALLER = "com.test.package.installer";
private static final Long TEST_PACKAGE_VERSION_CODE = 100L;
+ private @UserIdInt int mUserId;
private PackageManager mPackageManager;
private ShadowApplicationPackageManager mShadowPackageManager;
private File mFilesDir;
@@ -72,6 +74,7 @@
public void setUp() throws Exception {
Application application = RuntimeEnvironment.application;
+ mUserId = UserHandle.USER_SYSTEM;
mPackageManager = application.getPackageManager();
mShadowPackageManager = (ShadowApplicationPackageManager) shadowOf(mPackageManager);
@@ -352,7 +355,7 @@
byte[] obbBytes = "obb".getBytes();
File obbFile = createObbFileAndWrite(obbDir, obbBytes);
- mBackupWriter.backupObb(packageInfo);
+ mBackupWriter.backupObb(mUserId, packageInfo);
byte[] writtenBytes = getWrittenBytes(mBackupDataOutputFile, /* includeTarHeader */ false);
assertThat(writtenBytes).isEqualTo(obbBytes);
@@ -366,7 +369,7 @@
File obbDir = createObbDirForPackage(packageInfo.packageName);
// No obb file created.
- mBackupWriter.backupObb(packageInfo);
+ mBackupWriter.backupObb(mUserId, packageInfo);
assertThat(mBackupDataOutputFile.length()).isEqualTo(0);
}
diff --git a/services/robotests/backup/src/com/android/server/backup/internal/SetupObserverTest.java b/services/robotests/backup/src/com/android/server/backup/internal/SetupObserverTest.java
new file mode 100644
index 0000000..b754356
--- /dev/null
+++ b/services/robotests/backup/src/com/android/server/backup/internal/SetupObserverTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+
+import com.android.server.backup.KeyValueBackupJob;
+import com.android.server.backup.TransportManager;
+import com.android.server.backup.UserBackupManagerService;
+import com.android.server.backup.testing.BackupManagerServiceTestUtils;
+import com.android.server.backup.testing.TestUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.io.File;
+
+/**
+ * Tests verifying the interaction between {@link SetupObserver} and {@link
+ * UserBackupManagerService}.
+ */
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class SetupObserverTest {
+ private static final String TAG = "SetupObserverTest";
+ private static final int USER_ID = 10;
+
+ @Mock private TransportManager mTransportManager;
+
+ private Context mContext;
+ private UserBackupManagerService mUserBackupManagerService;
+ private HandlerThread mHandlerThread;
+
+ /** Setup state. */
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+ mHandlerThread = BackupManagerServiceTestUtils.startSilentBackupThread(TAG);
+ mUserBackupManagerService =
+ BackupManagerServiceTestUtils.createUserBackupManagerServiceAndRunTasks(
+ USER_ID,
+ mContext,
+ mHandlerThread,
+ new File(mContext.getDataDir(), "test1"),
+ new File(mContext.getDataDir(), "test2"),
+ mTransportManager);
+ }
+
+ /** Test observer handles changes from not setup -> setup correctly. */
+ @Test
+ public void testOnChange_whenNewlySetup_updatesState() throws Exception {
+ SetupObserver setupObserver = new SetupObserver(mUserBackupManagerService, new Handler());
+ mUserBackupManagerService.setSetupComplete(false);
+ changeSetupCompleteSettingForUser(true, USER_ID);
+
+ setupObserver.onChange(true);
+
+ assertThat(mUserBackupManagerService.isSetupComplete()).isTrue();
+ }
+
+ /** Test observer handles changes from setup -> not setup correctly. */
+ @Test
+ public void testOnChange_whenPreviouslySetup_doesNotUpdateState() throws Exception {
+ SetupObserver setupObserver = new SetupObserver(mUserBackupManagerService, new Handler());
+ mUserBackupManagerService.setSetupComplete(true);
+ changeSetupCompleteSettingForUser(false, USER_ID);
+
+ setupObserver.onChange(true);
+
+ assertThat(mUserBackupManagerService.isSetupComplete()).isTrue();
+ }
+
+ /** Test observer handles changes from not setup -> not setup correctly. */
+ @Test
+ public void testOnChange_whenNotPreviouslySetup_doesNotUpdateStateIfNoChange()
+ throws Exception {
+ SetupObserver setupObserver = new SetupObserver(mUserBackupManagerService, new Handler());
+ mUserBackupManagerService.setSetupComplete(false);
+ changeSetupCompleteSettingForUser(false, USER_ID);
+
+ setupObserver.onChange(true);
+
+ assertThat(mUserBackupManagerService.isSetupComplete()).isFalse();
+ }
+
+ /** Test observer handles changes from not setup -> setup correctly. */
+ @Test
+ public void testOnChange_whenNewlySetup_schedulesBackup() throws Exception {
+ SetupObserver setupObserver = new SetupObserver(mUserBackupManagerService, new Handler());
+ mUserBackupManagerService.setSetupComplete(false);
+ changeSetupCompleteSettingForUser(true, USER_ID);
+ // Setup conditions for a full backup job to be scheduled.
+ mUserBackupManagerService.setEnabled(true);
+ mUserBackupManagerService.enqueueFullBackup("testPackage", /* lastBackedUp */ 0);
+ // Clear the handler of all pending tasks. This is to prevent the below assertion on the
+ // handler from encountering false positives due to other tasks being scheduled as part of
+ // setup work.
+ TestUtils.runToEndOfTasks(mHandlerThread.getLooper());
+
+ setupObserver.onChange(true);
+
+ assertThat(KeyValueBackupJob.isScheduled()).isTrue();
+ // Verifies that the full backup job is scheduled. The job is scheduled via a posted message
+ // on the backup handler so we verify that a message exists.
+ assertThat(mUserBackupManagerService.getBackupHandler().hasMessagesOrCallbacks()).isTrue();
+ }
+
+ private void changeSetupCompleteSettingForUser(boolean value, int userId) {
+ Settings.Secure.putIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.USER_SETUP_COMPLETE,
+ value ? 1 : 0,
+ userId);
+ }
+}
diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
index 5e84ab0..7dac795 100644
--- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
@@ -71,6 +71,7 @@
import android.annotation.Nullable;
import android.app.Application;
+import android.app.ApplicationPackageManager;
import android.app.IBackupAgent;
import android.app.backup.BackupAgent;
import android.app.backup.BackupDataInput;
@@ -134,6 +135,8 @@
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
import org.robolectric.shadows.ShadowLooper;
import org.robolectric.shadows.ShadowPackageManager;
import org.robolectric.shadows.ShadowQueuedWork;
@@ -157,6 +160,7 @@
@Config(
shadows = {
FrameworkShadowLooper.class,
+ KeyValueBackupTaskTest.ShadowApplicationPackageManager.class,
ShadowBackupDataInput.class,
ShadowBackupDataOutput.class,
ShadowEventLog.class,
@@ -244,6 +248,7 @@
@After
public void tearDown() throws Exception {
ShadowBackupDataInput.reset();
+ ShadowApplicationPackageManager.reset();
}
@Test
@@ -2435,7 +2440,8 @@
mPackageManager.setApplicationEnabledSetting(
packageData.packageName, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
PackageInfo packageInfo = getPackageInfo(packageData);
- mShadowPackageManager.addPackage(packageInfo);
+ mShadowPackageManager.installPackage(packageInfo);
+ ShadowApplicationPackageManager.setPackageInfo(packageInfo);
mContext.sendBroadcast(getPackageAddedIntent(packageData));
// Run the backup looper because on the receiver we post MSG_SCHEDULE_BACKUP_PACKAGE
mShadowBackupLooper.runToEndOfTasks();
@@ -2848,4 +2854,29 @@
throw mException;
}
}
+
+ /**
+ * Extends {@link org.robolectric.shadows.ShadowApplicationPackageManager} to return the correct
+ * package in user-specific invocations.
+ */
+ @Implements(value = ApplicationPackageManager.class)
+ public static class ShadowApplicationPackageManager
+ extends org.robolectric.shadows.ShadowApplicationPackageManager {
+ private static PackageInfo sPackageInfo;
+
+ static void setPackageInfo(PackageInfo packageInfo) {
+ sPackageInfo = packageInfo;
+ }
+
+ @Override
+ protected PackageInfo getPackageInfoAsUser(String packageName, int flags, int userId) {
+ return sPackageInfo;
+ }
+
+ /** Clear {@link #sPackageInfo}. */
+ @Resetter
+ public static void reset() {
+ sPackageInfo = null;
+ }
+ }
}
diff --git a/services/startop/Android.bp b/services/startop/Android.bp
new file mode 100644
index 0000000..093b4ec
--- /dev/null
+++ b/services/startop/Android.bp
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+java_library_static {
+ name: "services.startop",
+
+ static_libs: [
+ // frameworks/base/startop/iorap
+ "services.startop.iorap",
+ ],
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
index 148faad..6a153d5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
@@ -60,6 +60,7 @@
import android.os.Looper;
import android.os.PowerManager;
import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
import android.util.Log;
import android.util.SparseArray;
@@ -81,6 +82,7 @@
import java.util.ArrayList;
+@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
public class AlarmManagerServiceTest {
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index 767eb60..2cc338c 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -29,6 +29,8 @@
import static android.app.ActivityManager.PROCESS_STATE_TOP;
import static android.util.DebugUtils.valueToString;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
import static com.android.server.am.ActivityManagerInternalTest.CustomThread;
import static com.android.server.am.ActivityManagerService.DISPATCH_UIDS_CHANGED_UI_MSG;
import static com.android.server.am.ActivityManagerService.Injector;
@@ -68,7 +70,10 @@
import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;
-import com.android.server.AppOpsService;
+import com.android.server.appop.AppOpsService;
+import com.android.server.am.ProcessList.IsolatedUidRange;
+import com.android.server.am.ProcessList.IsolatedUidRangeAllocator;
+import com.android.server.wm.ActivityTaskManagerService;
import org.junit.After;
import org.junit.Before;
@@ -109,7 +114,7 @@
UidRecord.CHANGE_ACTIVE
};
- @Mock private Context mContext;
+ private Context mContext = getInstrumentation().getTargetContext();
@Mock private AppOpsService mAppOpsService;
@Mock private PackageManager mPackageManager;
@@ -128,8 +133,8 @@
mInjector = new TestInjector();
mAms = new ActivityManagerService(mInjector);
mAms.mWaitForNetworkTimeoutMs = 2000;
-
- when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ mAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
+ mAms.mActivityTaskManager.initialize(null, null, mHandler.getLooper());
}
@After
@@ -291,6 +296,113 @@
}
}
+ private void validateAppZygoteIsolatedUidRange(IsolatedUidRange uidRange) {
+ assertNotNull(uidRange);
+ assertTrue(uidRange.mFirstUid >= Process.FIRST_APP_ZYGOTE_ISOLATED_UID
+ && uidRange.mFirstUid <= Process.LAST_APP_ZYGOTE_ISOLATED_UID);
+ assertTrue(uidRange.mLastUid >= Process.FIRST_APP_ZYGOTE_ISOLATED_UID
+ && uidRange.mLastUid <= Process.LAST_APP_ZYGOTE_ISOLATED_UID);
+ assertTrue(uidRange.mLastUid > uidRange.mFirstUid
+ && ((uidRange.mLastUid - uidRange.mFirstUid + 1)
+ == Process.NUM_UIDS_PER_APP_ZYGOTE));
+ }
+
+ private void verifyUidRangesNoOverlap(IsolatedUidRange uidRange1, IsolatedUidRange uidRange2) {
+ IsolatedUidRange lowRange = uidRange1.mFirstUid <= uidRange2.mFirstUid ? uidRange1 : uidRange2;
+ IsolatedUidRange highRange = lowRange == uidRange1 ? uidRange2 : uidRange1;
+
+ assertTrue(highRange.mFirstUid > lowRange.mLastUid);
+ }
+
+ @Test
+ public void testIsolatedUidRangeAllocator() {
+ final IsolatedUidRangeAllocator allocator = mAms.mProcessList.mAppIsolatedUidRangeAllocator;
+
+ // Create initial range
+ ApplicationInfo appInfo = new ApplicationInfo();
+ appInfo.processName = "com.android.test.app";
+ appInfo.uid = 10000;
+ final IsolatedUidRange range = allocator.getOrCreateIsolatedUidRangeLocked(appInfo);
+ validateAppZygoteIsolatedUidRange(range);
+ verifyIsolatedUidAllocator(range);
+
+ // Create a second range
+ ApplicationInfo appInfo2 = new ApplicationInfo();
+ appInfo2.processName = "com.android.test.app2";
+ appInfo2.uid = 10001;
+ IsolatedUidRange range2 = allocator.getOrCreateIsolatedUidRangeLocked(appInfo2);
+ validateAppZygoteIsolatedUidRange(range2);
+ verifyIsolatedUidAllocator(range2);
+
+ // Verify ranges don't overlap
+ verifyUidRangesNoOverlap(range, range2);
+
+ // Free range, reallocate and verify
+ allocator.freeUidRangeLocked(appInfo2);
+ range2 = allocator.getOrCreateIsolatedUidRangeLocked(appInfo2);
+ validateAppZygoteIsolatedUidRange(range2);
+ verifyUidRangesNoOverlap(range, range2);
+ verifyIsolatedUidAllocator(range2);
+
+ // Free both, then try to allocate the maximum number of UID ranges
+ allocator.freeUidRangeLocked(appInfo);
+ allocator.freeUidRangeLocked(appInfo2);
+
+ int maxNumUidRanges = (Process.LAST_APP_ZYGOTE_ISOLATED_UID
+ - Process.FIRST_APP_ZYGOTE_ISOLATED_UID + 1) / Process.NUM_UIDS_PER_APP_ZYGOTE;
+ for (int i = 0; i < maxNumUidRanges; i++) {
+ appInfo = new ApplicationInfo();
+ appInfo.uid = 10000 + i;
+ appInfo.processName = "com.android.test.app" + Integer.toString(i);
+ IsolatedUidRange uidRange = allocator.getOrCreateIsolatedUidRangeLocked(appInfo);
+ validateAppZygoteIsolatedUidRange(uidRange);
+ verifyIsolatedUidAllocator(uidRange);
+ }
+
+ // Try to allocate another one and make sure it fails
+ appInfo = new ApplicationInfo();
+ appInfo.uid = 9000;
+ appInfo.processName = "com.android.test.app.failed";
+ IsolatedUidRange failedRange = allocator.getOrCreateIsolatedUidRangeLocked(appInfo);
+
+ assertNull(failedRange);
+ }
+
+ public void verifyIsolatedUid(ProcessList.IsolatedUidRange range, int uid) {
+ assertTrue(uid >= range.mFirstUid && uid <= range.mLastUid);
+ }
+
+ public void verifyIsolatedUidAllocator(ProcessList.IsolatedUidRange range) {
+ int uid = range.allocateIsolatedUidLocked(0);
+ verifyIsolatedUid(range, uid);
+
+ int uid2 = range.allocateIsolatedUidLocked(0);
+ verifyIsolatedUid(range, uid2);
+ assertTrue(uid2 != uid);
+
+ // Free both
+ range.freeIsolatedUidLocked(uid);
+ range.freeIsolatedUidLocked(uid2);
+
+ // Allocate the entire range
+ for (int i = 0; i < (range.mLastUid - range.mFirstUid + 1); ++i) {
+ uid = range.allocateIsolatedUidLocked(0);
+ verifyIsolatedUid(range, uid);
+ }
+
+ // Ensure the next one fails
+ uid = range.allocateIsolatedUidLocked(0);
+ assertEquals(uid, -1);
+ }
+
+ @Test
+ public void testGlobalIsolatedUidAllocator() {
+ final IsolatedUidRange globalUidRange = mAms.mProcessList.mGlobalIsolatedUids;
+ assertEquals(globalUidRange.mFirstUid, Process.FIRST_ISOLATED_UID);
+ assertEquals(globalUidRange.mLastUid, Process.LAST_ISOLATED_UID);
+ verifyIsolatedUidAllocator(globalUidRange);
+ }
+
@Test
public void testBlockStateForUid() {
final UidRecord uidRec = new UidRecord(TEST_UID, null /* atmInternal */);
diff --git a/services/tests/servicestests/src/com/android/server/am/AppErrorDialogTest.java b/services/tests/servicestests/src/com/android/server/am/AppErrorDialogTest.java
index 05cb48b..8109b1c 100644
--- a/services/tests/servicestests/src/com/android/server/am/AppErrorDialogTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/AppErrorDialogTest.java
@@ -25,7 +25,7 @@
import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
-import com.android.server.AppOpsService;
+import com.android.server.appop.AppOpsService;
import org.junit.Before;
import org.junit.Test;
diff --git a/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java b/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java
index 9626990..cbdc6c3 100644
--- a/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java
@@ -34,7 +34,7 @@
import androidx.test.filters.SmallTest;
import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.server.AppOpsService;
+import com.android.server.appop.AppOpsService;
import org.junit.AfterClass;
import org.junit.Before;
diff --git a/services/tests/servicestests/src/com/android/server/appops/AppOpsActiveWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java
similarity index 99%
rename from services/tests/servicestests/src/com/android/server/appops/AppOpsActiveWatcherTest.java
rename to services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java
index 7e0dfcf..65c5781 100644
--- a/services/tests/servicestests/src/com/android/server/appops/AppOpsActiveWatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.appops;
+package com.android.server.appop;
import static com.google.common.truth.Truth.assertThat;
diff --git a/services/tests/servicestests/src/com/android/server/appops/AppOpsNotedWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/appops/AppOpsNotedWatcherTest.java
rename to services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java
diff --git a/services/tests/servicestests/src/com/android/server/appops/AppOpsServiceTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsServiceTest.java
similarity index 98%
rename from services/tests/servicestests/src/com/android/server/appops/AppOpsServiceTest.java
rename to services/tests/servicestests/src/com/android/server/appop/AppOpsServiceTest.java
index 4ae9bea..36f84d0 100644
--- a/services/tests/servicestests/src/com/android/server/appops/AppOpsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsServiceTest.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.server;
+package com.android.server.appop;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_ERRORED;
@@ -36,6 +36,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.appop.AppOpsService;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/services/tests/servicestests/src/com/android/server/AppOpsUpgradeTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
similarity index 99%
rename from services/tests/servicestests/src/com/android/server/AppOpsUpgradeTest.java
rename to services/tests/servicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
index aac96a1d..eb0c627 100644
--- a/services/tests/servicestests/src/com/android/server/AppOpsUpgradeTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server;
+package com.android.server.appop;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
index 34edd9f..757a046 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
@@ -32,7 +32,6 @@
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
-import android.os.RemoteException;
import android.test.AndroidTestCase;
import android.test.mock.MockPackageManager;
import android.view.inputmethod.InputMethodInfo;
@@ -40,7 +39,6 @@
import androidx.test.InstrumentationRegistry;
import com.android.internal.R;
-import com.android.internal.view.IInputMethodManager;
import org.junit.Before;
import org.junit.Test;
@@ -61,8 +59,8 @@
private @Mock
Resources mResources;
- private @Mock
- IInputMethodManager mIInputMethodManager;
+ @Mock
+ private OverlayPackagesProvider.Injector mInjector;
private @Mock
Context mTestContext;
private Resources mRealResources;
@@ -81,6 +79,7 @@
InstrumentationRegistry.getTargetContext().getCacheDir());
setSystemInputMethods();
+ setIsPerProfileModeEnabled(false);
setRequiredAppsManagedDevice();
setVendorRequiredAppsManagedDevice();
setDisallowedAppsManagedDevice();
@@ -95,7 +94,7 @@
setVendorDisallowedAppsManagedUser();
mRealResources = InstrumentationRegistry.getTargetContext().getResources();
- mHelper = new OverlayPackagesProvider(mTestContext, mIInputMethodManager);
+ mHelper = new OverlayPackagesProvider(mTestContext, mInjector);
}
@Test
@@ -165,6 +164,15 @@
}
@Test
+ public void testProfileOwnerImesAreRequiredForPerProfileImeMode() {
+ setSystemAppsWithLauncher("app.a", "app.b");
+ setSystemInputMethods("app.a");
+ setIsPerProfileModeEnabled(true);
+
+ verifyAppsAreNonRequired(ACTION_PROVISION_MANAGED_PROFILE, "app.b");
+ }
+
+ @Test
public void testManagedUserImesAreRequired() {
setSystemAppsWithLauncher("app.a", "app.b");
setSystemInputMethods("app.a");
@@ -333,11 +341,11 @@
InputMethodInfo inputMethodInfo = new InputMethodInfo(ri, false, null, null, 0, false);
inputMethods.add(inputMethodInfo);
}
- try {
- when(mIInputMethodManager.getInputMethodList()).thenReturn(inputMethods);
- } catch (RemoteException e) {
- fail(e.toString());
- }
+ when(mInjector.getInputMethodListAsUser(eq(TEST_USER_ID))).thenReturn(inputMethods);
+ }
+
+ private void setIsPerProfileModeEnabled(boolean enabled) {
+ when(mInjector.isPerProfileImeEnabled()).thenReturn(enabled);
}
private void setSystemAppsWithLauncher(String... apps) {
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index abf9040..4742a73 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -389,7 +389,7 @@
if (attr == null) return; //sampling not supported on device, skip remainder of test.
boolean enabled = displayManager.setDisplayedContentSamplingEnabledInternal(0, true, 0, 0);
- assertTrue(!enabled);
+ assertTrue(enabled);
displayManager.setDisplayedContentSamplingEnabledInternal(0, false, 0, 0);
DisplayedContentSample sample = displayManager.getDisplayedContentSampleInternal(0, 0, 0);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
index 7484edd..4255e37 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
@@ -18,8 +18,12 @@
import android.hardware.hdmi.HdmiPortInfo;
import android.hardware.tv.cec.V1_0.SendMessageResult;
import android.os.MessageQueue;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.hdmi.HdmiCecController.NativeWrapper;
+
+import com.google.common.collect.Iterables;
+
import java.util.ArrayList;
import java.util.List;
@@ -45,7 +49,6 @@
};
private final List<HdmiCecMessage> mResultMessages = new ArrayList<>();
- private HdmiCecMessage mResultMessage;
private int mMyPhysicalAddress = 0;
@Override
@@ -112,11 +115,12 @@
return new ArrayList<>(mResultMessages);
}
- public HdmiCecMessage getOnlyResultMessage() throws Exception {
- if (mResultMessages.size() != 1) {
- throw new Exception("There is not exactly one message");
- }
- return mResultMessages.get(0);
+ public HdmiCecMessage getOnlyResultMessage() throws IllegalArgumentException {
+ return Iterables.getOnlyElement(mResultMessages);
+ }
+
+ public void clearResultMessages() {
+ mResultMessages.clear();
}
public void setPollAddressResponse(int logicalAddress, int response) {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index 9e3a0ea..c442ae8 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -17,25 +17,25 @@
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
+import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
+import static com.android.server.hdmi.Constants.ADDR_TUNER_1;
import static com.android.server.hdmi.Constants.ADDR_TV;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static com.android.server.hdmi.HdmiControlService.STANDBY_SCREEN_OFF;
-import static com.google.common.truth.Truth.assertThat;
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
import android.media.AudioManager;
import android.os.Looper;
import android.os.SystemProperties;
import android.os.test.TestLooper;
+
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+
import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -54,6 +54,7 @@
private HdmiControlService mHdmiControlService;
private HdmiCecController mHdmiCecController;
private HdmiCecLocalDeviceAudioSystem mHdmiCecLocalDeviceAudioSystem;
+ private HdmiCecLocalDevicePlayback mHdmiCecLocalDevicePlayback;
private FakeNativeWrapper mNativeWrapper;
private Looper mMyLooper;
private TestLooper mTestLooper = new TestLooper();
@@ -61,90 +62,104 @@
private int mMusicVolume;
private int mMusicMaxVolume;
private boolean mMusicMute;
+ private int mAvrPhysicalAddress;
@Before
public void setUp() {
mHdmiControlService =
- new HdmiControlService(InstrumentationRegistry.getTargetContext()) {
- @Override
- AudioManager getAudioManager() {
- return new AudioManager() {
- @Override
- public int getStreamVolume(int streamType) {
- switch (streamType) {
- case STREAM_MUSIC:
- return mMusicVolume;
- default:
- return 0;
- }
+ new HdmiControlService(InstrumentationRegistry.getTargetContext()) {
+ @Override
+ AudioManager getAudioManager() {
+ return new AudioManager() {
+ @Override
+ public int getStreamVolume(int streamType) {
+ switch (streamType) {
+ case STREAM_MUSIC:
+ return mMusicVolume;
+ default:
+ return 0;
}
+ }
- @Override
- public boolean isStreamMute(int streamType) {
- switch (streamType) {
- case STREAM_MUSIC:
- return mMusicMute;
- default:
- return false;
- }
+ @Override
+ public boolean isStreamMute(int streamType) {
+ switch (streamType) {
+ case STREAM_MUSIC:
+ return mMusicMute;
+ default:
+ return false;
}
+ }
- @Override
- public int getStreamMaxVolume(int streamType) {
- switch (streamType) {
- case STREAM_MUSIC:
- return mMusicMaxVolume;
- default:
- return 100;
- }
+ @Override
+ public int getStreamMaxVolume(int streamType) {
+ switch (streamType) {
+ case STREAM_MUSIC:
+ return mMusicMaxVolume;
+ default:
+ return 100;
}
+ }
- @Override
- public void adjustStreamVolume(
- int streamType, int direction, int flags) {
- switch (streamType) {
- case STREAM_MUSIC:
- if (direction == AudioManager.ADJUST_UNMUTE) {
- mMusicMute = false;
- } else if (direction == AudioManager.ADJUST_MUTE) {
- mMusicMute = true;
- }
- default:
- }
+ @Override
+ public void adjustStreamVolume(
+ int streamType, int direction, int flags) {
+ switch (streamType) {
+ case STREAM_MUSIC:
+ if (direction == AudioManager.ADJUST_UNMUTE) {
+ mMusicMute = false;
+ } else if (direction == AudioManager.ADJUST_MUTE) {
+ mMusicMute = true;
+ }
+ break;
+ default:
}
+ }
- @Override
- public void setWiredDeviceConnectionState(
+ @Override
+ public void setWiredDeviceConnectionState(
int type, int state, String address, String name) {
- // Do nothing.
- }
- };
- }
+ // Do nothing.
+ }
+ };
+ }
- @Override
- void wakeUp() {}
- };
+ @Override
+ void wakeUp() {}
+
+ @Override
+ boolean isControlEnabled() {
+ return true;
+ }
+ };
mMyLooper = mTestLooper.getLooper();
mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(mHdmiControlService);
+ mHdmiCecLocalDevicePlayback = new HdmiCecLocalDevicePlayback(mHdmiControlService);
mHdmiCecLocalDeviceAudioSystem.init();
+ mHdmiCecLocalDevicePlayback.init();
mHdmiControlService.setIoLooper(mMyLooper);
mNativeWrapper = new FakeNativeWrapper();
mHdmiCecController =
- HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper);
+ HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper);
mHdmiControlService.setCecController(mHdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
mLocalDevices.add(mHdmiCecLocalDeviceAudioSystem);
+ mLocalDevices.add(mHdmiCecLocalDevicePlayback);
mHdmiControlService.initPortInfo();
// No TV device interacts with AVR so system audio control won't be turned on here
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
+ mNativeWrapper.clearResultMessages();
+ mAvrPhysicalAddress = 0x2000;
+ mNativeWrapper.setPhysicalAddress(mAvrPhysicalAddress);
SystemProperties.set(Constants.PROPERTY_ARC_SUPPORT, "true");
+ SystemProperties.set(Constants.PROPERTY_SYSTEM_AUDIO_MODE_MUTING_ENABLE, "true");
}
@Test
- public void handleGiveAudioStatus_volume_10_mute_true() {
+ public void handleGiveAudioStatus_volume_10_mute_true() throws Exception {
mMusicVolume = 10;
mMusicMute = true;
mMusicMaxVolume = 20;
@@ -154,25 +169,24 @@
ADDR_AUDIO_SYSTEM, ADDR_TV, scaledVolume, true);
HdmiCecMessage messageGive =
HdmiCecMessageBuilder.buildGiveAudioStatus(ADDR_TV, ADDR_AUDIO_SYSTEM);
- assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveAudioStatus(messageGive))
- .isTrue();
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveAudioStatus(messageGive)).isTrue();
mTestLooper.dispatchAll();
- assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
+ assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
}
@Test
- public void handleGiveSystemAudioModeStatus_originalOff() {
+ public void handleGiveSystemAudioModeStatus_originalOff() throws Exception {
HdmiCecMessage expectedMessage =
- HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false);
+ HdmiCecMessageBuilder.buildReportSystemAudioMode(
+ ADDR_AUDIO_SYSTEM, ADDR_TV, false);
HdmiCecMessage messageGive =
HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus(ADDR_TV, ADDR_AUDIO_SYSTEM);
assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive))
- .isTrue();
+ .isTrue();
mTestLooper.dispatchAll();
- assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
+ assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
}
- @Ignore("b/80297700")
@Test
public void handleRequestShortAudioDescriptor_featureDisabled() throws Exception {
HdmiCecMessage expectedMessage =
@@ -184,9 +198,9 @@
mHdmiCecLocalDeviceAudioSystem.setSystemAudioControlFeatureEnabled(false);
assertThat(
- mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
- MESSAGE_REQUEST_SAD_LCPM))
- .isTrue();
+ mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
+ MESSAGE_REQUEST_SAD_LCPM))
+ .isTrue();
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
}
@@ -200,11 +214,11 @@
Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR,
Constants.ABORT_NOT_IN_CORRECT_MODE);
- mHdmiCecLocalDeviceAudioSystem.setSystemAudioMode(false);
+ mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(false);
assertThat(
- mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
- MESSAGE_REQUEST_SAD_LCPM))
- .isEqualTo(true);
+ mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
+ MESSAGE_REQUEST_SAD_LCPM))
+ .isEqualTo(true);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
}
@@ -218,17 +232,17 @@
Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR,
Constants.ABORT_UNABLE_TO_DETERMINE);
- mHdmiCecLocalDeviceAudioSystem.setSystemAudioMode(true);
+ mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(true);
assertThat(
- mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
- MESSAGE_REQUEST_SAD_LCPM))
- .isEqualTo(true);
+ mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
+ MESSAGE_REQUEST_SAD_LCPM))
+ .isEqualTo(true);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
}
@Test
- public void handleSetSystemAudioMode_setOn_orignalOff() {
+ public void handleSetSystemAudioMode_setOn_orignalOff() throws Exception {
mMusicMute = true;
HdmiCecMessage messageSet =
HdmiCecMessageBuilder.buildSetSystemAudioMode(ADDR_TV, ADDR_AUDIO_SYSTEM, true);
@@ -238,25 +252,24 @@
HdmiCecMessage expectedMessage =
HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false);
assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive))
- .isTrue();
+ .isTrue();
mTestLooper.dispatchAll();
- assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
+ assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
// Check if correctly turned on
+ mNativeWrapper.clearResultMessages();
expectedMessage =
- HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, true);
- assertThat(mHdmiCecLocalDeviceAudioSystem.handleSetSystemAudioMode(messageSet))
- .isTrue();
+ HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, true);
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleSetSystemAudioMode(messageSet)).isTrue();
mTestLooper.dispatchAll();
assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive))
- .isTrue();
+ .isTrue();
mTestLooper.dispatchAll();
- assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
+ assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
assertThat(mMusicMute).isFalse();
}
- @Ignore("b/80297700")
@Test
- public void handleSystemAudioModeRequest_turnOffByTv() {
+ public void handleSystemAudioModeRequest_turnOffByTv() throws Exception {
assertThat(mMusicMute).isFalse();
// Check if feature correctly turned off
HdmiCecMessage messageGive =
@@ -268,91 +281,93 @@
HdmiCecMessageBuilder.buildSetSystemAudioMode(
ADDR_AUDIO_SYSTEM, ADDR_BROADCAST, false);
assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(messageRequestOff))
- .isTrue();
+ .isTrue();
mTestLooper.dispatchAll();
- assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
+ assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
+
+ mNativeWrapper.clearResultMessages();
expectedMessage =
- HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false);
+ HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false);
assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive))
- .isTrue();
+ .isTrue();
mTestLooper.dispatchAll();
- assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
+ assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
assertThat(mMusicMute).isTrue();
}
- @Ignore("b/80297700")
@Test
- public void onStandbyAudioSystem_currentSystemAudioControlOn() {
+ public void onStandbyAudioSystem_currentSystemAudioControlOn() throws Exception {
+ mHdmiCecLocalDeviceAudioSystem.setAutoDeviceOff(false);
+ mHdmiCecLocalDeviceAudioSystem.setAutoTvOff(false);
// Set system audio control on first
- mHdmiCecLocalDeviceAudioSystem.setSystemAudioMode(true);
+ mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(true);
// Check if standby correctly turns off the feature
mHdmiCecLocalDeviceAudioSystem.onStandby(false, STANDBY_SCREEN_OFF);
mTestLooper.dispatchAll();
HdmiCecMessage expectedMessage =
HdmiCecMessageBuilder.buildSetSystemAudioMode(
ADDR_AUDIO_SYSTEM, ADDR_BROADCAST, false);
- assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
+ assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
assertThat(mMusicMute).isTrue();
}
@Test
- public void systemAudioControlOnPowerOn_alwaysOn() {
+ public void systemAudioControlOnPowerOn_alwaysOn() throws Exception {
mHdmiCecLocalDeviceAudioSystem.removeAction(SystemAudioInitiationActionFromAvr.class);
mHdmiCecLocalDeviceAudioSystem.systemAudioControlOnPowerOn(
Constants.ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, true);
assertThat(
- mHdmiCecLocalDeviceAudioSystem.getActions(
- SystemAudioInitiationActionFromAvr.class))
- .isNotEmpty();
+ mHdmiCecLocalDeviceAudioSystem.getActions(
+ SystemAudioInitiationActionFromAvr.class))
+ .isNotEmpty();
}
@Test
- public void systemAudioControlOnPowerOn_neverOn() {
+ public void systemAudioControlOnPowerOn_neverOn() throws Exception {
mHdmiCecLocalDeviceAudioSystem.removeAction(SystemAudioInitiationActionFromAvr.class);
mHdmiCecLocalDeviceAudioSystem.systemAudioControlOnPowerOn(
Constants.NEVER_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, false);
assertThat(
- mHdmiCecLocalDeviceAudioSystem.getActions(
- SystemAudioInitiationActionFromAvr.class))
- .isEmpty();
+ mHdmiCecLocalDeviceAudioSystem.getActions(
+ SystemAudioInitiationActionFromAvr.class))
+ .isEmpty();
}
@Test
- public void systemAudioControlOnPowerOn_useLastState_off() {
+ public void systemAudioControlOnPowerOn_useLastState_off() throws Exception {
mHdmiCecLocalDeviceAudioSystem.removeAction(SystemAudioInitiationActionFromAvr.class);
mHdmiCecLocalDeviceAudioSystem.systemAudioControlOnPowerOn(
Constants.USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, false);
assertThat(
- mHdmiCecLocalDeviceAudioSystem.getActions(
- SystemAudioInitiationActionFromAvr.class))
- .isEmpty();
+ mHdmiCecLocalDeviceAudioSystem.getActions(
+ SystemAudioInitiationActionFromAvr.class))
+ .isEmpty();
}
@Test
- public void systemAudioControlOnPowerOn_useLastState_on() {
+ public void systemAudioControlOnPowerOn_useLastState_on() throws Exception {
mHdmiCecLocalDeviceAudioSystem.removeAction(SystemAudioInitiationActionFromAvr.class);
mHdmiCecLocalDeviceAudioSystem.systemAudioControlOnPowerOn(
Constants.USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, true);
assertThat(
- mHdmiCecLocalDeviceAudioSystem.getActions(
- SystemAudioInitiationActionFromAvr.class))
- .isNotEmpty();
+ mHdmiCecLocalDeviceAudioSystem.getActions(
+ SystemAudioInitiationActionFromAvr.class))
+ .isNotEmpty();
}
@Test
- public void handleActiveSource_updateActiveSource() {
+ public void handleActiveSource_updateActiveSource() throws Exception {
HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
ActiveSource expectedActiveSource = new ActiveSource(ADDR_TV, 0x0000);
- assertThat(mHdmiCecLocalDeviceAudioSystem.handleActiveSource(message))
- .isTrue();
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleActiveSource(message)).isTrue();
mTestLooper.dispatchAll();
assertThat(mHdmiCecLocalDeviceAudioSystem.getActiveSource().equals(expectedActiveSource))
- .isTrue();
+ .isTrue();
}
@Test
- public void terminateSystemAudioMode_systemAudioModeOff() {
- mHdmiCecLocalDeviceAudioSystem.setSystemAudioMode(false);
+ public void terminateSystemAudioMode_systemAudioModeOff() throws Exception {
+ mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(false);
assertThat(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()).isFalse();
mMusicMute = false;
HdmiCecMessage message =
@@ -361,13 +376,12 @@
mHdmiCecLocalDeviceAudioSystem.terminateSystemAudioMode();
assertThat(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()).isFalse();
assertThat(mMusicMute).isFalse();
- assertThat(mNativeWrapper.getResultMessages()).doesNotContain(message);
+ assertThat(mNativeWrapper.getResultMessages()).isEmpty();
}
- @Ignore("b/80297700")
@Test
- public void terminateSystemAudioMode_systemAudioModeOn() {
- mHdmiCecLocalDeviceAudioSystem.setSystemAudioMode(true);
+ public void terminateSystemAudioMode_systemAudioModeOn() throws Exception {
+ mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(true);
assertThat(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()).isTrue();
mMusicMute = false;
HdmiCecMessage expectedMessage =
@@ -381,124 +395,230 @@
}
@Test
- public void isPhysicalAddressMeOrBelow_isMe() {
+ public void pathToPort_isMe() throws Exception {
int targetPhysicalAddress = 0x1000;
mNativeWrapper.setPhysicalAddress(0x1000);
- assertThat(mHdmiCecLocalDeviceAudioSystem.isPhysicalAddressMeOrBelow(targetPhysicalAddress))
- .isTrue();
+ assertThat(mHdmiCecLocalDeviceAudioSystem
+ .getLocalPortFromPhysicalAddress(targetPhysicalAddress))
+ .isEqualTo(0);
}
@Test
- public void isPhysicalAddressMeOrBelow_isBelow() {
+ public void pathToPort_isBelow() throws Exception {
int targetPhysicalAddress = 0x1100;
mNativeWrapper.setPhysicalAddress(0x1000);
- assertThat(mHdmiCecLocalDeviceAudioSystem.isPhysicalAddressMeOrBelow(targetPhysicalAddress))
- .isTrue();
+ assertThat(mHdmiCecLocalDeviceAudioSystem
+ .getLocalPortFromPhysicalAddress(targetPhysicalAddress))
+ .isEqualTo(1);
}
@Test
- public void isPhysicalAddressMeOrBelow_neitherMeNorBelow() {
+ public void pathToPort_neitherMeNorBelow() throws Exception {
int targetPhysicalAddress = 0x3000;
mNativeWrapper.setPhysicalAddress(0x2000);
- assertThat(mHdmiCecLocalDeviceAudioSystem.isPhysicalAddressMeOrBelow(targetPhysicalAddress))
- .isFalse();
+ assertThat(mHdmiCecLocalDeviceAudioSystem
+ .getLocalPortFromPhysicalAddress(targetPhysicalAddress))
+ .isEqualTo(-1);
targetPhysicalAddress = 0x2200;
mNativeWrapper.setPhysicalAddress(0x3300);
- assertThat(mHdmiCecLocalDeviceAudioSystem.isPhysicalAddressMeOrBelow(targetPhysicalAddress))
- .isFalse();
+ assertThat(mHdmiCecLocalDeviceAudioSystem
+ .getLocalPortFromPhysicalAddress(targetPhysicalAddress))
+ .isEqualTo(-1);
targetPhysicalAddress = 0x2213;
mNativeWrapper.setPhysicalAddress(0x2212);
- assertThat(mHdmiCecLocalDeviceAudioSystem.isPhysicalAddressMeOrBelow(targetPhysicalAddress))
- .isFalse();
+ assertThat(mHdmiCecLocalDeviceAudioSystem
+ .getLocalPortFromPhysicalAddress(targetPhysicalAddress))
+ .isEqualTo(-1);
targetPhysicalAddress = 0x2340;
mNativeWrapper.setPhysicalAddress(0x2310);
- assertThat(mHdmiCecLocalDeviceAudioSystem.isPhysicalAddressMeOrBelow(targetPhysicalAddress))
- .isFalse();
+ assertThat(mHdmiCecLocalDeviceAudioSystem
+ .getLocalPortFromPhysicalAddress(targetPhysicalAddress))
+ .isEqualTo(-1);
}
@Test
- public void handleRequestArcInitiate_isNotDirectConnectedToTv() {
- HdmiCecMessage message = HdmiCecMessageBuilder
- .buildRequestArcInitiation(ADDR_TV, ADDR_AUDIO_SYSTEM);
- HdmiCecMessage expectedMessage = HdmiCecMessageBuilder
- .buildFeatureAbortCommand(
- ADDR_AUDIO_SYSTEM, ADDR_TV,
- Constants.MESSAGE_REQUEST_ARC_INITIATION,
- Constants.ABORT_NOT_IN_CORRECT_MODE);
+ public void handleRequestArcInitiate_isNotDirectConnectedToTv() throws Exception {
+ HdmiCecMessage message =
+ HdmiCecMessageBuilder.buildRequestArcInitiation(ADDR_TV, ADDR_AUDIO_SYSTEM);
+ HdmiCecMessage expectedMessage =
+ HdmiCecMessageBuilder.buildFeatureAbortCommand(
+ ADDR_AUDIO_SYSTEM,
+ ADDR_TV,
+ Constants.MESSAGE_REQUEST_ARC_INITIATION,
+ Constants.ABORT_NOT_IN_CORRECT_MODE);
mNativeWrapper.setPhysicalAddress(0x1100);
- assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message))
- .isTrue();
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message)).isTrue();
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
}
@Test
- public void handleRequestArcInitiate_startArcInitiationActionFromAvr() {
- HdmiCecMessage message = HdmiCecMessageBuilder
- .buildRequestArcInitiation(ADDR_TV, ADDR_AUDIO_SYSTEM);
+ public void handleRequestArcInitiate_startArcInitiationActionFromAvr() throws Exception {
+ HdmiCecMessage message =
+ HdmiCecMessageBuilder.buildRequestArcInitiation(ADDR_TV, ADDR_AUDIO_SYSTEM);
mNativeWrapper.setPhysicalAddress(0x1000);
- mHdmiCecLocalDeviceAudioSystem.removeAction(
- ArcInitiationActionFromAvr.class);
+ mHdmiCecLocalDeviceAudioSystem.removeAction(ArcInitiationActionFromAvr.class);
- assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message))
- .isTrue();
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message)).isTrue();
mTestLooper.dispatchAll();
- assertThat(mHdmiCecLocalDeviceAudioSystem
- .getActions(ArcInitiationActionFromAvr.class)).isNotEmpty();
+ assertThat(mHdmiCecLocalDeviceAudioSystem.getActions(ArcInitiationActionFromAvr.class))
+ .isNotEmpty();
}
@Test
- public void handleRequestArcTerminate_arcIsOn_startTerminationActionFromAvr() {
+ public void handleRequestArcTerminate_arcIsOn_startTerminationActionFromAvr() throws Exception {
mHdmiCecLocalDeviceAudioSystem.setArcStatus(true);
assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isTrue();
- HdmiCecMessage message = HdmiCecMessageBuilder
- .buildRequestArcTermination(ADDR_TV, ADDR_AUDIO_SYSTEM);
- mHdmiCecLocalDeviceAudioSystem.removeAction(
- ArcTerminationActionFromAvr.class);
+ HdmiCecMessage message =
+ HdmiCecMessageBuilder.buildRequestArcTermination(ADDR_TV, ADDR_AUDIO_SYSTEM);
+ mHdmiCecLocalDeviceAudioSystem.removeAction(ArcTerminationActionFromAvr.class);
- assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcTermination(message))
- .isTrue();
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcTermination(message)).isTrue();
mTestLooper.dispatchAll();
- assertThat(mHdmiCecLocalDeviceAudioSystem
- .getActions(ArcTerminationActionFromAvr.class)).isNotEmpty();
+ assertThat(mHdmiCecLocalDeviceAudioSystem.getActions(ArcTerminationActionFromAvr.class))
+ .isNotEmpty();
}
@Test
- public void handleRequestArcTerminate_arcIsNotOn() {
+ public void handleRequestArcTerminate_arcIsNotOn() throws Exception {
assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse();
- HdmiCecMessage message = HdmiCecMessageBuilder
- .buildRequestArcTermination(ADDR_TV, ADDR_AUDIO_SYSTEM);
- HdmiCecMessage expectedMessage = HdmiCecMessageBuilder
- .buildFeatureAbortCommand(
- ADDR_AUDIO_SYSTEM, ADDR_TV,
- Constants.MESSAGE_REQUEST_ARC_TERMINATION,
- Constants.ABORT_NOT_IN_CORRECT_MODE);
+ HdmiCecMessage message =
+ HdmiCecMessageBuilder.buildRequestArcTermination(ADDR_TV, ADDR_AUDIO_SYSTEM);
+ HdmiCecMessage expectedMessage =
+ HdmiCecMessageBuilder.buildFeatureAbortCommand(
+ ADDR_AUDIO_SYSTEM,
+ ADDR_TV,
+ Constants.MESSAGE_REQUEST_ARC_TERMINATION,
+ Constants.ABORT_NOT_IN_CORRECT_MODE);
- assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcTermination(message))
- .isTrue();
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcTermination(message)).isTrue();
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
}
@Test
- public void handleRequestArcInit_arcIsNotSupported() {
- HdmiCecMessage message = HdmiCecMessageBuilder
- .buildRequestArcInitiation(ADDR_TV, ADDR_AUDIO_SYSTEM);
- HdmiCecMessage expectedMessage = HdmiCecMessageBuilder
- .buildFeatureAbortCommand(
- ADDR_AUDIO_SYSTEM, ADDR_TV,
- Constants.MESSAGE_REQUEST_ARC_INITIATION,
- Constants.ABORT_UNRECOGNIZED_OPCODE);
+ public void handleRequestArcInit_arcIsNotSupported() throws Exception {
+ HdmiCecMessage message =
+ HdmiCecMessageBuilder.buildRequestArcInitiation(ADDR_TV, ADDR_AUDIO_SYSTEM);
+ HdmiCecMessage expectedMessage =
+ HdmiCecMessageBuilder.buildFeatureAbortCommand(
+ ADDR_AUDIO_SYSTEM,
+ ADDR_TV,
+ Constants.MESSAGE_REQUEST_ARC_INITIATION,
+ Constants.ABORT_UNRECOGNIZED_OPCODE);
SystemProperties.set(Constants.PROPERTY_ARC_SUPPORT, "false");
- assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message))
- .isTrue();
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message)).isTrue();
mTestLooper.dispatchAll();
- assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
+ assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
+ }
+
+ @Test
+ public void onStandby_setAutoDeviceOff_true() throws Exception {
+ HdmiCecMessage expectedMessage =
+ HdmiCecMessageBuilder.buildStandby(ADDR_AUDIO_SYSTEM, ADDR_BROADCAST);
+ mHdmiCecLocalDeviceAudioSystem.setAutoDeviceOff(true);
+ mHdmiCecLocalDeviceAudioSystem.onStandby(false, STANDBY_SCREEN_OFF);
+
+ mTestLooper.dispatchAll();
+ assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
+ }
+
+ public void handleSystemAudioModeRequest_fromNonTV_tVNotSupport() {
+ HdmiCecMessage message =
+ HdmiCecMessageBuilder.buildSystemAudioModeRequest(
+ ADDR_TUNER_1, ADDR_AUDIO_SYSTEM,
+ mAvrPhysicalAddress, true);
+ HdmiCecMessage expectedMessage =
+ HdmiCecMessageBuilder.buildFeatureAbortCommand(
+ ADDR_AUDIO_SYSTEM,
+ ADDR_TUNER_1,
+ Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST,
+ Constants.ABORT_REFUSED);
+
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(message)).isTrue();
+ mTestLooper.dispatchAll();
+ assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
+ }
+
+ @Test
+ public void handleSystemAudioModeRequest_fromNonTV_tVSupport() {
+ HdmiCecMessage message =
+ HdmiCecMessageBuilder.buildSystemAudioModeRequest(
+ ADDR_TUNER_1, ADDR_AUDIO_SYSTEM,
+ mAvrPhysicalAddress, true);
+ HdmiCecMessage expectedMessage =
+ HdmiCecMessageBuilder.buildSetSystemAudioMode(
+ ADDR_AUDIO_SYSTEM, Constants.ADDR_BROADCAST, true);
+ mHdmiCecLocalDeviceAudioSystem.setTvSystemAudioModeSupport(true);
+
+
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(message)).isTrue();
+ mTestLooper.dispatchAll();
+ assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
+ }
+
+ @Test
+ public void handleActiveSource_activeSourceFromTV_swithToArc() {
+ mHdmiCecLocalDeviceAudioSystem.setArcStatus(true);
+ HdmiCecMessage message =
+ HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+
+ ActiveSource expectedActiveSource = ActiveSource.of(ADDR_TV, 0x0000);
+
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleActiveSource(message)).isTrue();
+ mTestLooper.dispatchAll();
+ assertThat(mHdmiCecLocalDeviceAudioSystem.getActiveSource())
+ .isEqualTo(expectedActiveSource);
+ }
+
+ @Test
+ public void handleRoutingChange_currentActivePortIsHome() {
+ HdmiCecMessage message =
+ HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x3000, mAvrPhysicalAddress);
+
+ HdmiCecMessage expectedMessage =
+ HdmiCecMessageBuilder.buildActiveSource(ADDR_AUDIO_SYSTEM, mAvrPhysicalAddress);
+ ActiveSource expectedActiveSource = ActiveSource.of(ADDR_AUDIO_SYSTEM, mAvrPhysicalAddress);
+ int expectedLocalActivePort = Constants.CEC_SWITCH_HOME;
+
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingChange(message)).isTrue();
+ mTestLooper.dispatchAll();
+ assertThat(mHdmiCecLocalDeviceAudioSystem.getActiveSource())
+ .isEqualTo(expectedActiveSource);
+ assertThat(mHdmiCecLocalDeviceAudioSystem.getLocalActivePort())
+ .isEqualTo(expectedLocalActivePort);
+ assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
+ }
+
+ @Test
+ public void handleRoutingInformation_currentActivePortIsHDMI1() {
+ HdmiCecMessage message =
+ HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, 0x2000);
+ mHdmiCecLocalDeviceAudioSystem.setLocalActivePort(Constants.CEC_SWITCH_HDMI1);
+ HdmiCecMessage expectedMessage =
+ HdmiCecMessageBuilder.buildRoutingInformation(ADDR_AUDIO_SYSTEM, 0x2100);
+
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingInformation(message)).isTrue();
+ mTestLooper.dispatchAll();
+ assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
+ }
+
+ @Test
+ public void handleRoutingChange_homeIsActive_playbackSendActiveSource() {
+ HdmiCecMessage message =
+ HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000, 0x2000);
+
+ HdmiCecMessage expectedMessage =
+ HdmiCecMessageBuilder.buildActiveSource(ADDR_PLAYBACK_1, 0x2000);
+
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingChange(message)).isTrue();
+ mTestLooper.dispatchAll();
+ assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
new file mode 100644
index 0000000..76f638c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.
+ */
+package com.android.server.hdmi;
+
+import static com.android.server.hdmi.Constants.ADDR_TV;
+import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Looper;
+import android.os.test.TestLooper;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.ArrayList;
+
+@SmallTest
+@RunWith(JUnit4.class)
+/** Tests for {@link HdmiCecLocalDevicePlayback} class. */
+public class HdmiCecLocalDevicePlaybackTest {
+
+ private HdmiControlService mHdmiControlService;
+ private HdmiCecController mHdmiCecController;
+ private HdmiCecLocalDevicePlayback mHdmiCecLocalDevicePlayback;
+ private FakeNativeWrapper mNativeWrapper;
+ private Looper mMyLooper;
+ private TestLooper mTestLooper = new TestLooper();
+ private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
+ private int mPlaybackPhysicalAddress;
+
+ @Before
+ public void setUp() {
+ mHdmiControlService =
+ new HdmiControlService(InstrumentationRegistry.getTargetContext()) {
+ @Override
+ void wakeUp() {
+ }
+
+ @Override
+ boolean isControlEnabled() {
+ return true;
+ }
+ };
+
+ mMyLooper = mTestLooper.getLooper();
+ mHdmiCecLocalDevicePlayback = new HdmiCecLocalDevicePlayback(mHdmiControlService);
+ mHdmiCecLocalDevicePlayback.init();
+ mHdmiControlService.setIoLooper(mMyLooper);
+ mNativeWrapper = new FakeNativeWrapper();
+ mHdmiCecController =
+ HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper);
+ mHdmiControlService.setCecController(mHdmiCecController);
+ mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
+ mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
+ mLocalDevices.add(mHdmiCecLocalDevicePlayback);
+ mHdmiControlService.initPortInfo();
+ mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ mTestLooper.dispatchAll();
+ mNativeWrapper.clearResultMessages();
+ mPlaybackPhysicalAddress = 0x2000;
+ mNativeWrapper.setPhysicalAddress(mPlaybackPhysicalAddress);
+ }
+
+ @Test
+ public void handleSetStreamPath_underCurrentDevice() {
+ assertThat(mHdmiCecLocalDevicePlayback.getLocalActivePath()).isEqualTo(0);
+ HdmiCecMessage message =
+ HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x2100);
+ assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.getLocalActivePath()).isEqualTo(1);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java
index c7809d3..ef974f2 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java
@@ -18,6 +18,7 @@
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
import static com.android.server.hdmi.Constants.ADDR_TV;
+
import static com.google.common.truth.Truth.assertThat;
import android.hardware.hdmi.HdmiDeviceInfo;
@@ -51,6 +52,14 @@
assertThat(message).isEqualTo(buildMessage("05:A4:06:01"));
}
+ @Test
+ public void buildRoutingInformation() {
+ HdmiCecMessage message =
+ HdmiCecMessageBuilder.buildRoutingInformation(
+ ADDR_AUDIO_SYSTEM, 0x2100);
+ assertThat(message).isEqualTo(buildMessage("5F:81:21:00"));
+ }
+
/**
* Build a CEC message from a hex byte string with bytes separated by {@code :}.
*
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java
index 813fa82..d9faaa4 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java
@@ -16,8 +16,6 @@
package com.android.server.pm.dex;
-import static com.android.server.pm.PackageDexOptimizer.SKIP_SHARED_LIBRARY_CHECK;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@@ -31,16 +29,16 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
import dalvik.system.DelegateLastClassLoader;
import dalvik.system.DexClassLoader;
import dalvik.system.PathClassLoader;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.io.File;
-import java.util.Arrays;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -412,12 +410,6 @@
@Test
public void testEncodeClassLoader() {
- assertEquals(SKIP_SHARED_LIBRARY_CHECK, DexoptUtils.encodeClassLoader(
- SKIP_SHARED_LIBRARY_CHECK, "dalvik.system.PathClassLoader"));
- assertEquals(SKIP_SHARED_LIBRARY_CHECK, DexoptUtils.encodeClassLoader(
- SKIP_SHARED_LIBRARY_CHECK, "dalvik.system.DexClassLoader"));
- assertEquals(SKIP_SHARED_LIBRARY_CHECK, DexoptUtils.encodeClassLoader(
- SKIP_SHARED_LIBRARY_CHECK, "dalvik.system.DelegateLastClassLoader"));
assertEquals("PCL[xyz]", DexoptUtils.encodeClassLoader("xyz",
"dalvik.system.PathClassLoader"));
assertEquals("PCL[xyz]", DexoptUtils.encodeClassLoader("xyz",
@@ -435,15 +427,8 @@
@Test
public void testEncodeClassLoaderChain() {
- assertEquals(SKIP_SHARED_LIBRARY_CHECK, DexoptUtils.encodeClassLoaderChain(
- SKIP_SHARED_LIBRARY_CHECK, "PCL[a]"));
- assertEquals(SKIP_SHARED_LIBRARY_CHECK, DexoptUtils.encodeClassLoaderChain("PCL[a]",
- SKIP_SHARED_LIBRARY_CHECK));
assertEquals("PCL[a];DLC[b]", DexoptUtils.encodeClassLoaderChain("PCL[a]",
"DLC[b]"));
- assertEquals(SKIP_SHARED_LIBRARY_CHECK, DexoptUtils.encodeClassLoaderChain("PCL[a]",
- SKIP_SHARED_LIBRARY_CHECK));
-
try {
DexoptUtils.encodeClassLoaderChain("a", null);
fail(); // exception is expected
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index 561c61f..0dff03f 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -47,6 +47,7 @@
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.power.PowerManagerService.Injector;
import com.android.server.power.PowerManagerService.NativeWrapper;
+import com.android.server.power.batterysaver.BatterySaverPolicy;
import com.android.server.power.batterysaver.BatterySavingStats;
import org.junit.Rule;
diff --git a/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
similarity index 94%
rename from services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
rename to services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
index acf511e..73eefcf 100644
--- a/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.server.power;
+package com.android.server.power.batterysaver;
import static com.google.common.truth.Truth.assertThat;
@@ -31,13 +31,12 @@
import com.android.frameworks.servicestests.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
-import com.android.server.power.batterysaver.BatterySavingStats;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
/**
- * Tests for {@link com.android.server.power.BatterySaverPolicy}
+ * Tests for {@link com.android.server.power.batterysaver.BatterySaverPolicy}
*/
public class BatterySaverPolicyTest extends AndroidTestCase {
private static final boolean BATTERY_SAVER_ON = true;
@@ -62,7 +61,7 @@
private static final String BATTERY_SAVER_INCORRECT_CONSTANTS = "vi*,!=,,true";
private class BatterySaverPolicyForTest extends BatterySaverPolicy {
- public BatterySaverPolicyForTest(Object lock, Context context,
+ BatterySaverPolicyForTest(Object lock, Context context,
BatterySavingStats batterySavingStats) {
super(lock, context, batterySavingStats);
}
@@ -269,15 +268,15 @@
mBatterySaverPolicy.onChange();
assertThat(mBatterySaverPolicy.getFileValues(true).toString()).isEqualTo("{}");
assertThat(mBatterySaverPolicy.getFileValues(false).toString())
- .isEqualTo("{/sys/devices/system/cpu/cpu1/cpufreq/scaling_max_freq=123, " +
- "/sys/devices/system/cpu/cpu2/cpufreq/scaling_max_freq=456}");
+ .isEqualTo("{/sys/devices/system/cpu/cpu1/cpufreq/scaling_max_freq=123, "
+ + "/sys/devices/system/cpu/cpu2/cpufreq/scaling_max_freq=456}");
mDeviceSpecificConfigResId = R.string.config_batterySaverDeviceSpecificConfig_3;
mBatterySaverPolicy.onChange();
assertThat(mBatterySaverPolicy.getFileValues(true).toString())
- .isEqualTo("{/sys/devices/system/cpu/cpu3/cpufreq/scaling_max_freq=333, " +
- "/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq=444}");
+ .isEqualTo("{/sys/devices/system/cpu/cpu3/cpufreq/scaling_max_freq=333, "
+ + "/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq=444}");
assertThat(mBatterySaverPolicy.getFileValues(false).toString())
.isEqualTo("{/sys/devices/system/cpu/cpu2/cpufreq/scaling_max_freq=222}");
@@ -287,9 +286,9 @@
mBatterySaverPolicy.onChange();
assertThat(mBatterySaverPolicy.getFileValues(true).toString())
- .isEqualTo("{/sys/devices/system/cpu/cpu3/cpufreq/scaling_max_freq=1234567890, " +
- "/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq=14, " +
- "/sys/devices/system/cpu/cpu5/cpufreq/scaling_max_freq=15}");
+ .isEqualTo("{/sys/devices/system/cpu/cpu3/cpufreq/scaling_max_freq=1234567890, "
+ + "/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq=14, "
+ + "/sys/devices/system/cpu/cpu5/cpufreq/scaling_max_freq=15}");
assertThat(mBatterySaverPolicy.getFileValues(false).toString()).isEqualTo("{}");
}
}
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java
index 8496a96..b348aee 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java
@@ -699,10 +699,52 @@
assertTrue(hasUsageSessionObserver(UID, OBS_ID1));
}
+ /** Verify the timeout message is delivered at the right time after past usage was reported */
+ @Test
+ public void testAppUsageObserver_PastUsage() throws Exception {
+ setTime(10_000L);
+ addAppUsageObserver(OBS_ID1, GROUP1, 6_000L);
+ setTime(20_000L);
+ startPastUsage(PKG_SOC1, 5_000);
+ setTime(21_000L);
+ assertTrue(mLimitReachedLatch.await(2_000L, TimeUnit.MILLISECONDS));
+ stopUsage(PKG_SOC1);
+ // Verify that the observer was removed
+ assertFalse(hasAppUsageObserver(UID, OBS_ID1));
+ }
+
+ /**
+ * Verify the timeout message is delivered at the right time after past usage was reported
+ * that overlaps with already known usage
+ */
+ @Test
+ public void testAppUsageObserver_PastUsageOverlap() throws Exception {
+ setTime(0L);
+ addAppUsageObserver(OBS_ID1, GROUP1, 20_000L);
+ setTime(10_000L);
+ startUsage(PKG_SOC1);
+ setTime(20_000L);
+ stopUsage(PKG_SOC1);
+ setTime(25_000L);
+ startPastUsage(PKG_SOC1, 9_000);
+ setTime(26_000L);
+ // the 4 seconds of overlapped usage should not be counted
+ assertFalse(mLimitReachedLatch.await(2_000L, TimeUnit.MILLISECONDS));
+ setTime(30_000L);
+ assertTrue(mLimitReachedLatch.await(4_000L, TimeUnit.MILLISECONDS));
+ stopUsage(PKG_SOC1);
+ // Verify that the observer was removed
+ assertFalse(hasAppUsageObserver(UID, OBS_ID1));
+ }
+
private void startUsage(String packageName) {
mController.noteUsageStart(packageName, USER_ID);
}
+ private void startPastUsage(String packageName, int timeAgo) {
+ mController.noteUsageStart(packageName, USER_ID, timeAgo);
+ }
+
private void stopUsage(String packageName) {
mController.noteUsageStop(packageName, USER_ID);
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index 8b65e76..20f72bf 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -613,7 +613,7 @@
}
@Test
- public void testGetAllowedPackages() throws Exception {
+ public void testGetAllowedPackages_byUser() throws Exception {
for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
mIpm, approvalLevel);
@@ -681,6 +681,30 @@
}
@Test
+ public void testGetAllowedPackages() throws Exception {
+ ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
+ mIpm, APPROVAL_BY_COMPONENT);
+ loadXml(service);
+ service.mApprovalLevel = APPROVAL_BY_PACKAGE;
+ loadXml(service);
+
+ List<String> allowedPackages = new ArrayList<>();
+ allowedPackages.add("this.is.a.package.name");
+ allowedPackages.add("another.package");
+ allowedPackages.add("secondary");
+ allowedPackages.add("this.is.another.package");
+ allowedPackages.add("package");
+ allowedPackages.add("component");
+ allowedPackages.add("bananas!");
+
+ Set<String> actual = service.getAllowedPackages();
+ assertEquals(allowedPackages.size(), actual.size());
+ for (String pkg : allowedPackages) {
+ assertTrue(actual.contains(pkg));
+ }
+ }
+
+ @Test
public void testOnUserRemoved() throws Exception {
for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index 65e640f..1458266 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -825,4 +825,24 @@
assertNotEquals(-1, record.getLastAudiblyAlertedMs());
}
+
+ @Test
+ public void testIsNewEnoughForAlerting_new() {
+ StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
+ true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
+ false /* lights */, false /* defaultLights */, groupId /* group */);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+ assertTrue(record.isNewEnoughForAlerting(record.mUpdateTimeMs));
+ }
+
+ @Test
+ public void testIsNewEnoughForAlerting_old() {
+ StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
+ true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
+ false /* lights */, false /* defaultLights */, groupId /* group */);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+ assertFalse(record.isNewEnoughForAlerting(record.mUpdateTimeMs + (1000 * 60 * 60)));
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
index 49f134f..174c5fa 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
@@ -299,6 +299,59 @@
assertEquals(1, mSnoozeHelper.getSnoozed(UserHandle.USER_SYSTEM, "pkg").size());
}
+ @Test
+ public void testClearData() {
+ // snooze 2 from same package
+ NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
+ NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM);
+ mSnoozeHelper.snooze(r, 1000);
+ mSnoozeHelper.snooze(r2, 1000);
+ assertTrue(mSnoozeHelper.isSnoozed(
+ UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey()));
+ assertTrue(mSnoozeHelper.isSnoozed(
+ UserHandle.USER_SYSTEM, r2.sbn.getPackageName(), r2.getKey()));
+
+ // clear data
+ mSnoozeHelper.clearData(UserHandle.USER_SYSTEM, "pkg");
+
+ // nothing snoozed; alarms canceled
+ assertFalse(mSnoozeHelper.isSnoozed(
+ UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey()));
+ assertFalse(mSnoozeHelper.isSnoozed(
+ UserHandle.USER_SYSTEM, r2.sbn.getPackageName(), r2.getKey()));
+ // twice for initial snooze, twice for canceling the snooze
+ verify(mAm, times(4)).cancel(any(PendingIntent.class));
+ }
+
+ @Test
+ public void testClearData_otherRecordsUntouched() {
+ // 2 packages, 2 users
+ NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
+ NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.ALL);
+ NotificationRecord r3 = getNotificationRecord("pkg2", 3, "three", UserHandle.SYSTEM);
+ mSnoozeHelper.snooze(r, 1000);
+ mSnoozeHelper.snooze(r2, 1000);
+ mSnoozeHelper.snooze(r3, 1000);
+ assertTrue(mSnoozeHelper.isSnoozed(
+ UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey()));
+ assertTrue(mSnoozeHelper.isSnoozed(
+ UserHandle.USER_ALL, r2.sbn.getPackageName(), r2.getKey()));
+ assertTrue(mSnoozeHelper.isSnoozed(
+ UserHandle.USER_SYSTEM, r3.sbn.getPackageName(), r3.getKey()));
+
+ // clear data
+ mSnoozeHelper.clearData(UserHandle.USER_SYSTEM, "pkg");
+
+ assertFalse(mSnoozeHelper.isSnoozed(
+ UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey()));
+ assertTrue(mSnoozeHelper.isSnoozed(
+ UserHandle.USER_ALL, r2.sbn.getPackageName(), r2.getKey()));
+ assertTrue(mSnoozeHelper.isSnoozed(
+ UserHandle.USER_SYSTEM, r3.sbn.getPackageName(), r3.getKey()));
+ // once for each initial snooze, once for canceling one snooze
+ verify(mAm, times(4)).cancel(any(PendingIntent.class));
+ }
+
private NotificationRecord getNotificationRecord(String pkg, int id, String tag,
UserHandle user, String groupKey, boolean groupSummary) {
Notification n = new Notification.Builder(getContext(), TEST_CHANNEL_ID)
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index b6f1817..8f9d2ba 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -20,6 +20,9 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+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.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_BOTTOM;
import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_LEFT;
@@ -38,6 +41,7 @@
import android.app.ActivityOptions;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.PauseActivityItem;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.util.MutableBoolean;
@@ -210,4 +214,41 @@
assertNull(mActivity.pendingOptions);
assertNotNull(activity2.pendingOptions);
}
+
+ @Test
+ public void testNewOverrideConfigurationIncrementsSeq() {
+ final Configuration newConfig = new Configuration();
+
+ final int prevSeq = mActivity.getMergedOverrideConfiguration().seq;
+ mActivity.onRequestedOverrideConfigurationChanged(newConfig);
+ assertEquals(prevSeq + 1, mActivity.getMergedOverrideConfiguration().seq);
+ }
+
+ @Test
+ public void testNewParentConfigurationIncrementsSeq() {
+ final Configuration newConfig = new Configuration(
+ mTask.getRequestedOverrideConfiguration());
+ newConfig.orientation = newConfig.orientation == Configuration.ORIENTATION_PORTRAIT
+ ? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT;
+
+ final int prevSeq = mActivity.getMergedOverrideConfiguration().seq;
+ mTask.onRequestedOverrideConfigurationChanged(newConfig);
+ assertEquals(prevSeq + 1, mActivity.getMergedOverrideConfiguration().seq);
+ }
+
+ @Test
+ public void testNotifiesSeqIncrementToAppToken() {
+ final Configuration appWindowTokenRequestedOrientation = mock(Configuration.class);
+ mActivity.mAppWindowToken = mock(AppWindowToken.class);
+ doReturn(appWindowTokenRequestedOrientation).when(mActivity.mAppWindowToken)
+ .getRequestedOverrideConfiguration();
+
+ final Configuration newConfig = new Configuration();
+ newConfig.orientation = Configuration.ORIENTATION_PORTRAIT;
+
+ final int prevSeq = mActivity.getMergedOverrideConfiguration().seq;
+ mActivity.onRequestedOverrideConfigurationChanged(newConfig);
+ assertEquals(prevSeq + 1, appWindowTokenRequestedOrientation.seq);
+ verify(mActivity.mAppWindowToken).onMergedOverrideConfigurationChanged();
+ }
}
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 569c6d4..5589ca1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
@@ -31,6 +31,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyString;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
@@ -38,6 +39,9 @@
import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_DESTROYING;
import static com.android.server.wm.ActivityStackSupervisor.ON_TOP;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doCallRealMethod;
+
import android.app.ActivityManagerInternal;
import android.app.ActivityOptions;
import android.app.IApplicationThread;
@@ -62,7 +66,7 @@
import android.view.DisplayInfo;
import com.android.internal.app.IVoiceInteractor;
-import com.android.server.AppOpsService;
+import com.android.server.appop.AppOpsService;
import com.android.server.AttributeCache;
import com.android.server.ServiceThread;
import com.android.server.am.ActivityManagerService;
@@ -115,6 +119,10 @@
@After
public void tearDownBase() {
mTestInjector.tearDown();
+ if (mService != null) {
+ mService.setWindowManager(null);
+ mService = null;
+ }
}
ActivityTaskManagerService createActivityTaskManagerService() {
@@ -144,6 +152,13 @@
return display;
}
+ /** Creates and adds a {@link TestActivityDisplay} to supervisor at the given position. */
+ TestActivityDisplay addNewActivityDisplayAt(DisplayInfo info, int position) {
+ final TestActivityDisplay display = createNewActivityDisplay(info);
+ mRootActivityContainer.addChild(display, position);
+ return display;
+ }
+
/**
* Builder for creating new activities.
*/
@@ -154,6 +169,7 @@
private final ActivityTaskManagerService mService;
private ComponentName mComponent;
+ private String mTargetActivity;
private TaskRecord mTaskRecord;
private int mUid;
private boolean mCreateTask;
@@ -170,6 +186,11 @@
return this;
}
+ ActivityBuilder setTargetActivity(String targetActivity) {
+ mTargetActivity = targetActivity;
+ return this;
+ }
+
static ComponentName getDefaultComponent() {
return ComponentName.createRelative(DEFAULT_COMPONENT_PACKAGE_NAME,
DEFAULT_COMPONENT_PACKAGE_NAME);
@@ -224,6 +245,10 @@
aInfo.applicationInfo = new ApplicationInfo();
aInfo.applicationInfo.packageName = mComponent.getPackageName();
aInfo.applicationInfo.uid = mUid;
+ aInfo.packageName = mComponent.getPackageName();
+ if (mTargetActivity != null) {
+ aInfo.targetActivity = mTargetActivity;
+ }
aInfo.flags |= mActivityFlags;
aInfo.launchMode = mLaunchMode;
@@ -234,7 +259,13 @@
mService.mStackSupervisor, null /* options */, null /* sourceRecord */);
spyOn(activity);
activity.mAppWindowToken = mock(AppWindowToken.class);
+ doCallRealMethod().when(activity.mAppWindowToken).getOrientationIgnoreVisibility();
+ doCallRealMethod().when(activity.mAppWindowToken)
+ .setOrientation(anyInt(), any(), any());
+ doCallRealMethod().when(activity.mAppWindowToken).setOrientation(anyInt());
doNothing().when(activity).removeWindowContainer();
+ doReturn(mock(Configuration.class)).when(activity.mAppWindowToken)
+ .getRequestedOverrideConfiguration();
if (mTaskRecord != null) {
mTaskRecord.addActivityToTop(activity);
@@ -346,6 +377,7 @@
mStack.addTask(task, true, "creating test task");
task.setStack(mStack);
task.setTask();
+ mStack.getWindowContainerController().mContainer.addChild(task.mTask, 0);
}
task.touchActiveTime();
@@ -365,7 +397,10 @@
setTask();
}
- private void setTask() {
+ void setTask() {
+ Task mockTask = mock(Task.class);
+ mockTask.mTaskRecord = this;
+ doCallRealMethod().when(mockTask).onDescendantOrientationChanged(any(), any());
setTask(mock(Task.class));
}
}
@@ -619,7 +654,13 @@
}
}
+ private static WindowManagerService sMockWindowManagerService;
+
private static WindowManagerService prepareMockWindowManager() {
+ if (sMockWindowManagerService != null) {
+ return sMockWindowManagerService;
+ }
+
final WindowManagerService service = mock(WindowManagerService.class);
service.mRoot = mock(RootWindowContainer.class);
@@ -631,6 +672,7 @@
return null;
}).when(service).inSurfaceTransaction(any());
+ sMockWindowManagerService = service;
return service;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java
index dcfb879..d0b9225 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java
@@ -87,8 +87,8 @@
verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture());
callbackCaptor.getValue().onAnimationFinished(mSpec);
- verify(mTransaction).destroy(eq(leash));
- verify(mTransaction).destroy(eq(animationBoundsLayer));
+ verify(mTransaction).reparent(eq(leash), eq(null));
+ verify(mTransaction).reparent(eq(animationBoundsLayer), eq(null));
assertThat(mToken.mNeedsAnimationBoundsLayer).isFalse();
}
@@ -100,8 +100,8 @@
final SurfaceControl animationBoundsLayer = mToken.mAnimationBoundsLayer;
mToken.mSurfaceAnimator.cancelAnimation();
- verify(mTransaction).destroy(eq(leash));
- verify(mTransaction).destroy(eq(animationBoundsLayer));
+ verify(mTransaction).reparent(eq(leash), eq(null));
+ verify(mTransaction).reparent(eq(animationBoundsLayer), eq(null));
assertThat(mToken.mNeedsAnimationBoundsLayer).isFalse();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
index ee1c8df..f99cd4b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
@@ -212,7 +212,7 @@
mDimmer.updateDims(mTransaction, new Rect());
verify(mSurfaceAnimatorStarter).startAnimation(any(SurfaceAnimator.class), any(
SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean());
- verify(mHost.getPendingTransaction()).destroy(dimLayer);
+ verify(mHost.getPendingTransaction()).reparent(dimLayer, null);
}
@Test
@@ -269,7 +269,7 @@
mDimmer.updateDims(mTransaction, new Rect());
verify(mSurfaceAnimatorStarter, never()).startAnimation(any(SurfaceAnimator.class), any(
SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean());
- verify(mTransaction).destroy(dimLayer);
+ verify(mTransaction).reparent(dimLayer, null);
}
private SurfaceControl getDimLayer() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index 6b31e6f..198e7ce 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -63,6 +63,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
@@ -86,7 +87,7 @@
private StatusBarManagerInternal mPreviousStatusBarManagerInternal;
- private WindowManagerService mMockWm;
+ private static WindowManagerService sMockWm;
private DisplayContent mMockDisplayContent;
private DisplayPolicy mMockDisplayPolicy;
private Context mMockContext;
@@ -108,13 +109,16 @@
private DisplayRotation mTarget;
+ @BeforeClass
+ public static void setUpOnce() {
+ sMockWm = mock(WindowManagerService.class);
+ sMockWm.mPowerManagerInternal = mock(PowerManagerInternal.class);
+ }
+
@Before
public void setUp() {
FakeSettingsProvider.clearSettingsProvider();
- mMockWm = mock(WindowManagerService.class);
- mMockWm.mPowerManagerInternal = mock(PowerManagerInternal.class);
-
mPreviousStatusBarManagerInternal = LocalServices.getService(
StatusBarManagerInternal.class);
LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
@@ -452,7 +456,7 @@
mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_90));
assertTrue(waitForUiHandler());
- verify(mMockWm).updateRotation(false, false);
+ verify(sMockWm).updateRotation(false, false);
}
@Test
@@ -833,8 +837,9 @@
.thenReturn(mFakeSettingsProvider.getIContentProvider());
mMockDisplayWindowSettings = mock(DisplayWindowSettings.class);
- mTarget = new DisplayRotation(mMockWm, mMockDisplayContent, mMockDisplayPolicy,
+ mTarget = new DisplayRotation(sMockWm, mMockDisplayContent, mMockDisplayPolicy,
mMockDisplayWindowSettings, mMockContext, new Object());
+ reset(sMockWm);
captureObservers();
}
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 f1c6eab..bb3ab35 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -20,11 +20,9 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
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.spy;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -125,7 +123,6 @@
mDisplayContent = spy(mDisplayContent);
mWindow = createDropTargetWindow("Drag test window", 0);
doReturn(mWindow).when(mDisplayContent).getTouchableWinAtPointLocked(0, 0);
- when(mWm.mInputManager.transferTouchFocus(any(), any())).thenReturn(true);
synchronized (mWm.mGlobalLock) {
mWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow);
@@ -176,7 +173,6 @@
.setFormat(PixelFormat.TRANSLUCENT)
.build();
- assertTrue(mWm.mInputManager.transferTouchFocus(null, null));
mToken = mTarget.performDrag(
new SurfaceSession(), 0, 0, mWindow.mClient, flag, surface, 0, 0, 0, 0, 0,
data);
diff --git a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
index a9f150b..e24eb75 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
@@ -34,7 +34,12 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyString;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.isNull;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
@@ -47,10 +52,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
import android.app.StatusBarManager;
import android.app.admin.DevicePolicyManager;
@@ -115,6 +116,7 @@
private LockTaskController mLockTaskController;
private Context mContext;
+ private String mPackageName;
private String mLockToAppSetting;
@Before
@@ -122,6 +124,7 @@
MockitoAnnotations.initMocks(this);
mContext = getInstrumentation().getTargetContext();
+ mPackageName = mContext.getPackageName();
mLockToAppSetting = Settings.Secure.getString(mContext.getContentResolver(),
Settings.Secure.LOCK_TO_APP_EXIT_LOCKED);
@@ -146,6 +149,7 @@
@After
public void tearDown() throws Exception {
+ mLockTaskController.setWindowManager(null);
Settings.Secure.putString(mContext.getContentResolver(),
Settings.Secure.LOCK_TO_APP_EXIT_LOCKED, mLockToAppSetting);
}
@@ -223,7 +227,7 @@
// THEN lock task mode should be started
verifyLockTaskStarted(STATUS_BAR_MASK_PINNED, DISABLE2_NONE);
// THEN screen pinning toast should be shown
- verify(mStatusBarService).showPinningEnterExitToast(true /* entering */);
+ verify(mStatusBarService).showPinningEnterExitToast(eq(true /* entering */));
}
@Test
@@ -390,9 +394,9 @@
// THEN lock task mode should have been finished
verifyLockTaskStopped(times(1));
// THEN the keyguard should be shown
- verify(mLockPatternUtils).requireCredentialEntry(UserHandle.USER_ALL);
+ verify(mLockPatternUtils).requireCredentialEntry(eq(UserHandle.USER_ALL));
// THEN screen pinning toast should be shown
- verify(mStatusBarService).showPinningEnterExitToast(false /* entering */);
+ verify(mStatusBarService).showPinningEnterExitToast(eq(false /* entering */));
}
@Test
@@ -509,9 +513,9 @@
& ~DISABLE_HOME;
int expectedFlags2 = DISABLE2_MASK;
verify(mStatusBarService).disable(eq(expectedFlags), any(IBinder.class),
- eq(mContext.getPackageName()));
+ eq(mPackageName));
verify(mStatusBarService).disable2(eq(expectedFlags2), any(IBinder.class),
- eq(mContext.getPackageName()));
+ eq(mPackageName));
// reset invocation counter
reset(mStatusBarService);
@@ -526,9 +530,9 @@
expectedFlags2 = DISABLE2_MASK
& ~DISABLE2_NOTIFICATION_SHADE;
verify(mStatusBarService).disable(eq(expectedFlags), any(IBinder.class),
- eq(mContext.getPackageName()));
+ eq(mPackageName));
verify(mStatusBarService).disable2(eq(expectedFlags2), any(IBinder.class),
- eq(mContext.getPackageName()));
+ eq(mPackageName));
}
@Test
@@ -548,9 +552,9 @@
// THEN status bar shouldn't change
verify(mStatusBarService, never()).disable(anyInt(), any(IBinder.class),
- eq(mContext.getPackageName()));
+ eq(mPackageName));
verify(mStatusBarService, never()).disable2(anyInt(), any(IBinder.class),
- eq(mContext.getPackageName()));
+ eq(mPackageName));
}
@Test
@@ -657,14 +661,14 @@
verify(mWindowManager).disableKeyguard(any(IBinder.class), anyString(), eq(TEST_USER_ID));
// THEN the status bar should have been disabled
verify(mStatusBarService).disable(eq(statusBarMask), any(IBinder.class),
- eq(mContext.getPackageName()));
+ eq(mPackageName));
verify(mStatusBarService).disable2(eq(statusBarMask2), any(IBinder.class),
- eq(mContext.getPackageName()));
+ eq(mPackageName));
// THEN recents should have been notified
verify(mRecentTasks).onLockTaskModeStateChanged(anyInt(), eq(TEST_USER_ID));
// THEN the DO/PO should be informed about the operation
- verify(mDevicePolicyManager).notifyLockTaskModeChanged(true, TEST_PACKAGE_NAME,
- TEST_USER_ID);
+ verify(mDevicePolicyManager).notifyLockTaskModeChanged(eq(true), eq(TEST_PACKAGE_NAME),
+ eq(TEST_USER_ID));
}
private void verifyLockTaskStopped(VerificationMode mode) throws Exception {
@@ -672,11 +676,12 @@
verify(mWindowManager, mode).reenableKeyguard(any(IBinder.class), eq(TEST_USER_ID));
// THEN the status bar should have been disabled
verify(mStatusBarService, mode).disable(eq(StatusBarManager.DISABLE_NONE),
- any(IBinder.class), eq(mContext.getPackageName()));
+ any(IBinder.class), eq(mPackageName));
verify(mStatusBarService, mode).disable2(eq(StatusBarManager.DISABLE2_NONE),
- any(IBinder.class), eq(mContext.getPackageName()));
+ any(IBinder.class), eq(mPackageName));
// THEN the DO/PO should be informed about the operation
- verify(mDevicePolicyManager, mode).notifyLockTaskModeChanged(false, null, TEST_USER_ID);
+ verify(mDevicePolicyManager, mode).notifyLockTaskModeChanged(eq(false), isNull(),
+ eq(TEST_USER_ID));
}
/**
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index ad2a708..413b6f4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -143,8 +143,8 @@
@Test
public void testTimeout_scaled() throws Exception {
- mWm.setAnimationScale(2, 5.0f);
try {
+ mWm.setAnimationScale(2, 5.0f);
final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION,
"testWin");
final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken,
diff --git a/services/tests/wmtests/src/com/android/server/wm/ScreenDecorWindowTests.java b/services/tests/wmtests/src/com/android/server/wm/ScreenDecorWindowTests.java
index 36eccd1..03aba39 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ScreenDecorWindowTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ScreenDecorWindowTests.java
@@ -33,6 +33,8 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
@@ -58,7 +60,6 @@
import android.view.WindowManager;
import android.widget.TextView;
-import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import org.junit.After;
@@ -80,8 +81,8 @@
@Presubmit
public class ScreenDecorWindowTests {
- private final Context mContext = InstrumentationRegistry.getTargetContext();
- private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ private final Context mContext = getInstrumentation().getTargetContext();
+ private final Instrumentation mInstrumentation = getInstrumentation();
private WindowManager mWm;
private ArrayList<View> mWindows = new ArrayList<>();
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
index d14f30d..ad80cd6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
@@ -96,7 +96,7 @@
callbackCaptor.getValue().onAnimationFinished(mSpec);
assertNotAnimating(mAnimatable);
assertTrue(mAnimatable.mFinishedCallbackCalled);
- verify(mTransaction).destroy(eq(mAnimatable.mLeash));
+ verify(mTransaction).reparent(eq(mAnimatable.mLeash), eq(null));
// TODO: Verify reparenting once we use mPendingTransaction to reparent it back
}
@@ -106,7 +106,7 @@
final SurfaceControl firstLeash = mAnimatable.mLeash;
mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec2, true /* hidden */);
- verify(mTransaction).destroy(eq(firstLeash));
+ verify(mTransaction).reparent(eq(firstLeash), eq(null));
assertFalse(mAnimatable.mFinishedCallbackCalled);
final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass(
@@ -133,7 +133,7 @@
assertNotAnimating(mAnimatable);
verify(mSpec).onAnimationCancelled(any());
assertTrue(mAnimatable.mFinishedCallbackCalled);
- verify(mTransaction).destroy(eq(mAnimatable.mLeash));
+ verify(mTransaction).reparent(eq(mAnimatable.mLeash), eq(null));
}
@Test
@@ -155,7 +155,7 @@
verifyZeroInteractions(mSpec);
assertNotAnimating(mAnimatable);
assertTrue(mAnimatable.mFinishedCallbackCalled);
- verify(mTransaction).destroy(eq(mAnimatable.mLeash));
+ verify(mTransaction).reparent(eq(mAnimatable.mLeash), eq(null));
}
@Test
@@ -171,11 +171,11 @@
assertNotAnimating(mAnimatable);
assertAnimating(mAnimatable2);
assertEquals(leash, mAnimatable2.mSurfaceAnimator.mLeash);
- verify(mTransaction, never()).destroy(eq(leash));
+ verify(mTransaction, never()).reparent(eq(leash), eq(null));
callbackCaptor.getValue().onAnimationFinished(mSpec);
assertNotAnimating(mAnimatable2);
assertTrue(mAnimatable2.mFinishedCallbackCalled);
- verify(mTransaction).destroy(eq(leash));
+ verify(mTransaction).reparent(eq(leash), eq(null));
}
@Test
@@ -198,7 +198,7 @@
mDeferFinishAnimatable.mEndDeferFinishCallback.run();
assertNotAnimating(mAnimatable2);
assertTrue(mDeferFinishAnimatable.mFinishedCallbackCalled);
- verify(mTransaction).destroy(eq(mDeferFinishAnimatable.mLeash));
+ verify(mTransaction).reparent(eq(mDeferFinishAnimatable.mLeash), eq(null));
}
private void assertAnimating(MyAnimatable animatable) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
index c343fe7..8c6ac23 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
@@ -18,7 +18,6 @@
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
@@ -58,10 +57,6 @@
assertNotNull(mWm.mTaskPositioningController);
mTarget = mWm.mTaskPositioningController;
- when(mWm.mInputManager.transferTouchFocus(
- any(InputChannel.class),
- any(InputChannel.class))).thenReturn(true);
-
mWindow = createWindow(null, TYPE_BASE_APPLICATION, "window");
mWindow.mInputChannel = new InputChannel();
synchronized (mWm.mGlobalLock) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
index 7da85af..8a98cbe 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
@@ -21,6 +21,11 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+
+import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.sameInstance;
@@ -133,17 +138,6 @@
assertTrue(task.returnsToHomeStack());
}
- /** Ensures that bounds are clipped to their parent. */
- @Test
- public void testAppBounds_BoundsClipping() {
- final Rect shiftedBounds = new Rect(mParentBounds);
- shiftedBounds.offset(10, 10);
- final Rect expectedBounds = new Rect(mParentBounds);
- expectedBounds.intersect(shiftedBounds);
- testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, shiftedBounds,
- expectedBounds);
- }
-
/** Ensures that empty bounds are not propagated to the configuration. */
@Test
public void testAppBounds_EmptyBounds() {
@@ -167,36 +161,145 @@
final Rect insetBounds = new Rect(mParentBounds);
insetBounds.inset(5, 5, 5, 5);
testStackBoundsConfiguration(
- WINDOWING_MODE_FULLSCREEN, mParentBounds, insetBounds, insetBounds);
+ WINDOWING_MODE_FREEFORM, mParentBounds, insetBounds, insetBounds);
}
- /** Ensures that full screen free form bounds are clipped */
+ /** Tests that the task bounds adjust properly to changes between FULLSCREEN and FREEFORM */
@Test
- public void testAppBounds_FullScreenFreeFormBounds() {
+ public void testBoundsOnModeChangeFreeformToFullscreen() {
ActivityDisplay display = mService.mRootActivityContainer.getDefaultDisplay();
+ ActivityStack stack = new StackBuilder(mRootActivityContainer).setDisplay(display)
+ .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ TaskRecord task = stack.getChildAt(0);
+ task.getRootActivity().mAppWindowToken.setOrientation(SCREEN_ORIENTATION_UNSPECIFIED);
DisplayInfo info = new DisplayInfo();
display.mDisplay.getDisplayInfo(info);
final Rect fullScreenBounds = new Rect(0, 0, info.logicalWidth, info.logicalHeight);
- testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, fullScreenBounds,
- mParentBounds);
+ final Rect freeformBounds = new Rect(fullScreenBounds);
+ freeformBounds.inset((int) (freeformBounds.width() * 0.2),
+ (int) (freeformBounds.height() * 0.2));
+ task.setBounds(freeformBounds);
+
+ assertEquals(freeformBounds, task.getBounds());
+
+ // FULLSCREEN inherits bounds
+ stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ assertEquals(fullScreenBounds, task.getBounds());
+ assertEquals(freeformBounds, task.mLastNonFullscreenBounds);
+
+ // FREEFORM restores bounds
+ stack.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ assertEquals(freeformBounds, task.getBounds());
+ }
+
+ /**
+ * This is a temporary hack to trigger an onConfigurationChange at the task level after an
+ * orientation is requested. Normally this is done by the onDescendentOrientationChanged call
+ * up the WM hierarchy, but since the WM hierarchy is mocked out, it doesn't happen here.
+ * TODO: remove this when we either get a WM hierarchy or when hierarchies are merged.
+ */
+ private void setActivityRequestedOrientation(ActivityRecord activity, int orientation) {
+ activity.setRequestedOrientation(orientation);
+ ConfigurationContainer taskRecord = activity.getParent();
+ taskRecord.onConfigurationChanged(taskRecord.getParent().getConfiguration());
+ }
+
+ /**
+ * Tests that a task with forced orientation has orientation-consistent bounds within the
+ * parent.
+ */
+ @Test
+ public void testFullscreenBoundsForcedOrientation() {
+ final Rect fullScreenBounds = new Rect(0, 0, 1920, 1080);
+ final Rect fullScreenBoundsPort = new Rect(0, 0, 1080, 1920);
+ DisplayInfo info = new DisplayInfo();
+ info.logicalWidth = fullScreenBounds.width();
+ info.logicalHeight = fullScreenBounds.height();
+ ActivityDisplay display = addNewActivityDisplayAt(info, POSITION_TOP);
+ assertTrue(mRootActivityContainer.getActivityDisplay(display.mDisplayId) != null);
+ ActivityStack stack = new StackBuilder(mRootActivityContainer)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build();
+ TaskRecord task = stack.getChildAt(0);
+ ActivityRecord root = task.getRootActivity();
+ ActivityRecord top = new ActivityBuilder(mService).setTask(task).setStack(stack).build();
+ assertEquals(root, task.getRootActivity());
+
+ assertEquals(fullScreenBounds, task.getBounds());
+
+ // Setting app to fixed portrait fits within parent
+ setActivityRequestedOrientation(root, SCREEN_ORIENTATION_PORTRAIT);
+ assertEquals(root, task.getRootActivity());
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, task.getRootActivity().getOrientation());
+ assertTrue(task.getBounds().width() < task.getBounds().height());
+ assertEquals(fullScreenBounds.height(), task.getBounds().height());
+
+ // Setting non-root app has no effect
+ setActivityRequestedOrientation(root, SCREEN_ORIENTATION_LANDSCAPE);
+ assertTrue(task.getBounds().width() < task.getBounds().height());
+
+ // Setting app to unspecified restores
+ setActivityRequestedOrientation(root, SCREEN_ORIENTATION_UNSPECIFIED);
+ assertEquals(fullScreenBounds, task.getBounds());
+
+ // Setting app to fixed landscape and changing display
+ setActivityRequestedOrientation(root, SCREEN_ORIENTATION_LANDSCAPE);
+ display.setBounds(fullScreenBoundsPort);
+ assertTrue(task.getBounds().width() > task.getBounds().height());
+ assertEquals(fullScreenBoundsPort.width(), task.getBounds().width());
+
+ // in FREEFORM, no constraint
+ final Rect freeformBounds = new Rect(display.getBounds());
+ freeformBounds.inset((int) (freeformBounds.width() * 0.2),
+ (int) (freeformBounds.height() * 0.2));
+ stack.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ task.setBounds(freeformBounds);
+ assertEquals(freeformBounds, task.getBounds());
+
+ // FULLSCREEN letterboxes bounds
+ stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ assertTrue(task.getBounds().width() > task.getBounds().height());
+ assertEquals(fullScreenBoundsPort.width(), task.getBounds().width());
+
+ // FREEFORM restores bounds as before
+ stack.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ assertEquals(freeformBounds, task.getBounds());
}
/** Ensures that the alias intent won't have target component resolved. */
@Test
public void testTaskIntentActivityAlias() {
- final String aliasActivity = DEFAULT_COMPONENT_PACKAGE_NAME + ".aliasActivity";
- final String targetActivity = DEFAULT_COMPONENT_PACKAGE_NAME + ".targetActivity";
+ final String aliasClassName = DEFAULT_COMPONENT_PACKAGE_NAME + ".aliasActivity";
+ final String targetClassName = DEFAULT_COMPONENT_PACKAGE_NAME + ".targetActivity";
+ final ComponentName aliasComponent =
+ new ComponentName(DEFAULT_COMPONENT_PACKAGE_NAME, aliasClassName);
+ final ComponentName targetComponent =
+ new ComponentName(DEFAULT_COMPONENT_PACKAGE_NAME, targetClassName);
+
final Intent intent = new Intent();
- intent.setComponent(new ComponentName(DEFAULT_COMPONENT_PACKAGE_NAME, aliasActivity));
+ intent.setComponent(aliasComponent);
final ActivityInfo info = new ActivityInfo();
info.applicationInfo = new ApplicationInfo();
info.packageName = DEFAULT_COMPONENT_PACKAGE_NAME;
- info.targetActivity = targetActivity;
+ info.targetActivity = targetClassName;
final TaskRecord task = TaskRecord.create(mService, 1 /* taskId */, info, intent,
null /* taskDescription */);
- assertEquals("The alias activity component should be saved in task intent.", aliasActivity,
+ assertEquals("The alias activity component should be saved in task intent.", aliasClassName,
task.intent.getComponent().getClassName());
+
+ ActivityRecord aliasActivity = new ActivityBuilder(mService).setComponent(
+ aliasComponent).setTargetActivity(targetClassName).build();
+ assertEquals("Should be the same intent filter.", true,
+ task.isSameIntentFilter(aliasActivity));
+
+ ActivityRecord targetActivity = new ActivityBuilder(mService).setComponent(
+ targetComponent).build();
+ assertEquals("Should be the same intent filter.", true,
+ task.isSameIntentFilter(targetActivity));
+
+ ActivityRecord defaultActivity = new ActivityBuilder(mService).build();
+ assertEquals("Should not be the same intent filter.", false,
+ task.isSameIntentFilter(defaultActivity));
}
private void testStackBoundsConfiguration(int windowingMode, Rect parentBounds, Rect bounds,
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
index 624ef9b..ca815ec 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
@@ -39,6 +39,7 @@
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.view.Surface;
+import android.view.SurfaceControl;
import androidx.test.filters.SmallTest;
@@ -65,7 +66,7 @@
final TaskSnapshot snapshot = new TaskSnapshot(new ComponentName("", ""), buffer,
ORIENTATION_PORTRAIT, contentInsets, false, 1.0f, true /* isRealSnapshot */,
WINDOWING_MODE_FULLSCREEN, 0 /* systemUiVisibility */, false /* isTranslucent */);
- mSurface = new TaskSnapshotSurface(mWm, new Window(), new Surface(), snapshot, "Test",
+ mSurface = new TaskSnapshotSurface(mWm, new Window(), new SurfaceControl(), snapshot, "Test",
Color.WHITE, Color.RED, Color.BLUE, sysuiVis, windowFlags, 0, taskBounds,
ORIENTATION_PORTRAIT);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceRule.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceRule.java
deleted file mode 100644
index 04e433e..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceRule.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.server.wm;
-
-import static android.testing.DexmakerShareClassLoaderRule.runWithDexmakerShareClassLoader;
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.when;
-
-import android.app.ActivityManagerInternal;
-import android.content.Context;
-import android.hardware.display.DisplayManagerInternal;
-import android.os.PowerManagerInternal;
-import android.os.PowerSaveState;
-import android.view.Display;
-import android.view.InputChannel;
-import android.view.SurfaceControl;
-import android.view.SurfaceControl.Transaction;
-
-import com.android.server.LocalServices;
-import com.android.server.input.InputManagerService;
-import com.android.server.policy.WindowManagerPolicy;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-import org.mockito.invocation.InvocationOnMock;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A test rule that sets up a fresh WindowManagerService instance before each test and makes sure
- * to properly tear it down after.
- *
- * <p>
- * Usage:
- * <pre>
- * {@literal @}Rule
- * public final WindowManagerServiceRule mWmRule = new WindowManagerServiceRule();
- * </pre>
- */
-public class WindowManagerServiceRule implements TestRule {
-
- private WindowManagerService mService;
- private TestWindowManagerPolicy mPolicy;
- // Record all {@link SurfaceControl.Transaction} created while testing and releases native
- // resources when test finishes.
- private final List<WeakReference<Transaction>> mSurfaceTransactions = new ArrayList<>();
- // Record all {@link SurfaceControl} created while testing and releases native resources when
- // test finishes.
- private final List<WeakReference<SurfaceControl>> mSurfaceControls = new ArrayList<>();
-
- @Override
- public Statement apply(Statement base, Description description) {
- return new Statement() {
- @Override
- public void evaluate() throws Throwable {
- runWithDexmakerShareClassLoader(this::setUp);
- try {
- base.evaluate();
- } finally {
- tearDown();
- }
- }
-
- private void setUp() {
- final Context context = getInstrumentation().getTargetContext();
-
- removeServices();
-
- LocalServices.addService(DisplayManagerInternal.class,
- mock(DisplayManagerInternal.class));
-
- LocalServices.addService(PowerManagerInternal.class,
- mock(PowerManagerInternal.class));
- final PowerManagerInternal pm =
- LocalServices.getService(PowerManagerInternal.class);
- doNothing().when(pm).registerLowPowerModeObserver(any());
- PowerSaveState state = new PowerSaveState.Builder().build();
- doReturn(state).when(pm).getLowPowerState(anyInt());
-
- LocalServices.addService(ActivityManagerInternal.class,
- mock(ActivityManagerInternal.class));
- LocalServices.addService(ActivityTaskManagerInternal.class,
- mock(ActivityTaskManagerInternal.class));
- final ActivityTaskManagerInternal atm =
- LocalServices.getService(ActivityTaskManagerInternal.class);
- doAnswer((InvocationOnMock invocationOnMock) -> {
- final Runnable runnable = invocationOnMock.<Runnable>getArgument(0);
- if (runnable != null) {
- runnable.run();
- }
- return null;
- }).when(atm).notifyKeyguardFlagsChanged(any(), anyInt());
-
- InputManagerService ims = mock(InputManagerService.class);
- // InputChannel is final and can't be mocked.
- InputChannel[] input = InputChannel.openInputChannelPair(TAG_WM);
- if (input != null && input.length > 1) {
- doReturn(input[1]).when(ims).monitorInput(anyString(), anyInt());
- }
- ActivityTaskManagerService atms = mock(ActivityTaskManagerService.class);
- when(atms.getGlobalLock()).thenReturn(new WindowManagerGlobalLock());
-
- mService = WindowManagerService.main(context, ims, false, false,
- mPolicy = new TestWindowManagerPolicy(
- WindowManagerServiceRule.this::getWindowManagerService), atms);
- mService.mTransactionFactory = () -> {
- final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
- mSurfaceTransactions.add(new WeakReference<>(transaction));
- return transaction;
- };
- mService.mSurfaceBuilderFactory = session -> new SurfaceControl.Builder(session) {
- @Override
- public SurfaceControl build() {
- final SurfaceControl control = super.build();
- mSurfaceControls.add(new WeakReference<>(control));
- return control;
- }
- };
-
- mService.onInitReady();
-
- final Display display = mService.mDisplayManager.getDisplay(DEFAULT_DISPLAY);
- // Display creation is driven by the ActivityManagerService via
- // ActivityStackSupervisor. We emulate those steps here.
- mService.mRoot.createDisplayContent(display, mock(ActivityDisplay.class));
- }
-
- private void removeServices() {
- LocalServices.removeServiceForTest(DisplayManagerInternal.class);
- LocalServices.removeServiceForTest(PowerManagerInternal.class);
- LocalServices.removeServiceForTest(ActivityManagerInternal.class);
- LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
- LocalServices.removeServiceForTest(WindowManagerInternal.class);
- LocalServices.removeServiceForTest(WindowManagerPolicy.class);
- }
-
- private void tearDown() {
- cancelAllPendingAnimations();
- waitUntilWindowManagerHandlersIdle();
- destroyAllSurfaceTransactions();
- destroyAllSurfaceControls();
- removeServices();
- mService = null;
- mPolicy = null;
- }
- };
- }
-
- WindowManagerService getWindowManagerService() {
- return mService;
- }
-
- private void cancelAllPendingAnimations() {
- for (final WeakReference<SurfaceControl> reference : mSurfaceControls) {
- final SurfaceControl sc = reference.get();
- if (sc != null) {
- mService.mSurfaceAnimationRunner.onAnimationCancelled(sc);
- }
- }
- }
-
- void waitUntilWindowManagerHandlersIdle() {
- final WindowManagerService wm = getWindowManagerService();
- if (wm == null) {
- return;
- }
- wm.mH.removeCallbacksAndMessages(null);
- wm.mAnimationHandler.removeCallbacksAndMessages(null);
- SurfaceAnimationThread.getHandler().removeCallbacksAndMessages(null);
- wm.mH.runWithScissors(() -> { }, 0);
- wm.mAnimationHandler.runWithScissors(() -> { }, 0);
- SurfaceAnimationThread.getHandler().runWithScissors(() -> { }, 0);
- }
-
- private void destroyAllSurfaceTransactions() {
- for (final WeakReference<Transaction> reference : mSurfaceTransactions) {
- final Transaction transaction = reference.get();
- if (transaction != null) {
- reference.clear();
- transaction.close();
- }
- }
- }
-
- private void destroyAllSurfaceControls() {
- for (final WeakReference<SurfaceControl> reference : mSurfaceControls) {
- final SurfaceControl control = reference.get();
- if (control != null) {
- reference.clear();
- control.destroy();
- }
- }
- }
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceRuleTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceRuleTest.java
deleted file mode 100644
index 343d359..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceRuleTest.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.server.wm;
-
-import static org.hamcrest.CoreMatchers.notNullValue;
-import static org.junit.Assert.assertThat;
-
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.Rule;
-import org.junit.Test;
-
-/**
- * Build/InstallRun:
- * atest FrameworksServicesTests:WindowManagerServiceRuleTest
- */
-@Presubmit
-@SmallTest
-public class WindowManagerServiceRuleTest {
-
- @Rule
- public final WindowManagerServiceRule mRule = new WindowManagerServiceRule();
-
- @Test
- public void testWindowManagerSetUp() {
- assertThat(mRule.getWindowManagerService(), notNullValue());
- }
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 7f78034..c09cd46 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -19,6 +19,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.hardware.camera2.params.OutputConfiguration.ROTATION_90;
+import static android.view.InsetsState.TYPE_TOP_BAR;
import static android.view.Surface.ROTATION_0;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
@@ -31,6 +32,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
@@ -56,6 +58,7 @@
import android.platform.test.annotations.Presubmit;
import android.util.Size;
import android.view.DisplayCutout;
+import android.view.InsetsSource;
import android.view.SurfaceControl;
import android.view.WindowManager;
@@ -326,6 +329,20 @@
}
@Test
+ public void testVisibleWithInsetsProvider() throws Exception {
+ final WindowState topBar = createWindow(null, TYPE_STATUS_BAR, "topBar");
+ final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ topBar.mHasSurface = true;
+ assertTrue(topBar.isVisible());
+ mDisplayContent.getInsetsStateController().getSourceProvider(TYPE_TOP_BAR)
+ .setWindow(topBar, null);
+ mDisplayContent.getInsetsStateController().onBarControllingWindowChanged(app);
+ mDisplayContent.getInsetsStateController().getSourceProvider(TYPE_TOP_BAR)
+ .onInsetsModified(app, new InsetsSource(TYPE_TOP_BAR));
+ assertFalse(topBar.isVisible());
+ }
+
+ @Test
public void testIsSelfOrAncestorWindowAnimating() {
final WindowState root = createWindow(null, TYPE_APPLICATION, "root");
final WindowState child1 = createWindow(root, FIRST_SUB_WINDOW, "child1");
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 5c3368b..638cb03 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -53,6 +53,7 @@
import com.android.server.AttributeCache;
import org.junit.After;
+import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
@@ -95,17 +96,22 @@
public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
new DexmakerShareClassLoaderRule();
- @Rule
- public final WindowManagerServiceRule mWmRule = new WindowManagerServiceRule();
-
- static WindowState.PowerManagerWrapper sPowerManagerWrapper; // TODO(roosa): make non-static.
+ static WindowState.PowerManagerWrapper sPowerManagerWrapper;
@BeforeClass
public static void setUpOnceBase() {
AttributeCache.init(getInstrumentation().getTargetContext());
+
+ WmServiceUtils.setUpWindowManagerService();
+
sPowerManagerWrapper = mock(WindowState.PowerManagerWrapper.class);
}
+ @AfterClass
+ public static void tearDonwOnceBase() {
+ WmServiceUtils.tearDownWindowManagerService();
+ }
+
@Before
public void setUpBase() {
// If @Before throws an exception, the error isn't logged. This will make sure any failures
@@ -115,7 +121,7 @@
final Context context = getInstrumentation().getTargetContext();
- mWm = mWmRule.getWindowManagerService();
+ mWm = WmServiceUtils.getWindowManagerService();
beforeCreateDisplay();
context.getDisplay().getDisplayInfo(mDisplayInfo);
@@ -192,8 +198,8 @@
mDisplayContent.mInputMethodTarget = null;
}
- // Wait until everything is really cleaned up.
- waitUntilHandlersIdle();
+ // Cleaned up everything in Handler.
+ WmServiceUtils.cleanupWindowManagerHandlers();
} catch (Exception e) {
Log.e(TAG, "Failed to tear down test", e);
throw e;
@@ -214,7 +220,7 @@
* Waits until the main handler for WM has processed all messages.
*/
void waitUntilHandlersIdle() {
- mWmRule.waitUntilWindowManagerHandlersIdle();
+ WmServiceUtils.waitUntilWindowManagerHandlersIdle();
}
private WindowToken createWindowToken(
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTraceBufferTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowTraceBufferTest.java
new file mode 100644
index 0000000..8d83497
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTraceBufferTest.java
@@ -0,0 +1,115 @@
+/*
+ * 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.wm;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER;
+import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_H;
+import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_L;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+import android.util.proto.ProtoOutputStream;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+
+
+/**
+ * Test class for {@link WindowTraceBuffer} and {@link WindowTraceQueueBuffer}.
+ *
+ * Build/Install/Run:
+ * atest WmTests:WindowTraceBufferTest
+ */
+@SmallTest
+@Presubmit
+public class WindowTraceBufferTest {
+ private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
+
+ private File mFile;
+
+ @Before
+ public void setUp() throws Exception {
+ final Context testContext = getInstrumentation().getContext();
+ mFile = testContext.getFileStreamPath("tracing_test.dat");
+ mFile.delete();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mFile.delete();
+ }
+
+ @Test
+ public void testTraceQueueBuffer_addItem() throws Exception {
+ ProtoOutputStream toWrite1 = getDummy(1);
+ ProtoOutputStream toWrite2 = getDummy(2);
+ ProtoOutputStream toWrite3 = getDummy(3);
+ byte[] toWrite1Bytes = toWrite1.getBytes();
+ byte[] toWrite2Bytes = toWrite2.getBytes();
+ byte[] toWrite3Bytes = toWrite3.getBytes();
+
+ final int objectSize = toWrite1.getBytes().length;
+ final int bufferCapacity = objectSize * 2;
+
+ final WindowTraceBuffer buffer = buildQueueBuffer(bufferCapacity);
+
+ buffer.add(toWrite1);
+ assertTrue("First element should be in the list",
+ buffer.mBuffer.stream().anyMatch(p -> Arrays.equals(p, toWrite1Bytes)));
+
+ buffer.add(toWrite2);
+ assertTrue("First element should be in the list",
+ buffer.mBuffer.stream().anyMatch(p -> Arrays.equals(p, toWrite1Bytes)));
+ assertTrue("Second element should be in the list",
+ buffer.mBuffer.stream().anyMatch(p -> Arrays.equals(p, toWrite2Bytes)));
+
+ buffer.add(toWrite3);
+
+ assertTrue("Third element should not be in the list",
+ buffer.mBuffer.stream().noneMatch(p -> Arrays.equals(p, toWrite3Bytes)));
+
+ assertEquals("Buffer should have 2 elements", buffer.mBuffer.size(), 2);
+ assertEquals(String.format("Buffer is full, used space should be %d", bufferCapacity),
+ buffer.mBufferSize, bufferCapacity);
+ assertEquals("Buffer is full, available space should be 0",
+ buffer.getAvailableSpace(), 0);
+ }
+
+ private ProtoOutputStream getDummy(int value) {
+ ProtoOutputStream toWrite = new ProtoOutputStream();
+ toWrite.write(MAGIC_NUMBER, value);
+ toWrite.flush();
+
+ return toWrite;
+ }
+
+ private WindowTraceBuffer buildQueueBuffer(int size) throws IOException {
+ return new WindowTraceQueueBuffer(size, mFile, false);
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WmServiceUtils.java b/services/tests/wmtests/src/com/android/server/wm/WmServiceUtils.java
new file mode 100644
index 0000000..05ac8c1
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/WmServiceUtils.java
@@ -0,0 +1,214 @@
+/*
+ * 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.wm;
+
+import static android.testing.DexmakerShareClassLoaderRule.runWithDexmakerShareClassLoader;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyString;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.nullable;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.app.ActivityManagerInternal;
+import android.app.AppOpsManager;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.hardware.display.DisplayManagerInternal;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.PowerManagerInternal;
+import android.os.PowerSaveState;
+import android.os.UserHandle;
+import android.view.Display;
+import android.view.InputChannel;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.server.LocalServices;
+import com.android.server.LockGuard;
+import com.android.server.Watchdog;
+import com.android.server.input.InputManagerService;
+import com.android.server.policy.WindowManagerPolicy;
+
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.quality.Strictness;
+
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * A Test utility class to create a mock {@link WindowManagerService} instance for tests.
+ */
+class WmServiceUtils {
+ private static StaticMockitoSession sMockitoSession;
+ private static WindowManagerService sService;
+ private static TestWindowManagerPolicy sPolicy;
+
+ static void setUpWindowManagerService() {
+ sMockitoSession = mockitoSession()
+ .spyStatic(LockGuard.class)
+ .spyStatic(Watchdog.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+
+ runWithDexmakerShareClassLoader(WmServiceUtils::setUpTestWindowService);
+ }
+
+ static void tearDownWindowManagerService() {
+ waitUntilWindowManagerHandlersIdle();
+ removeLocalServices();
+ sService = null;
+ sPolicy = null;
+
+ sMockitoSession.finishMocking();
+ }
+
+ private static void setUpTestWindowService() {
+ doReturn(null).when(() -> LockGuard.installLock(any(), anyInt()));
+ doReturn(mock(Watchdog.class)).when(Watchdog::getInstance);
+
+ final Context context = getInstrumentation().getTargetContext();
+ spyOn(context);
+
+ doReturn(null).when(context)
+ .registerReceiver(nullable(BroadcastReceiver.class), any(IntentFilter.class));
+ doReturn(null).when(context)
+ .registerReceiverAsUser(any(BroadcastReceiver.class), any(UserHandle.class),
+ any(IntentFilter.class), nullable(String.class), nullable(Handler.class));
+
+ final ContentResolver contentResolver = context.getContentResolver();
+ spyOn(contentResolver);
+ doNothing().when(contentResolver)
+ .registerContentObserver(any(Uri.class), anyBoolean(), any(ContentObserver.class),
+ anyInt());
+
+ final AppOpsManager appOpsManager = mock(AppOpsManager.class);
+ doReturn(appOpsManager).when(context)
+ .getSystemService(eq(Context.APP_OPS_SERVICE));
+
+ removeLocalServices();
+
+ final DisplayManagerInternal dmi = mock(DisplayManagerInternal.class);
+ LocalServices.addService(DisplayManagerInternal.class, dmi);
+
+ final PowerManagerInternal pmi = mock(PowerManagerInternal.class);
+ LocalServices.addService(PowerManagerInternal.class, pmi);
+ final PowerSaveState state = new PowerSaveState.Builder().build();
+ doReturn(state).when(pmi).getLowPowerState(anyInt());
+
+ final ActivityManagerInternal ami = mock(ActivityManagerInternal.class);
+ LocalServices.addService(ActivityManagerInternal.class, ami);
+
+ final ActivityTaskManagerInternal atmi = mock(ActivityTaskManagerInternal.class);
+ LocalServices.addService(ActivityTaskManagerInternal.class, atmi);
+ doAnswer((InvocationOnMock invocationOnMock) -> {
+ final Runnable runnable = invocationOnMock.getArgument(0);
+ if (runnable != null) {
+ runnable.run();
+ }
+ return null;
+ }).when(atmi).notifyKeyguardFlagsChanged(nullable(Runnable.class), anyInt());
+
+ final InputManagerService ims = mock(InputManagerService.class);
+ // InputChannel is final and can't be mocked.
+ final InputChannel[] input = InputChannel.openInputChannelPair(TAG_WM);
+ if (input != null && input.length > 1) {
+ doReturn(input[1]).when(ims).monitorInput(anyString(), anyInt());
+ }
+
+ final ActivityTaskManagerService atms = mock(ActivityTaskManagerService.class);
+ final WindowManagerGlobalLock wmLock = new WindowManagerGlobalLock();
+ doReturn(wmLock).when(atms).getGlobalLock();
+
+ sPolicy = new TestWindowManagerPolicy(WmServiceUtils::getWindowManagerService);
+ sService = WindowManagerService.main(context, ims, false, false, sPolicy, atms);
+
+ sService.onInitReady();
+
+ final Display display = sService.mDisplayManager.getDisplay(DEFAULT_DISPLAY);
+ // Display creation is driven by the ActivityManagerService via
+ // ActivityStackSupervisor. We emulate those steps here.
+ sService.mRoot.createDisplayContent(display, mock(ActivityDisplay.class));
+ }
+
+ private static void removeLocalServices() {
+ LocalServices.removeServiceForTest(DisplayManagerInternal.class);
+ LocalServices.removeServiceForTest(PowerManagerInternal.class);
+ LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+ LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
+ LocalServices.removeServiceForTest(WindowManagerInternal.class);
+ LocalServices.removeServiceForTest(WindowManagerPolicy.class);
+ }
+
+ static WindowManagerService getWindowManagerService() {
+ return sService;
+ }
+
+ static void cleanupWindowManagerHandlers() {
+ final WindowManagerService wm = getWindowManagerService();
+ if (wm == null) {
+ return;
+ }
+ wm.mH.removeCallbacksAndMessages(null);
+ wm.mAnimationHandler.removeCallbacksAndMessages(null);
+ SurfaceAnimationThread.getHandler().removeCallbacksAndMessages(null);
+ }
+
+ static void waitUntilWindowManagerHandlersIdle() {
+ final WindowManagerService wm = getWindowManagerService();
+ if (wm == null) {
+ return;
+ }
+ // Removing delayed FORCE_GC message decreases time for waiting idle.
+ wm.mH.removeMessages(WindowManagerService.H.FORCE_GC);
+ waitHandlerIdle(wm.mH);
+ waitHandlerIdle(wm.mAnimationHandler);
+ waitHandlerIdle(SurfaceAnimationThread.getHandler());
+ }
+
+ private static void waitHandlerIdle(Handler handler) {
+ if (!handler.hasMessagesOrCallbacks()) {
+ return;
+ }
+ final CountDownLatch latch = new CountDownLatch(1);
+ // Wait for delayed messages are processed.
+ handler.getLooper().getQueue().addIdleHandler(() -> {
+ if (handler.hasMessagesOrCallbacks()) {
+ return true; // keep idle handler.
+ }
+ latch.countDown();
+ return false; // remove idle handler.
+ });
+ try {
+ latch.await();
+ } catch (InterruptedException e) {
+ }
+ }
+}
diff --git a/services/usage/java/com/android/server/usage/AppTimeLimitController.java b/services/usage/java/com/android/server/usage/AppTimeLimitController.java
index 8e1ede1..2ed11fe 100644
--- a/services/usage/java/com/android/server/usage/AppTimeLimitController.java
+++ b/services/usage/java/com/android/server/usage/AppTimeLimitController.java
@@ -23,7 +23,6 @@
import android.os.Message;
import android.os.SystemClock;
import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
@@ -62,6 +61,8 @@
private static final long ONE_MINUTE = 60_000L;
+ private static final Integer ONE = new Integer(1);
+
/** Collection of data for each user that has reported usage */
@GuardedBy("mLock")
private final SparseArray<UserData> mUsers = new SparseArray<>();
@@ -79,11 +80,11 @@
private @UserIdInt
int userId;
- /** Set of the currently active entities */
- private final ArraySet<String> currentlyActive = new ArraySet<>();
+ /** Count of the currently active entities */
+ public final ArrayMap<String, Integer> currentlyActive = new ArrayMap<>();
/** Map from entity name for quick lookup */
- private final ArrayMap<String, ArrayList<UsageGroup>> observedMap = new ArrayMap<>();
+ public final ArrayMap<String, ArrayList<UsageGroup>> observedMap = new ArrayMap<>();
private UserData(@UserIdInt int userId) {
this.userId = userId;
@@ -94,7 +95,7 @@
// TODO: Consider using a bloom filter here if number of actives becomes large
final int size = entities.length;
for (int i = 0; i < size; i++) {
- if (currentlyActive.contains(entities[i])) {
+ if (currentlyActive.containsKey(entities[i])) {
return true;
}
}
@@ -137,7 +138,7 @@
pw.print(" Currently Active:");
final int nActive = currentlyActive.size();
for (int i = 0; i < nActive; i++) {
- pw.print(currentlyActive.valueAt(i));
+ pw.print(currentlyActive.keyAt(i));
pw.print(", ");
}
pw.println();
@@ -233,6 +234,7 @@
protected long mUsageTimeMs;
protected int mActives;
protected long mLastKnownUsageTimeMs;
+ protected long mLastUsageEndTimeMs;
protected WeakReference<UserData> mUserRef;
protected WeakReference<ObserverAppData> mObserverAppRef;
protected PendingIntent mLimitReachedCallback;
@@ -271,9 +273,15 @@
@GuardedBy("mLock")
void noteUsageStart(long startTimeMs, long currentTimeMs) {
if (mActives++ == 0) {
+ // If last known usage ended after the start of this usage, there is overlap
+ // between the last usage session and this one. Avoid double counting by only
+ // counting from the end of the last session. This has a rare side effect that some
+ // usage will not be accounted for if the previous session started and stopped
+ // within this current usage.
+ startTimeMs = mLastUsageEndTimeMs > startTimeMs ? mLastUsageEndTimeMs : startTimeMs;
mLastKnownUsageTimeMs = startTimeMs;
final long timeRemaining =
- mTimeLimitMs - mUsageTimeMs + currentTimeMs - startTimeMs;
+ mTimeLimitMs - mUsageTimeMs - currentTimeMs + startTimeMs;
if (timeRemaining > 0) {
if (DEBUG) {
Slog.d(TAG, "Posting timeout for " + mObserverId + " for "
@@ -287,7 +295,7 @@
mActives = mObserved.length;
final UserData user = mUserRef.get();
if (user == null) return;
- final Object[] array = user.currentlyActive.toArray();
+ final Object[] array = user.currentlyActive.keySet().toArray();
Slog.e(TAG,
"Too many noted usage starts! Observed entities: " + Arrays.toString(
mObserved) + " Active Entities: " + Arrays.toString(array));
@@ -300,6 +308,8 @@
if (--mActives == 0) {
final boolean limitNotCrossed = mUsageTimeMs < mTimeLimitMs;
mUsageTimeMs += stopTimeMs - mLastKnownUsageTimeMs;
+
+ mLastUsageEndTimeMs = stopTimeMs;
if (limitNotCrossed && mUsageTimeMs >= mTimeLimitMs) {
// Crossed the limit
if (DEBUG) Slog.d(TAG, "MTB informing group obs=" + mObserverId);
@@ -312,7 +322,7 @@
mActives = 0;
final UserData user = mUserRef.get();
if (user == null) return;
- final Object[] array = user.currentlyActive.toArray();
+ final Object[] array = user.currentlyActive.keySet().toArray();
Slog.e(TAG,
"Too many noted usage stops! Observed entities: " + Arrays.toString(
mObserved) + " Active Entities: " + Arrays.toString(array));
@@ -409,7 +419,6 @@
}
class SessionUsageGroup extends UsageGroup {
- private long mLastUsageEndTimeMs;
private long mNewSessionThresholdMs;
private PendingIntent mSessionEndCallback;
@@ -451,7 +460,6 @@
public void noteUsageStop(long stopTimeMs) {
super.noteUsageStop(stopTimeMs);
if (mActives == 0) {
- mLastUsageEndTimeMs = stopTimeMs;
if (mUsageTimeMs >= mTimeLimitMs) {
// Usage has ended. Schedule the session end callback to be triggered once
// the new session threshold has been reached
@@ -467,7 +475,10 @@
UserData user = mUserRef.get();
if (user == null) return;
if (mListener != null) {
- mListener.onSessionEnd(mObserverId, user.userId, mUsageTimeMs, mSessionEndCallback);
+ mListener.onSessionEnd(mObserverId,
+ user.userId,
+ mUsageTimeMs,
+ mSessionEndCallback);
}
}
@@ -599,7 +610,7 @@
// TODO: Consider using a bloom filter here if number of actives becomes large
final int size = group.mObserved.length;
for (int i = 0; i < size; i++) {
- if (user.currentlyActive.contains(group.mObserved[i])) {
+ if (user.currentlyActive.containsKey(group.mObserved[i])) {
// Entity is currently active. Start group's usage.
group.noteUsageStart(currentTimeMs);
}
@@ -717,21 +728,28 @@
/**
* Called when an entity becomes active.
*
- * @param name The entity that became active
- * @param userId The user
+ * @param name The entity that became active
+ * @param userId The user
+ * @param timeAgoMs Time since usage was started
*/
- public void noteUsageStart(String name, int userId) throws IllegalArgumentException {
+ public void noteUsageStart(String name, int userId, long timeAgoMs)
+ throws IllegalArgumentException {
synchronized (mLock) {
UserData user = getOrCreateUserDataLocked(userId);
if (DEBUG) Slog.d(TAG, "Usage entity " + name + " became active");
- if (user.currentlyActive.contains(name)) {
- throw new IllegalArgumentException(
- "Unable to start usage for " + name + ", already in use");
+
+ final int index = user.currentlyActive.indexOfKey(name);
+ if (index >= 0) {
+ final Integer count = user.currentlyActive.valueAt(index);
+ if (count != null) {
+ // There are multiple instances of this entity. Just increment the count.
+ user.currentlyActive.setValueAt(index, count + 1);
+ return;
+ }
}
final long currentTime = getUptimeMillis();
- // Add to the list of active entities
- user.currentlyActive.add(name);
+ user.currentlyActive.put(name, ONE);
ArrayList<UsageGroup> groups = user.observedMap.get(name);
if (groups == null) return;
@@ -739,12 +757,22 @@
final int size = groups.size();
for (int i = 0; i < size; i++) {
UsageGroup group = groups.get(i);
- group.noteUsageStart(currentTime);
+ group.noteUsageStart(currentTime - timeAgoMs, currentTime);
}
}
}
/**
+ * Called when an entity becomes active.
+ *
+ * @param name The entity that became active
+ * @param userId The user
+ */
+ public void noteUsageStart(String name, int userId) throws IllegalArgumentException {
+ noteUsageStart(name, userId, 0);
+ }
+
+ /**
* Called when an entity becomes inactive.
*
* @param name The entity that became inactive
@@ -754,10 +782,21 @@
synchronized (mLock) {
UserData user = getOrCreateUserDataLocked(userId);
if (DEBUG) Slog.d(TAG, "Usage entity " + name + " became inactive");
- if (!user.currentlyActive.remove(name)) {
+
+ final int index = user.currentlyActive.indexOfKey(name);
+ if (index < 0) {
throw new IllegalArgumentException(
"Unable to stop usage for " + name + ", not in use");
}
+
+ final Integer count = user.currentlyActive.valueAt(index);
+ if (!count.equals(ONE)) {
+ // There are multiple instances of this entity. Just decrement the count.
+ user.currentlyActive.setValueAt(index, count - 1);
+ return;
+ }
+
+ user.currentlyActive.removeAt(index);
final long currentTime = getUptimeMillis();
// Check if any of the groups need to watch for this entity
@@ -769,6 +808,7 @@
UsageGroup group = groups.get(i);
group.noteUsageStop(currentTime);
}
+
}
}
@@ -780,7 +820,8 @@
@GuardedBy("mLock")
private void postInformSessionEndListenerLocked(SessionUsageGroup group, long timeout) {
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MyHandler.MSG_INFORM_SESSION_END, group),
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(MyHandler.MSG_INFORM_SESSION_END, group),
timeout);
}
@@ -800,7 +841,27 @@
mHandler.removeMessages(MyHandler.MSG_CHECK_TIMEOUT, group);
}
- void dump(PrintWriter pw) {
+ void dump(String[] args, PrintWriter pw) {
+ if (args != null) {
+ for (int i = 0; i < args.length; i++) {
+ String arg = args[i];
+ if ("actives".equals(arg)) {
+ synchronized (mLock) {
+ final int nUsers = mUsers.size();
+ for (int user = 0; user < nUsers; user++) {
+ final ArrayMap<String, Integer> actives =
+ mUsers.valueAt(user).currentlyActive;
+ final int nActive = actives.size();
+ for (int active = 0; active < nActive; active++) {
+ pw.println(actives.keyAt(active));
+ }
+ }
+ }
+ return;
+ }
+ }
+ }
+
synchronized (mLock) {
pw.println("\n App Time Limits");
final int nUsers = mUsers.size();
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 57dc08f..f146370 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -53,6 +53,7 @@
import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
+import android.os.IBinder;
import android.os.IDeviceIdleController;
import android.os.Looper;
import android.os.Message;
@@ -105,6 +106,8 @@
private static final boolean ENABLE_KERNEL_UPDATES = true;
private static final File KERNEL_COUNTER_FILE = new File("/proc/uid_procstat/set");
+ private static final char TOKEN_DELIMITER = '/';
+
// Handler message types.
static final int MSG_REPORT_EVENT = 0;
static final int MSG_FLUSH_TO_DISK = 1;
@@ -135,6 +138,10 @@
/** Manages app time limit observers */
AppTimeLimitController mAppTimeLimit;
+ final SparseArray<ArraySet<String>> mUsageReporters = new SparseArray();
+ final SparseArray<String> mVisibleActivities = new SparseArray();
+
+
private UsageStatsManagerInternal.AppIdleStateChangeListener mStandbyChangeListener =
new UsageStatsManagerInternal.AppIdleStateChangeListener() {
@Override
@@ -270,7 +277,7 @@
mHandler.obtainMessage(MSG_REMOVE_USER, userId, 0).sendToTarget();
}
} else if (Intent.ACTION_USER_STARTED.equals(action)) {
- if (userId >=0) {
+ if (userId >= 0) {
mAppStandby.postCheckIdleStates(userId);
}
}
@@ -434,17 +441,46 @@
mAppStandby.reportEvent(event, elapsedRealtime, userId);
switch (event.mEventType) {
case Event.ACTIVITY_RESUMED:
- try {
- mAppTimeLimit.noteUsageStart(event.getPackageName(), userId);
- } catch (IllegalArgumentException iae) {
- Slog.e(TAG, "Failed to note usage start", iae);
+ synchronized (mVisibleActivities) {
+ mVisibleActivities.put(event.mInstanceId, event.getClassName());
+ try {
+ mAppTimeLimit.noteUsageStart(event.getPackageName(), userId);
+ } catch (IllegalArgumentException iae) {
+ Slog.e(TAG, "Failed to note usage start", iae);
+ }
}
break;
- case Event.ACTIVITY_PAUSED:
- try {
- mAppTimeLimit.noteUsageStop(event.getPackageName(), userId);
- } catch (IllegalArgumentException iae) {
- Slog.e(TAG, "Failed to note usage stop", iae);
+ case Event.ACTIVITY_STOPPED:
+ case Event.ACTIVITY_DESTROYED:
+ ArraySet<String> tokens;
+ synchronized (mUsageReporters) {
+ tokens = mUsageReporters.removeReturnOld(event.mInstanceId);
+ }
+ if (tokens != null) {
+ synchronized (tokens) {
+ final int size = tokens.size();
+ // Stop usage on behalf of a UsageReporter that stopped
+ for (int i = 0; i < size; i++) {
+ final String token = tokens.valueAt(i);
+ try {
+ mAppTimeLimit.noteUsageStop(
+ buildFullToken(event.getPackageName(), token), userId);
+ } catch (IllegalArgumentException iae) {
+ Slog.w(TAG, "Failed to stop usage for during reporter death: "
+ + iae);
+ }
+ }
+ }
+ }
+
+ synchronized (mVisibleActivities) {
+ if (mVisibleActivities.removeReturnOld(event.mInstanceId) != null) {
+ try {
+ mAppTimeLimit.noteUsageStop(event.getPackageName(), userId);
+ } catch (IllegalArgumentException iae) {
+ Slog.w(TAG, "Failed to note usage stop", iae);
+ }
+ }
}
break;
}
@@ -599,6 +635,14 @@
return beginTime <= currentTime && beginTime < endTime;
}
+ private String buildFullToken(String packageName, String token) {
+ final StringBuilder sb = new StringBuilder(packageName.length() + token.length() + 1);
+ sb.append(packageName);
+ sb.append(TOKEN_DELIMITER);
+ sb.append(token);
+ return sb.toString();
+ }
+
private void flushToDiskLocked() {
final int userCount = mUserState.size();
for (int i = 0; i < userCount; i++) {
@@ -627,8 +671,7 @@
String arg = args[i];
if ("--checkin".equals(arg)) {
checkin = true;
- } else
- if ("-c".equals(arg)) {
+ } else if ("-c".equals(arg)) {
compact = true;
} else if ("flush".equals(arg)) {
flushToDiskLocked();
@@ -637,6 +680,15 @@
} else if ("is-app-standby-enabled".equals(arg)) {
pw.println(mAppStandby.mAppIdleEnabled);
return;
+ } else if ("apptimelimit".equals(arg)) {
+ if (i + 1 >= args.length) {
+ mAppTimeLimit.dump(null, pw);
+ } else {
+ final String[] remainingArgs =
+ Arrays.copyOfRange(args, i + 1, args.length);
+ mAppTimeLimit.dump(remainingArgs, pw);
+ }
+ return;
} else if (arg != null && !arg.startsWith("-")) {
// Anything else that doesn't start with '-' is a pkg to filter
pkg = arg;
@@ -666,7 +718,7 @@
mAppStandby.dumpState(args, pw);
}
- mAppTimeLimit.dump(pw);
+ mAppTimeLimit.dump(null, pw);
}
}
@@ -1231,16 +1283,82 @@
final int userId = UserHandle.getUserId(callingUid);
final long token = Binder.clearCallingIdentity();
try {
- UsageStatsService.this.unregisterUsageSessionObserver(callingUid, sessionObserverId, userId);
+ UsageStatsService.this.unregisterUsageSessionObserver(callingUid, sessionObserverId,
+ userId);
} finally {
Binder.restoreCallingIdentity(token);
}
}
+
+ @Override
+ public void reportUsageStart(IBinder activity, String token, String callingPackage) {
+ reportPastUsageStart(activity, token, 0, callingPackage);
+ }
+
+ @Override
+ public void reportPastUsageStart(IBinder activity, String token, long timeAgoMs,
+ String callingPackage) {
+
+ final int callingUid = Binder.getCallingUid();
+ final int userId = UserHandle.getUserId(callingUid);
+ final long binderToken = Binder.clearCallingIdentity();
+ try {
+ ArraySet<String> tokens;
+ synchronized (mUsageReporters) {
+ tokens = mUsageReporters.get(activity.hashCode());
+ if (tokens == null) {
+ tokens = new ArraySet();
+ mUsageReporters.put(activity.hashCode(), tokens);
+ }
+ }
+
+ synchronized (tokens) {
+ if (!tokens.add(token)) {
+ throw new IllegalArgumentException(token + " for " + callingPackage
+ + " is already reported as started for this activity");
+ }
+ }
+
+ mAppTimeLimit.noteUsageStart(buildFullToken(callingPackage, token),
+ userId, timeAgoMs);
+ } finally {
+ Binder.restoreCallingIdentity(binderToken);
+ }
+ }
+
+ @Override
+ public void reportUsageStop(IBinder activity, String token, String callingPackage) {
+ final int callingUid = Binder.getCallingUid();
+ final int userId = UserHandle.getUserId(callingUid);
+ final long binderToken = Binder.clearCallingIdentity();
+ try {
+ ArraySet<String> tokens;
+ synchronized (mUsageReporters) {
+ tokens = mUsageReporters.get(activity.hashCode());
+ if (tokens == null) {
+ throw new IllegalArgumentException(
+ "Unknown reporter trying to stop token " + token + " for "
+ + callingPackage);
+ }
+ }
+
+ synchronized (tokens) {
+ if (!tokens.remove(token)) {
+ throw new IllegalArgumentException(token + " for " + callingPackage
+ + " is already reported as stopped for this activity");
+ }
+ }
+ mAppTimeLimit.noteUsageStop(buildFullToken(callingPackage, token), userId);
+ } finally {
+ Binder.restoreCallingIdentity(binderToken);
+ }
+ }
}
void registerAppUsageObserver(int callingUid, int observerId, String[] packages,
long timeLimitMs, PendingIntent callbackIntent, int userId) {
- mAppTimeLimit.addAppUsageObserver(callingUid, observerId, packages, timeLimitMs, callbackIntent,
+ mAppTimeLimit.addAppUsageObserver(callingUid, observerId, packages, timeLimitMs,
+ callbackIntent,
userId);
}
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 294b750..f1e2281 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -1744,8 +1744,8 @@
mGadgetProxy.linkToDeath(new UsbGadgetDeathRecipient(),
USB_GADGET_HAL_DEATH_COOKIE);
mCurrentFunctions = UsbManager.FUNCTION_NONE;
- mGadgetProxy.getCurrentUsbFunctions(new UsbGadgetCallback());
mCurrentUsbFunctionsRequested = true;
+ mGadgetProxy.getCurrentUsbFunctions(new UsbGadgetCallback());
}
String state = FileUtils.readTextFile(new File(STATE_PATH), 0, null).trim();
updateState(state);
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 38efc74..3d7cbb5 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -657,9 +657,8 @@
return;
}
- if (event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) {
- model.setStopped();
- }
+ model.setStopped();
+
try {
callback.onGenericSoundTriggerDetected((GenericRecognitionEvent) event);
} catch (DeadObjectException e) {
@@ -802,9 +801,7 @@
return;
}
- if (event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) {
- modelData.setStopped();
- }
+ modelData.setStopped();
try {
modelData.getCallback().onKeyphraseDetected((KeyphraseRecognitionEvent) event);
diff --git a/startop/iorap/Android.bp b/startop/iorap/Android.bp
index b3b0900..59a80fb 100644
--- a/startop/iorap/Android.bp
+++ b/startop/iorap/Android.bp
@@ -13,7 +13,7 @@
// limitations under the License.
java_library_static {
- name: "libiorap-java",
+ name: "services.startop.iorap",
aidl: {
include_dirs: [
@@ -21,6 +21,8 @@
],
},
+ libs: ["services.core"],
+
srcs: [
":iorap-aidl",
"**/*.java",
diff --git a/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java b/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java
new file mode 100644
index 0000000..c2e4581
--- /dev/null
+++ b/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java
@@ -0,0 +1,368 @@
+/*
+ * 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.
+ */
+
+package com.google.android.startop.iorap;
+
+import android.annotation.LongDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+// TODO: fix this. either move this class into system server or add a dependency on
+// these wm classes to libiorap-java and libiorap-java-tests (somehow).
+import com.android.server.wm.ActivityMetricsLaunchObserver;
+import com.android.server.wm.ActivityMetricsLaunchObserver.ActivityRecordProto;
+import com.android.server.wm.ActivityMetricsLaunchObserver.Temperature;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Objects;
+
+/**
+ * Provide a hint to iorapd that an app launch sequence has transitioned state.<br /><br />
+ *
+ * Knowledge of when an activity starts/stops can be used by iorapd to increase system
+ * performance (e.g. by launching perfetto tracing to record an io profile, or by
+ * playing back an ioprofile via readahead) over the long run.<br /><br />
+ *
+ * /@see com.google.android.startop.iorap.IIorap#onAppLaunchEvent <br /><br />
+ * @see com.android.server.wm.ActivityMetricsLaunchObserver
+ * ActivityMetricsLaunchObserver for the possible event states.
+ * @hide
+ */
+public abstract class AppLaunchEvent implements Parcelable {
+ @LongDef
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SequenceId {}
+
+ public final @SequenceId
+ long sequenceId;
+
+ protected AppLaunchEvent(@SequenceId long sequenceId) {
+ this.sequenceId = sequenceId;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof AppLaunchEvent) {
+ return equals((AppLaunchEvent) other);
+ }
+ return false;
+ }
+
+ protected boolean equals(AppLaunchEvent other) {
+ return sequenceId == other.sequenceId;
+ }
+
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() +
+ "{" + "sequenceId=" + Long.toString(sequenceId) +
+ toStringBody() + "}";
+ }
+
+ protected String toStringBody() { return ""; };
+
+ // List of possible variants:
+
+ public static final class IntentStarted extends AppLaunchEvent {
+ @NonNull
+ public final Intent intent;
+
+ public IntentStarted(@SequenceId long sequenceId, Intent intent) {
+ super(sequenceId);
+ this.intent = intent;
+
+ Objects.requireNonNull(intent, "intent");
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof IntentStarted) {
+ return intent.equals(((IntentStarted)other).intent) &&
+ super.equals(other);
+ }
+ return false;
+ }
+
+ @Override
+ protected String toStringBody() {
+ return ", intent=" + intent.toString();
+ }
+
+
+ @Override
+ protected void writeToParcelImpl(Parcel p, int flags) {
+ super.writeToParcelImpl(p, flags);
+ intent.writeToParcel(p, flags);
+ }
+
+ IntentStarted(Parcel p) {
+ super(p);
+ intent = Intent.CREATOR.createFromParcel(p);
+ }
+ }
+
+ public static final class IntentFailed extends AppLaunchEvent {
+ public IntentFailed(@SequenceId long sequenceId) {
+ super(sequenceId);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof IntentFailed) {
+ return super.equals(other);
+ }
+ return false;
+ }
+
+ IntentFailed(Parcel p) {
+ super(p);
+ }
+ }
+
+ public static abstract class BaseWithActivityRecordData extends AppLaunchEvent {
+ public final @NonNull
+ @ActivityRecordProto byte[] activityRecordSnapshot;
+
+ protected BaseWithActivityRecordData(@SequenceId long sequenceId,
+ @NonNull @ActivityRecordProto byte[] snapshot) {
+ super(sequenceId);
+ activityRecordSnapshot = snapshot;
+
+ Objects.requireNonNull(snapshot, "snapshot");
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof BaseWithActivityRecordData) {
+ return activityRecordSnapshot.equals(
+ ((BaseWithActivityRecordData)other).activityRecordSnapshot) &&
+ super.equals(other);
+ }
+ return false;
+ }
+
+ @Override
+ protected String toStringBody() {
+ return ", " + activityRecordSnapshot.toString();
+ }
+
+ @Override
+ protected void writeToParcelImpl(Parcel p, int flags) {
+ super.writeToParcelImpl(p, flags);
+ ActivityRecordProtoParcelable.write(p, activityRecordSnapshot, flags);
+ }
+
+ BaseWithActivityRecordData(Parcel p) {
+ super(p);
+ activityRecordSnapshot = ActivityRecordProtoParcelable.create(p);
+ }
+ }
+
+ public static final class ActivityLaunched extends BaseWithActivityRecordData {
+ public final @ActivityMetricsLaunchObserver.Temperature
+ int temperature;
+
+ public ActivityLaunched(@SequenceId long sequenceId,
+ @NonNull @ActivityRecordProto byte[] snapshot,
+ @ActivityMetricsLaunchObserver.Temperature int temperature) {
+ super(sequenceId, snapshot);
+ this.temperature = temperature;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof ActivityLaunched) {
+ return temperature == ((ActivityLaunched)other).temperature &&
+ super.equals(other);
+ }
+ return false;
+ }
+
+ @Override
+ protected String toStringBody() {
+ return ", temperature=" + Integer.toString(temperature);
+ }
+
+ @Override
+ protected void writeToParcelImpl(Parcel p, int flags) {
+ super.writeToParcelImpl(p, flags);
+ p.writeInt(temperature);
+ }
+
+ ActivityLaunched(Parcel p) {
+ super(p);
+ temperature = p.readInt();
+ }
+ }
+
+ public static final class ActivityLaunchFinished extends BaseWithActivityRecordData {
+ public ActivityLaunchFinished(@SequenceId long sequenceId,
+ @NonNull @ActivityRecordProto byte[] snapshot) {
+ super(sequenceId, snapshot);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof ActivityLaunched) {
+ return super.equals(other);
+ }
+ return false;
+ }
+ }
+
+ public static class ActivityLaunchCancelled extends AppLaunchEvent {
+ public final @Nullable
+ @ActivityRecordProto byte[] activityRecordSnapshot;
+
+ public ActivityLaunchCancelled(@SequenceId long sequenceId,
+ @Nullable @ActivityRecordProto byte[] snapshot) {
+ super(sequenceId);
+ activityRecordSnapshot = snapshot;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof ActivityLaunchCancelled) {
+ return Objects.equals(activityRecordSnapshot,
+ ((ActivityLaunchCancelled)other).activityRecordSnapshot) &&
+ super.equals(other);
+ }
+ return false;
+ }
+
+ @Override
+ protected String toStringBody() {
+ return ", " + activityRecordSnapshot.toString();
+ }
+
+ @Override
+ protected void writeToParcelImpl(Parcel p, int flags) {
+ super.writeToParcelImpl(p, flags);
+ if (activityRecordSnapshot != null) {
+ p.writeBoolean(true);
+ ActivityRecordProtoParcelable.write(p, activityRecordSnapshot, flags);
+ } else {
+ p.writeBoolean(false);
+ }
+ }
+
+ ActivityLaunchCancelled(Parcel p) {
+ super(p);
+ if (p.readBoolean()) {
+ activityRecordSnapshot = ActivityRecordProtoParcelable.create(p);
+ } else {
+ activityRecordSnapshot = null;
+ }
+ }
+ }
+
+ @Override
+ public @ContentsFlags int describeContents() { return 0; }
+
+ @Override
+ public void writeToParcel(Parcel p, @WriteFlags int flags) {
+ p.writeInt(getTypeIndex());
+
+ writeToParcelImpl(p, flags);
+ }
+
+
+ public static Creator<AppLaunchEvent> CREATOR =
+ new Creator<AppLaunchEvent>() {
+ @Override
+ public AppLaunchEvent createFromParcel(Parcel source) {
+ int typeIndex = source.readInt();
+
+ Class<?> kls = getClassFromTypeIndex(typeIndex);
+ if (kls == null) {
+ throw new IllegalArgumentException("Invalid type index: " + typeIndex);
+ }
+
+ try {
+ return (AppLaunchEvent) kls.getConstructor(Parcel.class).newInstance(source);
+ } catch (InstantiationException e) {
+ throw new AssertionError(e);
+ } catch (IllegalAccessException e) {
+ throw new AssertionError(e);
+ } catch (InvocationTargetException e) {
+ throw new AssertionError(e);
+ } catch (NoSuchMethodException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ @Override
+ public AppLaunchEvent[] newArray(int size) {
+ return new AppLaunchEvent[0];
+ }
+ };
+
+ protected void writeToParcelImpl(Parcel p, int flags) {
+ p.writeLong(sequenceId);
+ }
+
+ protected AppLaunchEvent(Parcel p) {
+ sequenceId = p.readLong();
+ }
+
+ private int getTypeIndex() {
+ for (int i = 0; i < sTypes.length; ++i) {
+ if (sTypes[i].equals(this.getClass())) {
+ return i;
+ }
+ }
+ throw new AssertionError("sTypes did not include this type: " + this.getClass());
+ }
+
+ private static @Nullable Class<?> getClassFromTypeIndex(int typeIndex) {
+ if (typeIndex >= 0 && typeIndex < sTypes.length) {
+ return sTypes[typeIndex];
+ }
+ return null;
+ }
+
+ // Index position matters: It is used to encode the specific type in parceling.
+ // Keep up-to-date with C++ side.
+ private static Class<?>[] sTypes = new Class[] {
+ IntentStarted.class,
+ IntentFailed.class,
+ ActivityLaunched.class,
+ ActivityLaunchFinished.class,
+ ActivityLaunchCancelled.class,
+ };
+
+ // TODO: move to @ActivityRecordProto byte[] once we have unit tests.
+ public static class ActivityRecordProtoParcelable {
+ public static void write(Parcel p, @ActivityRecordProto byte[] activityRecordSnapshot,
+ int flags) {
+ p.writeByteArray(activityRecordSnapshot);
+ }
+
+ public static @ActivityRecordProto byte[] create(Parcel p) {
+ byte[] data = p.createByteArray();
+
+ return data;
+ }
+ }
+}
diff --git a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
new file mode 100644
index 0000000..7fcad36
--- /dev/null
+++ b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
@@ -0,0 +1,216 @@
+/*
+ * 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.
+ */
+
+package com.google.android.startop.iorap;
+// TODO: rename to com.android.server.startop.iorap
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+import com.android.server.wm.ActivityMetricsLaunchObserver;
+import com.android.server.wm.ActivityMetricsLaunchObserver.ActivityRecordProto;
+import com.android.server.wm.ActivityMetricsLaunchObserver.Temperature;
+import com.android.server.wm.ActivityMetricsLaunchObserverRegistry;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+/**
+ * System-server-local proxy into the {@code IIorap} native service.
+ */
+public class IorapForwardingService extends SystemService {
+
+ public static final boolean DEBUG = true; // TODO: read from a getprop?
+ public static final String TAG = "IorapForwardingService";
+
+ private IIorap mIorapRemote;
+
+ /**
+ * Initializes the system service.
+ * <p>
+ * Subclasses must define a single argument constructor that accepts the context
+ * and passes it to super.
+ * </p>
+ *
+ * @param context The system server context.
+ */
+ public IorapForwardingService(Context context) {
+ super(context);
+ }
+
+ //<editor-fold desc="Providers">
+ /*
+ * Providers for external dependencies:
+ * - These are marked as protected to allow tests to inject different values via mocks.
+ */
+
+ @VisibleForTesting
+ protected ActivityMetricsLaunchObserverRegistry provideLaunchObserverRegistry() {
+ ActivityTaskManagerInternal amtInternal =
+ LocalServices.getService(ActivityTaskManagerInternal.class);
+ ActivityMetricsLaunchObserverRegistry launchObserverRegistry =
+ amtInternal.getLaunchObserverRegistry();
+ return launchObserverRegistry;
+ }
+
+ @VisibleForTesting
+ protected IIorap provideIorapRemote() {
+ try {
+ return IIorap.Stub.asInterface(ServiceManager.getServiceOrThrow("iorapd"));
+ } catch (ServiceManager.ServiceNotFoundException e) {
+ // TODO: how do we handle service being missing?
+ throw new AssertionError(e);
+ }
+ }
+
+ //</editor-fold>
+
+ @Override
+ public void onStart() {
+ if (DEBUG) {
+ Log.v(TAG, "onStart");
+ }
+
+ // Connect to the native binder service.
+ mIorapRemote = provideIorapRemote();
+ invokeRemote( () -> mIorapRemote.setTaskListener(new RemoteTaskListener()) );
+
+ // Listen to App Launch Sequence events from ActivityTaskManager,
+ // and forward them to the native binder service.
+ ActivityMetricsLaunchObserverRegistry launchObserverRegistry =
+ provideLaunchObserverRegistry();
+ launchObserverRegistry.registerLaunchObserver(new AppLaunchObserver());
+ }
+
+ private class AppLaunchObserver implements ActivityMetricsLaunchObserver {
+ // We add a synthetic sequence ID here to make it easier to differentiate new
+ // launch sequences on the native side.
+ private @AppLaunchEvent.SequenceId long mSequenceId = -1;
+
+ @Override
+ public void onIntentStarted(@NonNull Intent intent) {
+ // #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));
+ }
+
+ invokeRemote(() ->
+ mIorapRemote.onAppLaunchEvent(RequestId.nextValueForSequence(),
+ new AppLaunchEvent.IntentStarted(mSequenceId, intent))
+ );
+ }
+
+ @Override
+ public void onIntentFailed() {
+ if (DEBUG) {
+ Log.v(TAG, String.format("AppLaunchObserver#onIntentFailed(%d)", mSequenceId));
+ }
+
+ invokeRemote(() ->
+ mIorapRemote.onAppLaunchEvent(RequestId.nextValueForSequence(),
+ new AppLaunchEvent.IntentFailed(mSequenceId))
+ );
+ }
+
+ @Override
+ public void onActivityLaunched(@NonNull @ActivityRecordProto byte[] activity,
+ @Temperature int temperature) {
+ if (DEBUG) {
+ Log.v(TAG, String.format("AppLaunchObserver#onActivityLaunched(%d, %s, %d)",
+ mSequenceId, activity, temperature));
+ }
+
+ invokeRemote(() ->
+ mIorapRemote.onAppLaunchEvent(RequestId.nextValueForSequence(),
+ new AppLaunchEvent.ActivityLaunched(mSequenceId, activity, temperature))
+ );
+ }
+
+ @Override
+ public void onActivityLaunchCancelled(@Nullable @ActivityRecordProto byte[] activity) {
+ if (DEBUG) {
+ Log.v(TAG, String.format("AppLaunchObserver#onActivityLaunchCancelled(%d, %s)",
+ mSequenceId, activity));
+ }
+
+ invokeRemote(() ->
+ mIorapRemote.onAppLaunchEvent(RequestId.nextValueForSequence(),
+ new AppLaunchEvent.ActivityLaunchCancelled(mSequenceId,
+ activity)));
+ }
+
+ @Override
+ public void onActivityLaunchFinished(@NonNull @ActivityRecordProto byte[] activity) {
+ if (DEBUG) {
+ Log.v(TAG, String.format("AppLaunchObserver#onActivityLaunchFinished(%d, %s)",
+ mSequenceId, activity));
+ }
+
+ invokeRemote(() ->
+ mIorapRemote.onAppLaunchEvent(RequestId.nextValueForSequence(),
+ new AppLaunchEvent.ActivityLaunchCancelled(mSequenceId, activity))
+ );
+ }
+ }
+
+ private class RemoteTaskListener extends ITaskListener.Stub {
+ @Override
+ public void onProgress(RequestId requestId, TaskResult result) throws RemoteException {
+ if (DEBUG) {
+ Log.v(TAG,
+ String.format("RemoteTaskListener#onProgress(%s, %s)", requestId, result));
+ }
+
+ // TODO: implement rest.
+ }
+
+ @Override
+ public void onComplete(RequestId requestId, TaskResult result) throws RemoteException {
+ if (DEBUG) {
+ Log.v(TAG,
+ String.format("RemoteTaskListener#onComplete(%s, %s)", requestId, result));
+ }
+
+ // TODO: implement rest.
+ }
+ }
+
+ private interface RemoteRunnable {
+ void run() throws RemoteException;
+ }
+
+ private static void invokeRemote(RemoteRunnable r) {
+ try {
+ r.run();
+ } catch (RemoteException e) {
+ // TODO: what do we do with exceptions?
+ throw new AssertionError("not implemented", e);
+ }
+ }
+}
diff --git a/startop/iorap/src/com/google/android/startop/iorap/RequestId.java b/startop/iorap/src/com/google/android/startop/iorap/RequestId.java
index 2c79319..adb3a91 100644
--- a/startop/iorap/src/com/google/android/startop/iorap/RequestId.java
+++ b/startop/iorap/src/com/google/android/startop/iorap/RequestId.java
@@ -71,7 +71,7 @@
@Override
public String toString() {
- return String.format("{requestId: %ld}", requestId);
+ return String.format("{requestId: %d}", requestId);
}
@Override
diff --git a/startop/iorap/tests/Android.bp b/startop/iorap/tests/Android.bp
index 7605784..5ac4a46 100644
--- a/startop/iorap/tests/Android.bp
+++ b/startop/iorap/tests/Android.bp
@@ -18,8 +18,15 @@
srcs: ["src/**/*.kt"],
static_libs: [
- // non-test dependencies
- "libiorap-java",
+ // Non-test dependencies
+
+ // library under test
+ "services.startop.iorap",
+ // need the system_server code to be on the classpath,
+ "services.core",
+
+ // Test Dependencies
+
// test android dependencies
"platform-test-annotations",
"android-support-test",
diff --git a/startop/view_compiler/Android.bp b/startop/view_compiler/Android.bp
index 91cec554..2fc3a0d 100644
--- a/startop/view_compiler/Android.bp
+++ b/startop/view_compiler/Android.bp
@@ -22,18 +22,37 @@
shared_libs: [
"libbase",
"libdexfile",
+ "libz",
"slicer",
],
static_libs: [
"libtinyxml2",
+ "liblog",
+ "libutils",
+ "libziparchive",
],
+ cppflags: ["-std=c++17"],
+ target: {
+ android: {
+ shared_libs: [
+ "libandroidfw",
+ ],
+ },
+ host: {
+ static_libs: [
+ "libandroidfw",
+ ],
+ },
+ },
}
cc_library_host_static {
name: "libviewcompiler",
defaults: ["viewcompiler_defaults"],
srcs: [
+ "apk_layout_compiler.cc",
"dex_builder.cc",
+ "dex_layout_compiler.cc",
"java_lang_builder.cc",
"tinyxml_layout_parser.cc",
"util.cc",
diff --git a/startop/view_compiler/apk_layout_compiler.cc b/startop/view_compiler/apk_layout_compiler.cc
new file mode 100644
index 0000000..e95041b
--- /dev/null
+++ b/startop/view_compiler/apk_layout_compiler.cc
@@ -0,0 +1,159 @@
+/*
+ * 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.
+ */
+
+#include "apk_layout_compiler.h"
+#include "dex_layout_compiler.h"
+#include "java_lang_builder.h"
+#include "layout_validation.h"
+#include "util.h"
+
+#include "androidfw/ApkAssets.h"
+#include "androidfw/AssetManager2.h"
+#include "androidfw/ResourceTypes.h"
+
+#include <iostream>
+#include <locale>
+
+#include "android-base/stringprintf.h"
+
+namespace startop {
+
+using android::ResXMLParser;
+using android::base::StringPrintf;
+
+class ResXmlVisitorAdapter {
+ public:
+ ResXmlVisitorAdapter(ResXMLParser* parser) : parser_{parser} {}
+
+ template <typename Visitor>
+ void Accept(Visitor* visitor) {
+ size_t depth{0};
+ do {
+ switch (parser_->next()) {
+ case ResXMLParser::START_DOCUMENT:
+ depth++;
+ visitor->VisitStartDocument();
+ break;
+ case ResXMLParser::END_DOCUMENT:
+ depth--;
+ visitor->VisitEndDocument();
+ break;
+ case ResXMLParser::START_TAG: {
+ depth++;
+ size_t name_length = 0;
+ const char16_t* name = parser_->getElementName(&name_length);
+ visitor->VisitStartTag(std::u16string{name, name_length});
+ break;
+ }
+ case ResXMLParser::END_TAG:
+ depth--;
+ visitor->VisitEndTag();
+ break;
+ default:;
+ }
+ } while (depth > 0 || parser_->getEventType() == ResXMLParser::FIRST_CHUNK_CODE);
+ }
+
+ private:
+ ResXMLParser* parser_;
+};
+
+bool CanCompileLayout(ResXMLParser* parser) {
+ ResXmlVisitorAdapter adapter{parser};
+ LayoutValidationVisitor visitor;
+ adapter.Accept(&visitor);
+
+ return visitor.can_compile();
+}
+
+void CompileApkLayouts(const std::string& filename, CompilationTarget target,
+ std::ostream& target_out) {
+ auto assets = android::ApkAssets::Load(filename);
+ android::AssetManager2 resources;
+ resources.SetApkAssets({assets.get()});
+
+ std::string package_name;
+
+ // TODO: handle multiple packages better
+ bool first = true;
+ for (const auto& package : assets->GetLoadedArsc()->GetPackages()) {
+ CHECK(first);
+ package_name = package->GetPackageName();
+ first = false;
+ }
+
+ dex::DexBuilder dex_file;
+ dex::ClassBuilder compiled_view{
+ dex_file.MakeClass(StringPrintf("%s.CompiledView", package_name.c_str()))};
+ std::vector<dex::MethodBuilder> methods;
+
+ assets->ForEachFile("res/", [&](const android::StringPiece& s, android::FileType) {
+ if (s == "layout") {
+ auto path = StringPrintf("res/%s/", s.to_string().c_str());
+ assets->ForEachFile(path, [&](const android::StringPiece& layout_file, android::FileType) {
+ auto layout_path = StringPrintf("%s%s", path.c_str(), layout_file.to_string().c_str());
+ android::ApkAssetsCookie cookie = android::kInvalidCookie;
+ auto asset = resources.OpenNonAsset(layout_path, android::Asset::ACCESS_RANDOM, &cookie);
+ CHECK(asset);
+ CHECK(android::kInvalidCookie != cookie);
+ const auto dynamic_ref_table = resources.GetDynamicRefTableForCookie(cookie);
+ CHECK(nullptr != dynamic_ref_table);
+ android::ResXMLTree xml_tree{dynamic_ref_table};
+ xml_tree.setTo(asset->getBuffer(/*wordAligned=*/true),
+ asset->getLength(),
+ /*copy_data=*/true);
+ android::ResXMLParser parser{xml_tree};
+ parser.restart();
+ if (CanCompileLayout(&parser)) {
+ parser.restart();
+ const std::string layout_name = startop::util::FindLayoutNameFromFilename(layout_path);
+ ResXmlVisitorAdapter adapter{&parser};
+ switch (target) {
+ case CompilationTarget::kDex: {
+ methods.push_back(compiled_view.CreateMethod(
+ layout_name,
+ dex::Prototype{dex::TypeDescriptor::FromClassname("android.view.View"),
+ dex::TypeDescriptor::FromClassname("android.content.Context"),
+ dex::TypeDescriptor::Int()}));
+ DexViewBuilder builder(&methods.back());
+ builder.Start();
+ LayoutCompilerVisitor visitor{&builder};
+ adapter.Accept(&visitor);
+ builder.Finish();
+ methods.back().Encode();
+ break;
+ }
+ case CompilationTarget::kJavaLanguage: {
+ JavaLangViewBuilder builder{package_name, layout_name, target_out};
+ builder.Start();
+ LayoutCompilerVisitor visitor{&builder};
+ adapter.Accept(&visitor);
+ builder.Finish();
+ break;
+ }
+ }
+ }
+ });
+ }
+ });
+
+ if (target == CompilationTarget::kDex) {
+ slicer::MemView image{dex_file.CreateImage()};
+ target_out.write(image.ptr<const char>(), image.size());
+ }
+}
+
+} // namespace startop
diff --git a/startop/view_compiler/apk_layout_compiler.h b/startop/view_compiler/apk_layout_compiler.h
new file mode 100644
index 0000000..c85ddd6
--- /dev/null
+++ b/startop/view_compiler/apk_layout_compiler.h
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+#ifndef APK_LAYOUT_COMPILER_H_
+#define APK_LAYOUT_COMPILER_H_
+
+#include <string>
+
+namespace startop {
+
+enum class CompilationTarget { kJavaLanguage, kDex };
+
+void CompileApkLayouts(const std::string& filename, CompilationTarget target,
+ std::ostream& target_out);
+
+} // namespace startop
+
+#endif // APK_LAYOUT_COMPILER_H_
\ No newline at end of file
diff --git a/startop/view_compiler/dex_builder_test/Android.bp b/startop/view_compiler/dex_builder_test/Android.bp
index 4449ea0..d4f38ed 100644
--- a/startop/view_compiler/dex_builder_test/Android.bp
+++ b/startop/view_compiler/dex_builder_test/Android.bp
@@ -14,16 +14,30 @@
// limitations under the License.
//
+genrule {
+ name: "generate_compiled_layout",
+ tools: [":viewcompiler"],
+ cmd: "$(location :viewcompiler) $(in) --dex --out $(out) --package android.startop.test",
+ srcs: ["res/layout/layout1.xml"],
+ out: [
+ "layout1.dex",
+ ],
+}
+
android_test {
name: "dex-builder-test",
- srcs: ["src/android/startop/test/DexBuilderTest.java"],
+ srcs: [
+ "src/android/startop/test/DexBuilderTest.java",
+ "src/android/startop/test/LayoutCompilerTest.java",
+ ],
sdk_version: "current",
- data: [":generate_dex_testcases"],
+ data: [":generate_dex_testcases", ":generate_compiled_layout"],
static_libs: [
"android-support-test",
"guava",
],
manifest: "AndroidManifest.xml",
+ resource_dirs: ["res"],
test_config: "AndroidTest.xml",
test_suites: ["general-tests"],
}
diff --git a/startop/view_compiler/dex_builder_test/AndroidTest.xml b/startop/view_compiler/dex_builder_test/AndroidTest.xml
index 6f90cf3..68d8fdc 100644
--- a/startop/view_compiler/dex_builder_test/AndroidTest.xml
+++ b/startop/view_compiler/dex_builder_test/AndroidTest.xml
@@ -25,6 +25,7 @@
<option name="cleanup" value="true" />
<option name="push" value="trivial.dex->/data/local/tmp/dex-builder-test/trivial.dex" />
<option name="push" value="simple.dex->/data/local/tmp/dex-builder-test/simple.dex" />
+ <option name="push" value="layout1.dex->/data/local/tmp/dex-builder-test/layout1.dex" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/startop/view_compiler/dex_builder_test/res/layout/layout1.xml b/startop/view_compiler/dex_builder_test/res/layout/layout1.xml
new file mode 100644
index 0000000..0f9375c
--- /dev/null
+++ b/startop/view_compiler/dex_builder_test/res/layout/layout1.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp"
+ android:orientation="vertical"
+ android:gravity="center">
+
+ <Button
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+ <Button
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+
+ </LinearLayout>
diff --git a/startop/view_compiler/dex_builder_test/src/android/startop/test/LayoutCompilerTest.java b/startop/view_compiler/dex_builder_test/src/android/startop/test/LayoutCompilerTest.java
new file mode 100644
index 0000000..ce3ce83
--- /dev/null
+++ b/startop/view_compiler/dex_builder_test/src/android/startop/test/LayoutCompilerTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+
+package android.startop.test;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.view.View;
+import com.google.common.io.ByteStreams;
+import dalvik.system.InMemoryDexClassLoader;
+import dalvik.system.PathClassLoader;
+import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+import org.junit.Assert;
+import org.junit.Test;
+
+// Adding tests here requires changes in several other places. See README.md in
+// the view_compiler directory for more information.
+public class LayoutCompilerTest {
+ static ClassLoader loadDexFile(String filename) throws Exception {
+ return new PathClassLoader("/data/local/tmp/dex-builder-test/" + filename,
+ ClassLoader.getSystemClassLoader());
+ }
+
+ @Test
+ public void loadAndInflaterLayout1() throws Exception {
+ ClassLoader dex_file = loadDexFile("layout1.dex");
+ Class compiled_view = dex_file.loadClass("android.startop.test.CompiledView");
+ Method layout1 = compiled_view.getMethod("layout1", Context.class, int.class);
+ Context context = InstrumentationRegistry.getTargetContext();
+ layout1.invoke(null, context, R.layout.layout1);
+ }
+}
diff --git a/startop/view_compiler/dex_layout_compiler.cc b/startop/view_compiler/dex_layout_compiler.cc
new file mode 100644
index 0000000..c68793d
--- /dev/null
+++ b/startop/view_compiler/dex_layout_compiler.cc
@@ -0,0 +1,226 @@
+/*
+ * 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.
+ */
+
+#include "dex_layout_compiler.h"
+#include "layout_validation.h"
+
+#include "android-base/stringprintf.h"
+
+namespace startop {
+
+using android::base::StringPrintf;
+
+void LayoutValidationVisitor::VisitStartTag(const std::u16string& name) {
+ if (0 == name.compare(u"merge")) {
+ message_ = "Merge tags are not supported";
+ can_compile_ = false;
+ }
+ if (0 == name.compare(u"include")) {
+ message_ = "Include tags are not supported";
+ can_compile_ = false;
+ }
+ if (0 == name.compare(u"view")) {
+ message_ = "View tags are not supported";
+ can_compile_ = false;
+ }
+ if (0 == name.compare(u"fragment")) {
+ message_ = "Fragment tags are not supported";
+ can_compile_ = false;
+ }
+}
+
+DexViewBuilder::DexViewBuilder(dex::MethodBuilder* method)
+ : method_{method},
+ context_{dex::Value::Parameter(0)},
+ resid_{dex::Value::Parameter(1)},
+ inflater_{method->MakeRegister()},
+ xml_{method->MakeRegister()},
+ attrs_{method->MakeRegister()},
+ classname_tmp_{method->MakeRegister()},
+ xml_next_{method->dex_file()->GetOrDeclareMethod(
+ dex::TypeDescriptor::FromClassname("android.content.res.XmlResourceParser"), "next",
+ dex::Prototype{dex::TypeDescriptor::Int()})},
+ try_create_view_{method->dex_file()->GetOrDeclareMethod(
+ dex::TypeDescriptor::FromClassname("android.view.LayoutInflater"), "tryCreateView",
+ dex::Prototype{dex::TypeDescriptor::FromClassname("android.view.View"),
+ dex::TypeDescriptor::FromClassname("android.view.View"),
+ dex::TypeDescriptor::FromClassname("java.lang.String"),
+ dex::TypeDescriptor::FromClassname("android.content.Context"),
+ dex::TypeDescriptor::FromClassname("android.util.AttributeSet")})},
+ generate_layout_params_{method->dex_file()->GetOrDeclareMethod(
+ dex::TypeDescriptor::FromClassname("android.view.ViewGroup"), "generateLayoutParams",
+ dex::Prototype{dex::TypeDescriptor::FromClassname("android.view.ViewGroup$LayoutParams"),
+ dex::TypeDescriptor::FromClassname("android.util.AttributeSet")})},
+ add_view_{method->dex_file()->GetOrDeclareMethod(
+ dex::TypeDescriptor::FromClassname("android.view.ViewGroup"), "addView",
+ dex::Prototype{
+ dex::TypeDescriptor::Void(),
+ dex::TypeDescriptor::FromClassname("android.view.View"),
+ dex::TypeDescriptor::FromClassname("android.view.ViewGroup$LayoutParams")})},
+ // The register stack starts with one register, which will be null for the root view.
+ register_stack_{{method->MakeRegister()}} {}
+
+void DexViewBuilder::Start() {
+ dex::DexBuilder* const dex = method_->dex_file();
+
+ // LayoutInflater inflater = LayoutInflater.from(context);
+ auto layout_inflater_from = dex->GetOrDeclareMethod(
+ dex::TypeDescriptor::FromClassname("android.view.LayoutInflater"),
+ "from",
+ dex::Prototype{dex::TypeDescriptor::FromClassname("android.view.LayoutInflater"),
+ dex::TypeDescriptor::FromClassname("android.content.Context")});
+ method_->AddInstruction(
+ dex::Instruction::InvokeStaticObject(layout_inflater_from.id, /*dest=*/inflater_, context_));
+
+ // Resources res = context.getResources();
+ auto context_type = dex::TypeDescriptor::FromClassname("android.content.Context");
+ auto resources_type = dex::TypeDescriptor::FromClassname("android.content.res.Resources");
+ auto get_resources =
+ dex->GetOrDeclareMethod(context_type, "getResources", dex::Prototype{resources_type});
+ method_->AddInstruction(dex::Instruction::InvokeVirtualObject(get_resources.id, xml_, context_));
+
+ // XmlResourceParser xml = res.getLayout(resid);
+ auto xml_resource_parser_type =
+ dex::TypeDescriptor::FromClassname("android.content.res.XmlResourceParser");
+ auto get_layout =
+ dex->GetOrDeclareMethod(resources_type,
+ "getLayout",
+ dex::Prototype{xml_resource_parser_type, dex::TypeDescriptor::Int()});
+ method_->AddInstruction(dex::Instruction::InvokeVirtualObject(get_layout.id, xml_, xml_, resid_));
+
+ // AttributeSet attrs = Xml.asAttributeSet(xml);
+ auto as_attribute_set = dex->GetOrDeclareMethod(
+ dex::TypeDescriptor::FromClassname("android.util.Xml"),
+ "asAttributeSet",
+ dex::Prototype{dex::TypeDescriptor::FromClassname("android.util.AttributeSet"),
+ dex::TypeDescriptor::FromClassname("org.xmlpull.v1.XmlPullParser")});
+ method_->AddInstruction(dex::Instruction::InvokeStaticObject(as_attribute_set.id, attrs_, xml_));
+
+ // xml.next(); // start document
+ method_->AddInstruction(dex::Instruction::InvokeInterface(xml_next_.id, {}, xml_));
+}
+
+void DexViewBuilder::Finish() {}
+
+namespace {
+std::string ResolveName(const std::string& name) {
+ if (name == "View") return "android.view.View";
+ if (name == "ViewGroup") return "android.view.ViewGroup";
+ if (name.find(".") == std::string::npos) {
+ return StringPrintf("android.widget.%s", name.c_str());
+ }
+ return name;
+}
+} // namespace
+
+void DexViewBuilder::StartView(const std::string& name, bool is_viewgroup) {
+ bool const is_root_view = view_stack_.empty();
+
+ // xml.next(); // start tag
+ method_->AddInstruction(dex::Instruction::InvokeInterface(xml_next_.id, {}, xml_));
+
+ dex::Value view = AcquireRegister();
+ // try to create the view using the factories
+ method_->BuildConstString(classname_tmp_,
+ name); // TODO: the need to fully qualify the classname
+ if (is_root_view) {
+ dex::Value null = AcquireRegister();
+ method_->BuildConst4(null, 0);
+ method_->AddInstruction(dex::Instruction::InvokeVirtualObject(
+ try_create_view_.id, view, inflater_, null, classname_tmp_, context_, attrs_));
+ ReleaseRegister();
+ } else {
+ method_->AddInstruction(dex::Instruction::InvokeVirtualObject(
+ try_create_view_.id, view, inflater_, GetCurrentView(), classname_tmp_, context_, attrs_));
+ }
+ auto label = method_->MakeLabel();
+ // branch if not null
+ method_->AddInstruction(
+ dex::Instruction::OpWithArgs(dex::Instruction::Op::kBranchNEqz, /*dest=*/{}, view, label));
+
+ // If null, create the class directly.
+ method_->BuildNew(view,
+ dex::TypeDescriptor::FromClassname(ResolveName(name)),
+ dex::Prototype{dex::TypeDescriptor::Void(),
+ dex::TypeDescriptor::FromClassname("android.content.Context"),
+ dex::TypeDescriptor::FromClassname("android.util.AttributeSet")},
+ context_,
+ attrs_);
+
+ method_->AddInstruction(
+ dex::Instruction::OpWithArgs(dex::Instruction::Op::kBindLabel, /*dest=*/{}, label));
+
+ if (is_viewgroup) {
+ // Cast to a ViewGroup so we can add children later.
+ const ir::Type* view_group_def = method_->dex_file()->GetOrAddType(
+ dex::TypeDescriptor::FromClassname("android.view.ViewGroup").descriptor());
+ method_->AddInstruction(dex::Instruction::Cast(view, dex::Value::Type(view_group_def->orig_index)));
+ }
+
+ if (!is_root_view) {
+ // layout_params = parent.generateLayoutParams(attrs);
+ dex::Value layout_params{AcquireRegister()};
+ method_->AddInstruction(dex::Instruction::InvokeVirtualObject(
+ generate_layout_params_.id, layout_params, GetCurrentView(), attrs_));
+ view_stack_.push_back({view, layout_params});
+ } else {
+ view_stack_.push_back({view, {}});
+ }
+}
+
+void DexViewBuilder::FinishView() {
+ if (view_stack_.size() == 1) {
+ method_->BuildReturn(GetCurrentView(), /*is_object=*/true);
+ } else {
+ // parent.add(view, layout_params)
+ method_->AddInstruction(dex::Instruction::InvokeVirtual(
+ add_view_.id, /*dest=*/{}, GetParentView(), GetCurrentView(), GetCurrentLayoutParams()));
+ // xml.next(); // end tag
+ method_->AddInstruction(dex::Instruction::InvokeInterface(xml_next_.id, {}, xml_));
+ }
+ PopViewStack();
+}
+
+dex::Value DexViewBuilder::AcquireRegister() {
+ top_register_++;
+ if (register_stack_.size() == top_register_) {
+ register_stack_.push_back(method_->MakeRegister());
+ }
+ return register_stack_[top_register_];
+}
+
+void DexViewBuilder::ReleaseRegister() { top_register_--; }
+
+dex::Value DexViewBuilder::GetCurrentView() const { return view_stack_.back().view; }
+dex::Value DexViewBuilder::GetCurrentLayoutParams() const {
+ return view_stack_.back().layout_params.value();
+}
+dex::Value DexViewBuilder::GetParentView() const {
+ return view_stack_[view_stack_.size() - 2].view;
+}
+
+void DexViewBuilder::PopViewStack() {
+ const auto& top = view_stack_.back();
+ // release the layout params if we have them
+ if (top.layout_params.has_value()) {
+ ReleaseRegister();
+ }
+ // Unconditionally release the view register.
+ ReleaseRegister();
+ view_stack_.pop_back();
+}
+
+} // namespace startop
\ No newline at end of file
diff --git a/startop/view_compiler/dex_layout_compiler.h b/startop/view_compiler/dex_layout_compiler.h
new file mode 100644
index 0000000..170a1a6
--- /dev/null
+++ b/startop/view_compiler/dex_layout_compiler.h
@@ -0,0 +1,118 @@
+/*
+ * 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.
+ */
+
+#ifndef DEX_LAYOUT_COMPILER_H_
+#define DEX_LAYOUT_COMPILER_H_
+
+#include "dex_builder.h"
+
+#include <codecvt>
+#include <locale>
+#include <string>
+#include <vector>
+
+namespace startop {
+
+// This visitor does the actual view compilation, using a supplied builder.
+template <typename Builder>
+class LayoutCompilerVisitor {
+ public:
+ explicit LayoutCompilerVisitor(Builder* builder) : builder_{builder} {}
+
+ void VisitStartDocument() { builder_->Start(); }
+ void VisitEndDocument() { builder_->Finish(); }
+ void VisitStartTag(const std::u16string& name) {
+ parent_stack_.push_back(ViewEntry{
+ std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{}.to_bytes(name), {}});
+ }
+ void VisitEndTag() {
+ auto entry = parent_stack_.back();
+ parent_stack_.pop_back();
+
+ if (parent_stack_.empty()) {
+ GenerateCode(entry);
+ } else {
+ parent_stack_.back().children.push_back(entry);
+ }
+ }
+
+ private:
+ struct ViewEntry {
+ std::string name;
+ std::vector<ViewEntry> children;
+ };
+
+ void GenerateCode(const ViewEntry& view) {
+ builder_->StartView(view.name, !view.children.empty());
+ for (const auto& child : view.children) {
+ GenerateCode(child);
+ }
+ builder_->FinishView();
+ }
+
+ Builder* builder_;
+
+ std::vector<ViewEntry> parent_stack_;
+};
+
+class DexViewBuilder {
+ public:
+ DexViewBuilder(dex::MethodBuilder* method);
+
+ void Start();
+ void Finish();
+ void StartView(const std::string& name, bool is_viewgroup);
+ void FinishView();
+
+ private:
+ // Accessors for the stack of views that are under construction.
+ dex::Value AcquireRegister();
+ void ReleaseRegister();
+ dex::Value GetCurrentView() const;
+ dex::Value GetCurrentLayoutParams() const;
+ dex::Value GetParentView() const;
+ void PopViewStack();
+
+ dex::MethodBuilder* method_;
+
+ // Registers used for code generation
+ dex::Value const context_;
+ dex::Value const resid_;
+ const dex::Value inflater_;
+ const dex::Value xml_;
+ const dex::Value attrs_;
+ const dex::Value classname_tmp_;
+
+ const dex::MethodDeclData xml_next_;
+ const dex::MethodDeclData try_create_view_;
+ const dex::MethodDeclData generate_layout_params_;
+ const dex::MethodDeclData add_view_;
+
+ // used for keeping track of which registers are in use
+ size_t top_register_{0};
+ std::vector<dex::Value> register_stack_;
+
+ // Keep track of the views currently in progress.
+ struct ViewEntry {
+ dex::Value view;
+ std::optional<dex::Value> layout_params;
+ };
+ std::vector<ViewEntry> view_stack_;
+};
+
+} // namespace startop
+
+#endif // DEX_LAYOUT_COMPILER_H_
diff --git a/startop/view_compiler/java_lang_builder.cc b/startop/view_compiler/java_lang_builder.cc
index 0b8754f..920caee 100644
--- a/startop/view_compiler/java_lang_builder.cc
+++ b/startop/view_compiler/java_lang_builder.cc
@@ -67,7 +67,7 @@
"}\n"; // end CompiledView
}
-void JavaLangViewBuilder::StartView(const string& class_name) {
+void JavaLangViewBuilder::StartView(const string& class_name, bool /*is_viewgroup*/) {
const string view_var = MakeVar("view");
const string layout_var = MakeVar("layout");
std::string parent = "null";
diff --git a/startop/view_compiler/java_lang_builder.h b/startop/view_compiler/java_lang_builder.h
index c8d20b2..69356d3 100644
--- a/startop/view_compiler/java_lang_builder.h
+++ b/startop/view_compiler/java_lang_builder.h
@@ -35,7 +35,7 @@
void Finish() const;
// Begin creating a view (i.e. process the opening tag)
- void StartView(const std::string& class_name);
+ void StartView(const std::string& class_name, bool is_viewgroup);
// Finish a view, after all of its child nodes have been processed.
void FinishView();
diff --git a/startop/view_compiler/main.cc b/startop/view_compiler/main.cc
index 609bcf3..871a421 100644
--- a/startop/view_compiler/main.cc
+++ b/startop/view_compiler/main.cc
@@ -16,8 +16,12 @@
#include "gflags/gflags.h"
+#include "android-base/stringprintf.h"
+#include "apk_layout_compiler.h"
#include "dex_builder.h"
+#include "dex_layout_compiler.h"
#include "java_lang_builder.h"
+#include "layout_validation.h"
#include "tinyxml_layout_parser.h"
#include "util.h"
@@ -32,43 +36,60 @@
namespace {
using namespace tinyxml2;
+using android::base::StringPrintf;
+using startop::dex::ClassBuilder;
+using startop::dex::DexBuilder;
+using startop::dex::MethodBuilder;
+using startop::dex::Prototype;
+using startop::dex::TypeDescriptor;
using namespace startop::util;
using std::string;
constexpr char kStdoutFilename[]{"stdout"};
+DEFINE_bool(apk, false, "Compile layouts in an APK");
DEFINE_bool(dex, false, "Generate a DEX file instead of Java");
DEFINE_string(out, kStdoutFilename, "Where to write the generated class");
DEFINE_string(package, "", "The package name for the generated class (required)");
-class ViewCompilerXmlVisitor : public XMLVisitor {
+template <typename Visitor>
+class XmlVisitorAdapter : public XMLVisitor {
public:
- explicit ViewCompilerXmlVisitor(JavaLangViewBuilder* builder) : builder_(builder) {}
+ explicit XmlVisitorAdapter(Visitor* visitor) : visitor_{visitor} {}
bool VisitEnter(const XMLDocument& /*doc*/) override {
- builder_->Start();
+ visitor_->VisitStartDocument();
return true;
}
bool VisitExit(const XMLDocument& /*doc*/) override {
- builder_->Finish();
+ visitor_->VisitEndDocument();
return true;
}
bool VisitEnter(const XMLElement& element, const XMLAttribute* /*firstAttribute*/) override {
- builder_->StartView(element.Name());
+ visitor_->VisitStartTag(
+ std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{}.from_bytes(
+ element.Name()));
return true;
}
bool VisitExit(const XMLElement& /*element*/) override {
- builder_->FinishView();
+ visitor_->VisitEndTag();
return true;
}
private:
- JavaLangViewBuilder* builder_;
+ Visitor* visitor_;
};
+template <typename Builder>
+void CompileLayout(XMLDocument* xml, Builder* builder) {
+ startop::LayoutCompilerVisitor visitor{builder};
+ XmlVisitorAdapter<decltype(visitor)> adapter{&visitor};
+ xml->Accept(&adapter);
+}
+
} // end namespace
int main(int argc, char** argv) {
@@ -88,16 +109,23 @@
return 1;
}
- if (FLAGS_dex) {
- startop::dex::WriteTestDexFile("test.dex");
+ const char* const filename = argv[kFileNameParam];
+ const bool is_stdout = FLAGS_out == kStdoutFilename;
+
+ std::ofstream outfile;
+ if (!is_stdout) {
+ outfile.open(FLAGS_out);
+ }
+
+ if (FLAGS_apk) {
+ startop::CompileApkLayouts(
+ filename,
+ FLAGS_dex ? startop::CompilationTarget::kDex : startop::CompilationTarget::kJavaLanguage,
+ is_stdout ? std::cout : outfile);
return 0;
}
- const char* const filename = argv[kFileNameParam];
- const string layout_name = FindLayoutNameFromFilename(filename);
-
- // We want to generate Java language code to inflate exactly this layout. This means
- // generating code to walk the resource XML too.
+ const string layout_name = startop::util::FindLayoutNameFromFilename(filename);
XMLDocument xml;
xml.LoadFile(filename);
@@ -108,15 +136,27 @@
return 1;
}
- std::ofstream outfile;
- if (FLAGS_out != kStdoutFilename) {
- outfile.open(FLAGS_out);
+ if (FLAGS_dex) {
+ DexBuilder dex_file;
+ string class_name = StringPrintf("%s.CompiledView", FLAGS_package.c_str());
+ ClassBuilder compiled_view{dex_file.MakeClass(class_name)};
+ MethodBuilder method{compiled_view.CreateMethod(
+ layout_name,
+ Prototype{TypeDescriptor::FromClassname("android.view.View"),
+ TypeDescriptor::FromClassname("android.content.Context"),
+ TypeDescriptor::Int()})};
+ startop::DexViewBuilder builder{&method};
+ CompileLayout(&xml, &builder);
+ method.Encode();
+
+ slicer::MemView image{dex_file.CreateImage()};
+
+ (is_stdout ? std::cout : outfile).write(image.ptr<const char>(), image.size());
+ } else {
+ // Generate Java language output.
+ JavaLangViewBuilder builder{FLAGS_package, layout_name, is_stdout ? std::cout : outfile};
+
+ CompileLayout(&xml, &builder);
}
- JavaLangViewBuilder builder{
- FLAGS_package, layout_name, FLAGS_out == kStdoutFilename ? std::cout : outfile};
-
- ViewCompilerXmlVisitor visitor{&builder};
- xml.Accept(&visitor);
-
return 0;
}
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 2820836..dcaa499 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -239,6 +239,30 @@
"android.telecom.event.HANDOVER_FAILED";
public static class Details {
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = { "DIRECTION_" },
+ value = {DIRECTION_UNKNOWN, DIRECTION_INCOMING, DIRECTION_OUTGOING})
+ public @interface CallDirection {}
+
+ /**
+ * Indicates that the call is neither and incoming nor an outgoing call. This can be the
+ * case for calls reported directly by a {@link ConnectionService} in special cases such as
+ * call handovers.
+ */
+ public static final int DIRECTION_UNKNOWN = -1;
+
+ /**
+ * Indicates that the call is an incoming call.
+ */
+ public static final int DIRECTION_INCOMING = 0;
+
+ /**
+ * Indicates that the call is an outgoing call.
+ */
+ public static final int DIRECTION_OUTGOING = 1;
+
/** Call can currently be put on hold or unheld. */
public static final int CAPABILITY_HOLD = 0x00000001;
@@ -519,6 +543,7 @@
private final Bundle mIntentExtras;
private final long mCreationTimeMillis;
private final CallIdentification mCallIdentification;
+ private final @CallDirection int mCallDirection;
/**
* Whether the supplied capabilities supports the specified capability.
@@ -838,6 +863,14 @@
return mCallIdentification;
}
+ /**
+ * Indicates whether the call is an incoming or outgoing call.
+ * @return The call's direction.
+ */
+ public @CallDirection int getCallDirection() {
+ return mCallDirection;
+ }
+
@Override
public boolean equals(Object o) {
if (o instanceof Details) {
@@ -859,7 +892,8 @@
areBundlesEqual(mExtras, d.mExtras) &&
areBundlesEqual(mIntentExtras, d.mIntentExtras) &&
Objects.equals(mCreationTimeMillis, d.mCreationTimeMillis) &&
- Objects.equals(mCallIdentification, d.mCallIdentification);
+ Objects.equals(mCallIdentification, d.mCallIdentification) &&
+ Objects.equals(mCallDirection, d.mCallDirection);
}
return false;
}
@@ -881,7 +915,8 @@
mExtras,
mIntentExtras,
mCreationTimeMillis,
- mCallIdentification);
+ mCallIdentification,
+ mCallDirection);
}
/** {@hide} */
@@ -902,7 +937,8 @@
Bundle extras,
Bundle intentExtras,
long creationTimeMillis,
- CallIdentification callIdentification) {
+ CallIdentification callIdentification,
+ int callDirection) {
mTelecomCallId = telecomCallId;
mHandle = handle;
mHandlePresentation = handlePresentation;
@@ -920,6 +956,7 @@
mIntentExtras = intentExtras;
mCreationTimeMillis = creationTimeMillis;
mCallIdentification = callIdentification;
+ mCallDirection = callDirection;
}
/** {@hide} */
@@ -941,7 +978,8 @@
parcelableCall.getExtras(),
parcelableCall.getIntentExtras(),
parcelableCall.getCreationTimeMillis(),
- parcelableCall.getCallIdentification());
+ parcelableCall.getCallIdentification(),
+ parcelableCall.getCallDirection());
}
@Override
diff --git a/telecomm/java/android/telecom/CallIdentification.java b/telecomm/java/android/telecom/CallIdentification.java
index 97af06c..87834fd 100644
--- a/telecomm/java/android/telecom/CallIdentification.java
+++ b/telecomm/java/android/telecom/CallIdentification.java
@@ -250,8 +250,8 @@
mDetails = details;
mPhoto = photo;
mNuisanceConfidence = nuisanceConfidence;
- mCallScreeningAppName = callScreeningPackageName;
- mCallScreeningPackageName = callScreeningAppName;
+ mCallScreeningAppName = callScreeningAppName;
+ mCallScreeningPackageName = callScreeningPackageName;
}
private String mName;
@@ -430,4 +430,22 @@
return Objects.hash(mName, mDescription, mDetails, mPhoto, mNuisanceConfidence,
mCallScreeningAppName, mCallScreeningPackageName);
}
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("[CallId mName=");
+ sb.append(Log.pii(mName));
+ sb.append(", mDesc=");
+ sb.append(mDescription);
+ sb.append(", mDet=");
+ sb.append(mDetails);
+ sb.append(", conf=");
+ sb.append(mNuisanceConfidence);
+ sb.append(", appName=");
+ sb.append(mCallScreeningAppName);
+ sb.append(", pkgName=");
+ sb.append(mCallScreeningPackageName);
+ return sb.toString();
+ }
}
diff --git a/telecomm/java/android/telecom/CallRedirectionService.java b/telecomm/java/android/telecom/CallRedirectionService.java
index b906d0b..3299117 100644
--- a/telecomm/java/android/telecom/CallRedirectionService.java
+++ b/telecomm/java/android/telecom/CallRedirectionService.java
@@ -16,6 +16,7 @@
package android.telecom;
+import android.annotation.NonNull;
import android.annotation.SdkConstant;
import android.app.Service;
import android.content.Intent;
@@ -27,8 +28,8 @@
import android.os.RemoteException;
import com.android.internal.os.SomeArgs;
-import com.android.internal.telecom.ICallRedirectionService;
import com.android.internal.telecom.ICallRedirectionAdapter;
+import com.android.internal.telecom.ICallRedirectionService;
/**
* This service can be implemented to interact between Telecom and its implementor
@@ -62,22 +63,35 @@
/**
* Telecom calls this method to inform the implemented {@link CallRedirectionService} of
- * a new outgoing call which is being placed.
+ * a new outgoing call which is being placed. Telecom does not request to redirect emergency
+ * calls and does not request to redirect calls with gateway information.
*
- * The implemented {@link CallRedirectionService} can call {@link #placeCallUnmodified()},
- * {@link #redirectCall(Uri, PhoneAccountHandle)}, and {@link #cancelCall()} only from here.
+ * <p>Telecom will cancel the call if Telecom does not receive a response in 5 seconds from
+ * the implemented {@link CallRedirectionService} set by users.
*
- * @param handle the phone number dialed by the user
- * @param targetPhoneAccount the {@link PhoneAccountHandle} on which the call will be placed.
+ * <p>The implemented {@link CallRedirectionService} can call {@link #placeCallUnmodified()},
+ * {@link #redirectCall(Uri, PhoneAccountHandle, boolean)}, and {@link #cancelCall()} only
+ * from here.
+ *
+ * @param handle the phone number dialed by the user, represented in E.164 format if possible
+ * @param initialPhoneAccount the {@link PhoneAccountHandle} on which the call will be placed.
+ * @param allowInteractiveResponse a boolean to tell if the implemented
+ * {@link CallRedirectionService} should allow interactive
+ * responses with users. Will be {@code false} if, for example
+ * the device is in car mode and the user would not be able to
+ * interact with their device.
*/
- public abstract void onPlaceCall(Uri handle, PhoneAccountHandle targetPhoneAccount);
+ public abstract void onPlaceCall(@NonNull Uri handle,
+ @NonNull PhoneAccountHandle initialPhoneAccount,
+ boolean allowInteractiveResponse);
/**
* The implemented {@link CallRedirectionService} calls this method to response a request
- * received via {@link #onPlaceCall(Uri, PhoneAccountHandle)} to inform Telecom that no changes
- * are required to the outgoing call, and that the call should be placed as-is.
+ * received via {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)} to inform Telecom that
+ * no changes are required to the outgoing call, and that the call should be placed as-is.
*
- * This can only be called from implemented {@link #onPlaceCall(Uri, PhoneAccountHandle)}.
+ * <p>This can only be called from implemented
+ * {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)}.
*
*/
public final void placeCallUnmodified() {
@@ -89,29 +103,39 @@
/**
* The implemented {@link CallRedirectionService} calls this method to response a request
- * received via {@link #onPlaceCall(Uri, PhoneAccountHandle)} to inform Telecom that changes
- * are required to the phone number or/and {@link PhoneAccountHandle} for the outgoing call.
+ * received via {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)} to inform Telecom that
+ * changes are required to the phone number or/and {@link PhoneAccountHandle} for the outgoing
+ * call. Telecom will cancel the call if the implemented {@link CallRedirectionService}
+ * replies Telecom a handle for an emergency number.
*
- * This can only be called from implemented {@link #onPlaceCall(Uri, PhoneAccountHandle)}.
+ * <p>This can only be called from implemented
+ * {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)}.
*
* @param handle the new phone number to dial
* @param targetPhoneAccount the {@link PhoneAccountHandle} to use when placing the call.
* If {@code null}, no change will be made to the
* {@link PhoneAccountHandle} used to place the call.
+ * @param confirmFirst Telecom will ask users to confirm the redirection via a yes/no dialog
+ * if the confirmFirst is true, and if the redirection request of this
+ * response was sent with a true flag of allowInteractiveResponse via
+ * {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)}
*/
- public final void redirectCall(Uri handle, PhoneAccountHandle targetPhoneAccount) {
+ public final void redirectCall(@NonNull Uri handle,
+ @NonNull PhoneAccountHandle targetPhoneAccount,
+ boolean confirmFirst) {
try {
- mCallRedirectionAdapter.redirectCall(handle, targetPhoneAccount);
+ mCallRedirectionAdapter.redirectCall(handle, targetPhoneAccount, confirmFirst);
} catch (RemoteException e) {
}
}
/**
* The implemented {@link CallRedirectionService} calls this method to response a request
- * received via {@link #onPlaceCall(Uri, PhoneAccountHandle)} to inform Telecom that an outgoing
- * call should be canceled entirely.
+ * received via {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)} to inform Telecom that
+ * an outgoing call should be canceled entirely.
*
- * This can only be called from implemented {@link #onPlaceCall(Uri, PhoneAccountHandle)}.
+ * <p>This can only be called from implemented
+ * {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)}.
*
*/
public final void cancelCall() {
@@ -137,7 +161,8 @@
SomeArgs args = (SomeArgs) msg.obj;
try {
mCallRedirectionAdapter = (ICallRedirectionAdapter) args.arg1;
- onPlaceCall((Uri) args.arg2, (PhoneAccountHandle) args.arg3);
+ onPlaceCall((Uri) args.arg2, (PhoneAccountHandle) args.arg3,
+ (boolean) args.arg4);
} finally {
args.recycle();
}
@@ -152,15 +177,20 @@
* Telecom calls this method to inform the CallRedirectionService of a new outgoing call
* which is about to be placed.
* @param handle the phone number dialed by the user
- * @param targetPhoneAccount the URI of the number the user dialed
+ * @param initialPhoneAccount the URI of the number the user dialed
+ * @param allowInteractiveResponse a boolean to tell if the implemented
+ * {@link CallRedirectionService} should allow interactive
+ * responses with users.
*/
@Override
- public void placeCall(ICallRedirectionAdapter adapter, Uri handle,
- PhoneAccountHandle targetPhoneAccount) {
+ public void placeCall(@NonNull ICallRedirectionAdapter adapter, @NonNull Uri handle,
+ @NonNull PhoneAccountHandle initialPhoneAccount,
+ boolean allowInteractiveResponse) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = adapter;
args.arg2 = handle;
- args.arg3 = targetPhoneAccount;
+ args.arg3 = initialPhoneAccount;
+ args.arg4 = allowInteractiveResponse;
mHandler.obtainMessage(MSG_PLACE_CALL, args).sendToTarget();
}
}
diff --git a/telecomm/java/android/telecom/CallScreeningService.java b/telecomm/java/android/telecom/CallScreeningService.java
index be96b3c..826ad82 100644
--- a/telecomm/java/android/telecom/CallScreeningService.java
+++ b/telecomm/java/android/telecom/CallScreeningService.java
@@ -21,6 +21,7 @@
import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
+import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -33,8 +34,9 @@
/**
* This service can be implemented by the default dialer (see
- * {@link TelecomManager#getDefaultDialerPackage()}) to allow or disallow incoming calls before
- * they are shown to a user.
+ * {@link TelecomManager#getDefaultDialerPackage()}) or a third party app to allow or disallow
+ * incoming calls before they are shown to a user. This service can also provide
+ * {@link CallIdentification} information for calls.
* <p>
* Below is an example manifest registration for a {@code CallScreeningService}.
* <pre>
@@ -56,6 +58,34 @@
* information about a {@link Call.Details call} which will be shown to the user in the
* Dialer app.</li>
* </ol>
+ * <p>
+ * <h2>Becoming the {@link CallScreeningService}</h2>
+ * Telecom will bind to a single app chosen by the user which implements the
+ * {@link CallScreeningService} API when there are new incoming and outgoing calls.
+ * <p>
+ * The code snippet below illustrates how your app can request that it fills the call screening
+ * role.
+ * <pre>
+ * {@code
+ * private static final int REQUEST_ID = 1;
+ *
+ * public void requestRole() {
+ * RoleManager roleManager = (RoleManager) getSystemService(ROLE_SERVICE);
+ * Intent intent = roleManager.createRequestRoleIntent("android.app.role.CALL_SCREENING_APP");
+ * startActivityForResult(intent, REQUEST_ID);
+ * }
+ *
+ * @Override
+ * public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ * if (requestCode == REQUEST_ID) {
+ * if (resultCode == android.app.Activity.RESULT_OK) {
+ * // Your app is now the call screening app
+ * } else {
+ * // Your app is not the call screening app
+ * }
+ * }
+ * }
+ * </pre>
*/
public abstract class CallScreeningService extends Service {
/**
@@ -222,30 +252,46 @@
}
/**
- * Called when a new incoming call is added.
- * {@link CallScreeningService#respondToCall(Call.Details, CallScreeningService.CallResponse)}
- * should be called to allow or disallow the call.
+ * Called when a new incoming or outgoing call is added which is not in the user's contact list.
+ * <p>
+ * A {@link CallScreeningService} must indicate whether an incoming call is allowed or not by
+ * calling
+ * {@link CallScreeningService#respondToCall(Call.Details, CallScreeningService.CallResponse)}.
+ * Your app can tell if a call is an incoming call by checking to see if
+ * {@link Call.Details#getCallDirection()} is {@link Call.Details#DIRECTION_INCOMING}.
+ * <p>
+ * For incoming or outgoing calls, the {@link CallScreeningService} can call
+ * {@link #provideCallIdentification(Call.Details, CallIdentification)} in order to provide
+ * {@link CallIdentification} for the call.
* <p>
* Note: The {@link Call.Details} instance provided to a call screening service will only have
* the following properties set. The rest of the {@link Call.Details} properties will be set to
* their default value or {@code null}.
* <ul>
- * <li>{@link Call.Details#getState()}</li>
+ * <li>{@link Call.Details#getCallDirection()}</li>
* <li>{@link Call.Details#getConnectTimeMillis()}</li>
* <li>{@link Call.Details#getCreationTimeMillis()}</li>
* <li>{@link Call.Details#getHandle()}</li>
* <li>{@link Call.Details#getHandlePresentation()}</li>
* </ul>
+ * <p>
+ * Only calls where the {@link Call.Details#getHandle() handle} {@link Uri#getScheme() scheme}
+ * is {@link PhoneAccount#SCHEME_TEL} are passed for call
+ * screening. Further, only calls which are not in the user's contacts are passed for
+ * screening. For outgoing calls, no post-dial digits are passed.
*
- * @param callDetails Information about a new incoming call, see {@link Call.Details}.
+ * @param callDetails Information about a new call, see {@link Call.Details}.
*/
public abstract void onScreenCall(@NonNull Call.Details callDetails);
/**
- * Responds to the given call, either allowing it or disallowing it.
+ * Responds to the given incoming call, either allowing it or disallowing it.
* <p>
* The {@link CallScreeningService} calls this method to inform the system whether the call
* should be silently blocked or not.
+ * <p>
+ * Calls to this method are ignored unless the {@link Call.Details#getCallDirection()} is
+ * {@link Call.Details#DIRECTION_INCOMING}.
*
* @param callDetails The call to allow.
* <p>
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index 1aeeca7..f5f0af7 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -40,11 +40,30 @@
import java.util.List;
/**
- * This service is implemented by any app that wishes to provide the user-interface for managing
- * phone calls. Telecom binds to this service while there exists a live (active or incoming) call,
- * and uses it to notify the in-call app of any live and recently disconnected calls. An app must
- * first be set as the default phone app (See {@link TelecomManager#getDefaultDialerPackage()})
- * before the telecom service will bind to its {@code InCallService} implementation.
+ * This service is implemented by an app that wishes to provide functionality for managing
+ * phone calls.
+ * <p>
+ * There are three types of apps which Telecom can bind to when there exists a live (active or
+ * incoming) call:
+ * <ol>
+ * <li>Default Dialer/Phone app - the default dialer/phone app is one which provides the
+ * in-call user interface while the device is in a call. A device is bundled with a system
+ * provided default dialer/phone app. The user may choose a single app to take over this role
+ * from the system app.</li>
+ * <li>Default Car-mode Dialer/Phone app - the default car-mode dialer/phone app is one which
+ * provides the in-call user interface while the device is in a call and the device is in car
+ * mode. The user may choose a single app to fill this role.</li>
+ * <li>Call Companion app - a call companion app is one which provides no user interface itself,
+ * but exposes call information to another display surface, such as a wearable device. The
+ * user may choose multiple apps to fill this role.</li>
+ * </ol>
+ * <p>
+ * Apps which wish to fulfill one of the above roles use the {@link android.app.role.RoleManager}
+ * to request that they fill the desired role.
+ *
+ * <h2>Becoming the Default Phone App</h2>
+ * An app filling the role of the default phone app provides a user interface while the device is in
+ * a call, and the device is not in car mode.
* <p>
* Below is an example manifest registration for an {@code InCallService}. The meta-data
* {@link TelecomManager#METADATA_IN_CALL_SERVICE_UI} indicates that this particular
@@ -82,12 +101,34 @@
* }
* </pre>
* <p>
- * When a user installs your application and runs it for the first time, you should prompt the user
- * to see if they would like your application to be the new default phone app. See the
- * {@link TelecomManager#ACTION_CHANGE_DEFAULT_DIALER} intent documentation for more information on
- * how to do this.
+ * When a user installs your application and runs it for the first time, you should use the
+ * {@link android.app.role.RoleManager} to prompt the user to see if they would like your app to
+ * be the new default phone app.
+ * <p id="requestRole">
+ * The code below shows how your app can request to become the default phone/dialer app:
+ * <pre>
+ * {@code
+ * private static final int REQUEST_ID = 1;
+ *
+ * public void requestRole() {
+ * RoleManager roleManager = (RoleManager) getSystemService(ROLE_SERVICE);
+ * Intent intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_DIALER);
+ * startActivityForResult(intent, REQUEST_ID);
+ * }
+ *
+ * @Override
+ * public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ * if (requestCode == REQUEST_ID) {
+ * if (resultCode == android.app.Activity.RESULT_OK) {
+ * // Your app is now the default dialer app
+ * } else {
+ * // Your app is not the default dialer app
+ * }
+ * }
+ * }
+ * </pre>
* <p id="incomingCallNotification">
- * <h2>Showing the Incoming Call Notification</h2>
+ * <h3>Showing the Incoming Call Notification</h3>
* When your app receives a new incoming call via {@link InCallService#onCallAdded(Call)}, it is
* responsible for displaying an incoming call UI for the incoming call. It should do this using
* {@link android.app.NotificationManager} APIs to post a new incoming call notification.
@@ -121,7 +162,7 @@
* heads-up notification if the user is actively using the phone. When the user is not using the
* phone, your full-screen incoming call UI is used instead.
* For example:
- * <pre><code>
+ * <pre><code>{@code
* // Create an intent which triggers your fullscreen incoming call user interface.
* Intent intent = new Intent(Intent.ACTION_MAIN, null);
* intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -151,7 +192,49 @@
* NotificationManager notificationManager = mContext.getSystemService(
* NotificationManager.class);
* notificationManager.notify(YOUR_CHANNEL_ID, YOUR_TAG, YOUR_ID, builder.build());
- * </code></pre>
+ * }</pre>
+ * <p>
+ * <h2>Becoming the Default Car-mode Phone App</h2>
+ * An app filling the role of the default car-mode dialer/phone app provides a user interface while
+ * the device is in a call, and in car mode. See
+ * {@link android.app.UiModeManager#ACTION_ENTER_CAR_MODE} for more information about car mode.
+ * When the device is in car mode, Telecom binds to the default car-mode dialer/phone app instead
+ * of the usual dialer/phone app.
+ * <p>
+ * Similar to the requirements for becoming the default dialer/phone app, your app must declare a
+ * manifest entry for its {@link InCallService} implementation. Your manifest entry should ensure
+ * the following conditions are met:
+ * <ul>
+ * <li>Do NOT declare the {@link TelecomManager#METADATA_IN_CALL_SERVICE_UI} metadata.</li>
+ * <li>Set the {@link TelecomManager#METADATA_IN_CALL_SERVICE_CAR_MODE_UI} metadata to
+ * {@code true}<li>
+ * <li>Your app must request the permission
+ * {@link android.Manifest.permission.CALL_COMPANION_APP}.</li>
+ * </ul>
+ * <p>
+ * Your app should request to fill the role {@code android.app.role.CAR_MODE_DIALER_APP} in order to
+ * become the default (see <a href="#requestRole">above</a> for how to request your app fills this
+ * role).
+ *
+ * <h2>Becoming a Call Companion App</h2>
+ * An app which fills the companion app role does not directly provide a user interface while the
+ * device is in a call. Instead, it is typically used to relay information about calls to another
+ * display surface, such as a wearable device.
+ * <p>
+ * Similar to the requirements for becoming the default dialer/phone app, your app must declare a
+ * manifest entry for its {@link InCallService} implementation. Your manifest entry should
+ * ensure the following conditions are met:
+ * <ul>
+ * <li>Do NOT declare the {@link TelecomManager#METADATA_IN_CALL_SERVICE_UI} metadata.</li>
+ * <li>Do NOT declare the {@link TelecomManager#METADATA_IN_CALL_SERVICE_CAR_MODE_UI}
+ * metadata.</li>
+ * <li>Your app must request the permission
+ * {@link android.Manifest.permission.CALL_COMPANION_APP}.</li>
+ * </ul>
+ * <p>
+ * Your app should request to fill the role {@code android.app.role.CALL_COMPANION_APP} in order to
+ * become a call companion app (see <a href="#requestRole">above</a> for how to request your app
+ * fills this role).
*/
public abstract class InCallService extends Service {
diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java
index 911786e..f7dec83 100644
--- a/telecomm/java/android/telecom/ParcelableCall.java
+++ b/telecomm/java/android/telecom/ParcelableCall.java
@@ -24,6 +24,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
+import android.telecom.Call.Details.CallDirection;
import java.util.ArrayList;
import java.util.Collections;
@@ -64,6 +65,7 @@
private final Bundle mExtras;
private final long mCreationTimeMillis;
private final CallIdentification mCallIdentification;
+ private final int mCallDirection;
public ParcelableCall(
String id,
@@ -92,7 +94,8 @@
Bundle intentExtras,
Bundle extras,
long creationTimeMillis,
- CallIdentification callIdentification) {
+ CallIdentification callIdentification,
+ int callDirection) {
mId = id;
mState = state;
mDisconnectCause = disconnectCause;
@@ -120,6 +123,7 @@
mExtras = extras;
mCreationTimeMillis = creationTimeMillis;
mCallIdentification = callIdentification;
+ mCallDirection = callDirection;
}
/** The unique ID of the call. */
@@ -318,6 +322,13 @@
return mCallIdentification;
}
+ /**
+ * Indicates whether the call is an incoming or outgoing call.
+ */
+ public @CallDirection int getCallDirection() {
+ return mCallDirection;
+ }
+
/** Responsible for creating ParcelableCall objects for deserialized Parcels. */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public static final Parcelable.Creator<ParcelableCall> CREATOR =
@@ -356,6 +367,7 @@
ParcelableRttCall rttCall = source.readParcelable(classLoader);
long creationTimeMillis = source.readLong();
CallIdentification callIdentification = source.readParcelable(classLoader);
+ int callDirection = source.readInt();
return new ParcelableCall(
id,
state,
@@ -383,7 +395,8 @@
intentExtras,
extras,
creationTimeMillis,
- callIdentification);
+ callIdentification,
+ callDirection);
}
@Override
@@ -429,6 +442,7 @@
destination.writeParcelable(mRttCall, 0);
destination.writeLong(mCreationTimeMillis);
destination.writeParcelable(mCallIdentification, 0);
+ destination.writeInt(mCallDirection);
}
@Override
diff --git a/telecomm/java/com/android/internal/telecom/ICallRedirectionAdapter.aidl b/telecomm/java/com/android/internal/telecom/ICallRedirectionAdapter.aidl
index 46bf983..0a42a3f 100644
--- a/telecomm/java/com/android/internal/telecom/ICallRedirectionAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/ICallRedirectionAdapter.aidl
@@ -31,5 +31,6 @@
void placeCallUnmodified();
- void redirectCall(in Uri handle, in PhoneAccountHandle targetPhoneAccount);
+ void redirectCall(in Uri handle, in PhoneAccountHandle targetPhoneAccount,
+ boolean confirmFirst);
}
diff --git a/telecomm/java/com/android/internal/telecom/ICallRedirectionService.aidl b/telecomm/java/com/android/internal/telecom/ICallRedirectionService.aidl
index d8d360b..c1bc440 100644
--- a/telecomm/java/com/android/internal/telecom/ICallRedirectionService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ICallRedirectionService.aidl
@@ -30,5 +30,5 @@
*/
oneway interface ICallRedirectionService {
void placeCall(in ICallRedirectionAdapter adapter, in Uri handle,
- in PhoneAccountHandle targetPhoneAccount);
+ in PhoneAccountHandle initialPhoneAccount, boolean allowInteractiveResponse);
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index a9a128b..312b318 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -2231,7 +2231,7 @@
* e.g.) To use RSCP by default, set the value to "rscp". The signal strength level will
* then be determined by #KEY_WCDMA_RSCP_THRESHOLDS_INT_ARRAY
* <p>
- * Currently this only supports the value "rscp"
+ * Currently this supports the value "rscp" and "rssi".
* @hide
*/
// FIXME: this key and related keys must not be exposed without a consistent philosophy for
@@ -2875,7 +2875,7 @@
-95, /* SIGNAL_STRENGTH_GOOD */
-85 /* SIGNAL_STRENGTH_GREAT */
});
- sDefaults.putString(KEY_WCDMA_DEFAULT_SIGNAL_STRENGTH_MEASUREMENT_STRING, "");
+ sDefaults.putString(KEY_WCDMA_DEFAULT_SIGNAL_STRENGTH_MEASUREMENT_STRING, "rssi");
sDefaults.putBoolean(KEY_CONFIG_SHOW_ORIG_DIAL_STRING_FOR_CDMA_BOOL, false);
sDefaults.putBoolean(KEY_SHOW_CALL_BLOCKING_DISABLED_NOTIFICATION_ALWAYS_BOOL, false);
sDefaults.putBoolean(KEY_CALL_FORWARDING_OVER_UT_WARNING_BOOL, false);
diff --git a/telephony/java/android/telephony/CellSignalStrength.java b/telephony/java/android/telephony/CellSignalStrength.java
index f2c14a1..e6182ed 100644
--- a/telephony/java/android/telephony/CellSignalStrength.java
+++ b/telephony/java/android/telephony/CellSignalStrength.java
@@ -42,6 +42,9 @@
public static final int NUM_SIGNAL_STRENGTH_BINS = 5;
/** @hide */
+ protected static final int NUM_SIGNAL_STRENGTH_THRESHOLDS = NUM_SIGNAL_STRENGTH_BINS - 1;
+
+ /** @hide */
public static final String[] SIGNAL_STRENGTH_NAMES = {
"none", "poor", "moderate", "good", "great"
};
diff --git a/telephony/java/android/telephony/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java
index 893dbe3..61c6b48 100644
--- a/telephony/java/android/telephony/CellSignalStrengthLte.java
+++ b/telephony/java/android/telephony/CellSignalStrengthLte.java
@@ -30,7 +30,7 @@
public final class CellSignalStrengthLte extends CellSignalStrength implements Parcelable {
private static final String LOG_TAG = "CellSignalStrengthLte";
- private static final boolean DBG = true;
+ private static final boolean DBG = false;
/**
* Indicates the unknown or undetectable RSSI value in ASU.
diff --git a/telephony/java/android/telephony/CellSignalStrengthWcdma.java b/telephony/java/android/telephony/CellSignalStrengthWcdma.java
index 88f6fbc..0760407 100644
--- a/telephony/java/android/telephony/CellSignalStrengthWcdma.java
+++ b/telephony/java/android/telephony/CellSignalStrengthWcdma.java
@@ -21,6 +21,7 @@
import android.os.Parcelable;
import android.os.PersistableBundle;
import android.telephony.Rlog;
+import android.text.TextUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -41,8 +42,18 @@
private static final int WCDMA_RSSI_POOR = -107;
private static final int WCDMA_RSSI_MIN = -113;
- private static final int WCDMA_RSCP_MIN = -120;
+ private static final int[] sRssiThresholds = new int[]{
+ WCDMA_RSSI_POOR, WCDMA_RSSI_MODERATE, WCDMA_RSSI_GOOD, WCDMA_RSSI_GREAT};
+
private static final int WCDMA_RSCP_MAX = -24;
+ private static final int WCDMA_RSCP_GREAT = -85;
+ private static final int WCDMA_RSCP_GOOD = -95;
+ private static final int WCDMA_RSCP_MODERATE = -105;
+ private static final int WCDMA_RSCP_POOR = -115;
+ private static final int WCDMA_RSCP_MIN = -120;
+
+ private static final int[] sRscpThresholds = new int[] {
+ WCDMA_RSCP_POOR, WCDMA_RSCP_MODERATE, WCDMA_RSCP_GOOD, WCDMA_RSCP_GREAT};
// TODO: Because these are used as values in CarrierConfig, they should be exposed somehow.
/** @hide */
@@ -54,6 +65,9 @@
/** @hide */
public static final String LEVEL_CALCULATION_METHOD_RSCP = "rscp";
+ // Default to RSSI for backwards compatibility with older devices
+ private static final String sLevelCalculationMethod = LEVEL_CALCULATION_METHOD_RSSI;
+
private int mRssi; // in dBm [-113, 51] or CellInfo.UNAVAILABLE if unknown
private int mBitErrorRate; // bit error rate (0-7, 99) as defined in TS 27.007 8.5 or
// CellInfo.UNAVAILABLE if unknown
@@ -121,10 +135,6 @@
mLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
}
- private static final String sLevelCalculationMethod = LEVEL_CALCULATION_METHOD_RSSI;
- private static final int[] sThresholds = new int[]{
- WCDMA_RSSI_POOR, WCDMA_RSSI_GOOD, WCDMA_RSSI_GOOD, WCDMA_RSSI_GREAT};
-
/**
* Retrieve an abstract level value for the overall signal strength.
*
@@ -140,41 +150,46 @@
@Override
public void updateLevel(PersistableBundle cc, ServiceState ss) {
String calcMethod;
- int[] thresholds;
+ int[] rscpThresholds;
if (cc == null) {
calcMethod = sLevelCalculationMethod;
- thresholds = sThresholds;
+ rscpThresholds = sRscpThresholds;
} else {
// TODO: abstract this entire thing into a series of functions
calcMethod = cc.getString(
CarrierConfigManager.KEY_WCDMA_DEFAULT_SIGNAL_STRENGTH_MEASUREMENT_STRING,
sLevelCalculationMethod);
- thresholds = cc.getIntArray(
+ if (TextUtils.isEmpty(calcMethod)) calcMethod = sLevelCalculationMethod;
+ rscpThresholds = cc.getIntArray(
CarrierConfigManager.KEY_WCDMA_RSCP_THRESHOLDS_INT_ARRAY);
- if (thresholds == null) thresholds = sThresholds;
+ if (rscpThresholds == null || rscpThresholds.length != NUM_SIGNAL_STRENGTH_THRESHOLDS) {
+ rscpThresholds = sRscpThresholds;
+ }
}
- int level = thresholds.length;
+ int level = NUM_SIGNAL_STRENGTH_THRESHOLDS;
switch (calcMethod) {
case LEVEL_CALCULATION_METHOD_RSCP:
if (mRscp < WCDMA_RSCP_MIN || mRscp > WCDMA_RSCP_MAX) {
mLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
return;
}
- while (level > 0 && mRscp < thresholds[level - 1]) level--;
+ while (level > 0 && mRscp < rscpThresholds[level - 1]) level--;
mLevel = level;
return;
+ default:
+ loge("Invalid Level Calculation Method for CellSignalStrengthWcdma = "
+ + calcMethod);
+ /** fall through */
case LEVEL_CALCULATION_METHOD_RSSI:
if (mRssi < WCDMA_RSSI_MIN || mRssi > WCDMA_RSSI_MAX) {
mLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
return;
}
- while (level > 0 && mRssi < thresholds[level - 1]) level--;
+ while (level > 0 && mRssi < sRssiThresholds[level - 1]) level--;
mLevel = level;
return;
- default:
- mLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
}
}
@@ -204,7 +219,7 @@
}
/**
- * Get the signal strength as dBm
+ * Get the RSSI as dBm
*
* @hide
*/
@@ -214,12 +229,32 @@
/**
* Get the RSCP as dBm
+ *
* @hide
*/
public int getRscp() {
return mRscp;
}
+ /**
+ * Get the Ec/No as dB
+ *
+ * @hide
+ */
+ public int getEcNo() {
+ return mEcNo;
+ }
+
+ /**
+ * Return the Bit Error Rate
+ *
+ * @returns the bit error rate (0-7, 99) as defined in TS 27.007 8.5 or UNAVAILABLE.
+ * @hide
+ */
+ public int getBitErrorRate() {
+ return mBitErrorRate;
+ }
+
@Override
public int hashCode() {
return Objects.hash(mRssi, mBitErrorRate, mRscp, mEcNo, mLevel);
@@ -304,9 +339,16 @@
};
/**
- * log
+ * log warning
*/
private static void log(String s) {
Rlog.w(LOG_TAG, s);
}
+
+ /**
+ * log error
+ */
+ private static void loge(String s) {
+ Rlog.e(LOG_TAG, s);
+ }
}
diff --git a/telephony/java/android/telephony/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java
index c53b37d..26ec6de 100644
--- a/telephony/java/android/telephony/DataFailCause.java
+++ b/telephony/java/android/telephony/DataFailCause.java
@@ -17,6 +17,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
import android.content.Context;
import android.os.PersistableBundle;
@@ -34,6 +35,7 @@
* Returned as the reason for a data connection failure as defined by modem and some local errors.
* @hide
*/
+@SystemApi
public final class DataFailCause {
/** There is no failure */
public static final int NONE = 0;
@@ -101,8 +103,8 @@
public static final int PDN_CONN_DOES_NOT_EXIST = 0x36;
/** Multiple connections to a same PDN is not allowed. */
public static final int MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED = 0x37;
- /** Packet Data Protocol (PDP) */
- public static final int MAX_ACTIVE_PDP_CONTEXT_REACHED = 0x41;
+ /** Max number of Packet Data Protocol (PDP) context reached. */
+ public static final int ACTIVE_PDP_CONTEXT_MAX_NUMBER_REACHED = 0x41;
/** Unsupported APN in current public land mobile network (PLMN). */
public static final int UNSUPPORTED_APN_IN_CURRENT_PLMN = 0x42;
/** Invalid transaction id. */
@@ -165,22 +167,36 @@
// Local errors generated by Vendor RIL
// specified in ril.h
+ /** Data fail due to registration failure. */
public static final int REGISTRATION_FAIL = -1;
+ /** Data fail due to GPRS registration failure. */
public static final int GPRS_REGISTRATION_FAIL = -2;
+ /** Data call drop due to network/modem disconnect. */
public static final int SIGNAL_LOST = -3; /* no retry */
+ /**
+ * Preferred technology has changed, must retry with parameters appropriate for new technology.
+ */
public static final int PREF_RADIO_TECH_CHANGED = -4;
+ /** data call was disconnected because radio was resetting, powered off. */
public static final int RADIO_POWER_OFF = -5; /* no retry */
+ /** Data call was disconnected by modem because tethered. */
public static final int TETHERED_CALL_ACTIVE = -6; /* no retry */
+ /** Data call fail due to unspecific errors. */
public static final int ERROR_UNSPECIFIED = 0xFFFF;
// Errors generated by the Framework
// specified here
+ /** Unknown data failure cause. */
public static final int UNKNOWN = 0x10000;
+ /** Data fail due to radio not unavailable. */
public static final int RADIO_NOT_AVAILABLE = 0x10001; /* no retry */
+ /** @hide */
public static final int UNACCEPTABLE_NETWORK_PARAMETER = 0x10002; /* no retry */
+ /** @hide */
public static final int CONNECTION_TO_DATACONNECTIONAC_BROKEN = 0x10003;
+ /** Data connection was lost. */
public static final int LOST_CONNECTION = 0x10004;
- /** Data was reset by framework. */
+ /** @hide */
public static final int RESET_BY_FRAMEWORK = 0x10005;
/** @hide */
@@ -216,7 +232,7 @@
ESM_INFO_NOT_RECEIVED,
PDN_CONN_DOES_NOT_EXIST,
MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED,
- MAX_ACTIVE_PDP_CONTEXT_REACHED,
+ ACTIVE_PDP_CONTEXT_MAX_NUMBER_REACHED,
UNSUPPORTED_APN_IN_CURRENT_PLMN,
INVALID_TRANSACTION_ID,
MESSAGE_INCORRECT_SEMANTIC,
@@ -308,8 +324,8 @@
sFailCauseMap.put(PDN_CONN_DOES_NOT_EXIST, "PDN_CONN_DOES_NOT_EXIST");
sFailCauseMap.put(MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED,
"MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED");
- sFailCauseMap.put(MAX_ACTIVE_PDP_CONTEXT_REACHED,
- "MAX_ACTIVE_PDP_CONTEXT_REACHED");
+ sFailCauseMap.put(ACTIVE_PDP_CONTEXT_MAX_NUMBER_REACHED,
+ "ACTIVE_PDP_CONTEXT_MAX_NUMBER_REACHED");
sFailCauseMap.put(UNSUPPORTED_APN_IN_CURRENT_PLMN,
"UNSUPPORTED_APN_IN_CURRENT_PLMN");
sFailCauseMap.put(INVALID_TRANSACTION_ID, "INVALID_TRANSACTION_ID");
@@ -369,6 +385,9 @@
sFailCauseMap.put(RESET_BY_FRAMEWORK, "RESET_BY_FRAMEWORK");
}
+ private DataFailCause() {
+ }
+
/**
* Map of subId -> set of data call setup permanent failure for the carrier.
*/
@@ -382,6 +401,8 @@
* @param cause data disconnect cause
* @param subId subscription index
* @return true if the fail cause code needs platform to trigger a modem restart.
+ *
+ * @hide
*/
public static boolean isRadioRestartFailure(@NonNull Context context, @FailCause int cause,
int subId) {
@@ -410,6 +431,7 @@
return false;
}
+ /** @hide */
public static boolean isPermanentFailure(@NonNull Context context, @FailCause int failCause,
int subId) {
synchronized (sPermanentFailureCache) {
@@ -469,6 +491,7 @@
}
}
+ /** @hide */
public static boolean isEventLoggable(@FailCause int dataFailCause) {
return (dataFailCause == OPERATOR_BARRED) || (dataFailCause == INSUFFICIENT_RESOURCES)
|| (dataFailCause == UNKNOWN_PDP_ADDRESS_TYPE)
@@ -488,11 +511,13 @@
|| (dataFailCause == UNACCEPTABLE_NETWORK_PARAMETER);
}
+ /** @hide */
public static String toString(@FailCause int dataFailCause) {
int cause = getFailCause(dataFailCause);
return (cause == UNKNOWN) ? "UNKNOWN(" + dataFailCause + ")" : sFailCauseMap.get(cause);
}
+ /** @hide */
public static int getFailCause(@FailCause int failCause) {
if (sFailCauseMap.containsKey(failCause)) {
return failCause;
diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java
index 9317aa7..e27b385 100644
--- a/telephony/java/android/telephony/PhoneStateListener.java
+++ b/telephony/java/android/telephony/PhoneStateListener.java
@@ -184,14 +184,17 @@
public static final int LISTEN_PRECISE_CALL_STATE = 0x00000800;
/**
- * Listen for precise changes and fails on the data connection (cellular).
+ * Listen for {@link PreciseDataConnectionState} on the data connection (cellular).
+ *
* {@more}
* Requires Permission: {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE
* READ_PRECISE_PHONE_STATE}
*
* @see #onPreciseDataConnectionStateChanged
+ *
* @hide
*/
+ @SystemApi
public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE = 0x00001000;
/**
@@ -564,10 +567,11 @@
/**
* Callback invoked when data connection state changes with precise information.
+ * @param dataConnectionState {@link PreciseDataConnectionState}
*
* @hide
*/
- @UnsupportedAppUsage
+ @SystemApi
public void onPreciseDataConnectionStateChanged(
PreciseDataConnectionState dataConnectionState) {
// default implementation empty
diff --git a/telephony/java/android/telephony/PreciseDataConnectionState.java b/telephony/java/android/telephony/PreciseDataConnectionState.java
index b258f52..57a1826 100644
--- a/telephony/java/android/telephony/PreciseDataConnectionState.java
+++ b/telephony/java/android/telephony/PreciseDataConnectionState.java
@@ -16,11 +16,14 @@
package android.telephony;
+import android.annotation.SystemApi;
import android.annotation.UnsupportedAppUsage;
+import android.net.LinkProperties;
import android.os.Parcel;
import android.os.Parcelable;
-import android.telephony.TelephonyManager;
-import android.net.LinkProperties;
+import android.telephony.data.ApnSetting;
+
+import java.util.Objects;
/**
* Contains precise data connection state.
@@ -30,24 +33,23 @@
* <ul>
* <li>Data connection state.
* <li>Network type of the connection.
- * <li>APN type.
+ * <li>APN types.
* <li>APN.
- * <li>Data connection change reason.
* <li>The properties of the network link.
* <li>Data connection fail cause.
* </ul>
*
* @hide
*/
-public class PreciseDataConnectionState implements Parcelable {
+@SystemApi
+public final class PreciseDataConnectionState implements Parcelable {
- private int mState = TelephonyManager.DATA_UNKNOWN;
- private int mNetworkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
- private String mAPNType = "";
+ private @TelephonyManager.DataState int mState = TelephonyManager.DATA_UNKNOWN;
+ private @TelephonyManager.NetworkType int mNetworkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+ private @DataFailCause.FailCause int mFailCause = DataFailCause.NONE;
+ private @ApnSetting.ApnType int mAPNTypes = ApnSetting.TYPE_NONE;
private String mAPN = "";
- private String mReason = "";
private LinkProperties mLinkProperties = null;
- private String mFailCause = "";
/**
* Constructor
@@ -55,14 +57,15 @@
* @hide
*/
@UnsupportedAppUsage
- public PreciseDataConnectionState(int state, int networkType,
- String apnType, String apn, String reason,
- LinkProperties linkProperties, String failCause) {
+ public PreciseDataConnectionState(@TelephonyManager.DataState int state,
+ @TelephonyManager.NetworkType int networkType,
+ @ApnSetting.ApnType int apnTypes, String apn,
+ LinkProperties linkProperties,
+ @DataFailCause.FailCause int failCause) {
mState = state;
mNetworkType = networkType;
- mAPNType = apnType;
+ mAPNTypes = apnTypes;
mAPN = apn;
- mReason = reason;
mLinkProperties = linkProperties;
mFailCause = failCause;
}
@@ -77,82 +80,52 @@
/**
* Construct a PreciseDataConnectionState object from the given parcel.
+ *
+ * @hide
*/
private PreciseDataConnectionState(Parcel in) {
mState = in.readInt();
mNetworkType = in.readInt();
- mAPNType = in.readString();
+ mAPNTypes = in.readInt();
mAPN = in.readString();
- mReason = in.readString();
mLinkProperties = (LinkProperties)in.readParcelable(null);
- mFailCause = in.readString();
+ mFailCause = in.readInt();
}
/**
- * Get data connection state
- *
- * @see TelephonyManager#DATA_UNKNOWN
- * @see TelephonyManager#DATA_DISCONNECTED
- * @see TelephonyManager#DATA_CONNECTING
- * @see TelephonyManager#DATA_CONNECTED
- * @see TelephonyManager#DATA_SUSPENDED
+ * Returns the state of data connection that supported the apn types returned by
+ * {@link #getDataConnectionApnTypeBitMask()}
*/
- @UnsupportedAppUsage
- public int getDataConnectionState() {
+ public @TelephonyManager.DataState int getDataConnectionState() {
return mState;
}
/**
- * Get data connection network type
- *
- * @see TelephonyManager#NETWORK_TYPE_UNKNOWN
- * @see TelephonyManager#NETWORK_TYPE_GPRS
- * @see TelephonyManager#NETWORK_TYPE_EDGE
- * @see TelephonyManager#NETWORK_TYPE_UMTS
- * @see TelephonyManager#NETWORK_TYPE_CDMA
- * @see TelephonyManager#NETWORK_TYPE_EVDO_0
- * @see TelephonyManager#NETWORK_TYPE_EVDO_A
- * @see TelephonyManager#NETWORK_TYPE_1xRTT
- * @see TelephonyManager#NETWORK_TYPE_HSDPA
- * @see TelephonyManager#NETWORK_TYPE_HSUPA
- * @see TelephonyManager#NETWORK_TYPE_HSPA
- * @see TelephonyManager#NETWORK_TYPE_IDEN
- * @see TelephonyManager#NETWORK_TYPE_EVDO_B
- * @see TelephonyManager#NETWORK_TYPE_LTE
- * @see TelephonyManager#NETWORK_TYPE_EHRPD
- * @see TelephonyManager#NETWORK_TYPE_HSPAP
+ * Returns the network type associated with this data connection.
+ * @hide
*/
- @UnsupportedAppUsage
- public int getDataConnectionNetworkType() {
+ public @TelephonyManager.NetworkType int getDataConnectionNetworkType() {
return mNetworkType;
}
/**
- * Get data connection APN type
+ * Returns the data connection APN types supported by this connection and triggers
+ * {@link PreciseDataConnectionState} change.
*/
- @UnsupportedAppUsage
- public String getDataConnectionAPNType() {
- return mAPNType;
+ public @ApnSetting.ApnType int getDataConnectionApnTypeBitMask() {
+ return mAPNTypes;
}
/**
- * Get data connection APN.
+ * Returns APN {@link ApnSetting} of this data connection.
*/
- @UnsupportedAppUsage
- public String getDataConnectionAPN() {
+ public String getDataConnectionApn() {
return mAPN;
}
/**
- * Get data connection change reason.
- */
- @UnsupportedAppUsage
- public String getDataConnectionChangeReason() {
- return mReason;
- }
-
- /**
- * Get the properties of the network link.
+ * Get the properties of the network link {@link LinkProperties}.
+ * @hide
*/
@UnsupportedAppUsage
public LinkProperties getDataConnectionLinkProperties() {
@@ -160,10 +133,9 @@
}
/**
- * Get data connection fail cause, in case there was a failure.
+ * Returns data connection fail cause, in case there was a failure.
*/
- @UnsupportedAppUsage
- public String getDataConnectionFailCause() {
+ public @DataFailCause.FailCause int getDataConnectionFailCause() {
return mFailCause;
}
@@ -176,11 +148,10 @@
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mState);
out.writeInt(mNetworkType);
- out.writeString(mAPNType);
+ out.writeInt(mAPNTypes);
out.writeString(mAPN);
- out.writeString(mReason);
out.writeParcelable(mLinkProperties, flags);
- out.writeString(mFailCause);
+ out.writeInt(mFailCause);
}
public static final Parcelable.Creator<PreciseDataConnectionState> CREATOR
@@ -197,72 +168,23 @@
@Override
public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + mState;
- result = prime * result + mNetworkType;
- result = prime * result + ((mAPNType == null) ? 0 : mAPNType.hashCode());
- result = prime * result + ((mAPN == null) ? 0 : mAPN.hashCode());
- result = prime * result + ((mReason == null) ? 0 : mReason.hashCode());
- result = prime * result + ((mLinkProperties == null) ? 0 : mLinkProperties.hashCode());
- result = prime * result + ((mFailCause == null) ? 0 : mFailCause.hashCode());
- return result;
+ return Objects.hash(mState, mNetworkType, mAPNTypes, mAPN, mLinkProperties,
+ mFailCause);
}
@Override
public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null) {
+
+ if (!(obj instanceof PreciseDataConnectionState)) {
return false;
}
- if (getClass() != obj.getClass()) {
- return false;
- }
+
PreciseDataConnectionState other = (PreciseDataConnectionState) obj;
- if (mAPN == null) {
- if (other.mAPN != null) {
- return false;
- }
- } else if (!mAPN.equals(other.mAPN)) {
- return false;
- }
- if (mAPNType == null) {
- if (other.mAPNType != null) {
- return false;
- }
- } else if (!mAPNType.equals(other.mAPNType)) {
- return false;
- }
- if (mFailCause == null) {
- if (other.mFailCause != null) {
- return false;
- }
- } else if (!mFailCause.equals(other.mFailCause)) {
- return false;
- }
- if (mLinkProperties == null) {
- if (other.mLinkProperties != null) {
- return false;
- }
- } else if (!mLinkProperties.equals(other.mLinkProperties)) {
- return false;
- }
- if (mNetworkType != other.mNetworkType) {
- return false;
- }
- if (mReason == null) {
- if (other.mReason != null) {
- return false;
- }
- } else if (!mReason.equals(other.mReason)) {
- return false;
- }
- if (mState != other.mState) {
- return false;
- }
- return true;
+ return Objects.equals(mAPN, other.mAPN) && mAPNTypes == other.mAPNTypes
+ && mFailCause == other.mFailCause
+ && Objects.equals(mLinkProperties, other.mLinkProperties)
+ && mNetworkType == other.mNetworkType
+ && mState == other.mState;
}
@Override
@@ -271,11 +193,10 @@
sb.append("Data Connection state: " + mState);
sb.append(", Network type: " + mNetworkType);
- sb.append(", APN type: " + mAPNType);
+ sb.append(", APN types: " + ApnSetting.getApnTypesStringFromBitmask(mAPNTypes));
sb.append(", APN: " + mAPN);
- sb.append(", Change reason: " + mReason);
sb.append(", Link properties: " + mLinkProperties);
- sb.append(", Fail cause: " + mFailCause);
+ sb.append(", Fail cause: " + DataFailCause.toString(mFailCause));
return sb.toString();
}
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 9fc1b6f7..bf9bf9a 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -170,7 +170,8 @@
RIL_RADIO_TECHNOLOGY_GSM,
RIL_RADIO_TECHNOLOGY_TD_SCDMA,
RIL_RADIO_TECHNOLOGY_IWLAN,
- RIL_RADIO_TECHNOLOGY_LTE_CA})
+ RIL_RADIO_TECHNOLOGY_LTE_CA,
+ RIL_RADIO_TECHNOLOGY_NR})
public @interface RilRadioTechnology {}
/**
* Available radio technologies for GSM, UMTS and CDMA.
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index ef185c5..2271069 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -369,7 +369,7 @@
public int getLevel() {
int level = getPrimary().getLevel();
if (level < SIGNAL_STRENGTH_NONE_OR_UNKNOWN || level > SIGNAL_STRENGTH_GREAT) {
- log("Invalid Level " + level + ", this=" + this);
+ loge("Invalid Level " + level + ", this=" + this);
return SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
}
return getPrimary().getLevel();
@@ -657,9 +657,16 @@
}
/**
- * log
+ * log warning
*/
private static void log(String s) {
Rlog.w(LOG_TAG, s);
}
+
+ /**
+ * log error
+ */
+ private static void loge(String s) {
+ Rlog.e(LOG_TAG, s);
+ }
}
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index a1e8b19..51d5ab1 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -174,6 +174,16 @@
private boolean mIsGroupDisabled = false;
/**
+ * Profile class, PROFILE_CLASS_TESTING, PROFILE_CLASS_OPERATIONAL
+ * PROFILE_CLASS_PROVISIONING, or PROFILE_CLASS_UNSET.
+ * A profile on the eUICC can be defined as test, operational, provisioning, or unset.
+ * The profile class will be populated from the profile metadata if present. Otherwise,
+ * the profile class defaults to unset if there is no profile metadata or the subscription
+ * is not on an eUICC ({@link #isEmbedded} returns false).
+ */
+ private int mProfileClass;
+
+ /**
* @hide
*/
public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
@@ -182,7 +192,8 @@
@Nullable UiccAccessRule[] accessRules, String cardString) {
this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number,
roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardString,
- false, null, true, TelephonyManager.UNKNOWN_CARRIER_ID);
+ false, null, true, TelephonyManager.UNKNOWN_CARRIER_ID,
+ SubscriptionManager.PROFILE_CLASS_DEFAULT);
}
/**
@@ -192,10 +203,10 @@
CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
@Nullable UiccAccessRule[] accessRules, String cardString, boolean isOpportunistic,
- @Nullable String groupUUID, boolean isMetered, int carrierId) {
+ @Nullable String groupUUID, boolean isMetered, int carrierId, int profileClass) {
this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number,
roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardString, -1,
- isOpportunistic, groupUUID, isMetered, false, carrierId);
+ isOpportunistic, groupUUID, isMetered, false, carrierId, profileClass);
}
/**
@@ -206,7 +217,7 @@
Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
@Nullable UiccAccessRule[] accessRules, String cardString, int cardId,
boolean isOpportunistic, @Nullable String groupUUID, boolean isMetered,
- boolean isGroupDisabled, int carrierid) {
+ boolean isGroupDisabled, int carrierid, int profileClass) {
this.mId = id;
this.mIccId = iccId;
this.mSimSlotIndex = simSlotIndex;
@@ -229,6 +240,7 @@
this.mIsMetered = isMetered;
this.mIsGroupDisabled = isGroupDisabled;
this.mCarrierId = carrierid;
+ this.mProfileClass = profileClass;
}
@@ -466,6 +478,15 @@
}
/**
+ * @return the profile class of this subscription.
+ * @hide
+ */
+ @SystemApi
+ public @SubscriptionManager.ProfileClass int getProfileClass() {
+ return this.mProfileClass;
+ }
+
+ /**
* Checks whether the app with the given context is authorized to manage this subscription
* according to its metadata. Only supported for embedded subscriptions (if {@link #isEmbedded}
* returns true).
@@ -590,11 +611,12 @@
boolean isMetered = source.readBoolean();
boolean isGroupDisabled = source.readBoolean();
int carrierid = source.readInt();
+ int profileClass = source.readInt();
return new SubscriptionInfo(id, iccId, simSlotIndex, displayName, carrierName,
nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso,
isEmbedded, accessRules, cardString, cardId, isOpportunistic, groupUUID,
- isMetered, isGroupDisabled, carrierid);
+ isMetered, isGroupDisabled, carrierid, profileClass);
}
@Override
@@ -627,6 +649,7 @@
dest.writeBoolean(mIsMetered);
dest.writeBoolean(mIsGroupDisabled);
dest.writeInt(mCarrierId);
+ dest.writeInt(mProfileClass);
}
@Override
@@ -662,7 +685,8 @@
+ " accessRules " + Arrays.toString(mAccessRules)
+ " cardString=" + cardStringToPrint + " cardId=" + mCardId
+ " isOpportunistic " + mIsOpportunistic + " mGroupUUID=" + mGroupUUID
- + " isMetered=" + mIsMetered + " mIsGroupDisabled=" + mIsGroupDisabled + "}";
+ + " isMetered=" + mIsMetered + " mIsGroupDisabled=" + mIsGroupDisabled
+ + " profileClass=" + mProfileClass + "}";
}
@Override
@@ -670,7 +694,7 @@
return Objects.hash(mId, mSimSlotIndex, mNameSource, mIconTint, mDataRoaming, mIsEmbedded,
mIsOpportunistic, mGroupUUID, mIsMetered, mIccId, mNumber, mMcc, mMnc,
mCountryIso, mCardString, mCardId, mDisplayName, mCarrierName, mAccessRules,
- mIsGroupDisabled, mCarrierId);
+ mIsGroupDisabled, mCarrierId, mProfileClass);
}
@Override
@@ -705,6 +729,7 @@
&& Objects.equals(mCardId, toCompare.mCardId)
&& TextUtils.equals(mDisplayName, toCompare.mDisplayName)
&& TextUtils.equals(mCarrierName, toCompare.mCarrierName)
- && Arrays.equals(mAccessRules, toCompare.mAccessRules);
+ && Arrays.equals(mAccessRules, toCompare.mAccessRules)
+ && mProfileClass == toCompare.mProfileClass;
}
}
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 2c712a1..34f7abd 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -22,6 +22,7 @@
import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.DurationMillisLong;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -62,6 +63,8 @@
import com.android.internal.telephony.ITelephonyRegistry;
import com.android.internal.telephony.PhoneConstants;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -599,6 +602,73 @@
* @hide
*/
public static final String IS_METERED = "is_metered";
+
+ /**
+ * TelephonyProvider column name for the profile class of a subscription
+ * Only present if {@link #IS_EMBEDDED} is 1.
+ * <P>Type: INTEGER (int)</P>
+ * @hide
+ */
+ public static final String PROFILE_CLASS = "profile_class";
+
+ /**
+ * Profile class of the subscription
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "PROFILE_CLASS_" }, value = {
+ PROFILE_CLASS_TESTING,
+ PROFILE_CLASS_PROVISIONING,
+ PROFILE_CLASS_OPERATIONAL,
+ PROFILE_CLASS_UNSET,
+ PROFILE_CLASS_DEFAULT
+ })
+ public @interface ProfileClass {}
+
+ /**
+ * A testing profile can be pre-loaded or downloaded onto
+ * the eUICC and provides connectivity to test equipment
+ * for the purpose of testing the device and the eUICC. It
+ * is not intended to store any operator credentials.
+ * @hide
+ */
+ @SystemApi
+ public static final int PROFILE_CLASS_TESTING = 0;
+
+ /**
+ * A provisioning profile is pre-loaded onto the eUICC and
+ * provides connectivity to a mobile network solely for the
+ * purpose of provisioning profiles.
+ * @hide
+ */
+ @SystemApi
+ public static final int PROFILE_CLASS_PROVISIONING = 1;
+
+ /**
+ * An operational profile can be pre-loaded or downloaded
+ * onto the eUICC and provides services provided by the
+ * operator.
+ * @hide
+ */
+ @SystemApi
+ public static final int PROFILE_CLASS_OPERATIONAL = 2;
+
+ /**
+ * The profile class is unset. This occurs when profile class
+ * info is not available. The subscription either has no profile
+ * metadata or the profile metadata did not encode profile class.
+ * @hide
+ */
+ @SystemApi
+ public static final int PROFILE_CLASS_UNSET = -1;
+
+ /**
+ * Default profile class
+ * @hide
+ */
+ @SystemApi
+ public static final int PROFILE_CLASS_DEFAULT = PROFILE_CLASS_UNSET;
+
/**
* Broadcast Action: The user has changed one of the default subs related to
* data, phone calls, or sms</p>
@@ -1102,17 +1172,33 @@
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
public List<SubscriptionInfo> getActiveSubscriptionInfoList() {
- List<SubscriptionInfo> result = null;
+ return getActiveSubscriptionInfoList(false);
+ }
+
+ /**
+ * This is similar to {@link #getActiveSubscriptionInfoList()}, but if userVisibleOnly
+ * is true, it will filter out the hidden subscriptions.
+ *
+ * @hide
+ */
+ public List<SubscriptionInfo> getActiveSubscriptionInfoList(boolean userVisibleOnly) {
+ List<SubscriptionInfo> activeList = null;
try {
ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
if (iSub != null) {
- result = iSub.getActiveSubscriptionInfoList(mContext.getOpPackageName());
+ activeList = iSub.getActiveSubscriptionInfoList(mContext.getOpPackageName());
}
} catch (RemoteException ex) {
// ignore it
}
- return result;
+
+ if (!userVisibleOnly || activeList == null) {
+ return activeList;
+ } else {
+ return activeList.stream().filter(subInfo -> !shouldHideSubscription(subInfo))
+ .collect(Collectors.toList());
+ }
}
/**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 4598afa..6484005 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -772,7 +772,6 @@
* The {@link #EXTRA_DATA_NETWORK_TYPE} extra indicates the connection network type.
* The {@link #EXTRA_DATA_APN_TYPE} extra indicates the APN type.
* The {@link #EXTRA_DATA_APN} extra indicates the APN.
- * The {@link #EXTRA_DATA_CHANGE_REASON} extra indicates the connection change reason.
* The {@link #EXTRA_DATA_IFACE_PROPERTIES} extra indicates the connection interface.
* The {@link #EXTRA_DATA_FAILURE_CAUSE} extra indicates the connection fail cause.
*
@@ -783,7 +782,6 @@
* @see #EXTRA_DATA_NETWORK_TYPE
* @see #EXTRA_DATA_APN_TYPE
* @see #EXTRA_DATA_APN
- * @see #EXTRA_DATA_CHANGE_REASON
* @see #EXTRA_DATA_IFACE
* @see #EXTRA_DATA_FAILURE_CAUSE
* @hide
@@ -873,18 +871,6 @@
/**
* The lookup key used with the {@link #ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED} broadcast
- * for an String representation of the change reason.
- *
- * <p class="note">
- * Retrieve with
- * {@link android.content.Intent#getStringExtra(String name)}.
- *
- * @hide
- */
- public static final String EXTRA_DATA_CHANGE_REASON = PhoneConstants.STATE_CHANGE_REASON_KEY;
-
- /**
- * The lookup key used with the {@link #ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED} broadcast
* for an String representation of the data interface.
*
* <p class="note">
@@ -4599,9 +4585,18 @@
}
}
- /** Data connection state: Unknown. Used before we know the state.
- * @hide
- */
+ /** @hide */
+ @IntDef(prefix = {"DATA_"}, value = {
+ DATA_UNKNOWN,
+ DATA_DISCONNECTED,
+ DATA_CONNECTING,
+ DATA_CONNECTED,
+ DATA_SUSPENDED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DataState{}
+
+ /** Data connection state: Unknown. Used before we know the state. */
public static final int DATA_UNKNOWN = -1;
/** Data connection state: Disconnected. IP traffic not available. */
public static final int DATA_DISCONNECTED = 0;
@@ -5863,9 +5858,14 @@
/**
* Returns the IMS Service Table (IST) that was loaded from the ISIM.
+ *
+ * See 3GPP TS 31.103 (Section 4.2.7) for the definition and more information on this table.
+ *
* @return IMS Service Table or null if not present or not loaded
* @hide
*/
+ @SystemApi
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public String getIsimIst() {
try {
IPhoneSubInfo info = getSubscriberInfo();
diff --git a/telephony/java/android/telephony/emergency/EmergencyNumber.java b/telephony/java/android/telephony/emergency/EmergencyNumber.java
index fe062d5..a5f56bb 100644
--- a/telephony/java/android/telephony/emergency/EmergencyNumber.java
+++ b/telephony/java/android/telephony/emergency/EmergencyNumber.java
@@ -29,6 +29,7 @@
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
/**
@@ -198,21 +199,56 @@
EMERGENCY_NUMBER_SOURCE_SET.add(EMERGENCY_NUMBER_SOURCE_DEFAULT);
}
+ /**
+ * Indicated the framework does not know whether an emergency call should be placed using
+ * emergency or normal call routing. This means the underlying radio or IMS implementation is
+ * free to determine for itself how to route the call.
+ */
+ public static final int EMERGENCY_CALL_ROUTING_UNKNOWN = 0;
+ /**
+ * Indicates the radio or IMS implementation must handle the call through emergency routing.
+ */
+ public static final int EMERGENCY_CALL_ROUTING_EMERGENCY = 1;
+ /**
+ * Indicates the radio or IMS implementation must handle the call through normal call routing.
+ */
+ public static final int EMERGENCY_CALL_ROUTING_NORMAL = 2;
+
+ /**
+ * The routing to tell how to handle the call for the corresponding emergency number.
+ *
+ * @hide
+ */
+ @IntDef(flag = false, prefix = { "EMERGENCY_CALL_ROUTING_" }, value = {
+ EMERGENCY_CALL_ROUTING_UNKNOWN,
+ EMERGENCY_CALL_ROUTING_EMERGENCY,
+ EMERGENCY_CALL_ROUTING_NORMAL
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EmergencyCallRouting {}
+
+
private final String mNumber;
private final String mCountryIso;
private final String mMnc;
private final int mEmergencyServiceCategoryBitmask;
+ private final List<String> mEmergencyUrns;
private final int mEmergencyNumberSourceBitmask;
+ private final int mEmergencyCallRouting;
/** @hide */
- public EmergencyNumber(@NonNull String number, @NonNull String countryIso,
- @NonNull String mnc, int emergencyServiceCategories,
- int emergencyNumberSources) {
+ public EmergencyNumber(@NonNull String number, @NonNull String countryIso, @NonNull String mnc,
+ @EmergencyServiceCategories int emergencyServiceCategories,
+ @NonNull List<String> emergencyUrns,
+ @EmergencyNumberSources int emergencyNumberSources,
+ @EmergencyCallRouting int emergencyCallRouting) {
this.mNumber = number;
this.mCountryIso = countryIso;
this.mMnc = mnc;
this.mEmergencyServiceCategoryBitmask = emergencyServiceCategories;
+ this.mEmergencyUrns = emergencyUrns;
this.mEmergencyNumberSourceBitmask = emergencyNumberSources;
+ this.mEmergencyCallRouting = emergencyCallRouting;
}
/** @hide */
@@ -221,9 +257,36 @@
mCountryIso = source.readString();
mMnc = source.readString();
mEmergencyServiceCategoryBitmask = source.readInt();
+ mEmergencyUrns = source.createStringArrayList();
mEmergencyNumberSourceBitmask = source.readInt();
+ mEmergencyCallRouting = source.readInt();
}
+ @Override
+ /** @hide */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mNumber);
+ dest.writeString(mCountryIso);
+ dest.writeString(mMnc);
+ dest.writeInt(mEmergencyServiceCategoryBitmask);
+ dest.writeStringList(mEmergencyUrns);
+ dest.writeInt(mEmergencyNumberSourceBitmask);
+ dest.writeInt(mEmergencyCallRouting);
+ }
+
+ public static final Parcelable.Creator<EmergencyNumber> CREATOR =
+ new Parcelable.Creator<EmergencyNumber>() {
+ @Override
+ public EmergencyNumber createFromParcel(Parcel in) {
+ return new EmergencyNumber(in);
+ }
+
+ @Override
+ public EmergencyNumber[] newArray(int size) {
+ return new EmergencyNumber[size];
+ }
+ };
+
/**
* Get the dialing number of the emergency number.
*
@@ -287,6 +350,22 @@
}
/**
+ * Returns the list of emergency Uniform Resources Names (URN) of the emergency number.
+ *
+ * For example, {@code urn:service:sos} is the generic URN for contacting emergency services
+ * of all type.
+ *
+ * Reference: 3gpp 24.503, Section 5.1.6.8.1 - General;
+ * RFC 5031
+ *
+ * @return list of emergency Uniform Resources Names (URN) or an empty list if the emergency
+ * number does not have a specified emergency Uniform Resource Name.
+ */
+ public @NonNull List<String> getEmergencyUrns() {
+ return mEmergencyUrns;
+ }
+
+ /**
* Checks if the emergency service category is unspecified for the emergency number
* {@link #EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED}.
*
@@ -352,14 +431,17 @@
return (mEmergencyNumberSourceBitmask & sources) == sources;
}
- @Override
- /** @hide */
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(mNumber);
- dest.writeString(mCountryIso);
- dest.writeString(mMnc);
- dest.writeInt(mEmergencyServiceCategoryBitmask);
- dest.writeInt(mEmergencyNumberSourceBitmask);
+ /**
+ * Returns the emergency call routing information.
+ *
+ * <p>Some regions require some emergency numbers which are not routed using typical emergency
+ * call processing, but are instead placed as regular phone calls. The emergency call routing
+ * field provides information about how an emergency call will be routed when it is placed.
+ *
+ * @return the emergency call routing requirement
+ */
+ public @EmergencyCallRouting int getEmergencyCallRouting() {
+ return mEmergencyCallRouting;
}
@Override
@@ -373,7 +455,9 @@
return "EmergencyNumber:" + "Number-" + mNumber + "|CountryIso-" + mCountryIso
+ "|Mnc-" + mMnc
+ "|ServiceCategories-" + Integer.toBinaryString(mEmergencyServiceCategoryBitmask)
- + "|Sources-" + Integer.toBinaryString(mEmergencyNumberSourceBitmask);
+ + "|Urns-" + mEmergencyUrns
+ + "|Sources-" + Integer.toBinaryString(mEmergencyNumberSourceBitmask)
+ + "|Routing-" + Integer.toBinaryString(mEmergencyCallRouting);
}
@Override
@@ -381,7 +465,20 @@
if (!EmergencyNumber.class.isInstance(o)) {
return false;
}
- return (o == this || toString().equals(o.toString()));
+ EmergencyNumber other = (EmergencyNumber) o;
+ return mNumber.equals(other.mNumber)
+ && mCountryIso.equals(other.mCountryIso)
+ && mMnc.equals(other.mMnc)
+ && mEmergencyServiceCategoryBitmask == other.mEmergencyServiceCategoryBitmask
+ && mEmergencyUrns.equals(other.mEmergencyUrns)
+ && mEmergencyNumberSourceBitmask == other.mEmergencyNumberSourceBitmask
+ && mEmergencyCallRouting == other.mEmergencyCallRouting;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mNumber, mCountryIso, mMnc, mEmergencyServiceCategoryBitmask,
+ mEmergencyUrns, mEmergencyNumberSourceBitmask, mEmergencyCallRouting);
}
/**
@@ -462,12 +559,12 @@
continue;
}
for (int j = i + 1; j < emergencyNumberList.size(); j++) {
- if (isSameEmergencyNumber(
+ if (areSameEmergencyNumbers(
emergencyNumberList.get(i), emergencyNumberList.get(j))) {
Rlog.e(LOG_TAG, "Found unexpected duplicate numbers: "
+ emergencyNumberList.get(i) + " vs " + emergencyNumberList.get(j));
// Set the merged emergency number in the current position
- emergencyNumberList.set(i, mergeNumbers(
+ emergencyNumberList.set(i, mergeSameEmergencyNumbers(
emergencyNumberList.get(i), emergencyNumberList.get(j)));
// Mark the emergency number has been merged
mergedEmergencyNumber.add(emergencyNumberList.get(j));
@@ -486,8 +583,8 @@
* Check if two emergency numbers are the same.
*
* A unique EmergencyNumber has a unique combination of ‘number’, ‘mcc’, 'mnc' and
- * 'categories' fields. Multiple Emergency Number Sources should be merged into one bitfield
- * for the same EmergencyNumber.
+ * 'categories', and 'routing' fields. Multiple Emergency Number Sources should be
+ * merged into one bitfield for the same EmergencyNumber.
*
* @param first first EmergencyNumber to compare
* @param second second EmergencyNumber to compare
@@ -495,8 +592,8 @@
*
* @hide
*/
- public static boolean isSameEmergencyNumber(@NonNull EmergencyNumber first,
- @NonNull EmergencyNumber second) {
+ public static boolean areSameEmergencyNumbers(@NonNull EmergencyNumber first,
+ @NonNull EmergencyNumber second) {
if (!first.getNumber().equals(second.getNumber())) {
return false;
}
@@ -510,11 +607,18 @@
!= second.getEmergencyServiceCategoryBitmask()) {
return false;
}
+ if (first.getEmergencyUrns().equals(second.getEmergencyUrns())) {
+ return false;
+ }
+ if (first.getEmergencyCallRouting() != second.getEmergencyCallRouting()) {
+ return false;
+ }
return true;
}
/**
- * Get a merged EmergencyNumber for two numbers if they are the same.
+ * Get a merged EmergencyNumber from two same emergency numbers. Two emergency numbers are
+ * the same if {@link #areSameEmergencyNumbers} returns {@code true}.
*
* @param first first EmergencyNumber to compare
* @param second second EmergencyNumber to compare
@@ -522,27 +626,16 @@
*
* @hide
*/
- public static EmergencyNumber mergeNumbers(@NonNull EmergencyNumber first,
- @NonNull EmergencyNumber second) {
- if (isSameEmergencyNumber(first, second)) {
+ public static EmergencyNumber mergeSameEmergencyNumbers(@NonNull EmergencyNumber first,
+ @NonNull EmergencyNumber second) {
+ if (areSameEmergencyNumbers(first, second)) {
return new EmergencyNumber(first.getNumber(), first.getCountryIso(), first.getMnc(),
first.getEmergencyServiceCategoryBitmask(),
+ first.getEmergencyUrns(),
first.getEmergencyNumberSourceBitmask()
- | second.getEmergencyNumberSourceBitmask());
+ | second.getEmergencyNumberSourceBitmask(),
+ first.getEmergencyCallRouting());
}
return null;
}
-
- public static final Parcelable.Creator<EmergencyNumber> CREATOR =
- new Parcelable.Creator<EmergencyNumber>() {
- @Override
- public EmergencyNumber createFromParcel(Parcel in) {
- return new EmergencyNumber(in);
- }
-
- @Override
- public EmergencyNumber[] newArray(int size) {
- return new EmergencyNumber[size];
- }
- };
}
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index 6326cc6..cc9befe 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -616,14 +616,13 @@
/**
* Update the nickname for the given subscription.
*
- * <p>Requires that the calling app has the
- * {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission. This is for
- * internal system use only.
+ * <p>Requires that the calling app has carrier privileges according to the metadata of the
+ * profile to be updated, or the
+ * {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission.
*
* @param subscriptionId the ID of the subscription to update.
* @param nickname the new nickname to apply.
* @param callbackIntent a PendingIntent to launch when the operation completes.
- * @hide
*/
@RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
public void updateSubscriptionNickname(
@@ -634,7 +633,7 @@
}
try {
getIEuiccController().updateSubscriptionNickname(
- subscriptionId, nickname, callbackIntent);
+ subscriptionId, nickname, mContext.getOpPackageName(), callbackIntent);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java
index cb6fcd7..525a96a 100644
--- a/telephony/java/android/telephony/ims/ImsCallProfile.java
+++ b/telephony/java/android/telephony/ims/ImsCallProfile.java
@@ -24,13 +24,17 @@
import android.os.Parcelable;
import android.telecom.VideoProfile;
import android.telephony.emergency.EmergencyNumber;
+import android.telephony.emergency.EmergencyNumber.EmergencyCallRouting;
import android.telephony.emergency.EmergencyNumber.EmergencyServiceCategories;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.PhoneConstants;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
/**
* Parcelable object to handle IMS call profile.
@@ -321,6 +325,29 @@
EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED;
/**
+ * The emergency Uniform Resource Names (URN), only valid if {@link #getServiceType} returns
+ * {@link #SERVICE_TYPE_EMERGENCY}.
+ *
+ * Reference: 3gpp 24.503, Section 5.1.6.8.1 - General;
+ * 3gpp 22.101, Section 10 - Emergency Calls.
+ */
+ private List<String> mEmergencyUrns = new ArrayList<>();
+
+ /**
+ * The emergency call routing, only valid if {@link #getServiceType} returns
+ * {@link #SERVICE_TYPE_EMERGENCY}
+ *
+ * If valid, the value is any of the following constants:
+ * <ol>
+ * <li>{@link EmergencyNumber#EMERGENCY_CALL_ROUTING_UNKNOWN} </li>
+ * <li>{@link EmergencyNumber#EMERGENCY_CALL_ROUTING_NORMAL} </li>
+ * <li>{@link EmergencyNumber#EMERGENCY_CALL_ROUTING_EMERGENCY} </li>
+ * </ol>
+ */
+ private @EmergencyCallRouting int mEmergencyCallRouting =
+ EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN;
+
+ /**
* Extras associated with this {@link ImsCallProfile}.
* <p>
* Valid data types include:
@@ -503,10 +530,13 @@
@Override
public String toString() {
- return "{ serviceType=" + mServiceType +
- ", callType=" + mCallType +
- ", restrictCause=" + mRestrictCause +
- ", mediaProfile=" + mMediaProfile.toString() + " }";
+ return "{ serviceType=" + mServiceType
+ + ", callType=" + mCallType
+ + ", restrictCause=" + mRestrictCause
+ + ", mediaProfile=" + mMediaProfile.toString()
+ + ", emergencyServiceCategories=" + mEmergencyCallRouting
+ + ", emergencyUrns=" + mEmergencyUrns
+ + ", emergencyCallRouting=" + mEmergencyCallRouting + " }";
}
@Override
@@ -522,6 +552,8 @@
out.writeBundle(filteredExtras);
out.writeParcelable(mMediaProfile, 0);
out.writeInt(mEmergencyServiceCategories);
+ out.writeStringList(mEmergencyUrns);
+ out.writeInt(mEmergencyCallRouting);
}
private void readFromParcel(Parcel in) {
@@ -530,6 +562,8 @@
mCallExtras = in.readBundle();
mMediaProfile = in.readParcelable(ImsStreamMediaProfile.class.getClassLoader());
mEmergencyServiceCategories = in.readInt();
+ mEmergencyUrns = in.createStringArrayList();
+ mEmergencyCallRouting = in.readInt();
}
public static final Creator<ImsCallProfile> CREATOR = new Creator<ImsCallProfile>() {
@@ -740,6 +774,22 @@
}
/**
+ * Set the emergency number information. The set value is valid
+ * only if {@link #getServiceType} returns {@link #SERVICE_TYPE_EMERGENCY}
+ *
+ * Reference: 3gpp 23.167, Section 6 - Functional description;
+ * 3gpp 24.503, Section 5.1.6.8.1 - General;
+ * 3gpp 22.101, Section 10 - Emergency Calls.
+ *
+ * @hide
+ */
+ public void setEmergencyCallInfo(EmergencyNumber num) {
+ setEmergencyServiceCategories(num.getEmergencyServiceCategoryBitmask());
+ setEmergencyUrns(num.getEmergencyUrns());
+ setEmergencyCallRouting(num.getEmergencyCallRouting());
+ }
+
+ /**
* Set the emergency service categories. The set value is valid only if
* {@link #getServiceType} returns {@link #SERVICE_TYPE_EMERGENCY}
*
@@ -758,12 +808,41 @@
* Reference: 3gpp 23.167, Section 6 - Functional description;
* 3gpp 22.101, Section 10 - Emergency Calls.
*/
+ @VisibleForTesting
public void setEmergencyServiceCategories(
@EmergencyServiceCategories int emergencyServiceCategories) {
mEmergencyServiceCategories = emergencyServiceCategories;
}
/**
+ * Set the emergency Uniform Resource Names (URN), only valid if {@link #getServiceType}
+ * returns {@link #SERVICE_TYPE_EMERGENCY}.
+ *
+ * Reference: 3gpp 24.503, Section 5.1.6.8.1 - General;
+ * 3gpp 22.101, Section 10 - Emergency Calls.
+ */
+ @VisibleForTesting
+ public void setEmergencyUrns(List<String> emergencyUrns) {
+ mEmergencyUrns = emergencyUrns;
+ }
+
+ /**
+ * Set the emergency call routing, only valid if {@link #getServiceType} returns
+ * {@link #SERVICE_TYPE_EMERGENCY}
+ *
+ * If valid, the value is any of the following constants:
+ * <ol>
+ * <li>{@link EmergencyNumber#EMERGENCY_CALL_ROUTING_UNKNOWN} </li>
+ * <li>{@link EmergencyNumber#EMERGENCY_CALL_ROUTING_NORMAL} </li>
+ * <li>{@link EmergencyNumber#EMERGENCY_CALL_ROUTING_EMERGENCY} </li>
+ * </ol>
+ */
+ @VisibleForTesting
+ public void setEmergencyCallRouting(@EmergencyCallRouting int emergencyCallRouting) {
+ mEmergencyCallRouting = emergencyCallRouting;
+ }
+
+ /**
* Get the emergency service categories, only valid if {@link #getServiceType} returns
* {@link #SERVICE_TYPE_EMERGENCY}
*
@@ -787,4 +866,30 @@
public @EmergencyServiceCategories int getEmergencyServiceCategories() {
return mEmergencyServiceCategories;
}
+
+ /**
+ * Get the emergency Uniform Resource Names (URN), only valid if {@link #getServiceType}
+ * returns {@link #SERVICE_TYPE_EMERGENCY}.
+ *
+ * Reference: 3gpp 24.503, Section 5.1.6.8.1 - General;
+ * 3gpp 22.101, Section 10 - Emergency Calls.
+ */
+ public List<String> getEmergencyUrns() {
+ return mEmergencyUrns;
+ }
+
+ /**
+ * Get the emergency call routing, only valid if {@link #getServiceType} returns
+ * {@link #SERVICE_TYPE_EMERGENCY}
+ *
+ * If valid, the value is any of the following constants:
+ * <ol>
+ * <li>{@link EmergencyNumber#EMERGENCY_CALL_ROUTING_UNKNOWN} </li>
+ * <li>{@link EmergencyNumber#EMERGENCY_CALL_ROUTING_NORMAL} </li>
+ * <li>{@link EmergencyNumber#EMERGENCY_CALL_ROUTING_EMERGENCY} </li>
+ * </ol>
+ */
+ public @EmergencyCallRouting int getEmergencyCallRouting() {
+ return mEmergencyCallRouting;
+ }
}
diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java
index 122626f..e2350fe 100644
--- a/telephony/java/android/telephony/ims/ImsMmTelManager.java
+++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java
@@ -714,7 +714,7 @@
* @see #setVoWiFiRoamingSetting(boolean)
*/
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
- @WiFiCallingMode int getVoWiFiRoamingModeSetting() {
+ public @WiFiCallingMode int getVoWiFiRoamingModeSetting() {
try {
return getITelephony().getVoWiFiRoamingModeSetting(mSubId);
} catch (RemoteException e) {
diff --git a/telephony/java/android/telephony/ims/Rcs1To1Thread.java b/telephony/java/android/telephony/ims/Rcs1To1Thread.java
index 5122c7d..709b3aa 100644
--- a/telephony/java/android/telephony/ims/Rcs1To1Thread.java
+++ b/telephony/java/android/telephony/ims/Rcs1To1Thread.java
@@ -23,6 +23,10 @@
* @hide - TODO(sahinc) make this public
*/
public class Rcs1To1Thread extends RcsThread {
+ public Rcs1To1Thread(int threadId) {
+ super(threadId);
+ }
+
public static final Creator<Rcs1To1Thread> CREATOR = new Creator<Rcs1To1Thread>() {
@Override
public Rcs1To1Thread createFromParcel(Parcel in) {
@@ -36,6 +40,7 @@
};
protected Rcs1To1Thread(Parcel in) {
+ super(in);
}
@Override
@@ -45,5 +50,7 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(RCS_1_TO_1_TYPE);
+ super.writeToParcel(dest, flags);
}
}
diff --git a/telephony/java/android/telephony/ims/RcsGroupThread.java b/telephony/java/android/telephony/ims/RcsGroupThread.java
index 150c4d4..d954b2d 100644
--- a/telephony/java/android/telephony/ims/RcsGroupThread.java
+++ b/telephony/java/android/telephony/ims/RcsGroupThread.java
@@ -36,6 +36,7 @@
};
protected RcsGroupThread(Parcel in) {
+ super(in);
}
@Override
@@ -45,5 +46,7 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(RCS_GROUP_TYPE);
+ super.writeToParcel(dest, flags);
}
}
diff --git a/telephony/java/android/telephony/ims/RcsMessageStore.java b/telephony/java/android/telephony/ims/RcsMessageStore.java
index 4198c78..1bf6ffd 100644
--- a/telephony/java/android/telephony/ims/RcsMessageStore.java
+++ b/telephony/java/android/telephony/ims/RcsMessageStore.java
@@ -16,6 +16,8 @@
package android.telephony.ims;
+import android.annotation.Nullable;
+import android.annotation.WorkerThread;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.telephony.Rlog;
@@ -27,26 +29,93 @@
* @hide - TODO make this public
*/
public class RcsMessageStore {
- private static final String TAG = "RcsMessageStore";
- private static final boolean VDBG = false;
+ static final String TAG = "RcsMessageStore";
/**
- * Delete the RcsThread identified by the given threadId.
+ * Returns the first chunk of existing {@link RcsThread}s in the common storage.
+ * @param queryParameters Parameters to specify to return a subset of all RcsThreads.
+ * Passing a value of null will return all threads.
+ */
+ @WorkerThread
+ public RcsThreadQueryResult getRcsThreads(@Nullable RcsThreadQueryParameters queryParameters) {
+ try {
+ IRcs iRcs = IRcs.Stub.asInterface(ServiceManager.getService("ircs"));
+ if (iRcs != null) {
+ return iRcs.getRcsThreads(queryParameters);
+ }
+ } catch (RemoteException re) {
+ Rlog.e(TAG, "RcsMessageStore: Exception happened during getRcsThreads", re);
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the next chunk of {@link RcsThread}s in the common storage.
+ * @param continuationToken A token to continue the query to get the next chunk. This is
+ * obtained through {@link RcsThreadQueryResult#nextChunkToken}.
+ */
+ @WorkerThread
+ public RcsThreadQueryResult getRcsThreads(RcsThreadQueryContinuationToken continuationToken) {
+ try {
+ IRcs iRcs = IRcs.Stub.asInterface(ServiceManager.getService("ircs"));
+ if (iRcs != null) {
+ return iRcs.getRcsThreadsWithToken(continuationToken);
+ }
+ } catch (RemoteException re) {
+ Rlog.e(TAG, "RcsMessageStore: Exception happened during getRcsThreads", re);
+ }
+
+ return null;
+ }
+
+ /**
+ * Creates a new 1 to 1 thread with the given participant and persists it in the storage.
+ */
+ @WorkerThread
+ public Rcs1To1Thread createRcs1To1Thread(RcsParticipant recipient) {
+ try {
+ IRcs iRcs = IRcs.Stub.asInterface(ServiceManager.getService("ircs"));
+ if (iRcs != null) {
+ return iRcs.createRcs1To1Thread(recipient);
+ }
+ } catch (RemoteException re) {
+ Rlog.e(TAG, "RcsMessageStore: Exception happened during createRcs1To1Thread", re);
+ }
+
+ return null;
+ }
+
+ /**
+ * Delete the {@link RcsThread} identified by the given threadId.
* @param threadId threadId of the thread to be deleted.
*/
+ @WorkerThread
public void deleteThread(int threadId) {
- if (VDBG) logd("deleteThread: threadId: " + threadId);
try {
IRcs iRcs = IRcs.Stub.asInterface(ServiceManager.getService("ircs"));
if (iRcs != null) {
iRcs.deleteThread(threadId);
}
} catch (RemoteException re) {
-
+ Rlog.e(TAG, "RcsMessageStore: Exception happened during deleteThread", re);
}
}
- private static void logd(String msg) {
- Rlog.d(TAG, msg);
+ /**
+ * Creates a new participant and persists it in the storage.
+ * @param canonicalAddress The defining address (e.g. phone number) of the participant.
+ */
+ public RcsParticipant createRcsParticipant(String canonicalAddress) {
+ try {
+ IRcs iRcs = IRcs.Stub.asInterface(ServiceManager.getService("ircs"));
+ if (iRcs != null) {
+ return iRcs.createRcsParticipant(canonicalAddress);
+ }
+ } catch (RemoteException re) {
+ Rlog.e(TAG, "RcsMessageStore: Exception happened during createRcsParticipant", re);
+ }
+
+ return null;
}
}
diff --git a/telephony/java/android/telephony/ims/RcsParticipant.java b/telephony/java/android/telephony/ims/RcsParticipant.java
index 318dba3..f678ec7 100644
--- a/telephony/java/android/telephony/ims/RcsParticipant.java
+++ b/telephony/java/android/telephony/ims/RcsParticipant.java
@@ -15,14 +15,113 @@
*/
package android.telephony.ims;
+import static android.telephony.ims.RcsMessageStore.TAG;
+
+import android.annotation.NonNull;
+import android.annotation.WorkerThread;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.telephony.Rlog;
+import android.telephony.ims.aidl.IRcs;
+import android.text.TextUtils;
+
+import com.android.internal.util.Preconditions;
/**
* RcsParticipant is an RCS capable contact that can participate in {@link RcsThread}s.
* @hide - TODO(sahinc) make this public
*/
public class RcsParticipant implements Parcelable {
+ // The row ID of this participant in the database
+ private int mId;
+ // The phone number of this participant
+ private String mCanonicalAddress;
+ // The RCS alias of this participant. This is different than the name of the contact in the
+ // Contacts app - i.e. RCS protocol allows users to define aliases for themselves that doesn't
+ // require other users to add them as contacts and give them a name.
+ private String mAlias;
+
+ /**
+ * Constructor for {@link com.android.internal.telephony.ims.RcsMessageStoreController}
+ * to create instances of participants. This is not meant to be part of the SDK.
+ *
+ * @hide
+ */
+ public RcsParticipant(int id, @NonNull String canonicalAddress) {
+ mId = id;
+ mCanonicalAddress = canonicalAddress;
+ }
+
+ /**
+ * @return Returns the canonical address (i.e. normalized phone number) for this participant
+ */
+ public String getCanonicalAddress() {
+ return mCanonicalAddress;
+ }
+
+ /**
+ * Sets the canonical address for this participant and updates it in storage.
+ * @param canonicalAddress the canonical address to update to.
+ */
+ @WorkerThread
+ public void setCanonicalAddress(@NonNull String canonicalAddress) {
+ Preconditions.checkNotNull(canonicalAddress);
+ if (canonicalAddress.equals(mCanonicalAddress)) {
+ return;
+ }
+
+ mCanonicalAddress = canonicalAddress;
+
+ try {
+ IRcs iRcs = IRcs.Stub.asInterface(ServiceManager.getService("ircs"));
+ if (iRcs != null) {
+ iRcs.updateRcsParticipantCanonicalAddress(mId, mCanonicalAddress);
+ }
+ } catch (RemoteException re) {
+ Rlog.e(TAG, "RcsParticipant: Exception happened during setCanonicalAddress", re);
+ }
+ }
+
+ /**
+ * @return Returns the alias for this participant. Alias is usually the real name of the person
+ * themselves.
+ */
+ public String getAlias() {
+ return mAlias;
+ }
+
+ /**
+ * Sets the alias for this participant and persists it in storage. Alias is usually the real
+ * name of the person themselves.
+ */
+ @WorkerThread
+ public void setAlias(String alias) {
+ if (TextUtils.equals(mAlias, alias)) {
+ return;
+ }
+ mAlias = alias;
+
+ try {
+ IRcs iRcs = IRcs.Stub.asInterface(ServiceManager.getService("ircs"));
+ if (iRcs != null) {
+ iRcs.updateRcsParticipantAlias(mId, mAlias);
+ }
+ } catch (RemoteException re) {
+ Rlog.e(TAG, "RcsParticipant: Exception happened during setCanonicalAddress", re);
+ }
+ }
+
+ /**
+ * Returns the row id of this participant. This is not meant to be part of the SDK
+ *
+ * @hide
+ */
+ public int getId() {
+ return mId;
+ }
+
public static final Creator<RcsParticipant> CREATOR = new Creator<RcsParticipant>() {
@Override
public RcsParticipant createFromParcel(Parcel in) {
@@ -36,6 +135,9 @@
};
protected RcsParticipant(Parcel in) {
+ mId = in.readInt();
+ mCanonicalAddress = in.readString();
+ mAlias = in.readString();
}
@Override
@@ -45,6 +147,8 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
-
+ dest.writeInt(mId);
+ dest.writeString(mCanonicalAddress);
+ dest.writeString(mAlias);
}
}
diff --git a/telephony/java/android/telephony/ims/RcsThread.java b/telephony/java/android/telephony/ims/RcsThread.java
index 2969ffd..c0a0d94 100644
--- a/telephony/java/android/telephony/ims/RcsThread.java
+++ b/telephony/java/android/telephony/ims/RcsThread.java
@@ -16,7 +16,9 @@
package android.telephony.ims;
+import android.os.Parcel;
import android.os.Parcelable;
+import android.util.Log;
/**
* RcsThread represents a single RCS conversation thread. It holds messages that were sent and
@@ -24,5 +26,50 @@
* @hide - TODO(sahinc) make this public
*/
public abstract class RcsThread implements Parcelable {
+ // Since this is an abstract class that gets parcelled, the sub-classes need to write these
+ // magic values into the parcel so that we know which type to unparcel into.
+ protected static final int RCS_1_TO_1_TYPE = 998;
+ protected static final int RCS_GROUP_TYPE = 999;
+ protected int mThreadId;
+
+ protected RcsThread(int threadId) {
+ mThreadId = threadId;
+ }
+
+ protected RcsThread(Parcel in) {
+ mThreadId = in.readInt();
+ }
+
+ public static final Creator<RcsThread> CREATOR = new Creator<RcsThread>() {
+ @Override
+ public RcsThread createFromParcel(Parcel in) {
+ int type = in.readInt();
+
+ switch (type) {
+ case RCS_1_TO_1_TYPE:
+ return new Rcs1To1Thread(in);
+ case RCS_GROUP_TYPE:
+ return new RcsGroupThread(in);
+ default:
+ Log.e(RcsMessageStore.TAG, "Cannot unparcel RcsThread, wrong type: " + type);
+ }
+ return null;
+ }
+
+ @Override
+ public RcsThread[] newArray(int size) {
+ return new RcsThread[0];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mThreadId);
+ }
}
diff --git a/media/java/android/media/session/ParcelableVolumeInfo.aidl b/telephony/java/android/telephony/ims/RcsThreadQueryContinuationToken.aidl
similarity index 80%
copy from media/java/android/media/session/ParcelableVolumeInfo.aidl
copy to telephony/java/android/telephony/ims/RcsThreadQueryContinuationToken.aidl
index c4250f0..7bcebfa 100644
--- a/media/java/android/media/session/ParcelableVolumeInfo.aidl
+++ b/telephony/java/android/telephony/ims/RcsThreadQueryContinuationToken.aidl
@@ -1,4 +1,6 @@
-/* Copyright 2014, The Android Open Source Project
+/*
+**
+** Copyright 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.
@@ -13,6 +15,6 @@
** limitations under the License.
*/
-package android.media.session;
+package android.telephony.ims;
-parcelable ParcelableVolumeInfo;
+parcelable RcsThreadQueryContinuationToken;
diff --git a/telephony/java/android/telephony/ims/RcsThreadQueryContinuationToken.java b/telephony/java/android/telephony/ims/RcsThreadQueryContinuationToken.java
new file mode 100644
index 0000000..931e93d
--- /dev/null
+++ b/telephony/java/android/telephony/ims/RcsThreadQueryContinuationToken.java
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+
+package android.telephony.ims;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A continuation token to provide for {@link RcsMessageStore#getRcsThreads}. Use this token to
+ * break large queries into manageable chunks
+ * @hide - TODO make this public
+ */
+public class RcsThreadQueryContinuationToken implements Parcelable {
+ protected RcsThreadQueryContinuationToken(Parcel in) {
+ }
+
+ public static final Creator<RcsThreadQueryContinuationToken> CREATOR =
+ new Creator<RcsThreadQueryContinuationToken>() {
+ @Override
+ public RcsThreadQueryContinuationToken createFromParcel(Parcel in) {
+ return new RcsThreadQueryContinuationToken(in);
+ }
+
+ @Override
+ public RcsThreadQueryContinuationToken[] newArray(int size) {
+ return new RcsThreadQueryContinuationToken[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ }
+}
diff --git a/media/java/android/media/session/ParcelableVolumeInfo.aidl b/telephony/java/android/telephony/ims/RcsThreadQueryParameters.aidl
similarity index 81%
copy from media/java/android/media/session/ParcelableVolumeInfo.aidl
copy to telephony/java/android/telephony/ims/RcsThreadQueryParameters.aidl
index c4250f0..feb2d4d 100644
--- a/media/java/android/media/session/ParcelableVolumeInfo.aidl
+++ b/telephony/java/android/telephony/ims/RcsThreadQueryParameters.aidl
@@ -1,4 +1,6 @@
-/* Copyright 2014, The Android Open Source Project
+/*
+**
+** Copyright 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.
@@ -13,6 +15,6 @@
** limitations under the License.
*/
-package android.media.session;
+package android.telephony.ims;
-parcelable ParcelableVolumeInfo;
+parcelable RcsThreadQueryParameters;
diff --git a/telephony/java/android/telephony/ims/RcsThreadQueryParameters.java b/telephony/java/android/telephony/ims/RcsThreadQueryParameters.java
new file mode 100644
index 0000000..f2c4ab1
--- /dev/null
+++ b/telephony/java/android/telephony/ims/RcsThreadQueryParameters.java
@@ -0,0 +1,225 @@
+/*
+ * 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.
+ */
+
+package android.telephony.ims;
+
+import android.annotation.CheckResult;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.security.InvalidParameterException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * The parameters to pass into {@link RcsMessageStore#getRcsThreads(RcsThreadQueryParameters)} in
+ * order to select a subset of {@link RcsThread}s present in the message store.
+ * @hide TODO - make the Builder and builder() public. The rest should stay internal only.
+ */
+public class RcsThreadQueryParameters implements Parcelable {
+ private final boolean mIsGroup;
+ private final Set<RcsParticipant> mRcsParticipants;
+ private final int mLimit;
+ private final boolean mIsAscending;
+
+ RcsThreadQueryParameters(boolean isGroup, Set<RcsParticipant> participants, int limit,
+ boolean isAscending) {
+ mIsGroup = isGroup;
+ mRcsParticipants = participants;
+ mLimit = limit;
+ mIsAscending = isAscending;
+ }
+
+ /**
+ * Returns a new builder to build a query with.
+ * TODO - make public
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * This is used in {@link com.android.internal.telephony.ims.RcsMessageStoreController} to get
+ * the list of participants.
+ * @hide
+ */
+ public Set<RcsParticipant> getRcsParticipants() {
+ return mRcsParticipants;
+ }
+
+ /**
+ * This is used in {@link com.android.internal.telephony.ims.RcsMessageStoreController} to get
+ * whether group threads should be queried
+ * @hide
+ */
+ public boolean isGroupThread() {
+ return mIsGroup;
+ }
+
+ /**
+ * This is used in {@link com.android.internal.telephony.ims.RcsMessageStoreController} to get
+ * the number of tuples the result query should be limited to.
+ */
+ public int getLimit() {
+ return mLimit;
+ }
+
+ /**
+ * This is used in {@link com.android.internal.telephony.ims.RcsMessageStoreController} to
+ * determine the sort order.
+ */
+ public boolean isAscending() {
+ return mIsAscending;
+ }
+
+ /**
+ * A helper class to build the {@link RcsThreadQueryParameters}.
+ */
+ public static class Builder {
+ private boolean mIsGroupThread;
+ private Set<RcsParticipant> mParticipants;
+ private int mLimit = 100;
+ private boolean mIsAscending;
+
+ /**
+ * Package private constructor for {@link RcsThreadQueryParameters.Builder}. To obtain this,
+ * {@link RcsThreadQueryParameters#builder()} needs to be called.
+ */
+ Builder() {
+ mParticipants = new HashSet<>();
+ }
+
+ /**
+ * Limits the query to only return group threads.
+ * @param isGroupThread Whether to limit the query result to group threads.
+ * @return The same instance of the builder to chain parameters.
+ */
+ @CheckResult
+ public Builder isGroupThread(boolean isGroupThread) {
+ mIsGroupThread = isGroupThread;
+ return this;
+ }
+
+ /**
+ * Limits the query to only return threads that contain the given participant.
+ * @param participant The participant that must be included in all of the returned threads.
+ * @return The same instance of the builder to chain parameters.
+ */
+ @CheckResult
+ public Builder withParticipant(RcsParticipant participant) {
+ mParticipants.add(participant);
+ return this;
+ }
+
+ /**
+ * Limits the query to only return threads that contain the given list of participants.
+ * @param participants An iterable list of participants that must be included in all of the
+ * returned threads.
+ * @return The same instance of the builder to chain parameters.
+ */
+ @CheckResult
+ public Builder withParticipants(Iterable<RcsParticipant> participants) {
+ for (RcsParticipant participant : participants) {
+ mParticipants.add(participant);
+ }
+ return this;
+ }
+
+ /**
+ * Desired number of threads to be returned from the query. Passing in 0 will return all
+ * existing threads at once. The limit defaults to 100.
+ * @param limit The number to limit the query result to.
+ * @return The same instance of the builder to chain parameters.
+ * @throws InvalidParameterException If the given limit is negative.
+ */
+ @CheckResult
+ public Builder limitResultsTo(int limit) throws InvalidParameterException {
+ if (limit < 0) {
+ throw new InvalidParameterException("The query limit must be non-negative");
+ }
+
+ mLimit = limit;
+ return this;
+ }
+
+ /**
+ * Sorts the results returned from the query via thread IDs.
+ *
+ * TODO - add sorting support for other fields
+ *
+ * @param isAscending whether to sort in ascending order or not
+ * @return The same instance of the builder to chain parameters.
+ */
+ @CheckResult
+ public Builder sort(boolean isAscending) {
+ mIsAscending = isAscending;
+ return this;
+ }
+
+ /**
+ * Builds the {@link RcsThreadQueryParameters} to use in
+ * {@link RcsMessageStore#getRcsThreads(RcsThreadQueryParameters)}
+ *
+ * @return An instance of {@link RcsThreadQueryParameters} to use with the thread query.
+ */
+ public RcsThreadQueryParameters build() {
+ return new RcsThreadQueryParameters(
+ mIsGroupThread, mParticipants, mLimit, mIsAscending);
+ }
+ }
+
+ /**
+ * Parcelable boilerplate below.
+ */
+ protected RcsThreadQueryParameters(Parcel in) {
+ mIsGroup = in.readBoolean();
+
+ ArrayList<RcsParticipant> participantArrayList = new ArrayList<>();
+ in.readTypedList(participantArrayList, RcsParticipant.CREATOR);
+ mRcsParticipants = new HashSet<>(participantArrayList);
+
+ mLimit = in.readInt();
+ mIsAscending = in.readBoolean();
+ }
+
+ public static final Creator<RcsThreadQueryParameters> CREATOR =
+ new Creator<RcsThreadQueryParameters>() {
+ @Override
+ public RcsThreadQueryParameters createFromParcel(Parcel in) {
+ return new RcsThreadQueryParameters(in);
+ }
+
+ @Override
+ public RcsThreadQueryParameters[] newArray(int size) {
+ return new RcsThreadQueryParameters[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBoolean(mIsGroup);
+ dest.writeTypedList(new ArrayList<>(mRcsParticipants));
+ dest.writeInt(mLimit);
+ dest.writeBoolean(mIsAscending);
+ }
+
+}
diff --git a/media/java/android/media/session/ParcelableVolumeInfo.aidl b/telephony/java/android/telephony/ims/RcsThreadQueryResult.aidl
similarity index 81%
copy from media/java/android/media/session/ParcelableVolumeInfo.aidl
copy to telephony/java/android/telephony/ims/RcsThreadQueryResult.aidl
index c4250f0..4b06529 100644
--- a/media/java/android/media/session/ParcelableVolumeInfo.aidl
+++ b/telephony/java/android/telephony/ims/RcsThreadQueryResult.aidl
@@ -1,4 +1,6 @@
-/* Copyright 2014, The Android Open Source Project
+/*
+**
+** Copyright 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.
@@ -13,6 +15,6 @@
** limitations under the License.
*/
-package android.media.session;
+package android.telephony.ims;
-parcelable ParcelableVolumeInfo;
+parcelable RcsThreadQueryResult;
diff --git a/telephony/java/android/telephony/ims/RcsThreadQueryResult.java b/telephony/java/android/telephony/ims/RcsThreadQueryResult.java
new file mode 100644
index 0000000..47715f8
--- /dev/null
+++ b/telephony/java/android/telephony/ims/RcsThreadQueryResult.java
@@ -0,0 +1,92 @@
+/*
+ * 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.
+ */
+
+package android.telephony.ims;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.List;
+
+/**
+ * The result of a {@link RcsMessageStore#getRcsThreads(RcsThreadQueryContinuationToken,
+ * RcsThreadQueryParameters)}
+ * call. This class allows getting the token for querying the next batch of threads in order to
+ * prevent handling large amounts of data at once.
+ *
+ * @hide
+ */
+public class RcsThreadQueryResult implements Parcelable {
+ private RcsThreadQueryContinuationToken mContinuationToken;
+ private List<RcsThread> mRcsThreads;
+
+ /**
+ * Internal constructor for {@link com.android.internal.telephony.ims.RcsMessageStoreController}
+ * to create query results
+ *
+ * @hide
+ */
+ public RcsThreadQueryResult(
+ RcsThreadQueryContinuationToken continuationToken, List<RcsThread> rcsThreads) {
+ mContinuationToken = continuationToken;
+ mRcsThreads = rcsThreads;
+ }
+
+ /**
+ * Returns a token to call
+ * {@link RcsMessageStore#getRcsThreads(RcsThreadQueryContinuationToken)}
+ * to get the next batch of {@link RcsThread}s.
+ */
+ public RcsThreadQueryContinuationToken nextChunkToken() {
+ return mContinuationToken;
+ }
+
+ /**
+ * Returns all the RcsThreads in the current query result. Call {@link
+ * RcsMessageStore#getRcsThreads(RcsThreadQueryContinuationToken)} to get the next batch of
+ * {@link RcsThread}s.
+ */
+ public List<RcsThread> getThreads() {
+ return mRcsThreads;
+ }
+
+ protected RcsThreadQueryResult(Parcel in) {
+ // TODO - implement
+ }
+
+ public static final Creator<RcsThreadQueryResult> CREATOR =
+ new Creator<RcsThreadQueryResult>() {
+ @Override
+ public RcsThreadQueryResult createFromParcel(Parcel in) {
+ return new RcsThreadQueryResult(in);
+ }
+
+ @Override
+ public RcsThreadQueryResult[] newArray(int size) {
+ return new RcsThreadQueryResult[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ // TODO - implement
+ }
+}
diff --git a/telephony/java/android/telephony/ims/aidl/IRcs.aidl b/telephony/java/android/telephony/ims/aidl/IRcs.aidl
index b2e2fad..0c958ba 100644
--- a/telephony/java/android/telephony/ims/aidl/IRcs.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IRcs.aidl
@@ -16,14 +16,34 @@
package android.telephony.ims.aidl;
+import android.telephony.ims.RcsParticipant;
+import android.telephony.ims.Rcs1To1Thread;
+import android.telephony.ims.RcsThreadQueryContinuationToken;
+import android.telephony.ims.RcsThreadQueryParameters;
+import android.telephony.ims.RcsThreadQueryResult;
+
/**
* RPC definition between RCS storage APIs and phone process.
* {@hide}
*/
interface IRcs {
// RcsMessageStore APIs
+ RcsThreadQueryResult getRcsThreads(in RcsThreadQueryParameters queryParameters);
+
+ RcsThreadQueryResult getRcsThreadsWithToken(
+ in RcsThreadQueryContinuationToken continuationToken);
+
void deleteThread(int threadId);
+ Rcs1To1Thread createRcs1To1Thread(in RcsParticipant participant);
+
// RcsThread APIs
int getMessageCount(int rcsThreadId);
+
+ // RcsParticipant APIs
+ RcsParticipant createRcsParticipant(String canonicalAddress);
+
+ void updateRcsParticipantCanonicalAddress(int id, String canonicalAddress);
+
+ void updateRcsParticipantAlias(int id, String alias);
}
\ No newline at end of file
diff --git a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index d9f5c3f..5632c63 100644
--- a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -50,13 +50,13 @@
void notifyDataActivity(int state);
void notifyDataActivityForSubscriber(in int subId, int state);
void notifyDataConnection(int state, boolean isDataConnectivityPossible,
- String reason, String apn, String apnType, in LinkProperties linkProperties,
+ String apn, String apnType, in LinkProperties linkProperties,
in NetworkCapabilities networkCapabilities, int networkType, boolean roaming);
void notifyDataConnectionForSubscriber(int subId, int state, boolean isDataConnectivityPossible,
- String reason, String apn, String apnType, in LinkProperties linkProperties,
+ String apn, String apnType, in LinkProperties linkProperties,
in NetworkCapabilities networkCapabilities, int networkType, boolean roaming);
- void notifyDataConnectionFailed(String reason, String apnType);
- void notifyDataConnectionFailedForSubscriber(int subId, String reason, String apnType);
+ void notifyDataConnectionFailed(String apnType);
+ void notifyDataConnectionFailedForSubscriber(int subId, String apnType);
void notifyCellLocation(in Bundle cellLocation);
void notifyCellLocationForSubscriber(in int subId, in Bundle cellLocation);
void notifyOtaspChanged(in int otaspMode);
@@ -67,8 +67,8 @@
void notifyPreciseCallState(int ringingCallState, int foregroundCallState,
int backgroundCallState);
void notifyDisconnectCause(int disconnectCause, int preciseDisconnectCause);
- void notifyPreciseDataConnectionFailed(String reason, String apnType, String apn,
- String failCause);
+ void notifyPreciseDataConnectionFailed(String apnType, String apn,
+ int failCause);
void notifyCellInfoForSubscriber(in int subId, in List<CellInfo> cellInfo);
void notifySrvccStateChanged(in int subId, in int lteState);
void notifySimActivationStateChangedForPhoneId(in int phoneId, in int subId,
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstants.java b/telephony/java/com/android/internal/telephony/PhoneConstants.java
index 21f3b92..e87d28c 100644
--- a/telephony/java/com/android/internal/telephony/PhoneConstants.java
+++ b/telephony/java/com/android/internal/telephony/PhoneConstants.java
@@ -79,8 +79,6 @@
public static final int SIM_ACTIVATION_TYPE_DATA = 1;
public static final String PHONE_NAME_KEY = "phoneName";
- public static final String FAILURE_REASON_KEY = "reason";
- public static final String STATE_CHANGE_REASON_KEY = "reason";
public static final String DATA_NETWORK_TYPE_KEY = "networkType";
public static final String DATA_FAILURE_CAUSE_KEY = "failCause";
public static final String DATA_APN_TYPE_KEY = "apnType";
diff --git a/telephony/java/com/android/internal/telephony/SmsApplication.java b/telephony/java/com/android/internal/telephony/SmsApplication.java
index 9874f80..a508b06 100644
--- a/telephony/java/com/android/internal/telephony/SmsApplication.java
+++ b/telephony/java/com/android/internal/telephony/SmsApplication.java
@@ -18,6 +18,8 @@
import android.Manifest.permission;
import android.app.AppOpsManager;
+import android.app.role.RoleManager;
+import android.app.role.RoleManagerCallback;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -29,13 +31,12 @@
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
-import android.content.res.Resources;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Binder;
import android.os.Debug;
import android.os.Process;
import android.os.UserHandle;
-import android.provider.Settings;
import android.provider.Telephony;
import android.provider.Telephony.Sms.Intents;
import android.telephony.Rlog;
@@ -50,6 +51,10 @@
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
/**
* Class for managing the primary application that we will deliver SMS/MMS messages to
@@ -67,6 +72,7 @@
private static final String SCHEME_SMSTO = "smsto";
private static final String SCHEME_MMS = "mms";
private static final String SCHEME_MMSTO = "mmsto";
+ private static final boolean DEBUG = false;
private static final boolean DEBUG_MULTIUSER = false;
private static final int[] DEFAULT_APP_EXCLUSIVE_APPOPS = {
@@ -240,7 +246,11 @@
// Get the list of apps registered for SMS
Intent intent = new Intent(Intents.SMS_DELIVER_ACTION);
- List<ResolveInfo> smsReceivers = packageManager.queryBroadcastReceiversAsUser(intent, 0,
+ if (DEBUG) {
+ intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION);
+ }
+ List<ResolveInfo> smsReceivers = packageManager.queryBroadcastReceiversAsUser(intent,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
userId);
HashMap<String, SmsApplicationData> receivers = new HashMap<String, SmsApplicationData>();
@@ -266,7 +276,8 @@
// Update any existing entries with mms receiver class
intent = new Intent(Intents.WAP_PUSH_DELIVER_ACTION);
intent.setDataAndType(null, "application/vnd.wap.mms-message");
- List<ResolveInfo> mmsReceivers = packageManager.queryBroadcastReceiversAsUser(intent, 0,
+ List<ResolveInfo> mmsReceivers = packageManager.queryBroadcastReceiversAsUser(intent,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
userId);
for (ResolveInfo resolveInfo : mmsReceivers) {
final ActivityInfo activityInfo = resolveInfo.activityInfo;
@@ -286,7 +297,8 @@
// Update any existing entries with respond via message intent class.
intent = new Intent(TelephonyManager.ACTION_RESPOND_VIA_MESSAGE,
Uri.fromParts(SCHEME_SMSTO, "", null));
- List<ResolveInfo> respondServices = packageManager.queryIntentServicesAsUser(intent, 0,
+ List<ResolveInfo> respondServices = packageManager.queryIntentServicesAsUser(intent,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
userId);
for (ResolveInfo resolveInfo : respondServices) {
final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
@@ -306,7 +318,8 @@
// Update any existing entries with supports send to.
intent = new Intent(Intent.ACTION_SENDTO,
Uri.fromParts(SCHEME_SMSTO, "", null));
- List<ResolveInfo> sendToActivities = packageManager.queryIntentActivitiesAsUser(intent, 0,
+ List<ResolveInfo> sendToActivities = packageManager.queryIntentActivitiesAsUser(intent,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
userId);
for (ResolveInfo resolveInfo : sendToActivities) {
final ActivityInfo activityInfo = resolveInfo.activityInfo;
@@ -323,7 +336,9 @@
// Update any existing entries with the default sms changed handler.
intent = new Intent(Telephony.Sms.Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED);
List<ResolveInfo> smsAppChangedReceivers =
- packageManager.queryBroadcastReceiversAsUser(intent, 0, userId);
+ packageManager.queryBroadcastReceiversAsUser(intent,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
if (DEBUG_MULTIUSER) {
Log.i(LOG_TAG, "getApplicationCollectionInternal smsAppChangedActivities=" +
smsAppChangedReceivers);
@@ -348,7 +363,9 @@
// Update any existing entries with the external provider changed handler.
intent = new Intent(Telephony.Sms.Intents.ACTION_EXTERNAL_PROVIDER_CHANGE);
List<ResolveInfo> providerChangedReceivers =
- packageManager.queryBroadcastReceiversAsUser(intent, 0, userId);
+ packageManager.queryBroadcastReceiversAsUser(intent,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
if (DEBUG_MULTIUSER) {
Log.i(LOG_TAG, "getApplicationCollectionInternal providerChangedActivities=" +
providerChangedReceivers);
@@ -373,7 +390,9 @@
// Update any existing entries with the sim full handler.
intent = new Intent(Intents.SIM_FULL_ACTION);
List<ResolveInfo> simFullReceivers =
- packageManager.queryBroadcastReceiversAsUser(intent, 0, userId);
+ packageManager.queryBroadcastReceiversAsUser(intent,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
if (DEBUG_MULTIUSER) {
Log.i(LOG_TAG, "getApplicationCollectionInternal simFullReceivers="
+ simFullReceivers);
@@ -406,7 +425,8 @@
if (smsApplicationData != null) {
if (!smsApplicationData.isComplete()) {
Log.w(LOG_TAG, "Package " + packageName
- + " lacks required manifest declarations to be a default sms app");
+ + " lacks required manifest declarations to be a default sms app: "
+ + smsApplicationData);
receivers.remove(packageName);
}
}
@@ -418,7 +438,7 @@
* Checks to see if we have a valid installed SMS application for the specified package name
* @return Data for the specified package name or null if there isn't one
*/
- private static SmsApplicationData getApplicationForPackage(
+ public static SmsApplicationData getApplicationForPackage(
Collection<SmsApplicationData> applications, String packageName) {
if (packageName == null) {
return null;
@@ -456,8 +476,7 @@
Log.i(LOG_TAG, "getApplication userId=" + userId);
}
// Determine which application receives the broadcast
- String defaultApplication = Settings.Secure.getStringForUser(context.getContentResolver(),
- Settings.Secure.SMS_DEFAULT_APPLICATION, userId);
+ String defaultApplication = getDefaultSmsPackage(context, userId);
if (DEBUG_MULTIUSER) {
Log.i(LOG_TAG, "getApplication defaultApp=" + defaultApplication);
}
@@ -469,27 +488,6 @@
if (DEBUG_MULTIUSER) {
Log.i(LOG_TAG, "getApplication appData=" + applicationData);
}
- // Picking a new SMS app requires AppOps and Settings.Secure permissions, so we only do
- // this if the caller asked us to.
- if (updateIfNeeded && applicationData == null) {
- // Try to find the default SMS package for this device
- Resources r = context.getResources();
- String defaultPackage =
- r.getString(com.android.internal.R.string.default_sms_application);
- applicationData = getApplicationForPackage(applications, defaultPackage);
-
- if (applicationData == null) {
- // Are there any applications?
- if (applications.size() != 0) {
- applicationData = (SmsApplicationData)applications.toArray()[0];
- }
- }
-
- // If we found a new default app, update the setting
- if (applicationData != null) {
- setDefaultApplicationInternal(applicationData.mPackageName, context, userId);
- }
- }
// If we found a package, make sure AppOps permissions are set up correctly
if (applicationData != null) {
@@ -513,7 +511,7 @@
// current SMS app will already be the preferred activity - but checking whether or
// not this is true is just as expensive as reconfiguring the preferred activity so
// we just reconfigure every time.
- updateDefaultSmsApp(context, userId, applicationData);
+ defaultSmsAppChanged(context);
}
}
if (DEBUG_MULTIUSER) {
@@ -522,16 +520,17 @@
return applicationData;
}
- private static void updateDefaultSmsApp(Context context, int userId,
- SmsApplicationData applicationData) {
+ private static String getDefaultSmsPackage(Context context, int userId) {
+ return context.getSystemService(RoleManager.class).getDefaultSmsPackage(userId);
+ }
+
+ /**
+ * Grants various permissions and appops on sms app change
+ */
+ private static void defaultSmsAppChanged(Context context) {
PackageManager packageManager = context.getPackageManager();
AppOpsManager appOps = context.getSystemService(AppOpsManager.class);
- // Configure this as the preferred activity for SENDTO sms/mms intents
- configurePreferredActivity(packageManager, new ComponentName(
- applicationData.mPackageName, applicationData.mSendToClass),
- userId);
-
// Assign permission to special system apps
assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps,
PHONE_PACKAGE_NAME);
@@ -603,8 +602,7 @@
final UserHandle userHandle = UserHandle.of(userId);
// Get old package name
- String oldPackageName = Settings.Secure.getStringForUser(context.getContentResolver(),
- Settings.Secure.SMS_DEFAULT_APPLICATION, userId);
+ String oldPackageName = getDefaultSmsPackage(context, userId);
if (DEBUG_MULTIUSER) {
Log.i(LOG_TAG, "setDefaultApplicationInternal old=" + oldPackageName +
@@ -636,16 +634,29 @@
}
}
- // Update the secure setting.
- Settings.Secure.putStringForUser(context.getContentResolver(),
- Settings.Secure.SMS_DEFAULT_APPLICATION, applicationData.mPackageName,
- userId);
+ // Update the setting.
+ CompletableFuture<Void> res = new CompletableFuture<>();
+ context.getSystemService(RoleManager.class).addRoleHolderAsUser(
+ RoleManager.ROLE_SMS, applicationData.mPackageName, UserHandle.of(userId),
+ AsyncTask.THREAD_POOL_EXECUTOR, new RoleManagerCallback() {
+ @Override
+ public void onSuccess() {
+ res.complete(null);
+ }
- // Allow relevant appops for the newly configured default SMS app.
- setExclusiveAppops(applicationData.mPackageName, appOps, applicationData.mUid,
- AppOpsManager.MODE_ALLOWED);
+ @Override
+ public void onFailure() {
+ res.completeExceptionally(new RuntimeException());
+ }
+ });
+ try {
+ res.get(5, TimeUnit.SECONDS);
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ Log.e(LOG_TAG, "Exception while adding sms role holder " + applicationData, e);
+ return;
+ }
- updateDefaultSmsApp(context, userId, applicationData);
+ defaultSmsAppChanged(context);
if (DEBUG_MULTIUSER) {
Log.i(LOG_TAG, "setDefaultApplicationInternal oldAppData=" + oldAppData);
diff --git a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
index 870a689..dd40d56 100644
--- a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
+++ b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
@@ -39,7 +39,7 @@
oneway void switchToSubscription(int subscriptionId, String callingPackage,
in PendingIntent callbackIntent);
oneway void updateSubscriptionNickname(int subscriptionId, String nickname,
- in PendingIntent callbackIntent);
+ String callingPackage, in PendingIntent callbackIntent);
oneway void eraseSubscriptions(in PendingIntent callbackIntent);
oneway void retainSubscriptionsForFactoryReset(in PendingIntent callbackIntent);
}
\ No newline at end of file
diff --git a/test-mock/Android.bp b/test-mock/Android.bp
index 37158e5..e1d6e01 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -25,7 +25,8 @@
"android.test.mock",
],
+ srcs_lib: "framework",
+ srcs_lib_whitelist_dirs: ["core/java"],
srcs_lib_whitelist_pkgs: ["android"],
- metalava_enabled: false,
compile_dex: true,
}
diff --git a/test-mock/src/android/test/mock/MockContentProvider.java b/test-mock/src/android/test/mock/MockContentProvider.java
index 0ac35bc..e9a5ff7 100644
--- a/test-mock/src/android/test/mock/MockContentProvider.java
+++ b/test-mock/src/android/test/mock/MockContentProvider.java
@@ -119,7 +119,7 @@
@Override
public IBinder asBinder() {
- throw new UnsupportedOperationException();
+ return MockContentProvider.this.getIContentProviderBinder();
}
@Override
@@ -279,6 +279,13 @@
}
/**
+ * @hide
+ */
+ public IBinder getIContentProviderBinder() {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ /**
* Like {@link #attachInfo(Context, android.content.pm.ProviderInfo)}, but for use
* when directly instantiating the provider for testing.
*
diff --git a/tests/ActivityViewTest/Android.mk b/tests/ActivityViewTest/Android.mk
index 9c80764..9c7ca7e 100644
--- a/tests/ActivityViewTest/Android.mk
+++ b/tests/ActivityViewTest/Android.mk
@@ -1,7 +1,7 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := ActivityViewTest
LOCAL_PRIVATE_PLATFORM_APIS := true
diff --git a/tests/ActivityViewTest/AndroidManifest.xml b/tests/ActivityViewTest/AndroidManifest.xml
index de54cc9..0be1ea0 100644
--- a/tests/ActivityViewTest/AndroidManifest.xml
+++ b/tests/ActivityViewTest/AndroidManifest.xml
@@ -35,17 +35,20 @@
<activity android:name=".ActivityViewActivity"
android:label="AV"
- android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density">
+ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density"
+ android:windowSoftInputMode="stateHidden|adjustResize">
</activity>
<activity android:name=".ActivityViewResizeActivity"
android:label="AV Resize"
- android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density">
+ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density"
+ android:windowSoftInputMode="stateHidden|adjustResize">
</activity>
<activity android:name=".ActivityViewScrollActivity"
android:label="AV Scroll"
- android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density">
+ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density"
+ android:windowSoftInputMode="stateHidden">
</activity>
<activity android:name=".ActivityViewTestActivity"
diff --git a/tests/ActivityViewTest/res/layout/activity_view_test_activity.xml b/tests/ActivityViewTest/res/layout/activity_view_test_activity.xml
index f7ec562..338d68a 100644
--- a/tests/ActivityViewTest/res/layout/activity_view_test_activity.xml
+++ b/tests/ActivityViewTest/res/layout/activity_view_test_activity.xml
@@ -15,6 +15,7 @@
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/test_activity_root"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -60,11 +61,10 @@
android:background="#00000000"
android:gravity="center" />
- <View
- android:id="@+id/touch_intercept_view"
+ <EditText
+ android:id="@+id/test_activity_edittext"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="#00000000"
- />
-
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:layout_margin="16dp" />
</RelativeLayout>
\ No newline at end of file
diff --git a/tests/ActivityViewTest/src/com/google/android/test/activityview/ActivityViewTestActivity.java b/tests/ActivityViewTest/src/com/google/android/test/activityview/ActivityViewTestActivity.java
index 0d62786..ba2c764 100644
--- a/tests/ActivityViewTest/src/com/google/android/test/activityview/ActivityViewTestActivity.java
+++ b/tests/ActivityViewTest/src/com/google/android/test/activityview/ActivityViewTestActivity.java
@@ -29,26 +29,24 @@
import android.view.ViewTreeObserver;
import android.widget.TextView;
-public class ActivityViewTestActivity extends Activity implements View.OnTouchListener {
+public class ActivityViewTestActivity extends Activity {
+ private View mRoot;
private TextView mTextView;
private TextView mWidthTextView;
private TextView mHeightTextView;
private TextView mTouchStateTextView;
- private View mTouchInterceptView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view_test_activity);
-
+ mRoot = findViewById(R.id.test_activity_root);
mTextView = findViewById(R.id.test_activity_title);
mWidthTextView = findViewById(R.id.test_activity_width_text);
mHeightTextView = findViewById(R.id.test_activity_height_text);
mTouchStateTextView = findViewById(R.id.test_activity_touch_state);
- mTouchInterceptView = findViewById(R.id.touch_intercept_view);
- mTouchInterceptView.setOnTouchListener(this);
- ViewTreeObserver viewTreeObserver = mTouchInterceptView.getViewTreeObserver();
+ ViewTreeObserver viewTreeObserver = mRoot.getViewTreeObserver();
if (viewTreeObserver.isAlive()) {
viewTreeObserver.addOnGlobalLayoutListener(this::updateDimensionTexts);
}
@@ -90,8 +88,8 @@
}
private void updateDimensionTexts() {
- mWidthTextView.setText("" + mTouchInterceptView.getWidth());
- mHeightTextView.setText("" + mTouchInterceptView.getHeight());
+ mWidthTextView.setText("" + mRoot.getWidth());
+ mHeightTextView.setText("" + mRoot.getHeight());
}
private void updateTouchState(MotionEvent event) {
@@ -108,8 +106,8 @@
}
@Override
- public boolean onTouch(View v, MotionEvent event) {
+ public boolean dispatchTouchEvent(MotionEvent event) {
updateTouchState(event);
- return true;
+ return super.dispatchTouchEvent(event);
}
}
diff --git a/tests/PackageWatchdog/Android.mk b/tests/PackageWatchdog/Android.mk
new file mode 100644
index 0000000..b53f27d
--- /dev/null
+++ b/tests/PackageWatchdog/Android.mk
@@ -0,0 +1,34 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+# PackageWatchdogTest
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_PACKAGE_NAME := PackageWatchdogTest
+LOCAL_MODULE_TAGS := tests
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ junit \
+ frameworks-base-testutils \
+ android-support-test \
+ services
+
+LOCAL_JAVA_LIBRARIES := \
+ android.test.runner
+
+LOCAL_PRIVATE_PLATFORM_APIS := true
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
+include $(BUILD_PACKAGE)
diff --git a/tests/PackageWatchdog/AndroidManifest.xml b/tests/PackageWatchdog/AndroidManifest.xml
new file mode 100644
index 0000000..fa89528
--- /dev/null
+++ b/tests/PackageWatchdog/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.packagewatchdog" >
+
+ <application android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.tests.packagewatchdog"
+ android:label="PackageWatchdog Test"/>
+</manifest>
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
new file mode 100644
index 0000000..ec07037
--- /dev/null
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -0,0 +1,286 @@
+/*
+ * 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;
+
+import static com.android.server.PackageWatchdog.TRIGGER_FAILURE_COUNT;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.os.test.TestLooper;
+import android.support.test.InstrumentationRegistry;
+
+import com.android.server.PackageWatchdog.PackageHealthObserver;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+// TODO(zezeozue): Write test without using PackageWatchdog#getPackages. Just rely on
+// behavior of observers receiving crash notifications or not to determine if it's registered
+/**
+ * Test PackageWatchdog.
+ */
+public class PackageWatchdogTest {
+ private static final String APP_A = "com.package.a";
+ private static final String APP_B = "com.package.b";
+ private static final String OBSERVER_NAME_1 = "observer1";
+ private static final String OBSERVER_NAME_2 = "observer2";
+ private static final String OBSERVER_NAME_3 = "observer3";
+ private static final long SHORT_DURATION = TimeUnit.SECONDS.toMillis(1);
+ private static final long LONG_DURATION = TimeUnit.SECONDS.toMillis(5);
+ private TestLooper mTestLooper;
+
+ @Before
+ public void setUp() throws Exception {
+ mTestLooper = new TestLooper();
+ mTestLooper.startAutoDispatch();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ new File(InstrumentationRegistry.getContext().getFilesDir(),
+ "package-watchdog.xml").delete();
+ }
+
+ /**
+ * Test registration, unregistration, package expiry and duration reduction
+ */
+ @Test
+ public void testRegistration() throws Exception {
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
+ TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
+ TestObserver observer3 = new TestObserver(OBSERVER_NAME_3);
+
+ // Start observing for observer1 which will be unregistered
+ watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ // Start observing for observer2 which will expire
+ watchdog.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
+ // Start observing for observer3 which will have expiry duration reduced
+ watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), LONG_DURATION);
+
+ // Verify packages observed at start
+ // 1
+ assertEquals(1, watchdog.getPackages(observer1).size());
+ assertTrue(watchdog.getPackages(observer1).contains(APP_A));
+ // 2
+ assertEquals(2, watchdog.getPackages(observer2).size());
+ assertTrue(watchdog.getPackages(observer2).contains(APP_A));
+ assertTrue(watchdog.getPackages(observer2).contains(APP_B));
+ // 3
+ assertEquals(1, watchdog.getPackages(observer3).size());
+ assertTrue(watchdog.getPackages(observer3).contains(APP_A));
+
+ // Then unregister observer1
+ watchdog.unregisterHealthObserver(observer1);
+
+ // Verify observer2 and observer3 left
+ // 1
+ assertNull(watchdog.getPackages(observer1));
+ // 2
+ assertEquals(2, watchdog.getPackages(observer2).size());
+ assertTrue(watchdog.getPackages(observer2).contains(APP_A));
+ assertTrue(watchdog.getPackages(observer2).contains(APP_B));
+ // 3
+ assertEquals(1, watchdog.getPackages(observer3).size());
+ assertTrue(watchdog.getPackages(observer3).contains(APP_A));
+
+ // Then advance time a little and run messages in Handlers so observer2 expires
+ Thread.sleep(SHORT_DURATION);
+ mTestLooper.dispatchAll();
+
+ // Verify observer3 left with reduced expiry duration
+ // 1
+ assertNull(watchdog.getPackages(observer1));
+ // 2
+ assertNull(watchdog.getPackages(observer2));
+ // 3
+ assertEquals(1, watchdog.getPackages(observer3).size());
+ assertTrue(watchdog.getPackages(observer3).contains(APP_A));
+
+ // Then advance time some more and run messages in Handlers so observer3 expires
+ Thread.sleep(LONG_DURATION);
+ mTestLooper.dispatchAll();
+
+ // Verify observer3 expired
+ // 1
+ assertNull(watchdog.getPackages(observer1));
+ // 2
+ assertNull(watchdog.getPackages(observer2));
+ // 3
+ assertNull(watchdog.getPackages(observer3));
+ }
+
+ /**
+ * Test package observers are persisted and loaded on startup
+ */
+ @Test
+ public void testPersistence() throws Exception {
+ PackageWatchdog watchdog1 = createWatchdog();
+ TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
+ TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
+
+ watchdog1.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog1.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
+
+ // Verify 2 observers are registered and saved internally
+ // 1
+ assertEquals(1, watchdog1.getPackages(observer1).size());
+ assertTrue(watchdog1.getPackages(observer1).contains(APP_A));
+ // 2
+ assertEquals(2, watchdog1.getPackages(observer2).size());
+ assertTrue(watchdog1.getPackages(observer2).contains(APP_A));
+ assertTrue(watchdog1.getPackages(observer2).contains(APP_B));
+
+
+ // Then advance time and run IO Handler so file is saved
+ mTestLooper.dispatchAll();
+
+ // Then start a new watchdog
+ PackageWatchdog watchdog2 = createWatchdog();
+
+ // Verify the new watchdog loads observers on startup but nothing registered
+ assertEquals(0, watchdog2.getPackages(observer1).size());
+ assertEquals(0, watchdog2.getPackages(observer2).size());
+ // Verify random observer not saved returns null
+ assertNull(watchdog2.getPackages(new TestObserver(OBSERVER_NAME_3)));
+
+ // Then regiser observer1
+ watchdog2.registerHealthObserver(observer1);
+ watchdog2.registerHealthObserver(observer2);
+
+ // Verify 2 observers are registered after reload
+ // 1
+ assertEquals(1, watchdog1.getPackages(observer1).size());
+ assertTrue(watchdog1.getPackages(observer1).contains(APP_A));
+ // 2
+ assertEquals(2, watchdog1.getPackages(observer2).size());
+ assertTrue(watchdog1.getPackages(observer2).contains(APP_A));
+ assertTrue(watchdog1.getPackages(observer2).contains(APP_B));
+ }
+
+ /**
+ * Test package failure under threshold does not notify observers
+ */
+ @Test
+ public void testNoPackageFailureBeforeThreshold() throws Exception {
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
+ TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
+
+ watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+
+ // Then fail APP_A below the threshold
+ for (int i = 0; i < TRIGGER_FAILURE_COUNT - 1; i++) {
+ watchdog.onPackageFailure(new String[]{APP_A});
+ }
+
+ // Verify that observers are not notified
+ assertEquals(0, observer1.mFailedPackages.size());
+ assertEquals(0, observer2.mFailedPackages.size());
+ }
+
+ /**
+ * Test package failure and notifies all observer since none handles the failure
+ */
+ @Test
+ public void testPackageFailureNotifyAll() throws Exception {
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
+ TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
+
+ // Start observing for observer1 and observer2 without handling failures
+ watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.startObservingHealth(observer1, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
+
+ // Then fail APP_A and APP_B above the threshold
+ for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) {
+ watchdog.onPackageFailure(new String[]{APP_A, APP_B});
+ }
+
+ // Verify all observers are notifed of all package failures
+ List<String> observer1Packages = observer1.mFailedPackages;
+ List<String> observer2Packages = observer2.mFailedPackages;
+ assertEquals(2, observer1Packages.size());
+ assertEquals(1, observer2Packages.size());
+ assertEquals(APP_A, observer1Packages.get(0));
+ assertEquals(APP_B, observer1Packages.get(1));
+ assertEquals(APP_A, observer2Packages.get(0));
+ }
+
+ /**
+ * Test package failure and notifies only one observer because it handles the failure
+ */
+ @Test
+ public void testPackageFailureNotifyOne() throws Exception {
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver observer1 = new TestObserver(OBSERVER_NAME_1, true /* shouldHandle */);
+ TestObserver observer2 = new TestObserver(OBSERVER_NAME_2, true /* shouldHandle */);
+
+ // Start observing for observer1 and observer2 with failure handling
+ watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+
+ // Then fail APP_A above the threshold
+ for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) {
+ watchdog.onPackageFailure(new String[]{APP_A});
+ }
+
+ // Verify only one observer is notifed
+ assertEquals(1, observer1.mFailedPackages.size());
+ assertEquals(APP_A, observer1.mFailedPackages.get(0));
+ assertEquals(0, observer2.mFailedPackages.size());
+ }
+
+ private PackageWatchdog createWatchdog() {
+ return new PackageWatchdog(InstrumentationRegistry.getContext(),
+ mTestLooper.getLooper());
+ }
+
+ private static class TestObserver implements PackageHealthObserver {
+ private final String mName;
+ private boolean mShouldHandle;
+ final List<String> mFailedPackages = new ArrayList<>();
+
+ TestObserver(String name) {
+ mName = name;
+ }
+
+ TestObserver(String name, boolean shouldHandle) {
+ mName = name;
+ mShouldHandle = shouldHandle;
+ }
+
+ public boolean onHealthCheckFailed(String packageName) {
+ mFailedPackages.add(packageName);
+ return mShouldHandle;
+ }
+
+ public String getName() {
+ return mName;
+ }
+ }
+}
diff --git a/tests/RcsTests/Android.mk b/tests/RcsTests/Android.mk
index adc7cab..7b348d7 100644
--- a/tests/RcsTests/Android.mk
+++ b/tests/RcsTests/Android.mk
@@ -11,7 +11,7 @@
LOCAL_CERTIFICATE := platform
LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base
-LOCAL_STATIC_JAVA_LIBRARIES := junit android-support-test mockito-target-minus-junit4
+LOCAL_STATIC_JAVA_LIBRARIES := junit android-support-test mockito-target-minus-junit4 truth-prebuilt
include $(BUILD_PACKAGE)
diff --git a/tests/RcsTests/src/com/android/tests/rcs/RcsMessageStoreTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsMessageStoreTest.java
similarity index 97%
rename from tests/RcsTests/src/com/android/tests/rcs/RcsMessageStoreTest.java
rename to tests/RcsTests/src/com/android/tests/ims/RcsMessageStoreTest.java
index 290e04c..44277ed 100644
--- a/tests/RcsTests/src/com/android/tests/rcs/RcsMessageStoreTest.java
+++ b/tests/RcsTests/src/com/android/tests/ims/RcsMessageStoreTest.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.tests.rcs;
+package com.android.tests.ims;
import android.support.test.runner.AndroidJUnit4;
import android.telephony.ims.RcsMessageStore;
diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsParticipantTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsParticipantTest.java
new file mode 100644
index 0000000..c402dbf
--- /dev/null
+++ b/tests/RcsTests/src/com/android/tests/ims/RcsParticipantTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+package com.android.tests.ims;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Bundle;
+import android.support.test.runner.AndroidJUnit4;
+import android.telephony.ims.RcsParticipant;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class RcsParticipantTest {
+ private static final int ID = 123;
+ private static final String ALIAS = "alias";
+ private static final String CANONICAL_ADDRESS = "+1234567890";
+
+ @Test
+ public void testCanUnparcel() {
+ RcsParticipant rcsParticipant = new RcsParticipant(ID, CANONICAL_ADDRESS);
+ rcsParticipant.setAlias(ALIAS);
+
+ Bundle bundle = new Bundle();
+ bundle.putParcelable("Some key", rcsParticipant);
+ rcsParticipant = bundle.getParcelable("Some key");
+
+ assertThat(rcsParticipant.getId()).isEqualTo(ID);
+ assertThat(rcsParticipant.getAlias()).isEqualTo(ALIAS);
+ assertThat(rcsParticipant.getCanonicalAddress()).isEqualTo(CANONICAL_ADDRESS);
+ }
+}
diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsThreadQueryParametersTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsThreadQueryParametersTest.java
new file mode 100644
index 0000000..a890a38
--- /dev/null
+++ b/tests/RcsTests/src/com/android/tests/ims/RcsThreadQueryParametersTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+package com.android.tests.ims;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Bundle;
+import android.support.test.runner.AndroidJUnit4;
+import android.telephony.ims.RcsParticipant;
+import android.telephony.ims.RcsThreadQueryParameters;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+@RunWith(AndroidJUnit4.class)
+public class RcsThreadQueryParametersTest {
+ private RcsThreadQueryParameters mRcsThreadQueryParameters;
+ @Mock RcsParticipant mMockParticipant;
+
+ @Test
+ public void testUnparceling() {
+ String key = "some key";
+ mRcsThreadQueryParameters = RcsThreadQueryParameters.builder()
+ .isGroupThread(true)
+ .withParticipant(mMockParticipant)
+ .limitResultsTo(50)
+ .sort(true)
+ .build();
+
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(key, mRcsThreadQueryParameters);
+ mRcsThreadQueryParameters = bundle.getParcelable(key);
+
+ assertThat(mRcsThreadQueryParameters.isGroupThread()).isTrue();
+ assertThat(mRcsThreadQueryParameters.getRcsParticipants()).contains(mMockParticipant);
+ assertThat(mRcsThreadQueryParameters.getLimit()).isEqualTo(50);
+ assertThat(mRcsThreadQueryParameters.isAscending()).isTrue();
+ }
+}
diff --git a/tests/RollbackTest/TEST_MAPPING b/tests/RollbackTest/TEST_MAPPING
new file mode 100644
index 0000000..c1d95ac
--- /dev/null
+++ b/tests/RollbackTest/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "RollbackTest"
+ }
+ ]
+}
diff --git a/tests/UsageReportingTest/Android.mk b/tests/UsageReportingTest/Android.mk
new file mode 100644
index 0000000..afb6e16b1
--- /dev/null
+++ b/tests/UsageReportingTest/Android.mk
@@ -0,0 +1,17 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+# Only compile source java files in this apk.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_USE_AAPT2 := true
+LOCAL_STATIC_ANDROID_LIBRARIES := androidx.legacy_legacy-support-v4
+
+LOCAL_CERTIFICATE := platform
+
+LOCAL_PACKAGE_NAME := UsageReportingTest
+LOCAL_PRIVATE_PLATFORM_APIS := true
+
+include $(BUILD_PACKAGE)
diff --git a/tests/UsageReportingTest/AndroidManifest.xml b/tests/UsageReportingTest/AndroidManifest.xml
new file mode 100644
index 0000000..be0b09e
--- /dev/null
+++ b/tests/UsageReportingTest/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ Note: Add android:sharedUserId="android.uid.system" to the root element to simulate the system UID
+ caller case.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.usagereporter"
+ >
+
+ <application android:label="@string/reporter_app">
+ <activity android:name="UsageReporterActivity"
+ android:label="UsageReporter">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ </application>
+</manifest>
diff --git a/tests/UsageReportingTest/res/layout/row_item.xml b/tests/UsageReportingTest/res/layout/row_item.xml
new file mode 100644
index 0000000..1eb2dab
--- /dev/null
+++ b/tests/UsageReportingTest/res/layout/row_item.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp"
+ android:background="@color/inactive_color">
+
+ <TextView android:id="@+id/token"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:textStyle="bold"/>
+
+ <Button android:id="@+id/start" style="@style/ActionButton"
+ android:text="@string/start" />
+
+ <Button android:id="@+id/stop" style="@style/ActionButton"
+ android:text="@string/stop" />
+</LinearLayout>
diff --git a/tests/UsageReportingTest/res/menu/main.xml b/tests/UsageReportingTest/res/menu/main.xml
new file mode 100644
index 0000000..9847c2d
--- /dev/null
+++ b/tests/UsageReportingTest/res/menu/main.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- 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.
+-->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@+id/add_token"
+ android:title="@string/add_token"/>
+ <item android:id="@+id/add_many_tokens"
+ android:title="@string/add_many_tokens"/>
+ <item android:id="@+id/stop_all"
+ android:title="@string/stop_all_tokens"/>
+ <group android:checkableBehavior="all">
+ <item android:id="@+id/restore_on_start"
+ android:title="@string/restore_tokens_on_start"/>
+ </group>
+</menu>
\ No newline at end of file
diff --git a/tests/UsageReportingTest/res/values/colors.xml b/tests/UsageReportingTest/res/values/colors.xml
new file mode 100644
index 0000000..03bcf8a
--- /dev/null
+++ b/tests/UsageReportingTest/res/values/colors.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<resources>
+ <color name="active_color">#FFF</color>
+ <color name="inactive_color">#AAA</color>
+</resources>
\ No newline at end of file
diff --git a/tests/UsageReportingTest/res/values/strings.xml b/tests/UsageReportingTest/res/values/strings.xml
new file mode 100644
index 0000000..015290e
--- /dev/null
+++ b/tests/UsageReportingTest/res/values/strings.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources>
+ <!-- Do not translate -->
+ <string name="reporter_app">Usage Reporter App</string>
+ <!-- Do not translate -->
+ <string name="start">Start</string>
+ <!-- Do not translate -->
+ <string name="stop">Stop</string>
+ <!-- Do not translate -->
+ <string name="default_token">SuperSecretToken</string>
+
+ <!-- Do not translate -->
+ <string name="add_token">Add Token</string>
+ <!-- Do not translate -->
+ <string name="add_many_tokens">Add Many Tokens</string>
+ <!-- Do not translate -->
+ <string name="stop_all_tokens">Stop All</string>
+ <!-- Do not translate -->
+ <string name="restore_tokens_on_start">Readd Tokens on Start</string>
+
+
+ <!-- Do not translate -->
+ <string name="token_query">Enter token(s) (delimit tokens with commas)</string>
+ <!-- Do not translate -->
+ <string name="many_tokens_query">Generate how many tokens?</string>
+ <!-- Do not translate -->
+ <string name="stop_all_tokens_query">Stop all tokens?</string>
+ <!-- Do not translate -->
+ <string name="ok">OK</string>
+ <!-- Do not translate -->
+ <string name="cancel">Cancel</string>
+</resources>
\ No newline at end of file
diff --git a/tests/UsageReportingTest/res/values/styles.xml b/tests/UsageReportingTest/res/values/styles.xml
new file mode 100644
index 0000000..e5b86c5
--- /dev/null
+++ b/tests/UsageReportingTest/res/values/styles.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources>
+ <style name="ActionButton">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:textAppearance">@style/TextAppearance.ActionButton</item>
+ </style>
+
+ <style name="TextAppearance" parent="android:TextAppearance">
+ </style>
+
+ <style name="TextAppearance.ActionButton">
+ <item name="android:textStyle">italic</item>
+ </style>
+
+</resources>
diff --git a/tests/UsageReportingTest/src/com/android/tests/usagereporter/UsageReporterActivity.java b/tests/UsageReportingTest/src/com/android/tests/usagereporter/UsageReporterActivity.java
new file mode 100644
index 0000000..946be8f
--- /dev/null
+++ b/tests/UsageReportingTest/src/com/android/tests/usagereporter/UsageReporterActivity.java
@@ -0,0 +1,320 @@
+/*
+ * 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.
+ */
+
+package com.android.tests.usagereporter;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.ListActivity;
+import android.app.usage.UsageStatsManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.text.InputType;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+
+public class UsageReporterActivity extends ListActivity {
+
+ private Activity mActivity;
+ private final ArrayList<String> mTokens = new ArrayList();
+ private final ArraySet<String> mActives = new ArraySet();
+ private UsageStatsManager mUsageStatsManager;
+ private Adapter mAdapter;
+ private boolean mRestoreOnStart = false;
+ private static Context sContext;
+
+ /** Called with the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ sContext = getApplicationContext();
+
+ mUsageStatsManager = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE);
+
+ mAdapter = new Adapter();
+ setListAdapter(mAdapter);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ mActivity = this;
+
+
+ if (mRestoreOnStart) {
+ ArrayList<String> removed = null;
+ for (String token : mActives) {
+ try {
+ mUsageStatsManager.reportUsageStart(mActivity, token);
+ } catch (Exception e) {
+ // Somthing went wrong, recover and move on
+ if (removed == null) {
+ removed = new ArrayList();
+ }
+ removed.add(token);
+ }
+ }
+ if (removed != null) {
+ for (String token : removed) {
+ mActives.remove(token);
+ }
+ }
+ } else {
+ mActives.clear();
+ }
+ }
+
+ /**
+ * Called when the activity is about to start interacting with the user.
+ */
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mAdapter.notifyDataSetChanged();
+ }
+
+
+ /**
+ * Called when your activity's options menu needs to be created.
+ */
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.main, menu);
+ return super.onCreateOptionsMenu(menu);
+ }
+
+
+ /**
+ * Called when a menu item is selected.
+ */
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.add_token:
+ callAddToken();
+ return true;
+ case R.id.add_many_tokens:
+ callAddManyTokens();
+ return true;
+ case R.id.stop_all:
+ callStopAll();
+ return true;
+ case R.id.restore_on_start:
+ mRestoreOnStart = !mRestoreOnStart;
+ item.setChecked(mRestoreOnStart);
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void callAddToken() {
+ final AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(getString(R.string.token_query));
+ final EditText input = new EditText(this);
+ input.setInputType(InputType.TYPE_CLASS_TEXT);
+ input.setHint(getString(R.string.default_token));
+ builder.setView(input);
+
+ builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ String tokenNames = input.getText().toString().trim();
+ if (TextUtils.isEmpty(tokenNames)) {
+ tokenNames = getString(R.string.default_token);
+ }
+ String[] tokens = tokenNames.split(",");
+ for (String token : tokens) {
+ if (mTokens.contains(token)) continue;
+ mTokens.add(token);
+ }
+ mAdapter.notifyDataSetChanged();
+
+ }
+ });
+ builder.setNegativeButton(getString(R.string.cancel),
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.cancel();
+ }
+ });
+
+ builder.show();
+ }
+
+ private void callAddManyTokens() {
+ final AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(getString(R.string.many_tokens_query));
+ final EditText input = new EditText(this);
+ input.setInputType(InputType.TYPE_CLASS_NUMBER);
+ builder.setView(input);
+
+ builder.setPositiveButton(getString(R.string.ok),
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ String val = input.getText().toString().trim();
+ if (TextUtils.isEmpty(val)) return;
+ int n = Integer.parseInt(val);
+ for (int i = 0; i < n; i++) {
+ final String token = getString(R.string.default_token) + i;
+ if (mTokens.contains(token)) continue;
+ mTokens.add(token);
+ }
+ mAdapter.notifyDataSetChanged();
+
+ }
+ });
+ builder.setNegativeButton(getString(R.string.cancel),
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.cancel();
+ }
+ });
+
+ builder.show();
+ }
+
+ private void callStopAll() {
+ final AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(getString(R.string.stop_all_tokens_query));
+
+ builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ for (String token : mActives) {
+ mUsageStatsManager.reportUsageStop(mActivity, token);
+ }
+ mActives.clear();
+ mAdapter.notifyDataSetChanged();
+ }
+ });
+ builder.setNegativeButton(getString(R.string.cancel),
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.cancel();
+ }
+ });
+ builder.show();
+ }
+
+ /**
+ * A call-back for when the user presses the back button.
+ */
+ OnClickListener mStartListener = new OnClickListener() {
+ public void onClick(View v) {
+ final View parent = (View) v.getParent();
+ final String token = ((TextView) parent.findViewById(R.id.token)).getText().toString();
+ try {
+ mUsageStatsManager.reportUsageStart(mActivity, token);
+ } catch (Exception e) {
+ Toast.makeText(sContext, e.toString(), Toast.LENGTH_LONG).show();
+ }
+ parent.setBackgroundColor(getColor(R.color.active_color));
+ mActives.add(token);
+ }
+ };
+
+ /**
+ * A call-back for when the user presses the clear button.
+ */
+ OnClickListener mStopListener = new OnClickListener() {
+ public void onClick(View v) {
+ final View parent = (View) v.getParent();
+
+ final String token = ((TextView) parent.findViewById(R.id.token)).getText().toString();
+ try {
+ mUsageStatsManager.reportUsageStop(mActivity, token);
+ } catch (Exception e) {
+ Toast.makeText(sContext, e.toString(), Toast.LENGTH_LONG).show();
+ }
+ parent.setBackgroundColor(getColor(R.color.inactive_color));
+ mActives.remove(token);
+ }
+ };
+
+
+ private class Adapter extends BaseAdapter {
+ @Override
+ public int getCount() {
+ return mTokens.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return mTokens.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ final ViewHolder holder;
+ if (convertView == null) {
+ convertView = LayoutInflater.from(UsageReporterActivity.this)
+ .inflate(R.layout.row_item, parent, false);
+ holder = new ViewHolder();
+ holder.tokenName = (TextView) convertView.findViewById(R.id.token);
+
+ holder.startButton = ((Button) convertView.findViewById(R.id.start));
+ holder.startButton.setOnClickListener(mStartListener);
+ holder.stopButton = ((Button) convertView.findViewById(R.id.stop));
+ holder.stopButton.setOnClickListener(mStopListener);
+ convertView.setTag(holder);
+ } else {
+ holder = (ViewHolder) convertView.getTag();
+ }
+
+ final String token = mTokens.get(position);
+ holder.tokenName.setText(mTokens.get(position));
+ if (mActives.contains(token)) {
+ convertView.setBackgroundColor(getColor(R.color.active_color));
+ } else {
+ convertView.setBackgroundColor(getColor(R.color.inactive_color));
+ }
+ return convertView;
+ }
+ }
+
+ private static class ViewHolder {
+ public TextView tokenName;
+ public Button startButton;
+ public Button stopButton;
+ }
+}
diff --git a/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java b/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java
index 3d8ce21..3c628f6 100644
--- a/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java
+++ b/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java
@@ -155,7 +155,7 @@
intent.setPackage(getPackageName());
intent.putExtra(EXTRA_KEY_TIMEOUT, true);
mUsageStatsManager.registerAppUsageObserver(1, packages,
- 30, TimeUnit.SECONDS, PendingIntent.getActivity(UsageStatsActivity.this,
+ 60, TimeUnit.SECONDS, PendingIntent.getActivity(UsageStatsActivity.this,
1, intent, 0));
}
}
diff --git a/tests/WindowManagerStressTest/src/test/windowmanagerstresstest/MainActivity.java b/tests/WindowManagerStressTest/src/test/windowmanagerstresstest/MainActivity.java
index d5987a5..8564698 100644
--- a/tests/WindowManagerStressTest/src/test/windowmanagerstresstest/MainActivity.java
+++ b/tests/WindowManagerStressTest/src/test/windowmanagerstresstest/MainActivity.java
@@ -28,6 +28,7 @@
import android.view.IWindowSession;
import android.view.InsetsState;
import android.view.Surface;
+import android.view.SurfaceControl;
import android.view.View;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
@@ -106,7 +107,7 @@
window.mSeq, mLayoutParams, -1, -1, View.VISIBLE, 0, -1, mTmpRect,
mTmpRect, mTmpRect, mTmpRect, mTmpRect, mTmpRect, mTmpRect,
new DisplayCutout.ParcelableWrapper(), new MergedConfiguration(),
- new Surface(), new InsetsState());
+ new SurfaceControl(), new InsetsState());
} catch (RemoteException e) {
e.printStackTrace();
}
diff --git a/tests/net/Android.mk b/tests/net/Android.mk
index 9d1edbf..f6f35fd 100644
--- a/tests/net/Android.mk
+++ b/tests/net/Android.mk
@@ -18,6 +18,7 @@
mockito-target-minus-junit4 \
platform-test-annotations \
services.core \
+ services.ipmemorystore \
services.net
LOCAL_JAVA_LIBRARIES := \
diff --git a/tests/net/java/android/net/IpMemoryStoreTest.java b/tests/net/java/android/net/IpMemoryStoreTest.java
new file mode 100644
index 0000000..eae9710
--- /dev/null
+++ b/tests/net/java/android/net/IpMemoryStoreTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+
+package android.net;
+
+import android.content.Context;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+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
+public class IpMemoryStoreTest {
+ @Mock
+ Context mMockContext;
+ @Mock
+ IIpMemoryStore mMockService;
+ IpMemoryStore mStore;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mStore = new IpMemoryStore(mMockContext, mMockService);
+ }
+
+ @Test
+ public void testNetworkAttributes() {
+ // TODO : implement this
+ }
+
+ @Test
+ public void testPrivateData() {
+ // TODO : implement this
+ }
+
+ @Test
+ public void testFindL2Key() {
+ // TODO : implement this
+ }
+
+ @Test
+ public void testIsSameNetwork() {
+ // TODO : implement this
+ }
+
+}
diff --git a/tests/net/java/android/net/LinkPropertiesTest.java b/tests/net/java/android/net/LinkPropertiesTest.java
index 9695e9a..f82b380 100644
--- a/tests/net/java/android/net/LinkPropertiesTest.java
+++ b/tests/net/java/android/net/LinkPropertiesTest.java
@@ -53,6 +53,8 @@
private static InetAddress DNS1 = NetworkUtils.numericToInetAddress("75.208.7.1");
private static InetAddress DNS2 = NetworkUtils.numericToInetAddress("69.78.7.1");
private static InetAddress DNS6 = NetworkUtils.numericToInetAddress("2001:4860:4860::8888");
+ private static InetAddress PCSCFV6 = NetworkUtils.numericToInetAddress(
+ "2001:0db8:85a3:0000:0000:8a2e:0370:1");
private static InetAddress GATEWAY1 = NetworkUtils.numericToInetAddress("75.208.8.1");
private static InetAddress GATEWAY2 = NetworkUtils.numericToInetAddress("69.78.8.1");
private static InetAddress GATEWAY61 = NetworkUtils.numericToInetAddress("fe80::6:0000:613");
@@ -86,6 +88,9 @@
assertTrue(source.isIdenticalValidatedPrivateDnses(target));
assertTrue(target.isIdenticalValidatedPrivateDnses(source));
+ assertTrue(source.isIdenticalPcscfs(target));
+ assertTrue(target.isIdenticalPcscfs(source));
+
assertTrue(source.isIdenticalRoutes(target));
assertTrue(target.isIdenticalRoutes(source));
@@ -128,6 +133,8 @@
// set 2 dnses
source.addDnsServer(DNS1);
source.addDnsServer(DNS2);
+ // set 1 pcscf
+ source.addPcscfServer(PCSCFV6);
// set 2 gateways
source.addRoute(new RouteInfo(GATEWAY1));
source.addRoute(new RouteInfo(GATEWAY2));
@@ -141,6 +148,7 @@
target.addLinkAddress(LINKADDRV6);
target.addDnsServer(DNS1);
target.addDnsServer(DNS2);
+ target.addPcscfServer(PCSCFV6);
target.addRoute(new RouteInfo(GATEWAY1));
target.addRoute(new RouteInfo(GATEWAY2));
target.setMtu(MTU);
@@ -154,6 +162,7 @@
target.addLinkAddress(LINKADDRV6);
target.addDnsServer(DNS1);
target.addDnsServer(DNS2);
+ target.addPcscfServer(PCSCFV6);
target.addRoute(new RouteInfo(GATEWAY1));
target.addRoute(new RouteInfo(GATEWAY2));
target.setMtu(MTU);
@@ -167,6 +176,7 @@
target.addLinkAddress(LINKADDRV6);
target.addDnsServer(DNS1);
target.addDnsServer(DNS2);
+ target.addPcscfServer(PCSCFV6);
target.addRoute(new RouteInfo(GATEWAY1));
target.addRoute(new RouteInfo(GATEWAY2));
target.setMtu(MTU);
@@ -179,6 +189,21 @@
// change dnses
target.addDnsServer(NetworkUtils.numericToInetAddress("75.208.7.2"));
target.addDnsServer(DNS2);
+ target.addPcscfServer(PCSCFV6);
+ target.addRoute(new RouteInfo(GATEWAY1));
+ target.addRoute(new RouteInfo(GATEWAY2));
+ target.setMtu(MTU);
+ assertFalse(source.equals(target));
+
+ target.clear();
+ target.setInterfaceName(NAME);
+ target.addLinkAddress(LINKADDRV4);
+ target.addLinkAddress(LINKADDRV6);
+ target.addDnsServer(NetworkUtils.numericToInetAddress("75.208.7.2"));
+ target.addDnsServer(DNS2);
+ // change pcscf
+ target.addPcscfServer(NetworkUtils.numericToInetAddress(
+ "2001::1"));
target.addRoute(new RouteInfo(GATEWAY1));
target.addRoute(new RouteInfo(GATEWAY2));
target.setMtu(MTU);
diff --git a/tests/net/java/android/net/dhcp/DhcpServingParamsParcelExtTest.java b/tests/net/java/android/net/dhcp/DhcpServingParamsParcelExtTest.java
new file mode 100644
index 0000000..4a6f20a
--- /dev/null
+++ b/tests/net/java/android/net/dhcp/DhcpServingParamsParcelExtTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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.
+ */
+
+package android.net.dhcp;
+
+import static android.net.InetAddresses.parseNumericAddress;
+
+import static com.google.android.collect.Sets.newHashSet;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.net.LinkAddress;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet4Address;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DhcpServingParamsParcelExtTest {
+ private static final Inet4Address TEST_ADDRESS = inet4Addr("192.168.0.123");
+ private static final int TEST_ADDRESS_PARCELED = 0xc0a8007b;
+ private static final int TEST_PREFIX_LENGTH = 17;
+ private static final int TEST_LEASE_TIME_SECS = 120;
+ private static final int TEST_MTU = 1000;
+ private static final Set<Inet4Address> TEST_ADDRESS_SET =
+ newHashSet(inet4Addr("192.168.1.123"), inet4Addr("192.168.1.124"));
+ private static final Set<Integer> TEST_ADDRESS_SET_PARCELED =
+ newHashSet(0xc0a8017b, 0xc0a8017c);
+
+ private DhcpServingParamsParcelExt mParcel;
+
+ @Before
+ public void setUp() {
+ mParcel = new DhcpServingParamsParcelExt();
+ }
+
+ @Test
+ public void testSetServerAddr() {
+ mParcel.setServerAddr(new LinkAddress(TEST_ADDRESS, TEST_PREFIX_LENGTH));
+
+ assertEquals(TEST_ADDRESS_PARCELED, mParcel.serverAddr);
+ assertEquals(TEST_PREFIX_LENGTH, mParcel.serverAddrPrefixLength);
+ }
+
+ @Test
+ public void testSetDefaultRouters() {
+ mParcel.setDefaultRouters(TEST_ADDRESS_SET);
+ assertEquals(TEST_ADDRESS_SET_PARCELED, asSet(mParcel.defaultRouters));
+ }
+
+ @Test
+ public void testSetDnsServers() {
+ mParcel.setDnsServers(TEST_ADDRESS_SET);
+ assertEquals(TEST_ADDRESS_SET_PARCELED, asSet(mParcel.dnsServers));
+ }
+
+ @Test
+ public void testSetExcludedAddrs() {
+ mParcel.setExcludedAddrs(TEST_ADDRESS_SET);
+ assertEquals(TEST_ADDRESS_SET_PARCELED, asSet(mParcel.excludedAddrs));
+ }
+
+ @Test
+ public void testSetDhcpLeaseTimeSecs() {
+ mParcel.setDhcpLeaseTimeSecs(TEST_LEASE_TIME_SECS);
+ assertEquals(TEST_LEASE_TIME_SECS, mParcel.dhcpLeaseTimeSecs);
+ }
+
+ @Test
+ public void testSetLinkMtu() {
+ mParcel.setLinkMtu(TEST_MTU);
+ assertEquals(TEST_MTU, mParcel.linkMtu);
+ }
+
+ @Test
+ public void testSetMetered() {
+ mParcel.setMetered(true);
+ assertTrue(mParcel.metered);
+ mParcel.setMetered(false);
+ assertFalse(mParcel.metered);
+ }
+
+ private static Inet4Address inet4Addr(String addr) {
+ return (Inet4Address) parseNumericAddress(addr);
+ }
+
+ private static Set<Integer> asSet(int[] ints) {
+ return IntStream.of(ints).boxed().collect(Collectors.toSet());
+ }
+}
diff --git a/tests/net/java/android/net/ip/IpServerTest.java b/tests/net/java/android/net/ip/IpServerTest.java
index 2c675c6..c3162af 100644
--- a/tests/net/java/android/net/ip/IpServerTest.java
+++ b/tests/net/java/android/net/ip/IpServerTest.java
@@ -16,31 +16,37 @@
package android.net.ip;
+import static android.net.ConnectivityManager.TETHERING_BLUETOOTH;
+import static android.net.ConnectivityManager.TETHERING_USB;
+import static android.net.ConnectivityManager.TETHERING_WIFI;
+import static android.net.ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR;
+import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
+import static android.net.ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR;
+import static android.net.NetworkUtils.intToInet4AddressHTH;
+import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
+import static android.net.ip.IpServer.STATE_AVAILABLE;
+import static android.net.ip.IpServer.STATE_TETHERED;
+import static android.net.ip.IpServer.STATE_UNAVAILABLE;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
-import static android.net.ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR;
-import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
-import static android.net.ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR;
-import static android.net.ConnectivityManager.TETHERING_BLUETOOTH;
-import static android.net.ConnectivityManager.TETHERING_USB;
-import static android.net.ConnectivityManager.TETHERING_WIFI;
-import static android.net.ip.IpServer.STATE_AVAILABLE;
-import static android.net.ip.IpServer.STATE_TETHERED;
-import static android.net.ip.IpServer.STATE_UNAVAILABLE;
-
import android.net.INetworkStatsService;
import android.net.InterfaceConfiguration;
import android.net.IpPrefix;
@@ -48,8 +54,9 @@
import android.net.LinkProperties;
import android.net.MacAddress;
import android.net.RouteInfo;
-import android.net.dhcp.DhcpServer;
-import android.net.dhcp.DhcpServingParams;
+import android.net.dhcp.DhcpServingParamsParcel;
+import android.net.dhcp.IDhcpServer;
+import android.net.dhcp.IDhcpServerCallbacks;
import android.net.util.InterfaceParams;
import android.net.util.InterfaceSet;
import android.net.util.SharedLog;
@@ -60,8 +67,6 @@
import android.support.test.runner.AndroidJUnit4;
import android.text.TextUtils;
-import java.net.Inet4Address;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -71,6 +76,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.net.Inet4Address;
+
@RunWith(AndroidJUnit4.class)
@SmallTest
public class IpServerTest {
@@ -82,16 +89,18 @@
private static final InterfaceParams TEST_IFACE_PARAMS = new InterfaceParams(
IFACE_NAME, 42 /* index */, MacAddress.ALL_ZEROS_ADDRESS, 1500 /* defaultMtu */);
+ private static final int MAKE_DHCPSERVER_TIMEOUT_MS = 1000;
+
@Mock private INetworkManagementService mNMService;
@Mock private INetworkStatsService mStatsService;
@Mock private IpServer.Callback mCallback;
@Mock private InterfaceConfiguration mInterfaceConfiguration;
@Mock private SharedLog mSharedLog;
- @Mock private DhcpServer mDhcpServer;
+ @Mock private IDhcpServer mDhcpServer;
@Mock private RouterAdvertisementDaemon mRaDaemon;
@Mock private IpServer.Dependencies mDependencies;
- @Captor private ArgumentCaptor<DhcpServingParams> mDhcpParamsCaptor;
+ @Captor private ArgumentCaptor<DhcpServingParamsParcel> mDhcpParamsCaptor;
private final TestLooper mLooper = new TestLooper();
private final ArgumentCaptor<LinkProperties> mLinkPropertiesCaptor =
@@ -112,8 +121,18 @@
mLooper.dispatchAll();
reset(mNMService, mStatsService, mCallback);
when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration);
- when(mDependencies.makeDhcpServer(
- any(), any(), mDhcpParamsCaptor.capture(), any())).thenReturn(mDhcpServer);
+
+ doAnswer(inv -> {
+ final IDhcpServerCallbacks cb = inv.getArgument(2);
+ new Thread(() -> {
+ try {
+ cb.onDhcpServerCreated(STATUS_SUCCESS, mDhcpServer);
+ } catch (RemoteException e) {
+ fail(e.getMessage());
+ }
+ }).run();
+ return null;
+ }).when(mDependencies).makeDhcpServer(any(), mDhcpParamsCaptor.capture(), any());
when(mDependencies.getRouterAdvertisementDaemon(any())).thenReturn(mRaDaemon);
when(mDependencies.getInterfaceParams(IFACE_NAME)).thenReturn(TEST_IFACE_PARAMS);
@@ -399,21 +418,20 @@
initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, true /* usingLegacyDhcp */);
dispatchTetherConnectionChanged(UPSTREAM_IFACE);
- verify(mDependencies, never()).makeDhcpServer(any(), any(), any(), any());
+ verify(mDependencies, never()).makeDhcpServer(any(), any(), any());
}
- private void assertDhcpStarted(IpPrefix expectedPrefix) {
- verify(mDependencies, times(1)).makeDhcpServer(
- eq(mLooper.getLooper()), eq(IFACE_NAME), any(), eq(mSharedLog));
- verify(mDhcpServer, times(1)).start();
- final DhcpServingParams params = mDhcpParamsCaptor.getValue();
+ private void assertDhcpStarted(IpPrefix expectedPrefix) throws Exception {
+ verify(mDependencies, times(1)).makeDhcpServer(eq(IFACE_NAME), any(), any());
+ verify(mDhcpServer, timeout(MAKE_DHCPSERVER_TIMEOUT_MS).times(1)).start(any());
+ final DhcpServingParamsParcel params = mDhcpParamsCaptor.getValue();
// Last address byte is random
- assertTrue(expectedPrefix.contains(params.serverAddr.getAddress()));
- assertEquals(expectedPrefix.getPrefixLength(), params.serverAddr.getPrefixLength());
- assertEquals(1, params.defaultRouters.size());
- assertEquals(params.serverAddr.getAddress(), params.defaultRouters.iterator().next());
- assertEquals(1, params.dnsServers.size());
- assertEquals(params.serverAddr.getAddress(), params.dnsServers.iterator().next());
+ assertTrue(expectedPrefix.contains(intToInet4AddressHTH(params.serverAddr)));
+ assertEquals(expectedPrefix.getPrefixLength(), params.serverAddrPrefixLength);
+ assertEquals(1, params.defaultRouters.length);
+ assertEquals(params.serverAddr, params.defaultRouters[0]);
+ assertEquals(1, params.dnsServers.length);
+ assertEquals(params.serverAddr, params.dnsServers[0]);
assertEquals(DHCP_LEASE_TIME_SECS, params.dhcpLeaseTimeSecs);
}
@@ -458,7 +476,7 @@
addr4 = addr;
break;
}
- assertTrue("missing IPv4 address", addr4 != null);
+ assertNotNull("missing IPv4 address", addr4);
// Assert the presence of the associated directly connected route.
final RouteInfo directlyConnected = new RouteInfo(addr4, null, lp.getInterfaceName());
diff --git a/tests/net/java/android/net/ipmemorystore/ParcelableTests.java b/tests/net/java/android/net/ipmemorystore/ParcelableTests.java
new file mode 100644
index 0000000..a9f9758
--- /dev/null
+++ b/tests/net/java/android/net/ipmemorystore/ParcelableTests.java
@@ -0,0 +1,113 @@
+/*
+ * 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.
+ */
+
+package android.net.ipmemorystore;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.util.Arrays;
+import java.util.Collections;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ParcelableTests {
+ @Test
+ public void testNetworkAttributesParceling() throws Exception {
+ final NetworkAttributes.Builder builder = new NetworkAttributes.Builder();
+ NetworkAttributes in = builder.build();
+ assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable())));
+
+ builder.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
+ // groupHint stays null this time around
+ builder.setDnsAddresses(Collections.emptyList());
+ builder.setMtu(18);
+ in = builder.build();
+ assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable())));
+
+ builder.setAssignedV4Address((Inet4Address) Inet4Address.getByName("6.7.8.9"));
+ builder.setGroupHint("groupHint");
+ builder.setDnsAddresses(Arrays.asList(
+ InetAddress.getByName("ACA1:652B:0911:DE8F:1200:115E:913B:AA2A"),
+ InetAddress.getByName("6.7.8.9")));
+ builder.setMtu(1_000_000);
+ in = builder.build();
+ assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable())));
+
+ builder.setMtu(null);
+ in = builder.build();
+ assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable())));
+ }
+
+ @Test
+ public void testPrivateDataParceling() throws Exception {
+ final Blob in = new Blob();
+ in.data = new byte[] {89, 111, 108, 111};
+ final Blob out = parcelingRoundTrip(in);
+ // Object.equals on byte[] tests the references
+ assertEquals(in.data.length, out.data.length);
+ assertTrue(Arrays.equals(in.data, out.data));
+ }
+
+ @Test
+ public void testSameL3NetworkResponseParceling() throws Exception {
+ final SameL3NetworkResponseParcelable parcelable = new SameL3NetworkResponseParcelable();
+ parcelable.l2Key1 = "key 1";
+ parcelable.l2Key2 = "key 2";
+ parcelable.confidence = 0.43f;
+
+ final SameL3NetworkResponse in = new SameL3NetworkResponse(parcelable);
+ assertEquals("key 1", in.l2Key1);
+ assertEquals("key 2", in.l2Key2);
+ assertEquals(0.43f, in.confidence, 0.01f /* delta */);
+
+ final SameL3NetworkResponse out =
+ new SameL3NetworkResponse(parcelingRoundTrip(in.toParcelable()));
+
+ assertEquals(in, out);
+ assertEquals(in.l2Key1, out.l2Key1);
+ assertEquals(in.l2Key2, out.l2Key2);
+ assertEquals(in.confidence, out.confidence, 0.01f /* delta */);
+ }
+
+ private <T extends Parcelable> T parcelingRoundTrip(final T in) throws Exception {
+ final Parcel p = Parcel.obtain();
+ in.writeToParcel(p, /* flags */ 0);
+ p.setDataPosition(0);
+ final byte[] marshalledData = p.marshall();
+ p.recycle();
+
+ final Parcel q = Parcel.obtain();
+ q.unmarshall(marshalledData, 0, marshalledData.length);
+ q.setDataPosition(0);
+
+ final Parcelable.Creator<T> creator = (Parcelable.Creator<T>)
+ in.getClass().getField("CREATOR").get(null); // static object, so null receiver
+ final T unmarshalled = (T) creator.createFromParcel(q);
+ q.recycle();
+ return unmarshalled;
+ }
+}
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 71529fd..bf39644 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -26,6 +26,8 @@
import static android.net.ConnectivityManager.TYPE_MOBILE_MMS;
import static android.net.ConnectivityManager.TYPE_NONE;
import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID;
+import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_VALID;
import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
@@ -69,17 +71,19 @@
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
-
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
@@ -89,7 +93,6 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
-import android.net.CaptivePortal;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.ConnectivityManager.PacketKeepalive;
@@ -97,6 +100,8 @@
import android.net.ConnectivityManager.TooManyRequestsException;
import android.net.ConnectivityThread;
import android.net.INetd;
+import android.net.INetworkMonitor;
+import android.net.INetworkMonitorCallbacks;
import android.net.INetworkPolicyListener;
import android.net.INetworkPolicyManager;
import android.net.INetworkStatsService;
@@ -114,12 +119,14 @@
import android.net.NetworkMisc;
import android.net.NetworkRequest;
import android.net.NetworkSpecifier;
+import android.net.NetworkStack;
import android.net.NetworkUtils;
import android.net.RouteInfo;
import android.net.StringNetworkSpecifier;
import android.net.UidRange;
-import android.net.captiveportal.CaptivePortalProbeResult;
import android.net.metrics.IpConnectivityLog;
+import android.net.shared.NetworkMonitorUtils;
+import android.net.shared.PrivateDnsConfig;
import android.net.util.MultinetworkPolicyTracker;
import android.os.ConditionVariable;
import android.os.Handler;
@@ -148,12 +155,9 @@
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.connectivity.ConnectivityConstants;
import com.android.server.connectivity.DefaultNetworkMetrics;
-import com.android.server.connectivity.DnsManager;
import com.android.server.connectivity.IpConnectivityMetrics;
import com.android.server.connectivity.MockableSystemProperties;
import com.android.server.connectivity.Nat464Xlat;
-import com.android.server.connectivity.NetworkAgentInfo;
-import com.android.server.connectivity.NetworkMonitor;
import com.android.server.connectivity.Tethering;
import com.android.server.connectivity.Vpn;
import com.android.server.net.NetworkPinner;
@@ -168,6 +172,7 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
+import org.mockito.stubbing.Answer;
import java.net.Inet4Address;
import java.net.InetAddress;
@@ -230,6 +235,7 @@
@Mock INetworkStatsService mStatsService;
@Mock INetworkPolicyManager mNpm;
@Mock INetd mMockNetd;
+ @Mock NetworkStack mNetworkStack;
private ArgumentCaptor<String[]> mStringArrayCaptor = ArgumentCaptor.forClass(String[].class);
@@ -299,6 +305,7 @@
public Object getSystemService(String name) {
if (Context.CONNECTIVITY_SERVICE.equals(name)) return mCm;
if (Context.NOTIFICATION_SERVICE.equals(name)) return mock(NotificationManager.class);
+ if (Context.NETWORK_STACK_SERVICE.equals(name)) return mNetworkStack;
return super.getSystemService(name);
}
@@ -386,7 +393,7 @@
}
private class MockNetworkAgent {
- private final WrappedNetworkMonitor mWrappedNetworkMonitor;
+ private final INetworkMonitor mNetworkMonitor;
private final NetworkInfo mNetworkInfo;
private final NetworkCapabilities mNetworkCapabilities;
private final HandlerThread mHandlerThread;
@@ -402,6 +409,26 @@
// mNetworkStatusReceived.
private String mRedirectUrl;
+ private INetworkMonitorCallbacks mNmCallbacks;
+ private int mNmValidationResult = NETWORK_TEST_RESULT_INVALID;
+ private String mNmValidationRedirectUrl = null;
+ private boolean mNmProvNotificationRequested = false;
+
+ void setNetworkValid() {
+ mNmValidationResult = NETWORK_TEST_RESULT_VALID;
+ mNmValidationRedirectUrl = null;
+ }
+
+ void setNetworkInvalid() {
+ mNmValidationResult = NETWORK_TEST_RESULT_INVALID;
+ mNmValidationRedirectUrl = null;
+ }
+
+ void setNetworkPortal(String redirectUrl) {
+ setNetworkInvalid();
+ mNmValidationRedirectUrl = redirectUrl;
+ }
+
MockNetworkAgent(int transport) {
this(transport, new LinkProperties());
}
@@ -434,6 +461,29 @@
}
mHandlerThread = new HandlerThread("Mock-" + typeName);
mHandlerThread.start();
+
+ mNetworkMonitor = mock(INetworkMonitor.class);
+ final Answer validateAnswer = inv -> {
+ new Thread(this::onValidationRequested).start();
+ return null;
+ };
+
+ try {
+ doAnswer(validateAnswer).when(mNetworkMonitor).notifyNetworkConnected();
+ doAnswer(validateAnswer).when(mNetworkMonitor).forceReevaluation(anyInt());
+ } catch (RemoteException e) {
+ fail(e.getMessage());
+ }
+
+ final ArgumentCaptor<Network> nmNetworkCaptor =
+ ArgumentCaptor.forClass(Network.class);
+ final ArgumentCaptor<INetworkMonitorCallbacks> nmCbCaptor =
+ ArgumentCaptor.forClass(INetworkMonitorCallbacks.class);
+ doNothing().when(mNetworkStack).makeNetworkMonitor(
+ nmNetworkCaptor.capture(),
+ any() /* name */,
+ nmCbCaptor.capture());
+
mNetworkAgent = new NetworkAgent(mHandlerThread.getLooper(), mServiceContext,
"Mock-" + typeName, mNetworkInfo, mNetworkCapabilities,
linkProperties, mScore, new NetworkMisc()) {
@@ -465,10 +515,40 @@
mPreventReconnectReceived.open();
}
};
+
+ assertEquals(mNetworkAgent.netId, nmNetworkCaptor.getValue().netId);
+ mNmCallbacks = nmCbCaptor.getValue();
+
+ try {
+ mNmCallbacks.onNetworkMonitorCreated(mNetworkMonitor);
+ } catch (RemoteException e) {
+ fail(e.getMessage());
+ }
+
// Waits for the NetworkAgent to be registered, which includes the creation of the
// NetworkMonitor.
waitForIdle();
- mWrappedNetworkMonitor = mService.getLastCreatedWrappedNetworkMonitor();
+ }
+
+ private void onValidationRequested() {
+ try {
+ if (mNmProvNotificationRequested
+ && mNmValidationResult == NETWORK_TEST_RESULT_VALID) {
+ mNmCallbacks.hideProvisioningNotification();
+ mNmProvNotificationRequested = false;
+ }
+
+ mNmCallbacks.notifyNetworkTested(
+ mNmValidationResult, mNmValidationRedirectUrl);
+
+ if (mNmValidationRedirectUrl != null) {
+ mNmCallbacks.showProvisioningNotification(
+ "test_provisioning_notif_action");
+ mNmProvNotificationRequested = true;
+ }
+ } catch (RemoteException e) {
+ fail(e.getMessage());
+ }
}
public void adjustScore(int change) {
@@ -539,7 +619,7 @@
NetworkCallback callback = null;
final ConditionVariable validatedCv = new ConditionVariable();
if (validated) {
- mWrappedNetworkMonitor.gen204ProbeResult = 204;
+ setNetworkValid();
NetworkRequest request = new NetworkRequest.Builder()
.addTransportType(mNetworkCapabilities.getTransportTypes()[0])
.clearCapabilities()
@@ -564,15 +644,14 @@
if (validated) {
// Wait for network to validate.
waitFor(validatedCv);
- mWrappedNetworkMonitor.gen204ProbeResult = 500;
+ setNetworkInvalid();
}
if (callback != null) mCm.unregisterNetworkCallback(callback);
}
public void connectWithCaptivePortal(String redirectUrl) {
- mWrappedNetworkMonitor.gen204ProbeResult = 200;
- mWrappedNetworkMonitor.gen204ProbeRedirectUrl = redirectUrl;
+ setNetworkPortal(redirectUrl);
connect(false);
}
@@ -603,10 +682,6 @@
return mDisconnected;
}
- public WrappedNetworkMonitor getWrappedNetworkMonitor() {
- return mWrappedNetworkMonitor;
- }
-
public void sendLinkProperties(LinkProperties lp) {
mNetworkAgent.sendLinkProperties(lp);
}
@@ -880,28 +955,6 @@
}
}
- // NetworkMonitor implementation allowing overriding of Internet connectivity probe result.
- private class WrappedNetworkMonitor extends NetworkMonitor {
- public final Handler connectivityHandler;
- // HTTP response code fed back to NetworkMonitor for Internet connectivity probe.
- public int gen204ProbeResult = 500;
- public String gen204ProbeRedirectUrl = null;
-
- public WrappedNetworkMonitor(Context context, Handler handler,
- NetworkAgentInfo networkAgentInfo, NetworkRequest defaultRequest,
- IpConnectivityLog log) {
- super(context, handler, networkAgentInfo, defaultRequest, log,
- NetworkMonitor.Dependencies.DEFAULT);
- connectivityHandler = handler;
- }
-
- @Override
- protected CaptivePortalProbeResult isCaptivePortal() {
- if (!mIsCaptivePortalCheckEnabled) { return new CaptivePortalProbeResult(204); }
- return new CaptivePortalProbeResult(gen204ProbeResult, gen204ProbeRedirectUrl, null);
- }
- }
-
private class WrappedMultinetworkPolicyTracker extends MultinetworkPolicyTracker {
public volatile boolean configRestrictsAvoidBadWifi;
public volatile int configMeteredMultipathPreference;
@@ -923,7 +976,6 @@
private class WrappedConnectivityService extends ConnectivityService {
public WrappedMultinetworkPolicyTracker wrappedMultinetworkPolicyTracker;
- private WrappedNetworkMonitor mLastCreatedNetworkMonitor;
private MockableSystemProperties mSystemProperties;
public WrappedConnectivityService(Context context, INetworkManagementService netManager,
@@ -971,15 +1023,6 @@
}
}
- @Override
- public NetworkMonitor createNetworkMonitor(Context context, Handler handler,
- NetworkAgentInfo nai, NetworkRequest defaultRequest) {
- final WrappedNetworkMonitor monitor = new WrappedNetworkMonitor(
- context, handler, nai, defaultRequest, mock(IpConnectivityLog.class));
- mLastCreatedNetworkMonitor = monitor;
- return monitor;
- }
-
public Nat464Xlat getNat464Xlat(MockNetworkAgent mna) {
return getNetworkAgentInfoForNetwork(mna.getNetwork()).clatd;
}
@@ -1017,10 +1060,6 @@
protected void registerNetdEventCallback() {
}
- public WrappedNetworkMonitor getLastCreatedWrappedNetworkMonitor() {
- return mLastCreatedNetworkMonitor;
- }
-
public void mockVpn(int uid) {
synchronized (mVpns) {
int userId = UserHandle.getUserId(uid);
@@ -2439,7 +2478,7 @@
// Make captive portal disappear then revalidate.
// Expect onLost callback because network no longer provides NET_CAPABILITY_CAPTIVE_PORTAL.
- mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 204;
+ mWiFiNetworkAgent.setNetworkValid();
mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
@@ -2448,13 +2487,13 @@
// Break network connectivity.
// Expect NET_CAPABILITY_VALIDATED onLost callback.
- mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 500;
+ mWiFiNetworkAgent.setNetworkInvalid();
mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);
validatedCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
}
@Test
- public void testCaptivePortalApp() {
+ public void testCaptivePortalApp() throws RemoteException {
final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
final NetworkRequest captivePortalRequest = new NetworkRequest.Builder()
.addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build();
@@ -2477,21 +2516,19 @@
mServiceContext.expectNoStartActivityIntent(fastTimeoutMs);
// Turn into a captive portal.
- mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 302;
+ mWiFiNetworkAgent.setNetworkPortal("http://example.com");
mCm.reportNetworkConnectivity(wifiNetwork, false);
captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
validatedCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
- // Check that startCaptivePortalApp sends the expected intent.
+ // Check that startCaptivePortalApp sends the expected command to NetworkMonitor.
mCm.startCaptivePortalApp(wifiNetwork);
- Intent intent = mServiceContext.expectStartActivityIntent(TIMEOUT_MS);
- assertEquals(ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN, intent.getAction());
- assertEquals(wifiNetwork, intent.getExtra(ConnectivityManager.EXTRA_NETWORK));
+ verify(mWiFiNetworkAgent.mNetworkMonitor, timeout(TIMEOUT_MS).times(1))
+ .launchCaptivePortalApp();
- // Have the app report that the captive portal is dismissed, and check that we revalidate.
- mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 204;
- CaptivePortal c = (CaptivePortal) intent.getExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL);
- c.reportCaptivePortalDismissed();
+ // Report that the captive portal is dismissed, and check that callbacks are fired
+ mWiFiNetworkAgent.setNetworkValid();
+ mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
@@ -2524,20 +2561,6 @@
waitFor(avoidCv);
assertNoCallbacks(captivePortalCallback, validatedCallback);
-
- // Now test ignore mode.
- setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_IGNORE);
-
- // Bring up a network with a captive portal.
- // Since we're ignoring captive portals, the network will validate.
- mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
- String secondRedirectUrl = "http://example.com/secondPath";
- mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl);
-
- // Expect NET_CAPABILITY_VALIDATED onAvailable callback.
- validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
- // But there should be no CaptivePortal callback.
- captivePortalCallback.assertNoCallback();
}
private NetworkRequest.Builder newWifiRequestBuilder() {
@@ -3169,7 +3192,7 @@
Network wifiNetwork = mWiFiNetworkAgent.getNetwork();
// Fail validation on wifi.
- mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 599;
+ mWiFiNetworkAgent.setNetworkInvalid();
mCm.reportNetworkConnectivity(wifiNetwork, false);
defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
validatedWifiCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
@@ -3213,7 +3236,7 @@
wifiNetwork = mWiFiNetworkAgent.getNetwork();
// Fail validation on wifi and expect the dialog to appear.
- mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 599;
+ mWiFiNetworkAgent.setNetworkInvalid();
mCm.reportNetworkConnectivity(wifiNetwork, false);
defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
validatedWifiCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
@@ -4002,11 +4025,9 @@
final String TLS_SERVER6 = "2001:db8:53::53";
final InetAddress[] TLS_IPS = new InetAddress[]{ InetAddress.getByName(TLS_SERVER6) };
final String[] TLS_SERVERS = new String[]{ TLS_SERVER6 };
- final Handler h = mCellNetworkAgent.getWrappedNetworkMonitor().connectivityHandler;
- h.sendMessage(h.obtainMessage(
- NetworkMonitor.EVENT_PRIVATE_DNS_CONFIG_RESOLVED, 0,
- mCellNetworkAgent.getNetwork().netId,
- new DnsManager.PrivateDnsConfig(TLS_SPECIFIER, TLS_IPS)));
+ mCellNetworkAgent.mNmCallbacks.notifyPrivateDnsConfigResolved(
+ new PrivateDnsConfig(TLS_SPECIFIER, TLS_IPS).toParcel());
+
waitForIdle();
verify(mNetworkManagementService, atLeastOnce()).setDnsConfigurationForNetwork(
anyInt(), mStringArrayCaptor.capture(), any(), any(),
@@ -4294,6 +4315,12 @@
ranges.add(new UidRange(uid, uid));
mMockVpn.setNetworkAgent(vpnNetworkAgent);
mMockVpn.setUids(ranges);
+ // VPN networks do not satisfy the default request and are automatically validated
+ // by NetworkMonitor
+ assertFalse(NetworkMonitorUtils.isValidationRequired(
+ mCm.getDefaultRequest().networkCapabilities, vpnNetworkAgent.mNetworkCapabilities));
+ vpnNetworkAgent.setNetworkValid();
+
vpnNetworkAgent.connect(false);
mMockVpn.connect();
diff --git a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java
index 01b468a..38322e9 100644
--- a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java
+++ b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java
@@ -17,7 +17,6 @@
package com.android.server.connectivity;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
-import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
import static android.provider.Settings.Global.PRIVATE_DNS_DEFAULT_MODE;
import static android.provider.Settings.Global.PRIVATE_DNS_MODE;
@@ -29,13 +28,13 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
-import android.content.ContentResolver;
import android.content.Context;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
import android.net.RouteInfo;
+import android.net.shared.PrivateDnsConfig;
import android.os.INetworkManagementService;
import android.provider.Settings;
import android.support.test.filters.SmallTest;
@@ -43,18 +42,16 @@
import android.test.mock.MockContentResolver;
import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.server.connectivity.DnsManager.PrivateDnsConfig;
-import com.android.server.connectivity.MockableSystemProperties;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import java.net.InetAddress;
import java.util.Arrays;
-import org.junit.runner.RunWith;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
/**
* Tests for {@link DnsManager}.
*
@@ -133,7 +130,7 @@
PRIVATE_DNS_MODE, PRIVATE_DNS_MODE_PROVIDER_HOSTNAME);
Settings.Global.putString(mContentResolver, PRIVATE_DNS_SPECIFIER, "strictmode.com");
mDnsManager.updatePrivateDns(new Network(TEST_NETID),
- new DnsManager.PrivateDnsConfig("strictmode.com", new InetAddress[] {
+ new PrivateDnsConfig("strictmode.com", new InetAddress[] {
InetAddress.parseNumericAddress("6.6.6.6"),
InetAddress.parseNumericAddress("2001:db8:66:66::1")
}));
diff --git a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
index 354cf2f..4c52d81 100644
--- a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
@@ -23,10 +23,10 @@
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.reset;
import android.app.PendingIntent;
import android.content.Context;
@@ -36,18 +36,18 @@
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkMisc;
-import android.support.test.runner.AndroidJUnit4;
+import android.net.NetworkStack;
import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
import android.text.format.DateUtils;
import com.android.internal.R;
import com.android.server.ConnectivityService;
-import com.android.server.connectivity.NetworkNotificationManager;
import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
-import org.junit.runner.RunWith;
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -70,13 +70,16 @@
@Mock NetworkMisc mMisc;
@Mock NetworkNotificationManager mNotifier;
@Mock Resources mResources;
+ @Mock NetworkStack mNetworkStack;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mCtx.getResources()).thenReturn(mResources);
when(mCtx.getPackageName()).thenReturn("com.android.server.connectivity");
- when(mConnService.createNetworkMonitor(any(), any(), any(), any())).thenReturn(null);
+ when(mCtx.getSystemServiceName(NetworkStack.class))
+ .thenReturn(Context.NETWORK_STACK_SERVICE);
+ when(mCtx.getSystemService(Context.NETWORK_STACK_SERVICE)).thenReturn(mNetworkStack);
mMonitor = new TestableLingerMonitor(mCtx, mNotifier, HIGH_DAILY_LIMIT, HIGH_RATE_LIMIT);
}
@@ -349,7 +352,7 @@
caps.addCapability(0);
caps.addTransportType(transport);
NetworkAgentInfo nai = new NetworkAgentInfo(null, null, new Network(netId), info, null,
- caps, 50, mCtx, null, mMisc, null, mConnService);
+ caps, 50, mCtx, null, mMisc, mConnService);
nai.everValidated = true;
return nai;
}
diff --git a/tests/net/java/com/android/server/connectivity/TetheringTest.java b/tests/net/java/com/android/server/connectivity/TetheringTest.java
index e6b43d2..1ea83c2 100644
--- a/tests/net/java/com/android/server/connectivity/TetheringTest.java
+++ b/tests/net/java/com/android/server/connectivity/TetheringTest.java
@@ -27,6 +27,7 @@
import static android.net.ConnectivityManager.TETHERING_WIFI;
import static android.net.ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME;
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE;
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE;
@@ -37,6 +38,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.notNull;
import static org.mockito.Matchers.anyInt;
@@ -47,6 +49,8 @@
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -74,8 +78,9 @@
import android.net.NetworkState;
import android.net.NetworkUtils;
import android.net.RouteInfo;
-import android.net.dhcp.DhcpServer;
-import android.net.dhcp.DhcpServingParams;
+import android.net.dhcp.DhcpServerCallbacks;
+import android.net.dhcp.DhcpServingParamsParcel;
+import android.net.dhcp.IDhcpServer;
import android.net.ip.IpServer;
import android.net.ip.RouterAdvertisementDaemon;
import android.net.util.InterfaceParams;
@@ -86,7 +91,6 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.INetworkManagementService;
-import android.os.Looper;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -129,6 +133,8 @@
private static final String TEST_USB_IFNAME = "test_rndis0";
private static final String TEST_WLAN_IFNAME = "test_wlan0";
+ private static final int DHCPSERVER_START_TIMEOUT_MS = 1000;
+
@Mock private ApplicationInfo mApplicationInfo;
@Mock private Context mContext;
@Mock private INetworkManagementService mNMService;
@@ -143,9 +149,11 @@
@Mock private UpstreamNetworkMonitor mUpstreamNetworkMonitor;
@Mock private IPv6TetheringCoordinator mIPv6TetheringCoordinator;
@Mock private RouterAdvertisementDaemon mRouterAdvertisementDaemon;
- @Mock private DhcpServer mDhcpServer;
+ @Mock private IDhcpServer mDhcpServer;
@Mock private INetd mNetd;
+ private final MockIpServerDependencies mIpServerDependencies =
+ spy(new MockIpServerDependencies());
private final MockTetheringDependencies mTetheringDependencies =
new MockTetheringDependencies();
@@ -185,6 +193,47 @@
}
}
+ public class MockIpServerDependencies extends IpServer.Dependencies {
+ MockIpServerDependencies() {
+ super(null);
+ }
+
+ @Override
+ public RouterAdvertisementDaemon getRouterAdvertisementDaemon(
+ InterfaceParams ifParams) {
+ return mRouterAdvertisementDaemon;
+ }
+
+ @Override
+ public InterfaceParams getInterfaceParams(String ifName) {
+ assertTrue("Non-mocked interface " + ifName,
+ ifName.equals(TEST_USB_IFNAME)
+ || ifName.equals(TEST_WLAN_IFNAME)
+ || ifName.equals(TEST_MOBILE_IFNAME));
+ final String[] ifaces = new String[] {
+ TEST_USB_IFNAME, TEST_WLAN_IFNAME, TEST_MOBILE_IFNAME };
+ return new InterfaceParams(ifName, ArrayUtils.indexOf(ifaces, ifName) + IFINDEX_OFFSET,
+ MacAddress.ALL_ZEROS_ADDRESS);
+ }
+
+ @Override
+ public INetd getNetdService() {
+ return mNetd;
+ }
+
+ @Override
+ public void makeDhcpServer(String ifName, DhcpServingParamsParcel params,
+ DhcpServerCallbacks cb) {
+ new Thread(() -> {
+ try {
+ cb.onDhcpServerCreated(STATUS_SUCCESS, mDhcpServer);
+ } catch (RemoteException e) {
+ fail(e.getMessage());
+ }
+ }).run();
+ }
+ }
+
public class MockTetheringDependencies extends TetheringDependencies {
StateMachine upstreamNetworkMonitorMasterSM;
ArrayList<IpServer> ipv6CoordinatorNotifyList;
@@ -216,35 +265,8 @@
}
@Override
- public IpServer.Dependencies getIpServerDependencies() {
- return new IpServer.Dependencies() {
- @Override
- public RouterAdvertisementDaemon getRouterAdvertisementDaemon(
- InterfaceParams ifParams) {
- return mRouterAdvertisementDaemon;
- }
-
- @Override
- public InterfaceParams getInterfaceParams(String ifName) {
- final String[] ifaces = new String[] {
- TEST_USB_IFNAME, TEST_WLAN_IFNAME, TEST_MOBILE_IFNAME };
- final int index = ArrayUtils.indexOf(ifaces, ifName);
- assertTrue("Non-mocked interface: " + ifName, index >= 0);
- return new InterfaceParams(ifName, index + IFINDEX_OFFSET,
- MacAddress.ALL_ZEROS_ADDRESS);
- }
-
- @Override
- public INetd getNetdService() {
- return mNetd;
- }
-
- @Override
- public DhcpServer makeDhcpServer(Looper looper, String ifName,
- DhcpServingParams params, SharedLog log) {
- return mDhcpServer;
- }
- };
+ public IpServer.Dependencies getIpServerDependencies(Context context) {
+ return mIpServerDependencies;
}
@Override
@@ -546,7 +568,7 @@
sendIPv6TetherUpdates(upstreamState);
verify(mRouterAdvertisementDaemon, never()).buildNewRa(any(), notNull());
- verify(mDhcpServer, times(1)).start();
+ verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
}
@Test
@@ -557,7 +579,7 @@
runUsbTethering(upstreamState);
sendIPv6TetherUpdates(upstreamState);
- verify(mDhcpServer, never()).start();
+ verify(mIpServerDependencies, never()).makeDhcpServer(any(), any(), any());
}
@Test
@@ -581,7 +603,7 @@
verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
verify(mRouterAdvertisementDaemon, times(1)).start();
- verify(mDhcpServer, times(1)).start();
+ verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
sendIPv6TetherUpdates(upstreamState);
verify(mRouterAdvertisementDaemon, times(1)).buildNewRa(any(), notNull());
@@ -595,7 +617,7 @@
verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME);
verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
- verify(mDhcpServer, times(1)).start();
+ verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME,
TEST_XLAT_MOBILE_IFNAME);
@@ -612,7 +634,7 @@
runUsbTethering(upstreamState);
verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
- verify(mDhcpServer, times(1)).start();
+ verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
// Then 464xlat comes up
@@ -636,7 +658,7 @@
verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
// DHCP not restarted on downstream (still times(1))
- verify(mDhcpServer, times(1)).start();
+ verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
}
@Test
diff --git a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
new file mode 100644
index 0000000..859a54d
--- /dev/null
+++ b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+
+package com.android.server.net.ipmemorystore;
+
+import android.content.Context;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Unit tests for {@link IpMemoryStoreServiceTest}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class IpMemoryStoreServiceTest {
+ @Mock
+ Context mMockContext;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testNetworkAttributes() {
+ final IpMemoryStoreService service = new IpMemoryStoreService(mMockContext);
+ // TODO : implement this
+ }
+
+ @Test
+ public void testPrivateData() {
+ final IpMemoryStoreService service = new IpMemoryStoreService(mMockContext);
+ // TODO : implement this
+ }
+
+ @Test
+ public void testFindL2Key() {
+ final IpMemoryStoreService service = new IpMemoryStoreService(mMockContext);
+ // TODO : implement this
+ }
+
+ @Test
+ public void testIsSameNetwork() {
+ final IpMemoryStoreService service = new IpMemoryStoreService(mMockContext);
+ // TODO : implement this
+ }
+}
diff --git a/tools/apilint/apilint.py b/tools/apilint/apilint.py
index 6476abd..acf1f1e 100644
--- a/tools/apilint/apilint.py
+++ b/tools/apilint/apilint.py
@@ -26,7 +26,7 @@
$ apilint.py /tmp/currentblame.txt previous.txt --no-color
"""
-import re, sys, collections, traceback, argparse
+import re, sys, collections, traceback, argparse, itertools
BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)
@@ -50,45 +50,37 @@
return "\033[%sm" % (";".join(codes))
-def ident(raw):
- """Strips superficial signature changes, giving us a strong key that
- can be used to identify members across API levels."""
- raw = raw.replace(" deprecated ", " ")
- raw = raw.replace(" synchronized ", " ")
- raw = raw.replace(" final ", " ")
- raw = re.sub("<.+?>", "", raw)
- if " throws " in raw:
- raw = raw[:raw.index(" throws ")]
- return raw
-
-
class Field():
- def __init__(self, clazz, line, raw, blame):
+ def __init__(self, clazz, line, raw, blame, sig_format = 1):
self.clazz = clazz
self.line = line
self.raw = raw.strip(" {;")
self.blame = blame
- # drop generics for now; may need multiple passes
- raw = re.sub("<[^<]+?>", "", raw)
- raw = re.sub("<[^<]+?>", "", raw)
+ if sig_format == 2:
+ V2LineParser(raw).parse_into_field(self)
+ elif sig_format == 1:
+ # drop generics for now; may need multiple passes
+ raw = re.sub("<[^<]+?>", "", raw)
+ raw = re.sub("<[^<]+?>", "", raw)
- raw = raw.split()
- self.split = list(raw)
+ raw = raw.split()
+ self.split = list(raw)
- for r in ["field", "volatile", "transient", "public", "protected", "static", "final", "deprecated"]:
- while r in raw: raw.remove(r)
+ for r in ["field", "volatile", "transient", "public", "protected", "static", "final", "deprecated"]:
+ while r in raw: raw.remove(r)
- # ignore annotations for now
- raw = [ r for r in raw if not r.startswith("@") ]
+ # ignore annotations for now
+ raw = [ r for r in raw if not r.startswith("@") ]
- self.typ = raw[0]
- self.name = raw[1].strip(";")
- if len(raw) >= 4 and raw[2] == "=":
- self.value = raw[3].strip(';"')
- else:
- self.value = None
- self.ident = ident(self.raw)
+ self.typ = raw[0]
+ self.name = raw[1].strip(";")
+ if len(raw) >= 4 and raw[2] == "=":
+ self.value = raw[3].strip(';"')
+ else:
+ self.value = None
+
+ self.ident = "-".join((self.typ, self.name, self.value or ""))
def __hash__(self):
return hash(self.raw)
@@ -96,48 +88,55 @@
def __repr__(self):
return self.raw
-
class Method():
- def __init__(self, clazz, line, raw, blame):
+ def __init__(self, clazz, line, raw, blame, sig_format = 1):
self.clazz = clazz
self.line = line
self.raw = raw.strip(" {;")
self.blame = blame
- # drop generics for now; may need multiple passes
- raw = re.sub("<[^<]+?>", "", raw)
- raw = re.sub("<[^<]+?>", "", raw)
+ if sig_format == 2:
+ V2LineParser(raw).parse_into_method(self)
+ elif sig_format == 1:
+ # drop generics for now; may need multiple passes
+ raw = re.sub("<[^<]+?>", "", raw)
+ raw = re.sub("<[^<]+?>", "", raw)
- # handle each clause differently
- raw_prefix, raw_args, _, raw_throws = re.match(r"(.*?)\((.*?)\)( throws )?(.*?);$", raw).groups()
+ # handle each clause differently
+ raw_prefix, raw_args, _, raw_throws = re.match(r"(.*?)\((.*?)\)( throws )?(.*?);$", raw).groups()
- # parse prefixes
- raw = re.split("[\s]+", raw_prefix)
- for r in ["", ";"]:
- while r in raw: raw.remove(r)
- self.split = list(raw)
+ # parse prefixes
+ raw = re.split("[\s]+", raw_prefix)
+ for r in ["", ";"]:
+ while r in raw: raw.remove(r)
+ self.split = list(raw)
- for r in ["method", "public", "protected", "static", "final", "deprecated", "abstract", "default", "operator"]:
- while r in raw: raw.remove(r)
+ for r in ["method", "public", "protected", "static", "final", "deprecated", "abstract", "default", "operator", "synchronized"]:
+ while r in raw: raw.remove(r)
- self.typ = raw[0]
- self.name = raw[1]
+ self.typ = raw[0]
+ self.name = raw[1]
- # parse args
- self.args = []
- for arg in re.split(",\s*", raw_args):
- arg = re.split("\s", arg)
- # ignore annotations for now
- arg = [ a for a in arg if not a.startswith("@") ]
- if len(arg[0]) > 0:
- self.args.append(arg[0])
+ # parse args
+ self.args = []
+ for arg in re.split(",\s*", raw_args):
+ arg = re.split("\s", arg)
+ # ignore annotations for now
+ arg = [ a for a in arg if not a.startswith("@") ]
+ if len(arg[0]) > 0:
+ self.args.append(arg[0])
- # parse throws
- self.throws = []
- for throw in re.split(",\s*", raw_throws):
- self.throws.append(throw)
+ # parse throws
+ self.throws = []
+ for throw in re.split(",\s*", raw_throws):
+ self.throws.append(throw)
+ else:
+ raise ValueError("Unknown signature format: " + sig_format)
- self.ident = ident(self.raw)
+ self.ident = "-".join((self.typ, self.name, "-".join(self.args)))
+
+ def sig_matches(self, typ, name, args):
+ return typ == self.typ and name == self.name and args == self.args
def __hash__(self):
return hash(self.raw)
@@ -147,7 +146,7 @@
class Class():
- def __init__(self, pkg, line, raw, blame):
+ def __init__(self, pkg, line, raw, blame, sig_format = 1):
self.pkg = pkg
self.line = line
self.raw = raw.strip(" {;")
@@ -156,31 +155,44 @@
self.fields = []
self.methods = []
- # drop generics for now; may need multiple passes
- raw = re.sub("<[^<]+?>", "", raw)
- raw = re.sub("<[^<]+?>", "", raw)
+ if sig_format == 2:
+ V2LineParser(raw).parse_into_class(self)
+ elif sig_format == 1:
+ # drop generics for now; may need multiple passes
+ raw = re.sub("<[^<]+?>", "", raw)
+ raw = re.sub("<[^<]+?>", "", raw)
- raw = raw.split()
- self.split = list(raw)
- if "class" in raw:
- self.fullname = raw[raw.index("class")+1]
- elif "interface" in raw:
- self.fullname = raw[raw.index("interface")+1]
- elif "@interface" in raw:
- self.fullname = raw[raw.index("@interface")+1]
- else:
- raise ValueError("Funky class type %s" % (self.raw))
+ raw = raw.split()
+ self.split = list(raw)
+ if "class" in raw:
+ self.fullname = raw[raw.index("class")+1]
+ elif "interface" in raw:
+ self.fullname = raw[raw.index("interface")+1]
+ elif "@interface" in raw:
+ self.fullname = raw[raw.index("@interface")+1]
+ else:
+ raise ValueError("Funky class type %s" % (self.raw))
- if "extends" in raw:
- self.extends = raw[raw.index("extends")+1]
- self.extends_path = self.extends.split(".")
+ if "extends" in raw:
+ self.extends = raw[raw.index("extends")+1]
+ else:
+ self.extends = None
+
+ if "implements" in raw:
+ self.implements = raw[raw.index("implements")+1]
+ else:
+ self.implements = None
else:
- self.extends = None
- self.extends_path = []
+ raise ValueError("Unknown signature format: " + sig_format)
self.fullname = self.pkg.name + "." + self.fullname
self.fullname_path = self.fullname.split(".")
+ if self.extends is not None:
+ self.extends_path = self.extends.split(".")
+ else:
+ self.extends_path = []
+
self.name = self.fullname[self.fullname.rindex(".")+1:]
def merge_from(self, other):
@@ -208,6 +220,318 @@
def __repr__(self):
return self.raw
+class V2Tokenizer(object):
+ __slots__ = ["raw"]
+
+ DELIMITER = re.compile(r'\s+|[()@<>;,={}/"!?]|\[\]|\.\.\.')
+ STRING_SPECIAL = re.compile(r'["\\]')
+
+ def __init__(self, raw):
+ self.raw = raw
+
+ def tokenize(self):
+ tokens = []
+ current = 0
+ raw = self.raw
+ length = len(raw)
+
+ while current < length:
+ while current < length:
+ start = current
+ match = V2Tokenizer.DELIMITER.search(raw, start)
+ if match is not None:
+ match_start = match.start()
+ if match_start == current:
+ end = match.end()
+ else:
+ end = match_start
+ else:
+ end = length
+
+ token = raw[start:end]
+ current = end
+
+ if token == "" or token[0] == " ":
+ continue
+ else:
+ break
+
+ if token == "@":
+ if raw[start:start+11] == "@interface ":
+ current = start + 11
+ tokens.append("@interface")
+ continue
+ elif token == '/':
+ if raw[start:start+2] == "//":
+ current = length
+ continue
+ elif token == '"':
+ current, string_token = self.tokenize_string(raw, length, current)
+ tokens.append(token + string_token)
+ continue
+
+ tokens.append(token)
+
+ return tokens
+
+ def tokenize_string(self, raw, length, current):
+ start = current
+ end = length
+ while start < end:
+ match = V2Tokenizer.STRING_SPECIAL.search(raw, start)
+ if match:
+ if match.group() == '"':
+ end = match.end()
+ break
+ elif match.group() == '\\':
+ # ignore whatever is after the slash
+ start += 2
+ else:
+ raise ValueError("Unexpected match: `%s`" % (match.group()))
+ else:
+ raise ValueError("Unexpected EOF tokenizing string: `%s`" % (raw[current - 1:],))
+
+ token = raw[current:end]
+ return end, token
+
+class V2LineParser(object):
+ __slots__ = ["tokenized", "current", "len"]
+
+ MODIFIERS = set("public protected internal private abstract default static final transient volatile synchronized".split())
+ JAVA_LANG_TYPES = set("AbstractMethodError AbstractStringBuilder Appendable ArithmeticException ArrayIndexOutOfBoundsException ArrayStoreException AssertionError AutoCloseable Boolean BootstrapMethodError Byte Character CharSequence Class ClassCastException ClassCircularityError ClassFormatError ClassLoader ClassNotFoundException Cloneable CloneNotSupportedException Comparable Compiler Deprecated Double Enum EnumConstantNotPresentException Error Exception ExceptionInInitializerError Float FunctionalInterface IllegalAccessError IllegalAccessException IllegalArgumentException IllegalMonitorStateException IllegalStateException IllegalThreadStateException IncompatibleClassChangeError IndexOutOfBoundsException InheritableThreadLocal InstantiationError InstantiationException Integer InternalError InterruptedException Iterable LinkageError Long Math NegativeArraySizeException NoClassDefFoundError NoSuchFieldError NoSuchFieldException NoSuchMethodError NoSuchMethodException NullPointerException Number NumberFormatException Object OutOfMemoryError Override Package package-info.java Process ProcessBuilder ProcessEnvironment ProcessImpl Readable ReflectiveOperationException Runnable Runtime RuntimeException RuntimePermission SafeVarargs SecurityException SecurityManager Short StackOverflowError StackTraceElement StrictMath String StringBuffer StringBuilder StringIndexOutOfBoundsException SuppressWarnings System Thread ThreadDeath ThreadGroup ThreadLocal Throwable TypeNotPresentException UNIXProcess UnknownError UnsatisfiedLinkError UnsupportedClassVersionError UnsupportedOperationException VerifyError VirtualMachineError Void".split())
+
+ def __init__(self, raw):
+ self.tokenized = V2Tokenizer(raw).tokenize()
+ self.current = 0
+ self.len = len(self.tokenized)
+
+ def parse_into_method(self, method):
+ method.split = []
+ kind = self.parse_one_of("ctor", "method")
+ method.split.append(kind)
+ annotations = self.parse_annotations()
+ method.split.extend(self.parse_modifiers())
+ self.parse_matching_paren("<", ">")
+ if "@Deprecated" in annotations:
+ method.split.append("deprecated")
+ if kind == "ctor":
+ method.typ = "ctor"
+ else:
+ method.typ = self.parse_type()
+ method.split.append(method.typ)
+ method.name = self.parse_name()
+ method.split.append(method.name)
+ self.parse_token("(")
+ method.args = self.parse_args()
+ self.parse_token(")")
+ method.throws = self.parse_throws()
+ if "@interface" in method.clazz.split:
+ self.parse_annotation_default()
+ self.parse_token(";")
+ self.parse_eof()
+
+ def parse_into_class(self, clazz):
+ clazz.split = []
+ annotations = self.parse_annotations()
+ if "@Deprecated" in annotations:
+ clazz.split.append("deprecated")
+ clazz.split.extend(self.parse_modifiers())
+ kind = self.parse_one_of("class", "interface", "@interface", "enum")
+ if kind == "enum":
+ # enums are implicitly final
+ clazz.split.append("final")
+ clazz.split.append(kind)
+ clazz.fullname = self.parse_name()
+ self.parse_matching_paren("<", ">")
+ extends = self.parse_extends()
+ clazz.extends = extends[0] if extends else None
+ implements = self.parse_implements()
+ clazz.implements = implements[0] if implements else None
+ # The checks assume that interfaces are always found in implements, which isn't true for
+ # subinterfaces.
+ if not implements and "interface" in clazz.split:
+ clazz.implements = clazz.extends
+ self.parse_token("{")
+ self.parse_eof()
+
+ def parse_into_field(self, field):
+ kind = self.parse_token("field")
+ field.split = [kind]
+ annotations = self.parse_annotations()
+ if "@Deprecated" in annotations:
+ field.split.append("deprecated")
+ field.split.extend(self.parse_modifiers())
+ field.typ = self.parse_type()
+ field.split.append(field.typ)
+ field.name = self.parse_name()
+ field.split.append(field.name)
+ if self.parse_if("="):
+ field.value = self.parse_value_stripped()
+ else:
+ field.value = None
+
+ self.parse_token(";")
+ self.parse_eof()
+
+ def lookahead(self):
+ return self.tokenized[self.current]
+
+ def parse_one_of(self, *options):
+ found = self.lookahead()
+ if found not in options:
+ raise ValueError("Parsing failed, expected one of `%s` but found `%s` in %s" % (options, found, repr(self.tokenized)))
+ return self.parse_token()
+
+ def parse_token(self, tok = None):
+ found = self.lookahead()
+ if tok is not None and found != tok:
+ raise ValueError("Parsing failed, expected `%s` but found `%s` in %s" % (tok, found, repr(self.tokenized)))
+ self.current += 1
+ return found
+
+ def eof(self):
+ return self.current == self.len
+
+ def parse_eof(self):
+ if not self.eof():
+ raise ValueError("Parsing failed, expected EOF, but %s has not been parsed in %s" % (self.tokenized[self.current:], self.tokenized))
+
+ def parse_if(self, tok):
+ if not self.eof() and self.lookahead() == tok:
+ self.parse_token()
+ return True
+ return False
+
+ def parse_annotations(self):
+ ret = []
+ while self.lookahead() == "@":
+ ret.append(self.parse_annotation())
+ return ret
+
+ def parse_annotation(self):
+ ret = self.parse_token("@") + self.parse_token()
+ self.parse_matching_paren("(", ")")
+ return ret
+
+ def parse_matching_paren(self, open, close):
+ start = self.current
+ if not self.parse_if(open):
+ return
+ length = len(self.tokenized)
+ count = 1
+ while count > 0:
+ if self.current == length:
+ raise ValueError("Unexpected EOF looking for closing paren: `%s`" % (self.tokenized[start:],))
+ t = self.parse_token()
+ if t == open:
+ count += 1
+ elif t == close:
+ count -= 1
+ return self.tokenized[start:self.current]
+
+ def parse_modifiers(self):
+ ret = []
+ while self.lookahead() in V2LineParser.MODIFIERS:
+ ret.append(self.parse_token())
+ return ret
+
+ def parse_kotlin_nullability(self):
+ t = self.lookahead()
+ if t == "?" or t == "!":
+ return self.parse_token()
+ return None
+
+ def parse_type(self):
+ type = self.parse_token()
+ if type in V2LineParser.JAVA_LANG_TYPES:
+ type = "java.lang." + type
+ self.parse_matching_paren("<", ">")
+ while True:
+ t = self.lookahead()
+ if t == "[]":
+ type += self.parse_token()
+ elif self.parse_kotlin_nullability() is not None:
+ pass # discard nullability for now
+ else:
+ break
+ return type
+
+ def parse_arg_type(self):
+ type = self.parse_type()
+ if self.parse_if("..."):
+ type += "..."
+ self.parse_kotlin_nullability() # discard nullability for now
+ return type
+
+ def parse_name(self):
+ return self.parse_token()
+
+ def parse_args(self):
+ args = []
+ if self.lookahead() == ")":
+ return args
+
+ while True:
+ args.append(self.parse_arg())
+ if self.lookahead() == ")":
+ return args
+ self.parse_token(",")
+
+ def parse_arg(self):
+ self.parse_annotations()
+ type = self.parse_arg_type()
+ l = self.lookahead()
+ if l != "," and l != ")":
+ self.parse_token() # kotlin argument name
+ if self.parse_if('='): # kotlin default value
+ (self.parse_matching_paren('(', ')') or
+ self.parse_matching_paren('{', '}') or
+ self.parse_token() and self.parse_matching_paren('(', ')'))
+ return type
+
+ def parse_throws(self):
+ ret = []
+ if self.parse_if("throws"):
+ ret.append(self.parse_type())
+ while self.parse_if(","):
+ ret.append(self.parse_type())
+ return ret
+
+ def parse_extends(self):
+ if self.parse_if("extends"):
+ return self.parse_space_delimited_type_list()
+ return []
+
+ def parse_implements(self):
+ if self.parse_if("implements"):
+ return self.parse_space_delimited_type_list()
+ return []
+
+ def parse_space_delimited_type_list(self, terminals = ["implements", "{"]):
+ types = []
+ while True:
+ types.append(self.parse_type())
+ if self.lookahead() in terminals:
+ return types
+
+ def parse_annotation_default(self):
+ if self.parse_if("default"):
+ self.parse_value()
+
+ def parse_value(self):
+ if self.lookahead() == "{":
+ return " ".join(self.parse_matching_paren("{", "}"))
+ elif self.lookahead() == "(":
+ return " ".join(self.parse_matching_paren("(", ")"))
+ else:
+ return self.parse_token()
+
+ def parse_value_stripped(self):
+ value = self.parse_value()
+ if value[0] == '"':
+ return value[1:-1]
+ return value
+
def _parse_stream(f, clazz_cb=None, base_f=None, out_classes_with_base=None,
in_classes_with_base=[]):
@@ -252,6 +576,7 @@
pkg = None
clazz = None
blame = None
+ sig_format = 1
re_blame = re.compile("^([a-z0-9]{7,}) \(<([^>]+)>.+?\) (.+?)$")
for raw in f:
@@ -264,16 +589,18 @@
else:
blame = None
- if raw.startswith("package"):
+ if line == 1 and raw == "// Signature format: 2.0":
+ sig_format = 2
+ elif raw.startswith("package"):
pkg = Package(line, raw, blame)
elif raw.startswith(" ") and raw.endswith("{"):
- clazz = Class(pkg, line, raw, blame)
+ clazz = Class(pkg, line, raw, blame, sig_format=sig_format)
elif raw.startswith(" ctor"):
- clazz.ctors.append(Method(clazz, line, raw, blame))
+ clazz.ctors.append(Method(clazz, line, raw, blame, sig_format=sig_format))
elif raw.startswith(" method"):
- clazz.methods.append(Method(clazz, line, raw, blame))
+ clazz.methods.append(Method(clazz, line, raw, blame, sig_format=sig_format))
elif raw.startswith(" field"):
- clazz.fields.append(Field(clazz, line, raw, blame))
+ clazz.fields.append(Field(clazz, line, raw, blame, sig_format=sig_format))
elif raw.startswith(" }") and clazz:
yield clazz
@@ -367,7 +694,7 @@
"""Records an API failure to be processed later."""
global failures
- sig = "%s-%s-%s" % (clazz.fullname, repr(detail), msg)
+ sig = "%s-%s-%s" % (clazz.fullname, detail.ident if detail else None, msg)
sig = sig.replace(" deprecated ", " ")
failures[sig] = Failure(sig, clazz, detail, error, rule, msg)
@@ -408,7 +735,7 @@
def verify_enums(clazz):
"""Enums are bad, mmkay?"""
- if "extends java.lang.Enum" in clazz.raw:
+ if clazz.extends == "java.lang.Enum" or "enum" in clazz.split:
error(clazz, None, "F5", "Enums are not allowed")
@@ -467,7 +794,7 @@
interface OnFooListener { void onFoo() }"""
if clazz.name.endswith("Listener"):
- if " abstract class " in clazz.raw:
+ if "abstract" in clazz.split and "class" in clazz.split:
error(clazz, None, "L1", "Listeners should be an interface, or otherwise renamed Callback")
for m in clazz.methods:
@@ -546,16 +873,16 @@
eq = False
hc = False
for m in clazz.methods:
- if " static " in m.raw: continue
- if "boolean equals(java.lang.Object)" in m.raw: eq = True
- if "int hashCode()" in m.raw: hc = True
+ if "static" in m.split: continue
+ if m.sig_matches("boolean", "equals", ["java.lang.Object"]): eq = True
+ if m.sig_matches("int", "hashCode", []): hc = True
if eq != hc:
error(clazz, None, "M8", "Must override both equals and hashCode; missing one")
def verify_parcelable(clazz):
"""Verify that Parcelable objects aren't hiding required bits."""
- if "implements android.os.Parcelable" in clazz.raw:
+ if clazz.implements == "android.os.Parcelable":
creator = [ i for i in clazz.fields if i.name == "CREATOR" ]
write = [ i for i in clazz.methods if i.name == "writeToParcel" ]
describe = [ i for i in clazz.methods if i.name == "describeContents" ]
@@ -563,8 +890,7 @@
if len(creator) == 0 or len(write) == 0 or len(describe) == 0:
error(clazz, None, "FW3", "Parcelable requires CREATOR, writeToParcel, and describeContents; missing one")
- if ((" final class " not in clazz.raw) and
- (" final deprecated class " not in clazz.raw)):
+ if "final" not in clazz.split:
error(clazz, None, "FW8", "Parcelable classes must be final")
for c in clazz.ctors:
@@ -684,7 +1010,7 @@
"""Verify that helper classes are named consistently with what they extend.
All developer extendable methods should be named onFoo()."""
test_methods = False
- if "extends android.app.Service" in clazz.raw:
+ if clazz.extends == "android.app.Service":
test_methods = True
if not clazz.name.endswith("Service"):
error(clazz, None, "CL4", "Inconsistent class name; should be FooService")
@@ -696,7 +1022,7 @@
if f.value != clazz.fullname:
error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
- if "extends android.content.ContentProvider" in clazz.raw:
+ if clazz.extends == "android.content.ContentProvider":
test_methods = True
if not clazz.name.endswith("Provider"):
error(clazz, None, "CL4", "Inconsistent class name; should be FooProvider")
@@ -708,12 +1034,12 @@
if f.value != clazz.fullname:
error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
- if "extends android.content.BroadcastReceiver" in clazz.raw:
+ if clazz.extends == "android.content.BroadcastReceiver":
test_methods = True
if not clazz.name.endswith("Receiver"):
error(clazz, None, "CL4", "Inconsistent class name; should be FooReceiver")
- if "extends android.app.Activity" in clazz.raw:
+ if clazz.extends == "android.app.Activity":
test_methods = True
if not clazz.name.endswith("Activity"):
error(clazz, None, "CL4", "Inconsistent class name; should be FooActivity")
@@ -731,7 +1057,7 @@
def verify_builder(clazz):
"""Verify builder classes.
Methods should return the builder to enable chaining."""
- if " extends " in clazz.raw: return
+ if clazz.extends: return
if not clazz.name.endswith("Builder"): return
if clazz.name != "Builder":
@@ -759,7 +1085,7 @@
def verify_aidl(clazz):
"""Catch people exposing raw AIDL."""
- if "extends android.os.Binder" in clazz.raw or "implements android.os.IInterface" in clazz.raw:
+ if clazz.extends == "android.os.Binder" or clazz.implements == "android.os.IInterface":
error(clazz, None, None, "Raw AIDL interfaces must not be exposed")
@@ -768,48 +1094,66 @@
if clazz.pkg.name.startswith("com.android"):
error(clazz, None, None, "Internal classes must not be exposed")
+def layering_build_ranking(ranking_list):
+ r = {}
+ for rank, ps in enumerate(ranking_list):
+ if not isinstance(ps, list):
+ ps = [ps]
+ for p in ps:
+ rs = r
+ for n in p.split('.'):
+ if n not in rs:
+ rs[n] = {}
+ rs = rs[n]
+ rs['-rank'] = rank
+ return r
+
+LAYERING_PACKAGE_RANKING = layering_build_ranking([
+ ["android.service","android.accessibilityservice","android.inputmethodservice","android.printservice","android.appwidget","android.webkit","android.preference","android.gesture","android.print"],
+ "android.app",
+ "android.widget",
+ "android.view",
+ "android.animation",
+ "android.provider",
+ ["android.content","android.graphics.drawable"],
+ "android.database",
+ "android.text",
+ "android.graphics",
+ "android.os",
+ "android.util"
+])
def verify_layering(clazz):
"""Catch package layering violations.
For example, something in android.os depending on android.app."""
- ranking = [
- ["android.service","android.accessibilityservice","android.inputmethodservice","android.printservice","android.appwidget","android.webkit","android.preference","android.gesture","android.print"],
- "android.app",
- "android.widget",
- "android.view",
- "android.animation",
- "android.provider",
- ["android.content","android.graphics.drawable"],
- "android.database",
- "android.text",
- "android.graphics",
- "android.os",
- "android.util"
- ]
def rank(p):
- for i in range(len(ranking)):
- if isinstance(ranking[i], list):
- for j in ranking[i]:
- if p.startswith(j): return i
+ r = None
+ l = LAYERING_PACKAGE_RANKING
+ for n in p.split('.'):
+ if n in l:
+ l = l[n]
+ if '-rank' in l:
+ r = l['-rank']
else:
- if p.startswith(ranking[i]): return i
+ break
+ return r
cr = rank(clazz.pkg.name)
if cr is None: return
for f in clazz.fields:
ir = rank(f.typ)
- if ir and ir < cr:
+ if ir is not None and ir < cr:
warn(clazz, f, "FW6", "Field type violates package layering")
- for m in clazz.methods:
+ for m in itertools.chain(clazz.methods, clazz.ctors):
ir = rank(m.typ)
- if ir and ir < cr:
+ if ir is not None and ir < cr:
warn(clazz, m, "FW6", "Method return type violates package layering")
for arg in m.args:
ir = rank(arg)
- if ir and ir < cr:
+ if ir is not None and ir < cr:
warn(clazz, m, "FW6", "Method argument type violates package layering")
@@ -900,21 +1244,18 @@
if len(m.args) == 0 and t in ["java.lang.IllegalArgumentException", "java.lang.NullPointerException"]:
warn(clazz, m, "S1", "Methods taking no arguments should throw IllegalStateException")
+GOOGLE_IGNORECASE = re.compile("google", re.IGNORECASE)
def verify_google(clazz):
"""Verifies that APIs never reference Google."""
- if re.search("google", clazz.raw, re.IGNORECASE):
+ if GOOGLE_IGNORECASE.search(clazz.raw) is not None:
error(clazz, None, None, "Must never reference Google")
- test = []
- test.extend(clazz.ctors)
- test.extend(clazz.fields)
- test.extend(clazz.methods)
-
- for t in test:
- if re.search("google", t.raw, re.IGNORECASE):
- error(clazz, t, None, "Must never reference Google")
+ for test in clazz.ctors, clazz.fields, clazz.methods:
+ for t in test:
+ if GOOGLE_IGNORECASE.search(t.raw) is not None:
+ error(clazz, t, None, "Must never reference Google")
def verify_bitset(clazz):
@@ -1168,7 +1509,7 @@
"""Verifies that abstract inner classes are static."""
if re.match(".+?\.[A-Z][^\.]+\.[A-Z]", clazz.fullname):
- if " abstract " in clazz.raw and " static " not in clazz.raw:
+ if "abstract" in clazz.split and "static" not in clazz.split:
warn(clazz, None, None, "Abstract inner classes should be static to improve testability")
@@ -1263,8 +1604,8 @@
def verify_closable(clazz):
"""Verifies that classes are AutoClosable."""
- if "implements java.lang.AutoCloseable" in clazz.raw: return
- if "implements java.io.Closeable" in clazz.raw: return
+ if clazz.implements == "java.lang.AutoCloseable": return
+ if clazz.implements == "java.io.Closeable": return
for m in clazz.methods:
if len(m.args) > 0: continue
@@ -1350,6 +1691,9 @@
def verify_collections_over_arrays(clazz):
"""Warn that [] should be Collections."""
+ if "@interface" in clazz.split:
+ return
+
safe = ["java.lang.String[]","byte[]","short[]","int[]","long[]","float[]","double[]","boolean[]","char[]"]
for m in clazz.methods:
if m.typ.endswith("[]") and m.typ not in safe:
@@ -1683,11 +2027,11 @@
del cur[prev_clazz.fullname]
for clazz in cur.values():
- if " deprecated " in clazz.raw and not clazz.fullname in prev:
+ if "deprecated" in clazz.split and not clazz.fullname in prev:
error(clazz, None, None, "Found API deprecation at birth")
for i in clazz.ctors + clazz.methods + clazz.fields:
- if " deprecated " in i.raw:
+ if "deprecated" in i.split:
error(clazz, i, None, "Found API deprecation at birth")
print "%s Deprecated at birth %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True),
diff --git a/tools/apilint/apilint_test.py b/tools/apilint/apilint_test.py
index ece69a9..081e98d 100644
--- a/tools/apilint/apilint_test.py
+++ b/tools/apilint/apilint_test.py
@@ -143,5 +143,148 @@
out_classes_with_base=classes_with_base)
self.assertEquals(map(lambda x: x.fullname, classes_with_base), ["android.app.WallpaperColors"])
+class V2TokenizerTests(unittest.TestCase):
+ def _test(self, raw, expected):
+ self.assertEquals(apilint.V2Tokenizer(raw).tokenize(), expected)
+
+ def test_simple(self):
+ self._test(" method public some.Type someName(some.Argument arg, int arg);",
+ ['method', 'public', 'some.Type', 'someName', '(', 'some.Argument',
+ 'arg', ',', 'int', 'arg', ')', ';'])
+ self._test("class Some.Class extends SomeOther {",
+ ['class', 'Some.Class', 'extends', 'SomeOther', '{'])
+
+ def test_varargs(self):
+ self._test("name(String...)",
+ ['name', '(', 'String', '...', ')'])
+
+ def test_kotlin(self):
+ self._test("String? name(String!...)",
+ ['String', '?', 'name', '(', 'String', '!', '...', ')'])
+
+ def test_annotation(self):
+ self._test("method @Nullable public void name();",
+ ['method', '@', 'Nullable', 'public', 'void', 'name', '(', ')', ';'])
+
+ def test_annotation_args(self):
+ self._test("@Some(val=1, other=2) class Class {",
+ ['@', 'Some', '(', 'val', '=', '1', ',', 'other', '=', '2', ')',
+ 'class', 'Class', '{'])
+ def test_comment(self):
+ self._test("some //comment", ['some'])
+
+ def test_strings(self):
+ self._test(r'"" "foo" "\"" "\\"', ['""', '"foo"', r'"\""', r'"\\"'])
+
+ def test_at_interface(self):
+ self._test("public @interface Annotation {",
+ ['public', '@interface', 'Annotation', '{'])
+
+ def test_array_type(self):
+ self._test("int[][]", ['int', '[]', '[]'])
+
+ def test_generics(self):
+ self._test("<>foobar<A extends Object>",
+ ['<', '>', 'foobar', '<', 'A', 'extends', 'Object', '>'])
+
+class V2ParserTests(unittest.TestCase):
+ def _cls(self, raw):
+ pkg = apilint.Package(999, "package pkg {", None)
+ return apilint.Class(pkg, 1, raw, '', sig_format=2)
+
+ def _method(self, raw, cls=None):
+ if not cls:
+ cls = self._cls("class Class {")
+ return apilint.Method(cls, 1, raw, '', sig_format=2)
+
+ def _field(self, raw):
+ cls = self._cls("class Class {")
+ return apilint.Field(cls, 1, raw, '', sig_format=2)
+
+ def test_class(self):
+ cls = self._cls("@Deprecated @IntRange(from=1, to=2) public static abstract class Some.Name extends Super<Class> implements Interface<Class> {")
+ self.assertTrue('deprecated' in cls.split)
+ self.assertTrue('static' in cls.split)
+ self.assertTrue('abstract' in cls.split)
+ self.assertTrue('class' in cls.split)
+ self.assertEquals('Super', cls.extends)
+ self.assertEquals('Interface', cls.implements)
+ self.assertEquals('pkg.Some.Name', cls.fullname)
+
+ def test_interface(self):
+ cls = self._cls("@Deprecated @IntRange(from=1, to=2) public interface Some.Name extends Interface<Class> {")
+ self.assertTrue('deprecated' in cls.split)
+ self.assertTrue('interface' in cls.split)
+ self.assertEquals('Interface', cls.extends)
+ self.assertEquals('Interface', cls.implements)
+ self.assertEquals('pkg.Some.Name', cls.fullname)
+
+ def test_at_interface(self):
+ cls = self._cls("@java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.LOCAL_VARIABLE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface SuppressLint {")
+ self.assertTrue('@interface' in cls.split)
+ self.assertEquals('pkg.SuppressLint', cls.fullname)
+
+ def test_parse_method(self):
+ m = self._method("method @Deprecated public static <T> Class<T>[][] name("
+ + "Class<T[]>[][], Class<T[][][]>[][]...) throws Exception, T;")
+ self.assertTrue('static' in m.split)
+ self.assertTrue('public' in m.split)
+ self.assertTrue('method' in m.split)
+ self.assertTrue('deprecated' in m.split)
+ self.assertEquals('java.lang.Class[][]', m.typ)
+ self.assertEquals('name', m.name)
+ self.assertEquals(['java.lang.Class[][]', 'java.lang.Class[][]...'], m.args)
+ self.assertEquals(['java.lang.Exception', 'T'], m.throws)
+
+ def test_ctor(self):
+ m = self._method("ctor @Deprecated <T> ClassName();")
+ self.assertTrue('ctor' in m.split)
+ self.assertTrue('deprecated' in m.split)
+ self.assertEquals('ctor', m.typ)
+ self.assertEquals('ClassName', m.name)
+
+ def test_parse_annotation_method(self):
+ cls = self._cls("@interface Annotation {")
+ self._method('method abstract String category() default "";', cls=cls)
+ self._method('method abstract boolean deepExport() default false;', cls=cls)
+ self._method('method abstract ViewDebug.FlagToString[] flagMapping() default {};', cls=cls)
+
+ def test_parse_string_field(self):
+ f = self._field('field @Deprecated public final String SOME_NAME = "value";')
+ self.assertTrue('field' in f.split)
+ self.assertTrue('deprecated' in f.split)
+ self.assertTrue('final' in f.split)
+ self.assertEquals('java.lang.String', f.typ)
+ self.assertEquals('SOME_NAME', f.name)
+ self.assertEquals('value', f.value)
+
+ def test_parse_field(self):
+ f = self._field('field public Object SOME_NAME;')
+ self.assertTrue('field' in f.split)
+ self.assertEquals('java.lang.Object', f.typ)
+ self.assertEquals('SOME_NAME', f.name)
+ self.assertEquals(None, f.value)
+
+ def test_parse_int_field(self):
+ f = self._field('field public int NAME = 123;')
+ self.assertTrue('field' in f.split)
+ self.assertEquals('int', f.typ)
+ self.assertEquals('NAME', f.name)
+ self.assertEquals('123', f.value)
+
+ def test_parse_quotient_field(self):
+ f = self._field('field public int NAME = (0.0/0.0);')
+ self.assertTrue('field' in f.split)
+ self.assertEquals('int', f.typ)
+ self.assertEquals('NAME', f.name)
+ self.assertEquals('( 0.0 / 0.0 )', f.value)
+
+ def test_kotlin_types(self):
+ self._field('field public List<Integer[]?[]!>?[]![]? NAME;')
+ self._method("method <T?> Class<T!>?[]![][]? name(Type!, Type argname,"
+ + "Class<T?>[][]?[]!...!) throws Exception, T;")
+ self._method("method <T> T name(T a = 1, T b = A(1), Lambda f = { false }, N? n = null, "
+ + """double c = (1/0), float d = 1.0f, String s = "heyo", char c = 'a');""")
+
if __name__ == "__main__":
- unittest.main()
\ No newline at end of file
+ unittest.main()
diff --git a/tools/bit/command.h b/tools/bit/command.h
index fb44900..dd7103e 100644
--- a/tools/bit/command.h
+++ b/tools/bit/command.h
@@ -25,7 +25,7 @@
struct Command
{
- Command(const string& prog);
+ explicit Command(const string& prog);
~Command();
void AddArg(const string& arg);
diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp
index 11b408f..4491a85 100644
--- a/tools/stats_log_api_gen/main.cpp
+++ b/tools/stats_log_api_gen/main.cpp
@@ -67,7 +67,7 @@
case JAVA_TYPE_STRING:
return "char const*";
case JAVA_TYPE_BYTE_ARRAY:
- return "char const*";
+ return "const BytesField&";
default:
return "UNKNOWN";
}
@@ -270,10 +270,6 @@
chainField.name.c_str(), chainField.name.c_str());
}
}
- } else if (*arg == JAVA_TYPE_BYTE_ARRAY) {
- fprintf(out, ", %s arg%d, size_t arg%d_length",
- cpp_type_name(*arg), argIndex, argIndex);
-
} else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) {
fprintf(out, ", const std::map<int, int32_t>& arg%d_1, "
"const std::map<int, int64_t>& arg%d_2, "
@@ -355,7 +351,8 @@
fprintf(out, " event.end();\n\n");
} else if (*arg == JAVA_TYPE_BYTE_ARRAY) {
fprintf(out,
- " event.AppendCharArray(arg%d, arg%d_length);\n",
+ " event.AppendCharArray(arg%d.arg, "
+ "arg%d.arg_length);\n",
argIndex, argIndex);
} else {
if (*arg == JAVA_TYPE_STRING) {
@@ -397,10 +394,6 @@
chainField.name.c_str(), chainField.name.c_str());
}
}
- } else if (*arg == JAVA_TYPE_BYTE_ARRAY) {
- fprintf(out, ", %s arg%d, size_t arg%d_length",
- cpp_type_name(*arg), argIndex, argIndex);
-
} else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) {
fprintf(out,
", const std::map<int, int32_t>& arg%d_1, "
@@ -434,8 +427,6 @@
chainField.name.c_str(), chainField.name.c_str());
}
}
- } else if (*arg == JAVA_TYPE_BYTE_ARRAY) {
- fprintf(out, ", arg%d, arg%d_length", argIndex, argIndex);
} else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) {
fprintf(out, ", arg%d_1, arg%d_2, arg%d_3, arg%d_4", argIndex,
argIndex, argIndex, argIndex);
@@ -494,7 +485,14 @@
fprintf(out, " arg%d = \"\";\n", argIndex);
fprintf(out, " }\n");
}
- fprintf(out, " event << arg%d;\n", argIndex);
+ if (*arg == JAVA_TYPE_BYTE_ARRAY) {
+ fprintf(out,
+ " event.AppendCharArray(arg%d.arg, "
+ "arg%d.arg_length);",
+ argIndex, argIndex);
+ } else {
+ fprintf(out, " event << arg%d;\n", argIndex);
+ }
if (argIndex == 2) {
fprintf(out, " event.end();\n\n");
fprintf(out, " event.end();\n\n");
@@ -577,7 +575,9 @@
static void write_cpp_usage(
FILE* out, const string& method_name, const string& atom_code_name,
const AtomDecl& atom, const AtomDecl &attributionDecl) {
- fprintf(out, " * Usage: %s(StatsLog.%s", method_name.c_str(), atom_code_name.c_str());
+ fprintf(out, " * Usage: %s(StatsLog.%s", method_name.c_str(),
+ atom_code_name.c_str());
+
for (vector<AtomField>::const_iterator field = atom.fields.begin();
field != atom.fields.end(); field++) {
if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
@@ -601,11 +601,6 @@
field->name.c_str(),
field->name.c_str(),
field->name.c_str());
- } else if (field->javaType == JAVA_TYPE_BYTE_ARRAY) {
- fprintf(out, ", %s %s, size_t %s_length",
- cpp_type_name(field->javaType), field->name.c_str(),
- field->name.c_str());
-
} else {
fprintf(out, ", %s %s", cpp_type_name(field->javaType), field->name.c_str());
}
@@ -639,9 +634,6 @@
"const std::map<int, char const*>& arg%d_3, "
"const std::map<int, float>& arg%d_4",
argIndex, argIndex, argIndex, argIndex);
- } else if (*arg == JAVA_TYPE_BYTE_ARRAY) {
- fprintf(out, ", %s arg%d, size_t arg%d_length",
- cpp_type_name(*arg), argIndex, argIndex);
} else {
fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex);
}
@@ -708,6 +700,15 @@
fprintf(out, "};\n");
fprintf(out, "\n");
+ fprintf(out, "struct BytesField {\n");
+ fprintf(out,
+ " BytesField(char const* array, size_t len) : arg(array), "
+ "arg_length(len) {}\n");
+ fprintf(out, " char const* arg;\n");
+ fprintf(out, " size_t arg_length;\n");
+ fprintf(out, "};\n");
+ fprintf(out, "\n");
+
fprintf(out, "struct StateAtomFieldOptions {\n");
fprintf(out, " std::vector<int> primaryFields;\n");
fprintf(out, " int exclusiveField;\n");
@@ -775,6 +776,7 @@
const AtomDecl &attributionDecl) {
for (set<vector<java_type_t>>::const_iterator signature = signatures.begin();
signature != signatures.end(); signature++) {
+ fprintf(out, " /** @hide */\n");
fprintf(out, " public static native int %s(int code", method_name.c_str());
int argIndex = 1;
for (vector<java_type_t>::const_iterator arg = signature->begin();
@@ -820,6 +822,7 @@
}
// Method header (signature)
+ fprintf(out, " /** @hide */\n");
fprintf(out, " public static void write(int code");
int argIndex = 1;
for (vector<java_type_t>::const_iterator arg = signature->begin();
@@ -900,6 +903,7 @@
if (non_chained_decl != atom_code_to_non_chained_decl_map.end()) {
write_java_usage(out, "write_non_chained", constant, *non_chained_decl->second);
}
+ fprintf(out, " * @hide\n");
fprintf(out, " */\n");
fprintf(out, " public static final int %s = %d;\n", constant.c_str(), atom->code);
}
@@ -916,6 +920,7 @@
field->name.c_str());
for (map<int, string>::const_iterator value = field->enumValues.begin();
value != field->enumValues.end(); value++) {
+ fprintf(out, " /** @hide */\n");
fprintf(out, " public static final int %s__%s__%s = %d;\n",
make_constant_name(atom->message).c_str(),
make_constant_name(field->name).c_str(),
@@ -1179,6 +1184,11 @@
fprintf(out, " str%d = NULL;\n", argIndex);
fprintf(out, " }\n");
+ fprintf(out,
+ " android::util::BytesField bytesField%d(str%d, "
+ "str%d_length);",
+ argIndex, argIndex, argIndex);
+
} else if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
hadStringOrChain = true;
for (auto chainField : attributionDecl.fields) {
@@ -1236,7 +1246,8 @@
// stats_write call
argIndex = 1;
- fprintf(out, "\n int ret = android::util::%s(code", cpp_method_name.c_str());
+ fprintf(out, "\n int ret = android::util::%s(code",
+ cpp_method_name.c_str());
for (vector<java_type_t>::const_iterator arg = signature->begin();
arg != signature->end(); arg++) {
if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
@@ -1251,16 +1262,12 @@
}
} else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) {
fprintf(out, ", int32_t_map, int64_t_map, string_map, float_map");
+ } else if (*arg == JAVA_TYPE_BYTE_ARRAY) {
+ fprintf(out, ", bytesField%d", argIndex);
} else {
- const char* argName = (*arg == JAVA_TYPE_STRING ||
- *arg == JAVA_TYPE_BYTE_ARRAY)
- ? "str"
- : "arg";
+ const char* argName =
+ (*arg == JAVA_TYPE_STRING) ? "str" : "arg";
fprintf(out, ", (%s)%s%d", cpp_type_name(*arg), argName, argIndex);
-
- if (*arg == JAVA_TYPE_BYTE_ARRAY) {
- fprintf(out, ", %s%d_length", argName, argIndex);
- }
}
argIndex++;
}
diff --git a/tools/streaming_proto/Errors.h b/tools/streaming_proto/Errors.h
index f14bbfd..bddd981 100644
--- a/tools/streaming_proto/Errors.h
+++ b/tools/streaming_proto/Errors.h
@@ -11,7 +11,7 @@
struct Error
{
Error();
- explicit Error(const Error& that);
+ Error(const Error& that);
Error(const string& filename, int lineno, const char* message);
string filename;
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 1700006..07f7cb3 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -16,7 +16,6 @@
package android.net.wifi;
-
import android.content.pm.ParceledListSlice;
import android.net.wifi.hotspot2.OsuProvider;
@@ -61,11 +60,11 @@
ParceledListSlice getConfiguredNetworks(String packageName);
- ParceledListSlice getPrivilegedConfiguredNetworks();
+ ParceledListSlice getPrivilegedConfiguredNetworks(String packageName);
- List<WifiConfiguration> getAllMatchingWifiConfigs(in List<ScanResult> scanResult);
+ Map getAllMatchingFqdnsForScanResults(in List<ScanResult> scanResult);
- List<OsuProvider> getMatchingOsuProviders(in List<ScanResult> scanResult);
+ Map getMatchingOsuProviders(in List<ScanResult> scanResult);
Map getMatchingPasspointConfigsForOsuProviders(in List<OsuProvider> osuProviders);
@@ -77,6 +76,8 @@
List<PasspointConfiguration> getPasspointConfigurations();
+ List<WifiConfiguration> getWifiConfigsForPasspointProfiles(in List<String> fqdnList);
+
void queryPasspointIcon(long bssid, String fileName);
int matchProviderWithCurrentNetwork(String fqdn);
diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java
index 7cdd16a..af5ad51 100644
--- a/wifi/java/android/net/wifi/WifiInfo.java
+++ b/wifi/java/android/net/wifi/WifiInfo.java
@@ -114,6 +114,11 @@
private boolean mTrusted;
/**
+ * OSU (Online Sign Up) AP for Passpoint R2.
+ */
+ private boolean mOsuAp;
+
+ /**
* Running total count of lost (not ACKed) transmitted unicast data packets.
* @hide
*/
@@ -190,6 +195,7 @@
setFrequency(-1);
setMeteredHint(false);
setEphemeral(false);
+ setOsuAp(false);
txBad = 0;
txSuccess = 0;
rxSuccess = 0;
@@ -219,6 +225,7 @@
mMeteredHint = source.mMeteredHint;
mEphemeral = source.mEphemeral;
mTrusted = source.mTrusted;
+ mOsuAp = source.mOsuAp;
txBad = source.txBad;
txRetries = source.txRetries;
txSuccess = source.txSuccess;
@@ -411,6 +418,15 @@
return mTrusted;
}
+ /** {@hide} */
+ public void setOsuAp(boolean osuAp) {
+ mOsuAp = osuAp;
+ }
+
+ /** {@hide} */
+ public boolean isOsuAp() {
+ return mOsuAp;
+ }
/** @hide */
@UnsupportedAppUsage
@@ -565,6 +581,7 @@
dest.writeLong(rxSuccess);
dest.writeDouble(rxSuccessRate);
mSupplicantState.writeToParcel(dest, flags);
+ dest.writeInt(mOsuAp ? 1 : 0);
}
/** Implement the Parcelable interface {@hide} */
@@ -600,6 +617,7 @@
info.rxSuccess = in.readLong();
info.rxSuccessRate = in.readDouble();
info.mSupplicantState = SupplicantState.CREATOR.createFromParcel(in);
+ info.mOsuAp = in.readInt() != 0;
return info;
}
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index e67e8ea..517bf3b 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -16,6 +16,10 @@
package android.net.wifi;
+import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.Manifest.permission.ACCESS_WIFI_STATE;
+import static android.Manifest.permission.READ_WIFI_CREDENTIAL;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -44,6 +48,7 @@
import android.os.RemoteException;
import android.os.WorkSource;
import android.util.Log;
+import android.util.Pair;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
@@ -184,6 +189,7 @@
*/
public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_REMOVE_INVALID = 5;
+ /** @hide */
@IntDef(prefix = { "STATUS_NETWORK_SUGGESTIONS_" }, value = {
STATUS_NETWORK_SUGGESTIONS_SUCCESS,
STATUS_NETWORK_SUGGESTIONS_ERROR_INTERNAL,
@@ -229,6 +235,12 @@
@SystemApi
public static final int WIFI_CREDENTIAL_FORGOT = 1;
+ /** @hide */
+ public static final int PASSPOINT_HOME_NETWORK = 0;
+
+ /** @hide */
+ public static final int PASSPOINT_ROAMING_NETWORK = 1;
+
/**
* Broadcast intent action indicating that a Passpoint provider icon has been received.
*
@@ -1135,6 +1147,10 @@
/**
* Return a list of all the networks configured for the current foreground
* user.
+ *
+ * Requires the same permissions as {@link #getScanResults}.
+ * If such access is not allowed, this API will always return an empty list.
+ *
* Not all fields of WifiConfiguration are returned. Only the following
* fields are filled in:
* <ul>
@@ -1161,6 +1177,7 @@
* {@link android.os.Build.VERSION_CODES#Q} or above, this API will always return an empty list.
*/
@Deprecated
+ @RequiresPermission(allOf = {ACCESS_COARSE_LOCATION, ACCESS_WIFI_STATE})
public List<WifiConfiguration> getConfiguredNetworks() {
try {
ParceledListSlice<WifiConfiguration> parceledList =
@@ -1176,11 +1193,11 @@
/** @hide */
@SystemApi
- @RequiresPermission(android.Manifest.permission.READ_WIFI_CREDENTIAL)
+ @RequiresPermission(allOf = {ACCESS_COARSE_LOCATION, ACCESS_WIFI_STATE, READ_WIFI_CREDENTIAL})
public List<WifiConfiguration> getPrivilegedConfiguredNetworks() {
try {
ParceledListSlice<WifiConfiguration> parceledList =
- mService.getPrivilegedConfiguredNetworks();
+ mService.getPrivilegedConfiguredNetworks(mContext.getOpPackageName());
if (parceledList == null) {
return Collections.emptyList();
}
@@ -1191,25 +1208,43 @@
}
/**
- * Returns all matching WifiConfigurations for a given list of ScanResult.
+ * Returns a list of all matching WifiConfigurations for a given list of ScanResult.
*
* An empty list will be returned when no configurations are installed or if no configurations
* match the ScanResult.
-
+ *
* @param scanResults a list of scanResult that represents the BSSID
- * @return A list of {@link WifiConfiguration} that can have duplicate entries.
+ * @return List that consists of {@link WifiConfiguration} and corresponding scanResults per
+ * network type({@link #PASSPOINT_HOME_NETWORK} and {@link #PASSPOINT_ROAMING_NETWORK}).
* @throws UnsupportedOperationException if Passpoint is not enabled on the device.
* @hide
*/
- @SystemApi
@RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
- public List<WifiConfiguration> getAllMatchingWifiConfigs(
+ public List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> getAllMatchingWifiConfigs(
@NonNull List<ScanResult> scanResults) {
+ List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> configs = new ArrayList<>();
try {
- return mService.getAllMatchingWifiConfigs(scanResults);
+ Map<String, Map<Integer, List<ScanResult>>> results =
+ mService.getAllMatchingFqdnsForScanResults(
+ scanResults);
+ if (results.isEmpty()) {
+ return configs;
+ }
+ List<WifiConfiguration> wifiConfigurations =
+ mService.getWifiConfigsForPasspointProfiles(
+ new ArrayList<>(results.keySet()));
+ for (WifiConfiguration configuration : wifiConfigurations) {
+ Map<Integer, List<ScanResult>> scanResultsPerNetworkType = results.get(
+ configuration.FQDN);
+ if (scanResultsPerNetworkType != null) {
+ configs.add(Pair.create(configuration, scanResultsPerNetworkType));
+ }
+ }
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
+
+ return configs;
}
/**
@@ -1219,12 +1254,13 @@
* An empty list will be returned if no match is found.
*
* @param scanResults a list of ScanResult
- * @return A list of {@link OsuProvider} that does not contain duplicate entries.
+ * @return Map that consists {@link OsuProvider} and a list of matching {@link ScanResult}
* @throws UnsupportedOperationException if Passpoint is not enabled on the device.
* @hide
*/
@RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
- public List<OsuProvider> getMatchingOsuProviders(List<ScanResult> scanResults) {
+ public Map<OsuProvider, List<ScanResult>> getMatchingOsuProviders(
+ List<ScanResult> scanResults) {
try {
return mService.getMatchingOsuProviders(scanResults);
} catch (RemoteException e) {
@@ -1617,7 +1653,7 @@
* suggestion back using this API.</li>
*
* @param networkSuggestions List of network suggestions provided by the app.
- * @return Status code corresponding to the values in {@link NetworkSuggestionsStatusCode}.
+ * @return Status code for the operation. One of the STATUS_NETWORK_SUGGESTIONS_ values.
* {@link WifiNetworkSuggestion#equals(Object)} any previously provided suggestions by the app.
* @throws {@link SecurityException} if the caller is missing required permissions.
*/
@@ -1638,8 +1674,7 @@
*
* @param networkSuggestions List of network suggestions to be removed. Pass an empty list
* to remove all the previous suggestions provided by the app.
- * @return Status code corresponding to the values in
- * {@link NetworkSuggestionsStatusCode}.
+ * @return Status code for the operation. One of the STATUS_NETWORK_SUGGESTIONS_ values.
* Any matching suggestions are removed from the device and will not be considered for any
* further connection attempts.
*/
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java b/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java
index 01feb1e..72e57a1 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java
@@ -17,15 +17,15 @@
package android.net.wifi.p2p;
import android.annotation.UnsupportedAppUsage;
-import android.os.Parcelable;
import android.os.Parcel;
+import android.os.Parcelable;
import java.util.ArrayList;
-import java.util.List;
import java.util.Collection;
import java.util.Collections;
-import java.util.regex.Pattern;
+import java.util.List;
import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* A class representing a Wi-Fi P2p group. A p2p group consists of a single group
@@ -67,6 +67,9 @@
/** The network id in the wpa_supplicant */
private int mNetId;
+ /** The frequency used by this group */
+ private int mFrequency;
+
/** P2P group started string pattern */
private static final Pattern groupStartedPattern = Pattern.compile(
"ssid=\"(.+)\" " +
@@ -116,8 +119,9 @@
}
mNetworkName = match.group(1);
- //freq and psk are unused right now
- //int freq = Integer.parseInt(match.group(2));
+ // It throws NumberFormatException if the string cannot be parsed as an integer.
+ mFrequency = Integer.parseInt(match.group(2));
+ // psk is unused right now
//String psk = match.group(3);
mPassphrase = match.group(4);
mOwner = new WifiP2pDevice(match.group(5));
@@ -269,6 +273,16 @@
this.mNetId = netId;
}
+ /** Get the operating frequency of the p2p group */
+ public int getFrequency() {
+ return mFrequency;
+ }
+
+ /** @hide */
+ public void setFrequency(int freq) {
+ this.mFrequency = freq;
+ }
+
public String toString() {
StringBuffer sbuf = new StringBuffer();
sbuf.append("network: ").append(mNetworkName);
@@ -279,6 +293,7 @@
}
sbuf.append("\n interface: ").append(mInterface);
sbuf.append("\n networkId: ").append(mNetId);
+ sbuf.append("\n frequency: ").append(mFrequency);
return sbuf.toString();
}
@@ -297,6 +312,7 @@
mPassphrase = source.getPassphrase();
mInterface = source.getInterface();
mNetId = source.getNetworkId();
+ mFrequency = source.getFrequency();
}
}
@@ -312,6 +328,7 @@
dest.writeString(mPassphrase);
dest.writeString(mInterface);
dest.writeInt(mNetId);
+ dest.writeInt(mFrequency);
}
/** Implement the Parcelable interface */
@@ -329,6 +346,7 @@
group.setPassphrase(in.readString());
group.setInterface(in.readString());
group.setNetworkId(in.readInt());
+ group.setFrequency(in.readInt());
return group;
}
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
index 068b959..1bed914 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
@@ -1281,7 +1281,16 @@
c.mAsyncChannel.sendMessage(REMOVE_GROUP, 0, c.putListener(listener));
}
- /** @hide */
+ /**
+ * Force p2p to enter or exit listen state
+ *
+ * @param c is the channel created at {@link #initialize(Context, Looper, ChannelListener)}
+ * @param enable enables or disables listening
+ * @param listener for callbacks on success or failure. Can be null.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
public void listen(Channel c, boolean enable, ActionListener listener) {
checkChannel(c);
c.mAsyncChannel.sendMessage(enable ? START_LISTEN : STOP_LISTEN,
diff --git a/wifi/java/com/android/server/wifi/AbstractWifiService.java b/wifi/java/com/android/server/wifi/BaseWifiService.java
similarity index 89%
rename from wifi/java/com/android/server/wifi/AbstractWifiService.java
rename to wifi/java/com/android/server/wifi/BaseWifiService.java
index e94b9e6..2c96c05 100644
--- a/wifi/java/com/android/server/wifi/AbstractWifiService.java
+++ b/wifi/java/com/android/server/wifi/BaseWifiService.java
@@ -21,6 +21,7 @@
import android.content.pm.ParceledListSlice;
import android.net.DhcpInfo;
import android.net.Network;
+import android.net.wifi.IDppCallback;
import android.net.wifi.INetworkRequestMatchCallback;
import android.net.wifi.ISoftApCallback;
import android.net.wifi.ITrafficStateCallback;
@@ -35,6 +36,7 @@
import android.net.wifi.hotspot2.PasspointConfiguration;
import android.os.IBinder;
import android.os.Messenger;
+import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.WorkSource;
@@ -42,7 +44,7 @@
import java.util.Map;
/**
- * Abstract class implementing IWifiManager with stub methods throwing runtime exceptions.
+ * Empty concrete class implementing IWifiManager with stub methods throwing runtime exceptions.
*
* This class is meant to be extended by real implementations of IWifiManager in order to facilitate
* cross-repo changes to WiFi internal APIs, including the introduction of new APIs, the removal of
@@ -53,10 +55,13 @@
* then given a short grace period to update themselves before the @Deprecated stub is removed for
* good. If the API scheduled for removal has a replacement or an overload (signature change),
* these should be introduced before the stub is removed to allow children to migrate.
+ *
+ * When a new API is added to IWifiManager.aidl, a stub should be added in BaseWifiService as
+ * well otherwise compilation will fail.
*/
-public abstract class AbstractWifiService extends IWifiManager.Stub {
+public class BaseWifiService extends IWifiManager.Stub {
- private static final String TAG = AbstractWifiService.class.getSimpleName();
+ private static final String TAG = BaseWifiService.class.getSimpleName();
@Override
public int getSupportedFeatures() {
@@ -79,51 +84,19 @@
}
@Override
- public ParceledListSlice getPrivilegedConfiguredNetworks() {
- throw new UnsupportedOperationException();
- }
-
- /**
- * Returns a WifiConfiguration matching this ScanResult
- * @param scanResult a single ScanResult Object
- * @return
- * @deprecated use {@link #getAllMatchingWifiConfigs(List)} instead.
- */
- @Deprecated
- public WifiConfiguration getMatchingWifiConfig(ScanResult scanResult) {
- throw new UnsupportedOperationException();
- }
-
- /**
- * Returns all matching WifiConfigurations for this ScanResult.
- * @param scanResult a single ScanResult Object
- * @return
- * @deprecated use {@link #getAllMatchingWifiConfigs(List)} instead.
- */
- @Deprecated
- public List<WifiConfiguration> getAllMatchingWifiConfigs(ScanResult scanResult) {
+ public ParceledListSlice getPrivilegedConfiguredNetworks(String packageName) {
throw new UnsupportedOperationException();
}
@Override
- public List<WifiConfiguration> getAllMatchingWifiConfigs(List<ScanResult> scanResults) {
- throw new UnsupportedOperationException();
- }
-
- /**
- * Returns a list of Hotspot 2.0 OSU (Online Sign-Up) providers associated with the given AP.
- *
- * @param scanResult a single ScanResult Object
- * @return
- * @deprecated use {@link #getMatchingOsuProviders(List)} instead.
- */
- @Deprecated
- public List<OsuProvider> getMatchingOsuProviders(ScanResult scanResult) {
+ public Map<String, Map<Integer, List<ScanResult>>> getAllMatchingFqdnsForScanResults(
+ List<ScanResult> scanResults) {
throw new UnsupportedOperationException();
}
@Override
- public List<OsuProvider> getMatchingOsuProviders(List<ScanResult> scanResults) {
+ public Map<OsuProvider, List<ScanResult>> getMatchingOsuProviders(
+ List<ScanResult> scanResults) {
throw new UnsupportedOperationException();
}
@@ -155,6 +128,11 @@
}
@Override
+ public List<WifiConfiguration> getWifiConfigsForPasspointProfiles(List<String> fqdnList) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public void queryPasspointIcon(long bssid, String fileName) {
throw new UnsupportedOperationException();
}
@@ -464,4 +442,26 @@
public String[] getFactoryMacAddresses() {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public void setDeviceMobilityState(int state) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void startDppAsConfiguratorInitiator(IBinder binder, String enrolleeUri,
+ int selectedNetworkId, int netRole, IDppCallback callback) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void startDppAsEnrolleeInitiator(IBinder binder, String configuratorUri,
+ IDppCallback callback) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void stopDppSession() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/wifi/tests/src/android/net/wifi/WifiInfoTest.java b/wifi/tests/src/android/net/wifi/WifiInfoTest.java
index fb0af5f..677bf37 100644
--- a/wifi/tests/src/android/net/wifi/WifiInfoTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiInfoTest.java
@@ -47,6 +47,7 @@
writeWifiInfo.txBad = TEST_TX_BAD;
writeWifiInfo.rxSuccess = TEST_RX_SUCCESS;
writeWifiInfo.setTrusted(true);
+ writeWifiInfo.setOsuAp(true);
Parcel parcel = Parcel.obtain();
writeWifiInfo.writeToParcel(parcel, 0);
@@ -60,5 +61,6 @@
assertEquals(TEST_TX_BAD, readWifiInfo.txBad);
assertEquals(TEST_RX_SUCCESS, readWifiInfo.rxSuccess);
assertTrue(readWifiInfo.isTrusted());
+ assertTrue(readWifiInfo.isOsuAp());
}
}
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index c43948b..4fbef5a 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -77,7 +77,9 @@
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* Unit tests for {@link android.net.wifi.WifiManager}.
@@ -1274,14 +1276,20 @@
}
/**
- * Check the call to getAllMatchingWifiConfigs calls getAllMatchingWifiConfigs of WifiService
- * with the provided a list of ScanResult.
+ * Check the call to getAllMatchingWifiConfigs calls getAllMatchingFqdnsForScanResults and
+ * getWifiConfigsForPasspointProfiles of WifiService in order.
*/
@Test
public void testGetAllMatchingWifiConfigs() throws Exception {
+ Map<String, List<ScanResult>> fqdns = new HashMap<>();
+ fqdns.put("www.test.com", new ArrayList<>());
+ when(mWifiService.getAllMatchingFqdnsForScanResults(any(List.class))).thenReturn(fqdns);
+ InOrder inOrder = inOrder(mWifiService);
+
mWifiManager.getAllMatchingWifiConfigs(new ArrayList<>());
- verify(mWifiService).getAllMatchingWifiConfigs(any(List.class));
+ inOrder.verify(mWifiService).getAllMatchingFqdnsForScanResults(any(List.class));
+ inOrder.verify(mWifiService).getWifiConfigsForPasspointProfiles(any(List.class));
}
/**