Merge "Add EXTRA_PERMISSION_GROUP_NAME"
diff --git a/Android.bp b/Android.bp
index eb9cbbb..980aa04 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",
@@ -593,7 +600,7 @@
"telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl",
"telephony/java/com/android/internal/telephony/ISms.aidl",
"telephony/java/com/android/internal/telephony/ISub.aidl",
- "telephony/java/com/android/internal/telephony/IAns.aidl",
+ "telephony/java/com/android/internal/telephony/IOns.aidl",
"telephony/java/com/android/internal/telephony/ITelephony.aidl",
"telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl",
"telephony/java/com/android/internal/telephony/IWapPushManager.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 60c6d89..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..0bc9ee4 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 13311d4..8ee83da 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);
}
@@ -7378,7 +7379,9 @@
method public boolean isRoleHeld(java.lang.String);
field public static final java.lang.String ROLE_BROWSER = "android.app.role.BROWSER";
field public static final java.lang.String ROLE_DIALER = "android.app.role.DIALER";
+ field public static final java.lang.String ROLE_EMERGENCY = "android.app.role.EMERGENCY";
field public static final java.lang.String ROLE_GALLERY = "android.app.role.GALLERY";
+ field public static final java.lang.String ROLE_HOME = "android.app.role.HOME";
field public static final java.lang.String ROLE_MUSIC = "android.app.role.MUSIC";
field public static final java.lang.String ROLE_SMS = "android.app.role.SMS";
}
@@ -8531,42 +8534,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 +8668,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 +10204,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 +10329,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";
@@ -25359,12 +25366,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();
@@ -25372,10 +25382,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();
@@ -25389,6 +25401,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);
@@ -26065,6 +26078,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();
@@ -27110,6 +27144,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);
@@ -27145,12 +27194,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
}
@@ -27341,6 +27393,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 {
@@ -29668,9 +29750,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();
@@ -33695,6 +33774,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();
@@ -34359,6 +34439,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();
@@ -34400,7 +34482,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[]);
@@ -34446,7 +34528,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[]);
@@ -34454,8 +34536,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;
}
@@ -42647,6 +42731,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();
@@ -42683,6 +42768,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
@@ -42758,10 +42846,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";
}
@@ -44603,6 +44691,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";
@@ -44832,16 +44921,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
@@ -44886,6 +44980,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
@@ -49098,6 +49193,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();
@@ -49249,6 +49345,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
@@ -52299,6 +52398,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);
@@ -53111,6 +53212,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
@@ -55425,7 +55527,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);
@@ -55438,10 +55540,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 269fac0..214b715 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";
@@ -1118,6 +1302,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;
@@ -1513,6 +1698,8 @@
public final class BrightnessConfiguration implements android.os.Parcelable {
method public int describeContents();
+ method public android.hardware.display.BrightnessCorrection getCorrectionByCategory(int);
+ method public android.hardware.display.BrightnessCorrection getCorrectionByPackageName(java.lang.String);
method public android.util.Pair<float[], float[]> getCurve();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.hardware.display.BrightnessConfiguration> CREATOR;
@@ -1520,10 +1707,26 @@
public static class BrightnessConfiguration.Builder {
ctor public BrightnessConfiguration.Builder(float[], float[]);
+ method public android.hardware.display.BrightnessConfiguration.Builder addCorrectionByCategory(int, android.hardware.display.BrightnessCorrection);
+ method public android.hardware.display.BrightnessConfiguration.Builder addCorrectionByPackageName(java.lang.String, android.hardware.display.BrightnessCorrection);
method public android.hardware.display.BrightnessConfiguration build();
+ method public int getMaxCorrectionsByCategory();
+ method public int getMaxCorrectionsByPackageName();
method public android.hardware.display.BrightnessConfiguration.Builder setDescription(java.lang.String);
}
+ public final class BrightnessCorrection implements android.os.Parcelable {
+ method public float apply(float);
+ method public static android.hardware.display.BrightnessCorrection createScaleAndTranslateLog(float, float);
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ 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();
@@ -1532,7 +1735,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);
}
}
@@ -2930,7 +3133,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);
}
@@ -3190,6 +3393,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);
@@ -3896,7 +4115,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();
@@ -4493,6 +4711,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
@@ -4597,6 +4816,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);
@@ -4604,6 +4834,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";
}
@@ -5110,6 +5341,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 {
@@ -5129,6 +5378,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";
}
@@ -5218,6 +5469,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);
@@ -5235,6 +5487,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 {
@@ -5399,7 +5664,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);
@@ -5921,6 +6186,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
@@ -6088,11 +6434,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
@@ -6117,6 +6465,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
@@ -6252,6 +6610,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 {
@@ -6261,6 +6620,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;
@@ -6319,6 +6683,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();
@@ -6719,7 +7084,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();
@@ -6732,7 +7099,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);
@@ -6861,6 +7230,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..f347867 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.
@@ -212,7 +216,7 @@
NumFingerprints num_fingerprints = 10031;
DiskIo disk_io = 10032;
PowerProfile power_profile = 10033;
- ProcStats proc_stats_pkg_proc = 10034;
+ ProcStatsPkgProc proc_stats_pkg_proc = 10034;
ProcessCpuTime process_cpu_time = 10035;
NativeProcessMemoryState native_process_memory_state = 10036;
CpuTimePerThreadFreq cpu_time_per_thread_freq = 10037;
@@ -3268,6 +3272,13 @@
optional ProcessStatsSectionProto proc_stats_section = 1;
}
+/**
+ * Pulled from ProcessStatsService.java
+ */
+message ProcStatsPkgProc {
+ optional ProcessStatsSectionProto proc_stats_section = 1;
+}
+
message PowerProfileProto {
optional double cpu_suspend = 1;
@@ -3656,6 +3667,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 041fbfa..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,92 +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;->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;
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..27581fc 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;
@@ -68,6 +69,8 @@
/**
* The name of the dialer role.
+ *
+ * @see Intent#ACTION_DIAL
*/
public static final String ROLE_DIALER = "android.app.role.DIALER";
@@ -100,6 +103,75 @@
public static final String ROLE_MUSIC = "android.app.role.MUSIC";
/**
+ * The name of the home role.
+ *
+ * @see Intent#CATEGORY_HOME
+ */
+ public static final String ROLE_HOME = "android.app.role.HOME";
+
+ /**
+ * The name of the emergency role
+ *
+ * @see android.telephony.TelephonyManager#ACTION_EMERGENCY_ASSISTANCE
+ */
+ public static final String ROLE_EMERGENCY = "android.app.role.EMERGENCY";
+
+ /**
+ * The name of the car mode dialer app role.
+ * <p>
+ * Similar to the {@link #ROLE_DIALER dialer} role, this role determines which app is
+ * responsible for showing the user interface for ongoing calls on the device. It is only used
+ * when the device is in car mode.
+ *
+ * @see #ROLE_DIALER
+ * @see android.app.UiModeManager#ACTION_ENTER_CAR_MODE
+ * @see android.telecom.InCallService
+ *
+ * TODO: STOPSHIP: Make name of required roles public API
+ * @hide
+ */
+ public static final String ROLE_CAR_MODE_DIALER_APP = "android.app.role.CAR_MODE_DIALER_APP";
+
+ /**
+ * The name of the proxy calling role.
+ * <p>
+ * A proxy calling app provides a means to re-write the phone number for an outgoing call to
+ * place the call through a proxy calling service.
+ *
+ * @see android.telecom.CallRedirectionService
+ *
+ * TODO: STOPSHIP: Make name of required roles public API
+ * @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.
+ *
+ * @see android.telecom.CallScreeningService
+ *
+ * TODO: STOPSHIP: Make name of required roles public API
+ * @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.
+ * <p>
+ * Multiple apps may hold this role at the same time.
+ *
+ * @see android.telecom.InCallService
+ *
+ * TODO: STOPSHIP: Make name of required roles public API
+ * @hide
+ */
+ public static final String ROLE_CALL_COMPANION_APP = "android.app.role.CALL_COMPANION_APP";
+
+ /**
* The action used to request user approval of a role for an application.
*
* @hide
@@ -542,7 +614,6 @@
}
}
-
/**
* Returns the list of all roles that the given package is currently holding
*
@@ -563,6 +634,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 3f3e216..e37126b 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/BrightnessConfiguration.java b/core/java/android/hardware/display/BrightnessConfiguration.java
index 7e52ca3..be054297 100644
--- a/core/java/android/hardware/display/BrightnessConfiguration.java
+++ b/core/java/android/hardware/display/BrightnessConfiguration.java
@@ -19,26 +19,54 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
+import android.content.pm.ApplicationInfo;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Pair;
import com.android.internal.util.Preconditions;
+import com.android.internal.util.XmlUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+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.Objects;
/** @hide */
@SystemApi
@TestApi
public final class BrightnessConfiguration implements Parcelable {
+ private static final String TAG_BRIGHTNESS_CURVE = "brightness-curve";
+ private static final String TAG_BRIGHTNESS_POINT = "brightness-point";
+ private static final String TAG_BRIGHTNESS_CORRECTIONS = "brightness-corrections";
+ private static final String TAG_BRIGHTNESS_CORRECTION = "brightness-correction";
+ private static final String ATTR_LUX = "lux";
+ private static final String ATTR_NITS = "nits";
+ private static final String ATTR_DESCRIPTION = "description";
+ private static final String ATTR_PACKAGE_NAME = "package-name";
+ private static final String ATTR_CATEGORY = "category";
+
private final float[] mLux;
private final float[] mNits;
+ private final Map<String, BrightnessCorrection> mCorrectionsByPackageName;
+ private final Map<Integer, BrightnessCorrection> mCorrectionsByCategory;
private final String mDescription;
- private BrightnessConfiguration(float[] lux, float[] nits, String description) {
+ private BrightnessConfiguration(float[] lux, float[] nits,
+ Map<String, BrightnessCorrection> correctionsByPackageName,
+ Map<Integer, BrightnessCorrection> correctionsByCategory, String description) {
mLux = lux;
mNits = nits;
+ mCorrectionsByPackageName = correctionsByPackageName;
+ mCorrectionsByCategory = correctionsByCategory;
mDescription = description;
}
@@ -56,6 +84,38 @@
}
/**
+ * Returns a brightness correction by app, or null.
+ *
+ * @param packageName
+ * The app's package name.
+ *
+ * @return The matching brightness correction, or null.
+ *
+ * @hide
+ */
+ @SystemApi
+ @Nullable
+ public BrightnessCorrection getCorrectionByPackageName(String packageName) {
+ return mCorrectionsByPackageName.get(packageName);
+ }
+
+ /**
+ * Returns a brightness correction by app category, or null.
+ *
+ * @param category
+ * The app category.
+ *
+ * @return The matching brightness correction, or null.
+ *
+ * @hide
+ */
+ @SystemApi
+ @Nullable
+ public BrightnessCorrection getCorrectionByCategory(int category) {
+ return mCorrectionsByCategory.get(category);
+ }
+
+ /**
* Returns description string.
* @hide
*/
@@ -67,6 +127,20 @@
public void writeToParcel(Parcel dest, int flags) {
dest.writeFloatArray(mLux);
dest.writeFloatArray(mNits);
+ dest.writeInt(mCorrectionsByPackageName.size());
+ for (Entry<String, BrightnessCorrection> entry : mCorrectionsByPackageName.entrySet()) {
+ final String packageName = entry.getKey();
+ final BrightnessCorrection correction = entry.getValue();
+ dest.writeString(packageName);
+ correction.writeToParcel(dest, flags);
+ }
+ dest.writeInt(mCorrectionsByCategory.size());
+ for (Entry<Integer, BrightnessCorrection> entry : mCorrectionsByCategory.entrySet()) {
+ final int category = entry.getKey();
+ final BrightnessCorrection correction = entry.getValue();
+ dest.writeInt(category);
+ correction.writeToParcel(dest, flags);
+ }
dest.writeString(mDescription);
}
@@ -85,7 +159,14 @@
}
sb.append("(").append(mLux[i]).append(", ").append(mNits[i]).append(")");
}
- sb.append("], '");
+ sb.append("], {");
+ for (Entry<String, BrightnessCorrection> entry : mCorrectionsByPackageName.entrySet()) {
+ sb.append("'" + entry.getKey() + "': " + entry.getValue() + ", ");
+ }
+ for (Entry<Integer, BrightnessCorrection> entry : mCorrectionsByCategory.entrySet()) {
+ sb.append(entry.getKey() + ": " + entry.getValue() + ", ");
+ }
+ sb.append("}, '");
if (mDescription != null) {
sb.append(mDescription);
}
@@ -98,6 +179,8 @@
int result = 1;
result = result * 31 + Arrays.hashCode(mLux);
result = result * 31 + Arrays.hashCode(mNits);
+ result = result * 31 + mCorrectionsByPackageName.hashCode();
+ result = result * 31 + mCorrectionsByCategory.hashCode();
if (mDescription != null) {
result = result * 31 + mDescription.hashCode();
}
@@ -114,6 +197,8 @@
}
final BrightnessConfiguration other = (BrightnessConfiguration) o;
return Arrays.equals(mLux, other.mLux) && Arrays.equals(mNits, other.mNits)
+ && mCorrectionsByPackageName.equals(other.mCorrectionsByPackageName)
+ && mCorrectionsByCategory.equals(other.mCorrectionsByCategory)
&& Objects.equals(mDescription, other.mDescription);
}
@@ -123,7 +208,25 @@
float[] lux = in.createFloatArray();
float[] nits = in.createFloatArray();
Builder builder = new Builder(lux, nits);
- builder.setDescription(in.readString());
+
+ int n = in.readInt();
+ for (int i = 0; i < n; i++) {
+ final String packageName = in.readString();
+ final BrightnessCorrection correction =
+ BrightnessCorrection.CREATOR.createFromParcel(in);
+ builder.addCorrectionByPackageName(packageName, correction);
+ }
+
+ n = in.readInt();
+ for (int i = 0; i < n; i++) {
+ final int category = in.readInt();
+ final BrightnessCorrection correction =
+ BrightnessCorrection.CREATOR.createFromParcel(in);
+ builder.addCorrectionByCategory(category, correction);
+ }
+
+ final String description = in.readString();
+ builder.setDescription(description);
return builder.build();
}
@@ -133,11 +236,146 @@
};
/**
+ * Writes the configuration to an XML serializer.
+ *
+ * @param serializer
+ * The XML serializer.
+ *
+ * @hide
+ */
+ public void saveToXml(XmlSerializer serializer) throws IOException {
+ serializer.startTag(null, TAG_BRIGHTNESS_CURVE);
+ if (mDescription != null) {
+ serializer.attribute(null, ATTR_DESCRIPTION, mDescription);
+ }
+ for (int i = 0; i < mLux.length; i++) {
+ serializer.startTag(null, TAG_BRIGHTNESS_POINT);
+ serializer.attribute(null, ATTR_LUX, Float.toString(mLux[i]));
+ serializer.attribute(null, ATTR_NITS, Float.toString(mNits[i]));
+ serializer.endTag(null, TAG_BRIGHTNESS_POINT);
+ }
+ serializer.endTag(null, TAG_BRIGHTNESS_CURVE);
+ serializer.startTag(null, TAG_BRIGHTNESS_CORRECTIONS);
+ for (Map.Entry<String, BrightnessCorrection> entry :
+ mCorrectionsByPackageName.entrySet()) {
+ final String packageName = entry.getKey();
+ final BrightnessCorrection correction = entry.getValue();
+ serializer.startTag(null, TAG_BRIGHTNESS_CORRECTION);
+ serializer.attribute(null, ATTR_PACKAGE_NAME, packageName);
+ correction.saveToXml(serializer);
+ serializer.endTag(null, TAG_BRIGHTNESS_CORRECTION);
+ }
+ for (Map.Entry<Integer, BrightnessCorrection> entry : mCorrectionsByCategory.entrySet()) {
+ final int category = entry.getKey();
+ final BrightnessCorrection correction = entry.getValue();
+ serializer.startTag(null, TAG_BRIGHTNESS_CORRECTION);
+ serializer.attribute(null, ATTR_CATEGORY, Integer.toString(category));
+ correction.saveToXml(serializer);
+ serializer.endTag(null, TAG_BRIGHTNESS_CORRECTION);
+ }
+ serializer.endTag(null, TAG_BRIGHTNESS_CORRECTIONS);
+ }
+
+ /**
+ * Read a configuration from an XML parser.
+ *
+ * @param parser
+ * The XML parser.
+ *
+ * @throws IOException
+ * The parser failed to read the XML file.
+ * @throws XmlPullParserException
+ * The parser failed to parse the XML file.
+ *
+ * @hide
+ */
+ public static BrightnessConfiguration loadFromXml(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ String description = null;
+ List<Float> luxList = new ArrayList<>();
+ List<Float> nitsList = new ArrayList<>();
+ Map<String, BrightnessCorrection> correctionsByPackageName = new HashMap<>();
+ Map<Integer, BrightnessCorrection> correctionsByCategory = new HashMap<>();
+ final int configDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, configDepth)) {
+ if (TAG_BRIGHTNESS_CURVE.equals(parser.getName())) {
+ description = parser.getAttributeValue(null, ATTR_DESCRIPTION);
+ final int curveDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, curveDepth)) {
+ if (!TAG_BRIGHTNESS_POINT.equals(parser.getName())) {
+ continue;
+ }
+ final float lux = loadFloatFromXml(parser, ATTR_LUX);
+ final float nits = loadFloatFromXml(parser, ATTR_NITS);
+ luxList.add(lux);
+ nitsList.add(nits);
+ }
+ }
+ if (TAG_BRIGHTNESS_CORRECTIONS.equals(parser.getName())) {
+ final int correctionsDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, correctionsDepth)) {
+ if (!TAG_BRIGHTNESS_CORRECTION.equals(parser.getName())) {
+ continue;
+ }
+ final String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME);
+ final String categoryText = parser.getAttributeValue(null, ATTR_CATEGORY);
+ BrightnessCorrection correction = BrightnessCorrection.loadFromXml(parser);
+ if (packageName != null) {
+ correctionsByPackageName.put(packageName, correction);
+ } else if (categoryText != null) {
+ try {
+ final int category = Integer.parseInt(categoryText);
+ correctionsByCategory.put(category, correction);
+ } catch (NullPointerException | NumberFormatException e) {
+ continue;
+ }
+ }
+ }
+ }
+ }
+ final int n = luxList.size();
+ float[] lux = new float[n];
+ float[] nits = new float[n];
+ for (int i = 0; i < n; i++) {
+ lux[i] = luxList.get(i);
+ nits[i] = nitsList.get(i);
+ }
+ final BrightnessConfiguration.Builder builder = new BrightnessConfiguration.Builder(lux,
+ nits);
+ builder.setDescription(description);
+ for (Map.Entry<String, BrightnessCorrection> entry : correctionsByPackageName.entrySet()) {
+ final String packageName = entry.getKey();
+ final BrightnessCorrection correction = entry.getValue();
+ builder.addCorrectionByPackageName(packageName, correction);
+ }
+ for (Map.Entry<Integer, BrightnessCorrection> entry : correctionsByCategory.entrySet()) {
+ final int category = entry.getKey();
+ final BrightnessCorrection correction = entry.getValue();
+ builder.addCorrectionByCategory(category, correction);
+ }
+ return builder.build();
+ }
+
+ private static float loadFloatFromXml(XmlPullParser parser, String attribute) {
+ final String string = parser.getAttributeValue(null, attribute);
+ try {
+ return Float.parseFloat(string);
+ } catch (NullPointerException | NumberFormatException e) {
+ return Float.NaN;
+ }
+ }
+
+ /**
* A builder class for {@link BrightnessConfiguration}s.
*/
public static class Builder {
+ private static final int MAX_CORRECTIONS_BY_PACKAGE_NAME = 20;
+ private static final int MAX_CORRECTIONS_BY_CATEGORY = 20;
+
private float[] mCurveLux;
private float[] mCurveNits;
+ private Map<String, BrightnessCorrection> mCorrectionsByPackageName;
+ private Map<Integer, BrightnessCorrection> mCorrectionsByCategory;
private String mDescription;
/**
@@ -169,6 +407,88 @@
checkMonotonic(nits, false /*strictly increasing*/, "nits");
mCurveLux = lux;
mCurveNits = nits;
+ mCorrectionsByPackageName = new HashMap<>();
+ mCorrectionsByCategory = new HashMap<>();
+ }
+
+ /**
+ * Returns the maximum number of corrections by package name allowed.
+ *
+ * @return The maximum number of corrections by package name allowed.
+ *
+ * @hide
+ */
+ @SystemApi
+ public int getMaxCorrectionsByPackageName() {
+ return MAX_CORRECTIONS_BY_PACKAGE_NAME;
+ }
+
+ /**
+ * Returns the maximum number of corrections by category allowed.
+ *
+ * @return The maximum number of corrections by category allowed.
+ *
+ * @hide
+ */
+ @SystemApi
+ public int getMaxCorrectionsByCategory() {
+ return MAX_CORRECTIONS_BY_CATEGORY;
+ }
+
+ /**
+ * Add a brightness correction by app package name.
+ * This correction is applied whenever an app with this package name has the top activity
+ * of the focused stack.
+ *
+ * @param packageName
+ * The app's package name.
+ * @param correction
+ * The brightness correction.
+ *
+ * @return The builder.
+ *
+ * @throws IllegalArgumentExceptions
+ * Maximum number of corrections by package name exceeded (see
+ * {@link #getMaxCorrectionsByPackageName}).
+ *
+ * @hide
+ */
+ @SystemApi
+ public Builder addCorrectionByPackageName(String packageName,
+ BrightnessCorrection correction) {
+ if (mCorrectionsByPackageName.size() >= getMaxCorrectionsByPackageName()) {
+ throw new IllegalArgumentException("Too many corrections by package name");
+ }
+ mCorrectionsByPackageName.put(packageName, correction);
+ return this;
+ }
+
+ /**
+ * Add a brightness correction by app category.
+ * This correction is applied whenever an app with this category has the top activity of
+ * the focused stack, and only if a correction by package name has not been applied.
+ *
+ * @param category
+ * The {@link android.content.pm.ApplicationInfo#category app category}.
+ * @param correction
+ * The brightness correction.
+ *
+ * @return The builder.
+ *
+ * @throws IllegalArgumentException
+ * Maximum number of corrections by category exceeded (see
+ * {@link #getMaxCorrectionsByCategory}).
+ *
+ * @hide
+ */
+ @SystemApi
+ public Builder addCorrectionByCategory(@ApplicationInfo.Category int category,
+ BrightnessCorrection correction) {
+ if (mCorrectionsByCategory.size() >= getMaxCorrectionsByCategory()) {
+ throw new IllegalArgumentException("Too many corrections by category");
+ }
+ mCorrectionsByCategory.put(category, correction);
+ return this;
}
/**
@@ -191,7 +511,8 @@
if (mCurveLux == null || mCurveNits == null) {
throw new IllegalStateException("A curve must be set!");
}
- return new BrightnessConfiguration(mCurveLux, mCurveNits, mDescription);
+ return new BrightnessConfiguration(mCurveLux, mCurveNits, mCorrectionsByPackageName,
+ mCorrectionsByCategory, mDescription);
}
private static void checkMonotonic(float[] vals, boolean strictlyIncreasing, String name) {
diff --git a/core/java/android/hardware/display/BrightnessCorrection.aidl b/core/java/android/hardware/display/BrightnessCorrection.aidl
new file mode 100644
index 0000000..3abe29c
--- /dev/null
+++ b/core/java/android/hardware/display/BrightnessCorrection.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.hardware.display;
+
+parcelable BrightnessCorrection;
diff --git a/core/java/android/hardware/display/BrightnessCorrection.java b/core/java/android/hardware/display/BrightnessCorrection.java
new file mode 100644
index 0000000..c4e0e3b
--- /dev/null
+++ b/core/java/android/hardware/display/BrightnessCorrection.java
@@ -0,0 +1,245 @@
+/*
+ * 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.hardware.display;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.MathUtils;
+
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+
+/**
+ * BrightnessCorrection encapsulates a correction to the brightness, without comitting to the
+ * actual correction scheme.
+ * It is used by the BrightnessConfiguration, which maps context (e.g. the foreground app's package
+ * name and category) to corrections that need to be applied to the brightness within that context.
+ * Corrections are currently done by the app that has the top activity of the focused stack, either
+ * by its package name, or (if its package name is not mapped to any correction) by its category.
+ *
+ * @hide
+ */
+@SystemApi
+public final class BrightnessCorrection implements Parcelable {
+
+ private static final int SCALE_AND_TRANSLATE_LOG = 1;
+
+ private static final String TAG_SCALE_AND_TRANSLATE_LOG = "scale-and-translate-log";
+
+ private BrightnessCorrectionImplementation mImplementation;
+
+ // Parcelable classes must be final, and protected methods are not allowed in APIs, so we can't
+ // make this class abstract and use composition instead of inheritence.
+ private BrightnessCorrection(BrightnessCorrectionImplementation implementation) {
+ mImplementation = implementation;
+ }
+
+ /**
+ * Creates a BrightnessCorrection that given {@code brightness}, corrects it to be
+ * {@code exp(scale * log(brightness) + translate)}.
+ *
+ * @param scale
+ * How much to scale the log brightness.
+ * @param translate
+ * How much to translate the log brightness.
+ *
+ * @return A BrightnessCorrection that given {@code brightness}, corrects it to be
+ * {@code exp(scale * log(brightness) + translate)}.
+ *
+ * @throws IllegalArgumentException
+ * - scale or translate are NaN.
+ */
+ @NonNull
+ public static BrightnessCorrection createScaleAndTranslateLog(float scale, float translate) {
+ BrightnessCorrectionImplementation implementation =
+ new ScaleAndTranslateLog(scale, translate);
+ return new BrightnessCorrection(implementation);
+ }
+
+ /**
+ * Applies the brightness correction to a given brightness.
+ *
+ * @param brightness
+ * The brightness.
+ *
+ * @return The corrected brightness.
+ */
+ public float apply(float brightness) {
+ return mImplementation.apply(brightness);
+ }
+
+ /**
+ * Returns a string representation.
+ *
+ * @return A string representation.
+ */
+ public String toString() {
+ return mImplementation.toString();
+ }
+
+ public static final Creator<BrightnessCorrection> CREATOR =
+ new Creator<BrightnessCorrection>() {
+ public BrightnessCorrection createFromParcel(Parcel in) {
+ final int type = in.readInt();
+ switch (type) {
+ case SCALE_AND_TRANSLATE_LOG:
+ return ScaleAndTranslateLog.readFromParcel(in);
+ }
+ return null;
+ }
+
+ public BrightnessCorrection[] newArray(int size) {
+ return new BrightnessCorrection[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ mImplementation.writeToParcel(dest);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Writes the correction to an XML serializer.
+ *
+ * @param serializer
+ * The XML serializer.
+ *
+ * @hide
+ */
+ public void saveToXml(XmlSerializer serializer) throws IOException {
+ mImplementation.saveToXml(serializer);
+ }
+
+ /**
+ * Read a correction from an XML parser.
+ *
+ * @param parser
+ * The XML parser.
+ *
+ * @throws IOException
+ * The parser failed to read the XML file.
+ * @throws XmlPullParserException
+ * The parser failed to parse the XML file.
+ *
+ * @hide
+ */
+ public static BrightnessCorrection loadFromXml(XmlPullParser parser) throws IOException,
+ XmlPullParserException {
+ final int depth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, depth)) {
+ if (TAG_SCALE_AND_TRANSLATE_LOG.equals(parser.getName())) {
+ return ScaleAndTranslateLog.loadFromXml(parser);
+ }
+ }
+ return null;
+ }
+
+ private static float loadFloatFromXml(XmlPullParser parser, String attribute) {
+ final String string = parser.getAttributeValue(null, attribute);
+ try {
+ return Float.parseFloat(string);
+ } catch (NullPointerException | NumberFormatException e) {
+ return Float.NaN;
+ }
+ }
+
+ private interface BrightnessCorrectionImplementation {
+ float apply(float brightness);
+ String toString();
+ void writeToParcel(Parcel dest);
+ void saveToXml(XmlSerializer serializer) throws IOException;
+ // Package-private static methods:
+ // static BrightnessCorrection readFromParcel(Parcel in);
+ // static BrightnessCorrection loadFromXml(XmlPullParser parser) throws IOException,
+ // XmlPullParserException;
+ }
+
+ /**
+ * A BrightnessCorrection that given {@code brightness}, corrects it to be
+ * {@code exp(scale * log(brightness) + translate)}.
+ */
+ private static class ScaleAndTranslateLog implements BrightnessCorrectionImplementation {
+ private static final float MIN_SCALE = 0.5f;
+ private static final float MAX_SCALE = 2.0f;
+ private static final float MIN_TRANSLATE = -0.6f;
+ private static final float MAX_TRANSLATE = 0.7f;
+
+ private static final String ATTR_SCALE = "scale";
+ private static final String ATTR_TRANSLATE = "translate";
+
+ private final float mScale;
+ private final float mTranslate;
+
+ ScaleAndTranslateLog(float scale, float translate) {
+ if (Float.isNaN(scale) || Float.isNaN(translate)) {
+ throw new IllegalArgumentException("scale and translate must be numbers");
+ }
+ mScale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
+ mTranslate = MathUtils.constrain(translate, MIN_TRANSLATE, MAX_TRANSLATE);
+ }
+
+ @Override
+ public float apply(float brightness) {
+ return MathUtils.exp(mScale * MathUtils.log(brightness) + mTranslate);
+ }
+
+ @Override
+ public String toString() {
+ return "ScaleAndTranslateLog(" + mScale + ", " + mTranslate + ")";
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest) {
+ dest.writeInt(SCALE_AND_TRANSLATE_LOG);
+ dest.writeFloat(mScale);
+ dest.writeFloat(mTranslate);
+ }
+
+ @Override
+ public void saveToXml(XmlSerializer serializer) throws IOException {
+ serializer.startTag(null, TAG_SCALE_AND_TRANSLATE_LOG);
+ serializer.attribute(null, ATTR_SCALE, Float.toString(mScale));
+ serializer.attribute(null, ATTR_TRANSLATE, Float.toString(mTranslate));
+ serializer.endTag(null, TAG_SCALE_AND_TRANSLATE_LOG);
+ }
+
+ static BrightnessCorrection readFromParcel(Parcel in) {
+ float scale = in.readFloat();
+ float translate = in.readFloat();
+ return BrightnessCorrection.createScaleAndTranslateLog(scale, translate);
+ }
+
+ static BrightnessCorrection loadFromXml(XmlPullParser parser) throws IOException,
+ XmlPullParserException {
+ final float scale = loadFloatFromXml(parser, ATTR_SCALE);
+ final float translate = loadFloatFromXml(parser, ATTR_TRANSLATE);
+ return BrightnessCorrection.createScaleAndTranslateLog(scale, translate);
+ }
+ }
+}
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/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/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/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/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/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/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..b84e6c9b 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/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 885b3e9..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:
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..ff5120d 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -58,7 +58,7 @@
/**
* SurfaceControl
- * @hide
+ * @hide
*/
public class SurfaceControl implements Parcelable {
private static final String TAG = "SurfaceControl";
@@ -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.
@@ -186,6 +186,7 @@
/**
* Surface creation flag: Surface is created hidden
+ * @hide
*/
@UnsupportedAppUsage
public static final int HIDDEN = 0x00000004;
@@ -196,7 +197,7 @@
* from another process. In particular, screenshots and VNC servers will
* be disabled, but other measures can take place, for instance the
* surface might not be hardware accelerated.
- *
+ * @hide
*/
public static final int SECURE = 0x00000080;
@@ -220,7 +221,7 @@
* pixel.
* <p>
* In some rare situations, a non pre-multiplied surface is preferable.
- *
+ * @hide
*/
public static final int NON_PREMULTIPLIED = 0x00000100;
@@ -240,6 +241,7 @@
* </ul>
* If the underlying buffer lacks an alpha channel, the OPAQUE flag is effectively
* set automatically.
+ * @hide
*/
public static final int OPAQUE = 0x00000400;
@@ -248,6 +250,7 @@
* external display sink. If a hardware-protected path is not available,
* then this surface will not be displayed on the external sink.
*
+ * @hide
*/
public static final int PROTECTED_APP = 0x00000800;
@@ -255,6 +258,7 @@
/**
* Surface creation flag: Window represents a cursor glyph.
+ * @hide
*/
public static final int CURSOR_WINDOW = 0x00002000;
@@ -262,6 +266,7 @@
* Surface creation flag: Creates a normal surface.
* This is the default.
*
+ * @hide
*/
public static final int FX_SURFACE_NORMAL = 0x00000000;
@@ -271,6 +276,7 @@
* in {@link #setAlpha}. It is an error to lock a Dim surface, since it
* doesn't have a backing store.
*
+ * @hide
*/
public static final int FX_SURFACE_DIM = 0x00020000;
@@ -278,12 +284,14 @@
* Surface creation flag: Creates a container surface.
* This surface will have no buffers and will only be used
* as a container for other surfaces, or for its InputInfo.
+ * @hide
*/
public static final int FX_SURFACE_CONTAINER = 0x00080000;
/**
* Mask used for FX values above.
*
+ * @hide
*/
public static final int FX_SURFACE_MASK = 0x000F0000;
@@ -309,12 +317,14 @@
/**
* Built-in physical display id: Main display.
* Use only with {@link SurfaceControl#getBuiltInDisplay(int)}.
+ * @hide
*/
public static final int BUILT_IN_DISPLAY_ID_MAIN = 0;
/**
* Built-in physical display id: Attached HDMI display.
* Use only with {@link SurfaceControl#getBuiltInDisplay(int)}.
+ * @hide
*/
public static final int BUILT_IN_DISPLAY_ID_HDMI = 1;
@@ -323,30 +333,35 @@
/**
* Display power mode off: used while blanking the screen.
* Use only with {@link SurfaceControl#setDisplayPowerMode}.
+ * @hide
*/
public static final int POWER_MODE_OFF = 0;
/**
* Display power mode doze: used while putting the screen into low power mode.
* Use only with {@link SurfaceControl#setDisplayPowerMode}.
+ * @hide
*/
public static final int POWER_MODE_DOZE = 1;
/**
* Display power mode normal: used while unblanking the screen.
* Use only with {@link SurfaceControl#setDisplayPowerMode}.
+ * @hide
*/
public static final int POWER_MODE_NORMAL = 2;
/**
* Display power mode doze: used while putting the screen into a suspended
* low power mode. Use only with {@link SurfaceControl#setDisplayPowerMode}.
+ * @hide
*/
public static final int POWER_MODE_DOZE_SUSPEND = 3;
/**
* Display power mode on: used while putting the screen into a suspended
* full power mode. Use only with {@link SurfaceControl#setDisplayPowerMode}.
+ * @hide
*/
public static final int POWER_MODE_ON_SUSPEND = 4;
@@ -359,8 +374,26 @@
*/
public static final int WINDOW_TYPE_DONT_SCREENSHOT = 441731;
+ private void assignNativeObject(long nativeObject) {
+ if (mNativeObject != 0) {
+ release();
+ }
+ mNativeObject = nativeObject;
+ }
+
+ /**
+ * @hide
+ */
+ public void copyFrom(SurfaceControl other) {
+ mName = other.mName;
+ mWidth = other.mWidth;
+ mHeight = other.mHeight;
+ assignNativeObject(nativeCopyFromSurfaceControl(other.mNativeObject));
+ }
+
/**
* Builder class for {@link SurfaceControl} objects.
+ * @hide
*/
public static class Builder {
private SurfaceSession mSession;
@@ -377,6 +410,7 @@
* Begin building a SurfaceControl with a given {@link SurfaceSession}.
*
* @param session The {@link SurfaceSession} with which to eventually construct the surface.
+ * @hide
*/
public Builder(SurfaceSession session) {
mSession = session;
@@ -384,6 +418,7 @@
/**
* Construct a new {@link SurfaceControl} with the set parameters.
+ * @hide
*/
public SurfaceControl build() {
if (mWidth < 0 || mHeight < 0) {
@@ -402,6 +437,7 @@
* Set a debugging-name for the SurfaceControl.
*
* @param name A name to identify the Surface in debugging.
+ * @hide
*/
public Builder setName(String name) {
mName = name;
@@ -413,6 +449,7 @@
*
* @param width The buffer width in pixels.
* @param height The buffer height in pixels.
+ * @hide
*/
public Builder setBufferSize(int width, int height) {
if (width < 0 || height < 0) {
@@ -427,6 +464,7 @@
/**
* Set the pixel format of the controlled surface's buffers, using constants from
* {@link android.graphics.PixelFormat}.
+ * @hide
*/
public Builder setFormat(@PixelFormat.Format int format) {
mFormat = format;
@@ -440,6 +478,7 @@
* not be displayed.
*
* @param protectedContent Whether to require a protected sink.
+ * @hide
*/
public Builder setProtected(boolean protectedContent) {
if (protectedContent) {
@@ -455,6 +494,7 @@
* will prevent the surfaces content from being copied by another process. In
* particular screenshots and VNC servers will be disabled. This is however
* not a complete prevention of readback as {@link #setProtected}.
+ * @hide
*/
public Builder setSecure(boolean secure) {
if (secure) {
@@ -487,6 +527,7 @@
* If the underlying buffer lacks an alpha channel, it is as if setOpaque(true)
* were set automatically.
* @param opaque Whether the Surface is OPAQUE.
+ * @hide
*/
public Builder setOpaque(boolean opaque) {
if (opaque) {
@@ -505,6 +546,7 @@
* of the parent.
*
* @param parent The parent control.
+ * @hide
*/
public Builder setParent(SurfaceControl parent) {
mParent = parent;
@@ -520,6 +562,7 @@
*
* @param windowType A window-type
* @param ownerUid UID of the window owner.
+ * @hide
*/
public Builder setMetadata(int windowType, int ownerUid) {
if (UserHandle.getAppId(Process.myUid()) != Process.SYSTEM_UID) {
@@ -538,6 +581,7 @@
* solid color (that is, solid before plane alpha). Currently that color is black.
*
* @param isColorLayer Whether to create a color layer.
+ * @hide
*/
public Builder setColorLayer(boolean isColorLayer) {
if (isColorLayer) {
@@ -559,6 +603,7 @@
* as a parent of renderable layers.
*
* @param isContainerLayer Whether to create a container layer.
+ * @hide
*/
public Builder setContainerLayer(boolean isContainerLayer) {
if (isContainerLayer) {
@@ -578,6 +623,7 @@
*
* TODO: Finish conversion to individual builder methods?
* @param flags The combined flags
+ * @hide
*/
public Builder setFlags(int flags) {
mFlags = flags;
@@ -646,9 +692,11 @@
mCloseGuard.open("release");
}
- // This is a transfer constructor, useful for transferring a live SurfaceControl native
- // object to another Java wrapper which could have some different behavior, e.g.
- // event logging.
+ /** This is a transfer constructor, useful for transferring a live SurfaceControl native
+ * object to another Java wrapper which could have some different behavior, e.g.
+ * event logging.
+ * @hide
+ */
public SurfaceControl(SurfaceControl other) {
mName = other.mName;
mWidth = other.mWidth;
@@ -660,27 +708,62 @@
}
private SurfaceControl(Parcel in) {
- 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);
- }
+ readFromParcel(in);
mCloseGuard.open("release");
}
+ /**
+ * @hide
+ */
+ public SurfaceControl() {
+ mCloseGuard.open("release");
+ }
+
+ /**
+ * @hide
+ */
+ 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();
+
+ long object = 0;
+ if (in.readInt() != 0) {
+ object = nativeReadFromParcel(in);
+ }
+ assignNativeObject(object);
+ }
+
+ /**
+ * @hide
+ */
@Override
public int describeContents() {
return 0;
}
+ /**
+ * @hide
+ */
@Override
public void writeToParcel(Parcel dest, int flags) {
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();
+ }
}
/**
@@ -698,6 +781,9 @@
proto.end(token);
}
+ /**
+ * @hide
+ */
public static final Creator<SurfaceControl> CREATOR
= new Creator<SurfaceControl>() {
public SurfaceControl createFromParcel(Parcel in) {
@@ -709,6 +795,9 @@
}
};
+ /**
+ * @hide
+ */
@Override
protected void finalize() throws Throwable {
try {
@@ -727,6 +816,7 @@
* Release the local reference to the server-side surface.
* Always call release() when you're done with a Surface.
* This will make the surface invalid.
+ * @hide
*/
public void release() {
if (mNativeObject != 0) {
@@ -740,6 +830,7 @@
* Free all server-side state associated with this surface and
* release this object's reference. This method can only be
* called from the process that created the service.
+ * @hide
*/
public void destroy() {
if (mNativeObject != 0) {
@@ -751,6 +842,7 @@
/**
* Disconnect any client still connected to the surface.
+ * @hide
*/
public void disconnect() {
if (mNativeObject != 0) {
@@ -763,12 +855,21 @@
"mNativeObject is null. Have you called release() already?");
}
+ /**
+ * @hide
+ */
+ public boolean isValid() {
+ return mNativeObject != 0;
+ }
+
/*
* set surface parameters.
* needs to be inside open/closeTransaction block
*/
- /** start a transaction */
+ /** start a transaction
+ * @hide
+ */
@UnsupportedAppUsage
public static void openTransaction() {
synchronized (SurfaceControl.class) {
@@ -797,6 +898,7 @@
* This clears the supplied transaction in an identical fashion to {@link Transaction#merge}.
* <p>
* This is a utility for interop with legacy-code and will go away with the Global Transaction.
+ * @hide
*/
@Deprecated
public static void mergeToGlobalTransaction(Transaction t) {
@@ -805,46 +907,69 @@
}
}
- /** end a transaction */
+ /** end a transaction
+ * @hide
+ */
@UnsupportedAppUsage
public static void closeTransaction() {
closeTransaction(false);
}
+ /**
+ * @hide
+ */
public static void closeTransactionSync() {
closeTransaction(true);
}
+ /**
+ * @hide
+ */
public void deferTransactionUntil(IBinder handle, long frame) {
synchronized(SurfaceControl.class) {
sGlobalTransaction.deferTransactionUntil(this, handle, frame);
}
}
+ /**
+ * @hide
+ */
public void deferTransactionUntil(Surface barrier, long frame) {
synchronized(SurfaceControl.class) {
sGlobalTransaction.deferTransactionUntilSurface(this, barrier, frame);
}
}
+ /**
+ * @hide
+ */
public void reparentChildren(IBinder newParentHandle) {
synchronized(SurfaceControl.class) {
sGlobalTransaction.reparentChildren(this, newParentHandle);
}
}
+ /**
+ * @hide
+ */
public void reparent(IBinder newParentHandle) {
synchronized(SurfaceControl.class) {
sGlobalTransaction.reparent(this, newParentHandle);
}
}
+ /**
+ * @hide
+ */
public void detachChildren() {
synchronized(SurfaceControl.class) {
sGlobalTransaction.detachChildren(this);
}
}
+ /**
+ * @hide
+ */
public void setOverrideScalingMode(int scalingMode) {
checkNotReleased();
synchronized(SurfaceControl.class) {
@@ -852,16 +977,25 @@
}
}
+ /**
+ * @hide
+ */
public IBinder getHandle() {
return nativeGetHandle(mNativeObject);
}
+ /**
+ * @hide
+ */
public static void setAnimationTransaction() {
synchronized (SurfaceControl.class) {
sGlobalTransaction.setAnimationTransaction();
}
}
+ /**
+ * @hide
+ */
@UnsupportedAppUsage
public void setLayer(int zorder) {
checkNotReleased();
@@ -870,6 +1004,9 @@
}
}
+ /**
+ * @hide
+ */
public void setRelativeLayer(SurfaceControl relativeTo, int zorder) {
checkNotReleased();
synchronized(SurfaceControl.class) {
@@ -877,6 +1014,9 @@
}
}
+ /**
+ * @hide
+ */
@UnsupportedAppUsage
public void setPosition(float x, float y) {
checkNotReleased();
@@ -885,6 +1025,9 @@
}
}
+ /**
+ * @hide
+ */
public void setGeometryAppliesWithResize() {
checkNotReleased();
synchronized(SurfaceControl.class) {
@@ -892,6 +1035,9 @@
}
}
+ /**
+ * @hide
+ */
public void setBufferSize(int w, int h) {
checkNotReleased();
synchronized(SurfaceControl.class) {
@@ -899,6 +1045,9 @@
}
}
+ /**
+ * @hide
+ */
@UnsupportedAppUsage
public void hide() {
checkNotReleased();
@@ -907,6 +1056,9 @@
}
}
+ /**
+ * @hide
+ */
@UnsupportedAppUsage
public void show() {
checkNotReleased();
@@ -915,6 +1067,9 @@
}
}
+ /**
+ * @hide
+ */
public void setTransparentRegionHint(Region region) {
checkNotReleased();
synchronized(SurfaceControl.class) {
@@ -922,24 +1077,39 @@
}
}
+ /**
+ * @hide
+ */
public boolean clearContentFrameStats() {
checkNotReleased();
return nativeClearContentFrameStats(mNativeObject);
}
+ /**
+ * @hide
+ */
public boolean getContentFrameStats(WindowContentFrameStats outStats) {
checkNotReleased();
return nativeGetContentFrameStats(mNativeObject, outStats);
}
+ /**
+ * @hide
+ */
public static boolean clearAnimationFrameStats() {
return nativeClearAnimationFrameStats();
}
+ /**
+ * @hide
+ */
public static boolean getAnimationFrameStats(WindowAnimationFrameStats outStats) {
return nativeGetAnimationFrameStats(outStats);
}
+ /**
+ * @hide
+ */
public void setAlpha(float alpha) {
checkNotReleased();
synchronized(SurfaceControl.class) {
@@ -947,6 +1117,9 @@
}
}
+ /**
+ * @hide
+ */
public void setColor(@Size(3) float[] color) {
checkNotReleased();
synchronized (SurfaceControl.class) {
@@ -954,6 +1127,9 @@
}
}
+ /**
+ * @hide
+ */
public void setMatrix(float dsdx, float dtdx, float dtdy, float dsdy) {
checkNotReleased();
synchronized(SurfaceControl.class) {
@@ -966,6 +1142,7 @@
*
* @param matrix The matrix to apply.
* @param float9 An array of 9 floats to be used to extract the values from the matrix.
+ * @hide
*/
public void setMatrix(Matrix matrix, float[] float9) {
checkNotReleased();
@@ -981,6 +1158,7 @@
* Sets the color transform for the Surface.
* @param matrix A float array with 9 values represents a 3x3 transform matrix
* @param translation A float array with 3 values represents a translation vector
+ * @hide
*/
public void setColorTransform(@Size(9) float[] matrix, @Size(3) float[] translation) {
checkNotReleased();
@@ -996,6 +1174,7 @@
* constrained by the size of its parent bounds.
*
* @param crop Bounds of the crop to apply.
+ * @hide
*/
public void setWindowCrop(Rect crop) {
checkNotReleased();
@@ -1009,6 +1188,7 @@
*
* @param width width of crop rect
* @param height height of crop rect
+ * @hide
*/
public void setWindowCrop(int width, int height) {
checkNotReleased();
@@ -1021,6 +1201,7 @@
* Sets the corner radius of a {@link SurfaceControl}.
*
* @param cornerRadius Corner radius in pixels.
+ * @hide
*/
public void setCornerRadius(float cornerRadius) {
checkNotReleased();
@@ -1029,6 +1210,9 @@
}
}
+ /**
+ * @hide
+ */
public void setLayerStack(int layerStack) {
checkNotReleased();
synchronized(SurfaceControl.class) {
@@ -1036,6 +1220,9 @@
}
}
+ /**
+ * @hide
+ */
public void setOpaque(boolean isOpaque) {
checkNotReleased();
@@ -1044,6 +1231,9 @@
}
}
+ /**
+ * @hide
+ */
public void setSecure(boolean isSecure) {
checkNotReleased();
@@ -1052,18 +1242,27 @@
}
}
+ /**
+ * @hide
+ */
public int getWidth() {
synchronized (mSizeLock) {
return mWidth;
}
}
+ /**
+ * @hide
+ */
public int getHeight() {
synchronized (mSizeLock) {
return mHeight;
}
}
+ /**
+ * @hide
+ */
@Override
public String toString() {
return "Surface(name=" + mName + ")/@0x" +
@@ -1079,38 +1278,85 @@
* Describes the properties of a physical display known to surface flinger.
*/
public static final class PhysicalDisplayInfo {
+ /**
+ * @hide
+ */
@UnsupportedAppUsage
public int width;
+
+ /**
+ * @hide
+ */
@UnsupportedAppUsage
public int height;
+
+ /**
+ * @hide
+ */
@UnsupportedAppUsage
public float refreshRate;
+
+ /**
+ * @hide
+ */
@UnsupportedAppUsage
public float density;
+
+ /**
+ * @hide
+ */
@UnsupportedAppUsage
public float xDpi;
+
+ /**
+ * @hide
+ */
@UnsupportedAppUsage
public float yDpi;
+
+ /**
+ * @hide
+ */
@UnsupportedAppUsage
public boolean secure;
+
+ /**
+ * @hide
+ */
@UnsupportedAppUsage
public long appVsyncOffsetNanos;
+
+ /**
+ * @hide
+ */
@UnsupportedAppUsage
public long presentationDeadlineNanos;
+ /**
+ * @hide
+ */
@UnsupportedAppUsage
public PhysicalDisplayInfo() {
}
+ /**
+ * @hide
+ */
public PhysicalDisplayInfo(PhysicalDisplayInfo other) {
copyFrom(other);
}
+ /**
+ * @hide
+ */
@Override
public boolean equals(Object o) {
return o instanceof PhysicalDisplayInfo && equals((PhysicalDisplayInfo)o);
}
+ /**
+ * @hide
+ */
public boolean equals(PhysicalDisplayInfo other) {
return other != null
&& width == other.width
@@ -1124,11 +1370,17 @@
&& presentationDeadlineNanos == other.presentationDeadlineNanos;
}
+ /**
+ * @hide
+ */
@Override
public int hashCode() {
return 0; // don't care
}
+ /**
+ * @hide
+ */
public void copyFrom(PhysicalDisplayInfo other) {
width = other.width;
height = other.height;
@@ -1141,7 +1393,9 @@
presentationDeadlineNanos = other.presentationDeadlineNanos;
}
- // For debugging purposes
+ /**
+ * @hide
+ */
@Override
public String toString() {
return "PhysicalDisplayInfo{" + width + " x " + height + ", " + refreshRate + " fps, "
@@ -1151,6 +1405,9 @@
}
}
+ /**
+ * @hide
+ */
public static void setDisplayPowerMode(IBinder displayToken, int mode) {
if (displayToken == null) {
throw new IllegalArgumentException("displayToken must not be null");
@@ -1158,6 +1415,9 @@
nativeSetDisplayPowerMode(displayToken, mode);
}
+ /**
+ * @hide
+ */
@UnsupportedAppUsage
public static SurfaceControl.PhysicalDisplayInfo[] getDisplayConfigs(IBinder displayToken) {
if (displayToken == null) {
@@ -1166,6 +1426,9 @@
return nativeGetDisplayConfigs(displayToken);
}
+ /**
+ * @hide
+ */
public static int getActiveConfig(IBinder displayToken) {
if (displayToken == null) {
throw new IllegalArgumentException("displayToken must not be null");
@@ -1212,6 +1475,9 @@
}
+ /**
+ * @hide
+ */
public static boolean setActiveConfig(IBinder displayToken, int id) {
if (displayToken == null) {
throw new IllegalArgumentException("displayToken must not be null");
@@ -1219,6 +1485,9 @@
return nativeSetActiveConfig(displayToken, id);
}
+ /**
+ * @hide
+ */
public static int[] getDisplayColorModes(IBinder displayToken) {
if (displayToken == null) {
throw new IllegalArgumentException("displayToken must not be null");
@@ -1226,6 +1495,9 @@
return nativeGetDisplayColorModes(displayToken);
}
+ /**
+ * @hide
+ */
public static int getActiveColorMode(IBinder displayToken) {
if (displayToken == null) {
throw new IllegalArgumentException("displayToken must not be null");
@@ -1233,6 +1505,9 @@
return nativeGetActiveColorMode(displayToken);
}
+ /**
+ * @hide
+ */
public static boolean setActiveColorMode(IBinder displayToken, int colorMode) {
if (displayToken == null) {
throw new IllegalArgumentException("displayToken must not be null");
@@ -1240,6 +1515,9 @@
return nativeSetActiveColorMode(displayToken, colorMode);
}
+ /**
+ * @hide
+ */
@UnsupportedAppUsage
public static void setDisplayProjection(IBinder displayToken,
int orientation, Rect layerStackRect, Rect displayRect) {
@@ -1249,6 +1527,9 @@
}
}
+ /**
+ * @hide
+ */
@UnsupportedAppUsage
public static void setDisplayLayerStack(IBinder displayToken, int layerStack) {
synchronized (SurfaceControl.class) {
@@ -1256,6 +1537,9 @@
}
}
+ /**
+ * @hide
+ */
@UnsupportedAppUsage
public static void setDisplaySurface(IBinder displayToken, Surface surface) {
synchronized (SurfaceControl.class) {
@@ -1263,12 +1547,18 @@
}
}
+ /**
+ * @hide
+ */
public static void setDisplaySize(IBinder displayToken, int width, int height) {
synchronized (SurfaceControl.class) {
sGlobalTransaction.setDisplaySize(displayToken, width, height);
}
}
+ /**
+ * @hide
+ */
public static Display.HdrCapabilities getHdrCapabilities(IBinder displayToken) {
if (displayToken == null) {
throw new IllegalArgumentException("displayToken must not be null");
@@ -1276,6 +1566,9 @@
return nativeGetHdrCapabilities(displayToken);
}
+ /**
+ * @hide
+ */
@UnsupportedAppUsage
public static IBinder createDisplay(String name, boolean secure) {
if (name == null) {
@@ -1284,6 +1577,9 @@
return nativeCreateDisplay(name, secure);
}
+ /**
+ * @hide
+ */
@UnsupportedAppUsage
public static void destroyDisplay(IBinder displayToken) {
if (displayToken == null) {
@@ -1292,6 +1588,9 @@
nativeDestroyDisplay(displayToken);
}
+ /**
+ * @hide
+ */
@UnsupportedAppUsage
public static IBinder getBuiltInDisplay(int builtInDisplayId) {
return nativeGetBuiltInDisplay(builtInDisplayId);
@@ -1299,6 +1598,7 @@
/**
* @see SurfaceControl#screenshot(IBinder, Surface, Rect, int, int, boolean, int)
+ * @hide
*/
public static void screenshot(IBinder display, Surface consumer) {
screenshot(display, consumer, new Rect(), 0, 0, false, 0);
@@ -1309,6 +1609,7 @@
*
* @param consumer The {@link Surface} to take the screenshot into.
* @see SurfaceControl#screenshotToBuffer(IBinder, Rect, int, int, boolean, int)
+ * @hide
*/
public static void screenshot(IBinder display, Surface consumer, Rect sourceCrop, int width,
int height, boolean useIdentityTransform, int rotation) {
@@ -1327,6 +1628,7 @@
/**
* @see SurfaceControl#screenshot(Rect, int, int, boolean, int)}
+ * @hide
*/
@UnsupportedAppUsage
public static Bitmap screenshot(Rect sourceCrop, int width, int height, int rotation) {
@@ -1344,6 +1646,7 @@
* {@link SurfaceControl#screenshotToBuffer(IBinder, Rect, int, int, boolean, int)}.
*
* @see SurfaceControl#screenshotToBuffer(IBinder, Rect, int, int, boolean, int)}
+ * @hide
*/
@UnsupportedAppUsage
public static Bitmap screenshot(Rect sourceCrop, int width, int height,
@@ -1387,6 +1690,7 @@
* this is useful for returning screenshots that are independent of
* device orientation.
* @return Returns a GraphicBuffer that contains the captured content.
+ * @hide
*/
public static GraphicBuffer screenshotToBuffer(IBinder display, Rect sourceCrop, int width,
int height, boolean useIdentityTransform, int rotation) {
@@ -1418,12 +1722,16 @@
* screen will be scaled up/down.
*
* @return Returns a GraphicBuffer that contains the layer capture.
+ * @hide
*/
public static GraphicBuffer captureLayers(IBinder layerHandleToken, Rect sourceCrop,
float frameScale) {
return nativeCaptureLayers(layerHandleToken, sourceCrop, frameScale);
}
+ /**
+ * @hide
+ */
public static class Transaction implements Closeable {
public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
Transaction.class.getClassLoader(),
@@ -1433,6 +1741,9 @@
private final ArrayMap<SurfaceControl, Point> mResizedSurfaces = new ArrayMap<>();
Runnable mFreeNativeResources;
+ /**
+ * @hide
+ */
@UnsupportedAppUsage
public Transaction() {
mNativeObject = nativeCreateTransaction();
@@ -1443,6 +1754,7 @@
/**
* Apply the transaction, clearing it's state, and making it usable
* as a new transaction.
+ * @hide
*/
@UnsupportedAppUsage
public void apply() {
@@ -1452,6 +1764,7 @@
/**
* Close the transaction, if the transaction was not already applied this will cancel the
* transaction.
+ * @hide
*/
@Override
public void close() {
@@ -1461,6 +1774,7 @@
/**
* Jankier version of apply. Avoid use (b/28068298).
+ * @hide
*/
public void apply(boolean sync) {
applyResizedSurfaces();
@@ -1479,6 +1793,9 @@
mResizedSurfaces.clear();
}
+ /**
+ * @hide
+ */
@UnsupportedAppUsage
public Transaction show(SurfaceControl sc) {
sc.checkNotReleased();
@@ -1486,6 +1803,9 @@
return this;
}
+ /**
+ * @hide
+ */
@UnsupportedAppUsage
public Transaction hide(SurfaceControl sc) {
sc.checkNotReleased();
@@ -1493,6 +1813,9 @@
return this;
}
+ /**
+ * @hide
+ */
@UnsupportedAppUsage
public Transaction setPosition(SurfaceControl sc, float x, float y) {
sc.checkNotReleased();
@@ -1500,6 +1823,9 @@
return this;
}
+ /**
+ * @hide
+ */
@UnsupportedAppUsage
public Transaction setBufferSize(SurfaceControl sc, int w, int h) {
sc.checkNotReleased();
@@ -1508,6 +1834,9 @@
return this;
}
+ /**
+ * @hide
+ */
@UnsupportedAppUsage
public Transaction setLayer(SurfaceControl sc, int z) {
sc.checkNotReleased();
@@ -1515,6 +1844,9 @@
return this;
}
+ /**
+ * @hide
+ */
public Transaction setRelativeLayer(SurfaceControl sc, SurfaceControl relativeTo, int z) {
sc.checkNotReleased();
nativeSetRelativeLayer(mNativeObject, sc.mNativeObject,
@@ -1522,6 +1854,9 @@
return this;
}
+ /**
+ * @hide
+ */
public Transaction setTransparentRegionHint(SurfaceControl sc, Region transparentRegion) {
sc.checkNotReleased();
nativeSetTransparentRegionHint(mNativeObject,
@@ -1529,6 +1864,9 @@
return this;
}
+ /**
+ * @hide
+ */
@UnsupportedAppUsage
public Transaction setAlpha(SurfaceControl sc, float alpha) {
sc.checkNotReleased();
@@ -1536,6 +1874,9 @@
return this;
}
+ /**
+ * @hide
+ */
public Transaction setInputWindowInfo(SurfaceControl sc, InputWindowHandle handle) {
sc.checkNotReleased();
nativeSetInputWindowInfo(mNativeObject, sc.mNativeObject, handle);
@@ -1551,12 +1892,16 @@
* @param fromToken The token of a window that currently has touch focus.
* @param toToken The token of the window that should receive touch focus in
* place of the first.
+ * @hide
*/
public Transaction transferTouchFocus(IBinder fromToken, IBinder toToken) {
nativeTransferTouchFocus(mNativeObject, fromToken, toToken);
return this;
}
+ /**
+ * @hide
+ */
@UnsupportedAppUsage
public Transaction setMatrix(SurfaceControl sc,
float dsdx, float dtdx, float dtdy, float dsdy) {
@@ -1566,6 +1911,9 @@
return this;
}
+ /**
+ * @hide
+ */
@UnsupportedAppUsage
public Transaction setMatrix(SurfaceControl sc, Matrix matrix, float[] float9) {
matrix.getValues(float9);
@@ -1579,6 +1927,7 @@
* Sets the color transform for the Surface.
* @param matrix A float array with 9 values represents a 3x3 transform matrix
* @param translation A float array with 3 values represents a translation vector
+ * @hide
*/
public Transaction setColorTransform(SurfaceControl sc, @Size(9) float[] matrix,
@Size(3) float[] translation) {
@@ -1587,6 +1936,9 @@
return this;
}
+ /**
+ * @hide
+ */
@UnsupportedAppUsage
public Transaction setWindowCrop(SurfaceControl sc, Rect crop) {
sc.checkNotReleased();
@@ -1600,6 +1952,9 @@
return this;
}
+ /**
+ * @hide
+ */
public Transaction setWindowCrop(SurfaceControl sc, int width, int height) {
sc.checkNotReleased();
nativeSetWindowCrop(mNativeObject, sc.mNativeObject, 0, 0, width, height);
@@ -1611,6 +1966,7 @@
* @param sc SurfaceControl
* @param cornerRadius Corner radius in pixels.
* @return Itself.
+ * @hide
*/
@UnsupportedAppUsage
public Transaction setCornerRadius(SurfaceControl sc, float cornerRadius) {
@@ -1621,6 +1977,9 @@
}
@UnsupportedAppUsage
+ /**
+ * @hide
+ */
public Transaction setLayerStack(SurfaceControl sc, int layerStack) {
sc.checkNotReleased();
nativeSetLayerStack(mNativeObject, sc.mNativeObject, layerStack);
@@ -1628,6 +1987,9 @@
}
@UnsupportedAppUsage
+ /**
+ * @hide
+ */
public Transaction deferTransactionUntil(SurfaceControl sc, IBinder handle,
long frameNumber) {
if (frameNumber < 0) {
@@ -1639,6 +2001,9 @@
}
@UnsupportedAppUsage
+ /**
+ * @hide
+ */
public Transaction deferTransactionUntilSurface(SurfaceControl sc, Surface barrierSurface,
long frameNumber) {
if (frameNumber < 0) {
@@ -1650,13 +2015,18 @@
return this;
}
+ /**
+ * @hide
+ */
public Transaction reparentChildren(SurfaceControl sc, IBinder newParentHandle) {
sc.checkNotReleased();
nativeReparentChildren(mNativeObject, sc.mNativeObject, newParentHandle);
return this;
}
- /** Re-parents a specific child layer to a new parent */
+ /** Re-parents a specific child layer to a new parent
+ * @hide
+ */
public Transaction reparent(SurfaceControl sc, IBinder newParentHandle) {
sc.checkNotReleased();
nativeReparent(mNativeObject, sc.mNativeObject,
@@ -1664,12 +2034,18 @@
return this;
}
+ /**
+ * @hide
+ */
public Transaction detachChildren(SurfaceControl sc) {
sc.checkNotReleased();
nativeSeverChildren(mNativeObject, sc.mNativeObject);
return this;
}
+ /**
+ * @hide
+ */
public Transaction setOverrideScalingMode(SurfaceControl sc, int overrideScalingMode) {
sc.checkNotReleased();
nativeSetOverrideScalingMode(mNativeObject, sc.mNativeObject,
@@ -1680,6 +2056,7 @@
/**
* Sets a color for the Surface.
* @param color A float array with three values to represent r, g, b in range [0..1]
+ * @hide
*/
@UnsupportedAppUsage
public Transaction setColor(SurfaceControl sc, @Size(3) float[] color) {
@@ -1694,6 +2071,7 @@
* arrives. As transform matrix and size are already frozen in this fashion,
* this enables totally freezing the surface until the resize has completed
* (at which point the geometry influencing aspects of this transaction will then occur)
+ * @hide
*/
public Transaction setGeometryAppliesWithResize(SurfaceControl sc) {
sc.checkNotReleased();
@@ -1704,6 +2082,7 @@
/**
* Sets the security of the surface. Setting the flag is equivalent to creating the
* Surface with the {@link #SECURE} flag.
+ * @hide
*/
public Transaction setSecure(SurfaceControl sc, boolean isSecure) {
sc.checkNotReleased();
@@ -1718,6 +2097,7 @@
/**
* Sets the opacity of the surface. Setting the flag is equivalent to creating the
* Surface with the {@link #OPAQUE} flag.
+ * @hide
*/
public Transaction setOpaque(SurfaceControl sc, boolean isOpaque) {
sc.checkNotReleased();
@@ -1730,29 +2110,8 @@
}
/**
- * Same as {@link #destroy()} except this is invoked in a transaction instead of
- * immediately.
+ * @hide
*/
- 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");
@@ -1768,6 +2127,9 @@
return this;
}
+ /**
+ * @hide
+ */
public Transaction setDisplayLayerStack(IBinder displayToken, int layerStack) {
if (displayToken == null) {
throw new IllegalArgumentException("displayToken must not be null");
@@ -1776,6 +2138,9 @@
return this;
}
+ /**
+ * @hide
+ */
public Transaction setDisplayProjection(IBinder displayToken,
int orientation, Rect layerStackRect, Rect displayRect) {
if (displayToken == null) {
@@ -1793,6 +2158,9 @@
return this;
}
+ /**
+ * @hide
+ */
public Transaction setDisplaySize(IBinder displayToken, int width, int height) {
if (displayToken == null) {
throw new IllegalArgumentException("displayToken must not be null");
@@ -1805,7 +2173,9 @@
return this;
}
- /** flag the transaction as an animation */
+ /** flag the transaction as an animation
+ * @hide
+ */
public Transaction setAnimationTransaction() {
nativeSetAnimationTransaction(mNativeObject);
return this;
@@ -1818,6 +2188,7 @@
* order not to miss frame deadlines.
* <p>
* Corresponds to setting ISurfaceComposer::eEarlyWakeup
+ * @hide
*/
public Transaction setEarlyWakeup() {
nativeSetEarlyWakeup(mNativeObject);
@@ -1827,6 +2198,7 @@
/**
* Merge the other transaction into this transaction, clearing the
* other transaction as if it had been applied.
+ * @hide
*/
public Transaction merge(Transaction other) {
mResizedSurfaces.putAll(other.mResizedSurfaces);
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/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/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/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/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_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp
index 163b86b..42e3942 100644
--- a/core/jni/android_os_HwBinder.cpp
+++ b/core/jni/android_os_HwBinder.cpp
@@ -329,7 +329,7 @@
return NULL;
}
- LOG(INFO) << "HwBinder: Starting thread pool for " << serviceName << "::" << ifaceName;
+ LOG(INFO) << "HwBinder: Starting thread pool for getting: " << ifaceName << "/" << serviceName;
::android::hardware::ProcessState::self()->startThreadPool();
return JHwRemoteBinder::NewObject(env, service);
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/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/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/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/MultiDexLegacyAndException/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk
index 80ab4ea..d161059 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk
@@ -18,7 +18,7 @@
## The application with a minimal main dex
include $(CLEAR_VARS)
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex android-support-multidex-instrumentation android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex 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/MultiDexLegacyAndException/AndroidManifest.xml b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/AndroidManifest.xml
index 98d8f27..d9d9eb2 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/AndroidManifest.xml
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/AndroidManifest.xml
@@ -30,7 +30,7 @@
android:targetPackage="com.android.multidexlegacyandexception"
android:label="Test for MultiDexLegacyAndException" />
- <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.multidexlegacyandexception"
android:label="Test for MultiDexLegacyAndException" />
</manifest>
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/tests/MultiDexAndroidJUnitRunner.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/tests/MultiDexAndroidJUnitRunner.java
index 92a3b0c..fae345f 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/tests/MultiDexAndroidJUnitRunner.java
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/tests/MultiDexAndroidJUnitRunner.java
@@ -17,8 +17,9 @@
package com.android.multidexlegacyandexception.tests;
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/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/tests/NoActivityJUnit4Test.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/tests/NoActivityJUnit4Test.java
index 94a5b7f..f4b02d0 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/tests/NoActivityJUnit4Test.java
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/tests/NoActivityJUnit4Test.java
@@ -16,7 +16,8 @@
package com.android.multidexlegacyandexception.tests;
-import android.support.test.runner.AndroidJUnit4;
+import androidx.test.runner.AndroidJUnit4;
+
import org.junit.runner.RunWith;
/**
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/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/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.java b/media/java/android/media/Session2Token.java
index 4634c69..e1fff38 100644
--- a/media/java/android/media/Session2Token.java
+++ b/media/java/android/media/Session2Token.java
@@ -171,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 51148e2..7ac3ef2 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -23,7 +23,7 @@
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;
@@ -32,9 +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 ef5cf00..56ea484 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -98,7 +98,7 @@
* @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);
}
@@ -179,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/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_qs_status_icons.xml b/packages/SystemUI/res/layout/quick_qs_status_icons.xml
index 2000104..74002ac 100644
--- a/packages/SystemUI/res/layout/quick_qs_status_icons.xml
+++ b/packages/SystemUI/res/layout/quick_qs_status_icons.xml
@@ -51,11 +51,4 @@
android:layout_width="wrap_content"
android:paddingEnd="2dp" />
- <TextView
- android:id="@+id/batteryRemainingText"
- android:textAppearance="@style/TextAppearance.QS.TileLabel"
- android:layout_height="match_parent"
- android:layout_width="wrap_content"
- android:gravity="center_vertical" />
-
</LinearLayout>
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..cd9f780 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" >
@@ -63,11 +64,5 @@
<include layout="@layout/ongoing_privacy_chip" />
- <com.android.systemui.BatteryMeterView
- android:id="@+id/battery"
- android:layout_height="match_parent"
- android:layout_width="wrap_content"
- android:gravity="center_vertical|end"
- android:layout_gravity="center_vertical|end" />
</LinearLayout>
</LinearLayout>
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..2006794 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;
@@ -71,11 +69,12 @@
@Retention(SOURCE)
- @IntDef({MODE_DEFAULT, MODE_ON, MODE_OFF})
+ @IntDef({MODE_DEFAULT, MODE_ON, MODE_OFF, MODE_ESTIMATE})
public @interface BatteryPercentMode {}
public static final int MODE_DEFAULT = 0;
public static final int MODE_ON = 1;
public static final int MODE_OFF = 2;
+ public static final int MODE_ESTIMATE = 3;
private final BatteryMeterDrawableBase mDrawable;
private final String mSlotBattery;
@@ -93,6 +92,7 @@
// Some places may need to show the battery conditionally, and not obey the tuner
private boolean mIgnoreTunerUpdates;
private boolean mIsSubscribedForTunerUpdates;
+ private boolean mCharging;
private int mDarkModeBackgroundColor;
private int mDarkModeFillColor;
@@ -276,9 +276,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);
}
}
@@ -308,6 +305,7 @@
public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
mDrawable.setBatteryLevel(level);
mDrawable.setCharging(pluggedIn);
+ mCharging = pluggedIn;
mLevel = level;
updatePercentText();
setContentDescription(
@@ -337,9 +335,19 @@
}
private void updatePercentText() {
+ if (mBatteryController == null) {
+ return;
+ }
+
if (mBatteryPercentView != null) {
- mBatteryPercentView.setText(
- NumberFormat.getPercentInstance().format(mLevel / 100f));
+ if (mShowPercentMode == MODE_ESTIMATE && !mCharging) {
+ mBatteryController.getEstimatedTimeRemainingString((String estimate) -> {
+ mBatteryPercentView.setText(estimate);
+ });
+ } else {
+ mBatteryPercentView.setText(
+ NumberFormat.getPercentInstance().format(mLevel / 100f));
+ }
}
}
@@ -350,7 +358,7 @@
SHOW_BATTERY_PERCENT, 0, mUser);
if ((mShowPercentAvailable && systemSetting && mShowPercentMode != MODE_OFF)
- || mShowPercentMode == MODE_ON) {
+ || mShowPercentMode == MODE_ON || mShowPercentMode == MODE_ESTIMATE) {
if (!showing) {
mBatteryPercentView = loadPercentView();
if (mTextColor != 0) mBatteryPercentView.setTextColor(mTextColor);
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/ForegroundServiceNotificationListener.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
index b0b7e6c..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,18 +49,18 @@
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,
+ NotificationEntry entry,
NotificationVisibility visibility,
boolean removedByUser) {
removeNotification(entry.notification);
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 5347a5c..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;
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 6447233..9f3ff78 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -23,21 +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;
@@ -68,6 +73,8 @@
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 final Context mContext;
private final NotificationEntryManager mNotificationEntryManager;
@@ -180,7 +187,7 @@
/**
* 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);
@@ -189,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;
@@ -216,6 +226,21 @@
}
}
+ @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}.
*/
@@ -223,10 +248,11 @@
BubbleView bv = mBubbles.get(key);
if (mStackView != null && bv != null) {
mStackView.removeBubble(bv);
+ bv.destroyActivityView(mStackView);
bv.getEntry().setBubbleDismissed(true);
}
- NotificationData.Entry entry = mNotificationEntryManager.getNotificationData().get(key);
+ NotificationEntry entry = mNotificationEntryManager.getNotificationData().get(key);
if (entry != null) {
entry.setBubbleDismissed(true);
if (!DEBUG_DEMOTE_TO_NOTIF) {
@@ -241,7 +267,7 @@
@SuppressWarnings("FieldCanBeLocal")
private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
@Override
- public void onPendingEntryAdded(NotificationData.Entry entry) {
+ public void onPendingEntryAdded(NotificationEntry entry) {
if (shouldAutoBubble(mContext, entry)) {
entry.setIsBubble(true);
}
@@ -275,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);
@@ -306,6 +333,17 @@
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
BubbleStackView getStackView() {
return mStackView;
@@ -332,7 +370,7 @@
/**
* Whether the notification should bubble or not.
*/
- private static boolean shouldAutoBubble(Context context, NotificationData.Entry entry) {
+ private static boolean shouldAutoBubble(Context context, NotificationEntry entry) {
if (entry.isBubbleDismissed()) {
return false;
}
@@ -378,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..75ab5df 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -141,14 +141,11 @@
private View mStatusSeparator;
private ImageView mRingerModeIcon;
private TextView mRingerModeTextView;
- private BatteryMeterView mBatteryMeterView;
private Clock mClockView;
private DateView mDateView;
private OngoingPrivacyChip mPrivacyChip;
private Space mSpace;
private BatteryMeterView mBatteryRemainingIcon;
- private TextView mBatteryRemainingText;
- private boolean mShowBatteryPercentAndEstimate;
private PrivacyItemController mPrivacyItemController;
/** Counts how many times the long press tooltip has been shown to the user. */
@@ -229,13 +226,6 @@
// Set the correct tint for the status icons so they contrast
mIconManager.setTint(fillColor);
- mShowBatteryPercentAndEstimate = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_battery_percentage_setting_available);
-
- mBatteryMeterView = findViewById(R.id.battery);
- mBatteryMeterView.setPercentShowMode(mShowBatteryPercentAndEstimate
- ? BatteryMeterView.MODE_ON : BatteryMeterView.MODE_OFF);
- mBatteryMeterView.setOnClickListener(this);
mClockView = findViewById(R.id.clock);
mClockView.setOnClickListener(this);
mDateView = findViewById(R.id.date);
@@ -245,13 +235,8 @@
// Tint for the battery icons are handled in setupHost()
mBatteryRemainingIcon = findViewById(R.id.batteryRemainingIcon);
- mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_OFF);
// Don't need to worry about tuner settings for this icon
mBatteryRemainingIcon.setIgnoreTunerUpdates(true);
-
- mBatteryRemainingText = findViewById(R.id.batteryRemainingText);
- mBatteryRemainingText.setTextColor(fillColor);
-
updateShowPercent();
}
@@ -268,10 +253,8 @@
}
private void setChipVisibility(boolean chipVisible) {
- mBatteryMeterView.setVisibility(View.VISIBLE);
if (chipVisible) {
mPrivacyChip.setVisibility(View.VISIBLE);
- if (mHasTopCutout) mBatteryMeterView.setVisibility(View.GONE);
} else {
mPrivacyChip.setVisibility(View.GONE);
}
@@ -339,7 +322,6 @@
// Update color schemes in landscape to use wallpaperTextColor
boolean shouldUseWallpaperTextColor =
newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE;
- mBatteryMeterView.useWallpaperTextColor(shouldUseWallpaperTextColor);
mClockView.useWallpaperTextColor(shouldUseWallpaperTextColor);
}
@@ -415,17 +397,11 @@
.build();
}
- private void updateBatteryRemainingText() {
- if (!mShowBatteryPercentAndEstimate) {
- return;
- }
- mBatteryRemainingText.setText(mBatteryController.getEstimatedTimeRemainingString());
- }
-
public void setExpanded(boolean expanded) {
if (mExpanded == expanded) return;
mExpanded = expanded;
mHeaderQsPanel.setExpanded(expanded);
+ updateEverything();
}
/**
@@ -518,7 +494,6 @@
}
}
mSpace.setLayoutParams(lp);
- // Decide whether to show BatteryMeterView
setChipVisibility(mPrivacyChip.getVisibility() == View.VISIBLE);
return super.onApplyWindowInsets(insets);
}
@@ -545,7 +520,6 @@
mAlarmController.addCallback(this);
mContext.registerReceiver(mRingerReceiver,
new IntentFilter(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION));
- updateBatteryRemainingText();
} else {
mZenController.removeCallback(this);
mAlarmController.removeCallback(this);
@@ -558,9 +532,6 @@
if (v == mClockView) {
mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
AlarmClock.ACTION_SHOW_ALARMS),0);
- } else if (v == mBatteryMeterView) {
- mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
- Intent.ACTION_POWER_USAGE_SUMMARY),0);
} else if (v == mPrivacyChip) {
Handler mUiHandler = new Handler(Looper.getMainLooper());
mUiHandler.post(() -> {
@@ -697,6 +668,10 @@
.start();
}
+ public void updateEverything() {
+ post(() -> setClickable(!mExpanded));
+ }
+
public void setQSPanel(final QSPanel qsPanel) {
mQsPanel = qsPanel;
setupHost(qsPanel.getHost());
@@ -708,9 +683,6 @@
mHeaderQsPanel.setQSPanelAndHeader(mQsPanel, this);
mHeaderQsPanel.setHost(host, null /* No customization in header */);
- // Use SystemUI context to get battery meter colors, and let it use the default tint (white)
- mBatteryMeterView.setColorsFromContext(mHost.getContext());
- mBatteryMeterView.onDarkChanged(new Rect(), 0, DarkIconDispatcher.DEFAULT_ICON_TINT);
Rect tintArea = new Rect(0, 0, 0, 0);
int colorForeground = Utils.getColorAttrDefaultColor(getContext(),
@@ -758,22 +730,8 @@
.getIntForUser(getContext().getContentResolver(),
SHOW_BATTERY_PERCENT, 0, ActivityManager.getCurrentUser());
- mShowBatteryPercentAndEstimate = systemSetting;
-
- updateBatteryViews();
- }
-
- private void updateBatteryViews() {
- if (mShowBatteryPercentAndEstimate) {
- mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ON);
- mBatteryRemainingIcon.setVisibility(View.VISIBLE);
- mBatteryRemainingText.setVisibility(View.VISIBLE);
- updateBatteryRemainingText();
- } else {
- mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_OFF);
- mBatteryRemainingIcon.setVisibility(View.GONE);
- mBatteryRemainingText.setVisibility(View.GONE);
- }
+ mBatteryRemainingIcon.setPercentShowMode(systemSetting
+ ? BatteryMeterView.MODE_ESTIMATE : BatteryMeterView.MODE_ON);
}
private final class PercentSettingObserver extends ContentObserver {
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 e59bc2a..7412702 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -46,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;
@@ -156,7 +156,7 @@
notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
@Override
public void onEntryRemoved(
- Entry entry,
+ NotificationEntry entry,
NotificationVisibility visibility,
boolean removedByUser) {
onNotificationRemoved(entry.key);
@@ -188,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;
}
@@ -210,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 =
@@ -258,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 1ab9c5c..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,7 +253,7 @@
notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
@Override
public void onEntryRemoved(
- @Nullable NotificationData.Entry entry,
+ @Nullable NotificationEntry entry,
NotificationVisibility visibility,
boolean removedByUser) {
if (removedByUser && entry != null) {
@@ -269,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);
@@ -413,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);
}
@@ -424,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);
@@ -437,7 +437,7 @@
return mKeysKeptForRemoteInputHistory.contains(key);
}
- public boolean shouldKeepForRemoteInputHistory(NotificationData.Entry entry) {
+ public boolean shouldKeepForRemoteInputHistory(NotificationEntry entry) {
if (entry.isDismissed()) {
return false;
}
@@ -447,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;
}
@@ -467,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;
@@ -530,7 +530,7 @@
}
@VisibleForTesting
- public Set<NotificationData.Entry> getEntriesKeptForRemoteInputActive() {
+ public Set<NotificationEntry> getEntriesKeptForRemoteInputActive() {
return mEntriesKeptForRemoteInputActive;
}
@@ -553,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;
@@ -599,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);
@@ -637,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;
}
@@ -645,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 2bb0d5c..60d8cf4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java
@@ -29,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;
@@ -71,18 +72,18 @@
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(
- NotificationData.Entry entry,
+ NotificationEntry entry,
NotificationVisibility visibility,
boolean removedByUser) {
stopAlerting(entry.key);
@@ -101,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.
@@ -123,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;
@@ -150,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 2f60f11..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) {
}
/**
@@ -77,7 +84,7 @@
* @param removedByUser true if the notification was removed by a user action
*/
default void onEntryRemoved(
- NotificationData.Entry entry,
+ NotificationEntry entry,
@Nullable NotificationVisibility visibility,
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 5d6f60e..45db002 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -18,7 +18,6 @@
import android.annotation.Nullable;
import android.app.Notification;
import android.content.Context;
-import android.os.Handler;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
@@ -36,8 +35,9 @@
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;
@@ -50,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.
@@ -63,15 +62,12 @@
NotificationUpdateHandler,
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 =
@@ -81,13 +77,11 @@
private NotificationRemoteInputManager mRemoteInputManager;
private NotificationRowBinder mNotificationRowBinder;
- private final Handler mDeferredNotificationViewUpdateHandler;
- private Runnable mUpdateNotificationViewsCallback;
-
private NotificationPresenter mPresenter;
private NotificationListenerService.RankingMap mLatestRankingMap;
+ @VisibleForTesting
protected NotificationData mNotificationData;
- protected NotificationListContainer mListContainer;
+ private NotificationListContainer mListContainer;
@VisibleForTesting
final ArrayList<NotificationLifetimeExtender> mNotificationLifetimeExtenders
= new ArrayList<>();
@@ -113,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);
}
}
@@ -122,7 +116,6 @@
public NotificationEntryManager(Context context) {
mContext = context;
mNotificationData = new NotificationData();
- mDeferredNotificationViewUpdateHandler = new Handler();
}
/** Adds a {@link NotificationEntryListener}. */
@@ -157,7 +150,6 @@
NotificationListContainer listContainer,
HeadsUpManager headsUpManager) {
mPresenter = presenter;
- mUpdateNotificationViewsCallback = mPresenter::updateNotificationViews;
mNotificationData.setHeadsUpManager(headsUpManager);
mListContainer = listContainer;
@@ -181,14 +173,6 @@
return mNotificationData;
}
- protected Context getContext() {
- return mContext;
- }
-
- protected NotificationPresenter getPresenter() {
- return mPresenter;
- }
-
@Override
public void onReorderingAllowed() {
updateNotifications();
@@ -205,11 +189,11 @@
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();
}
@@ -230,17 +214,8 @@
}
}
- 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
@@ -257,7 +232,6 @@
for (NotificationEntryListener listener : mNotificationEntryListeners) {
listener.onNotificationAdded(entry);
}
- maybeScheduleUpdateNotificationViews(entry);
} else {
for (NotificationEntryListener listener : mNotificationEntryListeners) {
listener.onEntryReinflated(entry);
@@ -279,7 +253,7 @@
@Nullable NotificationVisibility visibility,
boolean forceRemove,
boolean removedByUser) {
- final NotificationData.Entry entry = mNotificationData.get(key);
+ final NotificationEntry entry = mNotificationData.get(key);
abortExistingInflation(key);
@@ -337,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 =
@@ -368,19 +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 void addNotificationInternal(StatusBarNotification notification,
NotificationListenerService.RankingMap rankingMap) throws InflationException {
String key = notification.getKey();
@@ -392,7 +353,7 @@
NotificationListenerService.Ranking ranking = new NotificationListenerService.Ranking();
rankingMap.getRanking(key, ranking);
- NotificationData.Entry entry = new NotificationData.Entry(notification, ranking);
+ NotificationEntry entry = new NotificationEntry(notification, ranking);
Dependency.get(LeakDetector.class).trackInstance(entry);
// Construct the expanded view.
@@ -445,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;
}
@@ -461,14 +422,12 @@
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)
@@ -477,10 +436,8 @@
}
for (NotificationEntryListener listener : mNotificationEntryListeners) {
- listener.onEntryUpdated(entry);
+ listener.onPostEntryUpdated(entry);
}
-
- maybeScheduleUpdateNotificationViews(entry);
}
@Override
@@ -500,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);
@@ -519,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),
@@ -537,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);
}
@@ -548,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 058efca..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;
@@ -126,7 +127,7 @@
* Inflates the views for the given entry (possibly asynchronously).
*/
public void inflateViews(
- NotificationData.Entry entry,
+ NotificationEntry entry,
Runnable onDismissRunnable,
boolean isUpdate)
throws InflationException {
@@ -149,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());
@@ -197,7 +198,7 @@
* reinflating them.
*/
public void onNotificationRankingUpdated(
- NotificationData.Entry entry,
+ NotificationEntry entry,
@Nullable Integer oldImportance,
NotificationUiAdjustment oldAdjustment,
NotificationUiAdjustment newAdjustment,
@@ -224,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,
@@ -292,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 43048a2..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
@@ -33,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;
@@ -115,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);
@@ -167,7 +167,7 @@
entryManager.addNotificationEntryListener(new NotificationEntryListener() {
@Override
public void onEntryRemoved(
- NotificationData.Entry entry,
+ NotificationEntry entry,
NotificationVisibility visibility,
boolean removedByUser) {
if (removedByUser && visibility != null) {
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..99b0cd2 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;
@@ -429,7 +430,7 @@
private int mHeadsUpInset;
private HeadsUpAppearanceController mHeadsUpAppearanceController;
private NotificationIconAreaController mIconAreaController;
- private float mVerticalPanelTranslation;
+ private float mHorizontalPanelTranslation;
private final NotificationLockscreenUserManager mLockscreenUserManager =
Dependency.get(NotificationLockscreenUserManager.class);
protected final NotificationGutsManager mGutsManager =
@@ -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;
@@ -4366,12 +4377,12 @@
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
private void updatePanelTranslation() {
- setTranslationX(mVerticalPanelTranslation + mAntiBurnInOffsetX * mInterpolatedDarkAmount);
+ setTranslationX(mHorizontalPanelTranslation + mAntiBurnInOffsetX * mInterpolatedDarkAmount);
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
- public void setVerticalPanelTranslation(float verticalPanelTranslation) {
- mVerticalPanelTranslation = verticalPanelTranslation;
+ public void setHorizontalPanelTranslation(float verticalPanelTranslation) {
+ mHorizontalPanelTranslation = verticalPanelTranslation;
updatePanelTranslation();
}
@@ -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/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/NavigationAssistantAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationAssistantAction.java
new file mode 100644
index 0000000..ebcd39b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationAssistantAction.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.annotation.NonNull;
+import android.os.Bundle;
+import android.view.MotionEvent;
+
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.recents.OverviewProxyService;
+
+/**
+ * Assistant is triggered with this action
+ */
+public class NavigationAssistantAction extends NavigationGestureAction {
+ private static final String TAG = "NavigationAssistantActions";
+
+ private final AssistManager mAssistManager;
+
+ public NavigationAssistantAction(@NonNull NavigationBarView navigationBarView,
+ @NonNull OverviewProxyService service, AssistManager assistManager) {
+ super(navigationBarView, service);
+ mAssistManager = assistManager;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+
+ @Override
+ public void onGestureStart(MotionEvent event) {
+ mAssistManager.startAssist(new Bundle());
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBackAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBackAction.java
index 9c8b1b1..93605ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBackAction.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBackAction.java
@@ -67,7 +67,7 @@
@Override
public boolean isEnabled() {
- return !getGlobalBoolean(NavigationPrototypeController.NAVBAR_EXPERIMENTS_DISABLED);
+ return true;
}
@Override
@@ -102,8 +102,7 @@
}
private boolean shouldExecuteBackOnUp() {
- return !getGlobalBoolean(NavigationPrototypeController.NAVBAR_EXPERIMENTS_DISABLED)
- && getGlobalBoolean(BACK_AFTER_END_PROP);
+ return getGlobalBoolean(BACK_AFTER_END_PROP);
}
private void sendEvent(int action, int code) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 6d97d67..d3c6a1d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -269,7 +269,7 @@
mIsOnDefaultDisplay = mDisplayId == Display.DEFAULT_DISPLAY;
}
- mNavigationBarView.setComponents(mStatusBar.getPanel());
+ mNavigationBarView.setComponents(mStatusBar.getPanel(), mAssistManager);
mNavigationBarView.setDisabledFlags(mDisabledFlags1);
mNavigationBarView.setOnVerticalChangedListener(this::onVerticalChanged);
mNavigationBarView.setOnTouchListener(this::onNavigationTouch);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 2fc7b78..8bf1c58 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -69,6 +69,7 @@
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.SysUiServiceProvider;
+import com.android.systemui.assist.AssistManager;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.statusbar.phone.NavGesture;
import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper;
@@ -156,6 +157,7 @@
private QuickStepAction mQuickStepAction;
private NavigationBackAction mBackAction;
private QuickSwitchAction mQuickSwitchAction;
+ private NavigationAssistantAction mAssistantAction;
/**
* Helper that is responsible for showing the right toast when a disallowed activity operation
@@ -366,8 +368,12 @@
return mBarTransitions.getLightTransitionsController();
}
- public void setComponents(NotificationPanelView panel) {
+ public void setComponents(NotificationPanelView panel, AssistManager assistManager) {
mPanelView = panel;
+ if (mAssistantAction == null) {
+ mAssistantAction = new NavigationAssistantAction(this, mOverviewProxyService,
+ assistManager);
+ }
if (mGestureHelper instanceof QuickStepController) {
((QuickStepController) mGestureHelper).setComponents(this);
updateNavigationGestures();
@@ -398,6 +404,10 @@
return mBackAction;
case NavigationPrototypeController.ACTION_QUICKSWITCH:
return mQuickSwitchAction;
+ case NavigationPrototypeController.ACTION_ASSISTANT:
+ return mAssistantAction;
+ case NavigationPrototypeController.ACTION_NOTHING:
+ return null;
default:
return defaultAction;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java
index 8c57fc3..a5d9382 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java
@@ -24,6 +24,7 @@
import android.annotation.NonNull;
import android.content.Context;
import android.graphics.Canvas;
+import android.provider.Settings;
import android.view.MotionEvent;
import com.android.systemui.recents.OverviewProxyService;
@@ -32,6 +33,9 @@
* A gesture action that would be triggered and reassigned by {@link QuickStepController}
*/
public abstract class NavigationGestureAction {
+ private static final String ENABLE_TASK_STABILIZER_FLAG = "ENABLE_TASK_STABILIZER";
+
+ static private boolean sLastTaskStabilizationFlag;
protected final NavigationBarView mNavigationBarView;
protected final OverviewProxyService mProxySender;
@@ -45,6 +49,9 @@
@NonNull OverviewProxyService service) {
mNavigationBarView = navigationBarView;
mProxySender = service;
+ sLastTaskStabilizationFlag = Settings.Global.getInt(
+ mNavigationBarView.getContext().getContentResolver(),
+ ENABLE_TASK_STABILIZER_FLAG, 0) != 0;
}
/**
@@ -74,6 +81,15 @@
*/
public void startGesture(MotionEvent event) {
mIsActive = true;
+
+ // Tell launcher that this action requires a stable task list or not
+ boolean flag = requiresStableTaskList();
+ if (flag != sLastTaskStabilizationFlag) {
+ Settings.Global.putInt(mNavigationBarView.getContext().getContentResolver(),
+ ENABLE_TASK_STABILIZER_FLAG, flag ? 1 : 0);
+ sLastTaskStabilizationFlag = flag;
+ }
+
onGestureStart(event);
}
@@ -146,6 +162,13 @@
*/
public abstract boolean isEnabled();
+ /**
+ * @return action requires a stable task list from launcher
+ */
+ protected boolean requiresStableTaskList() {
+ return false;
+ }
+
protected void onDarkIntensityChange(float intensity) {
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java
index fb6254b..a09e585 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java
@@ -22,7 +22,6 @@
import android.net.Uri;
import android.os.Handler;
import android.provider.Settings;
-import android.provider.Settings.SettingNotFoundException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -36,18 +35,20 @@
private static final String HIDE_BACK_BUTTON_SETTING = "quickstepcontroller_hideback";
private static final String HIDE_HOME_BUTTON_SETTING = "quickstepcontroller_hidehome";
- static final String NAVBAR_EXPERIMENTS_DISABLED = "navbarexperiments_disabled";
private final String GESTURE_MATCH_SETTING = "quickstepcontroller_gesture_match_map";
public static final String NAV_COLOR_ADAPT_ENABLE_SETTING = "navbar_color_adapt_enable";
@Retention(RetentionPolicy.SOURCE)
- @IntDef({ACTION_DEFAULT, ACTION_QUICKSTEP, ACTION_QUICKSCRUB, ACTION_BACK})
+ @IntDef({ACTION_DEFAULT, ACTION_QUICKSTEP, ACTION_QUICKSCRUB, ACTION_BACK,
+ ACTION_QUICKSWITCH, ACTION_NOTHING, ACTION_ASSISTANT})
@interface GestureAction {}
static final int ACTION_DEFAULT = 0;
static final int ACTION_QUICKSTEP = 1;
static final int ACTION_QUICKSCRUB = 2;
static final int ACTION_BACK = 3;
static final int ACTION_QUICKSWITCH = 4;
+ static final int ACTION_NOTHING = 5;
+ static final int ACTION_ASSISTANT = 6;
private OnPrototypeChangedListener mListener;
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 af3257a..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,7 +219,7 @@
@Override
public void onEntryRemoved(
- @Nullable Entry entry,
+ @Nullable NotificationEntry entry,
NotificationVisibility visibility,
boolean removedByUser) {
// Removes any alerts pending on this entry. Note that this will not stop any inflation
@@ -241,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++;
}
@@ -260,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;
}
@@ -276,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)
@@ -293,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 =
@@ -309,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()
@@ -333,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);
@@ -353,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;
@@ -368,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 */);
@@ -399,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)) {
@@ -419,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;
}
@@ -439,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
@@ -450,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..512f56d 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) {
@@ -1233,7 +1233,7 @@
updateDozingVisibilities(false /* animate */);
}
- resetVerticalPanelPosition();
+ resetHorizontalPanelPosition();
updateQsState();
}
@@ -2043,7 +2043,7 @@
super.onConfigurationChanged(newConfig);
mAffordanceHelper.onConfigurationChanged();
if (newConfig.orientation != mLastOrientation) {
- resetVerticalPanelPosition();
+ resetHorizontalPanelPosition();
}
mLastOrientation = newConfig.orientation;
}
@@ -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);
}
@@ -2529,7 +2529,7 @@
@Override
protected void onClosingFinished() {
super.onClosingFinished();
- resetVerticalPanelPosition();
+ resetHorizontalPanelPosition();
setClosingWithAlphaFadeout(false);
}
@@ -2546,7 +2546,7 @@
*/
protected void updateVerticalPanelPosition(float x) {
if (mNotificationStackScroller.getWidth() * 1.75f > getWidth()) {
- resetVerticalPanelPosition();
+ resetHorizontalPanelPosition();
return;
}
float leftMost = mPositionMinSideMargin + mNotificationStackScroller.getWidth() / 2;
@@ -2556,16 +2556,17 @@
x = getWidth() / 2;
}
x = Math.min(rightMost, Math.max(leftMost, x));
- setVerticalPanelTranslation(x -
- (mNotificationStackScroller.getLeft() + mNotificationStackScroller.getWidth() / 2));
+ float center =
+ mNotificationStackScroller.getLeft() + mNotificationStackScroller.getWidth() / 2;
+ setHorizontalPanelTranslation(x - center);
}
- private void resetVerticalPanelPosition() {
- setVerticalPanelTranslation(0f);
+ private void resetHorizontalPanelPosition() {
+ setHorizontalPanelTranslation(0f);
}
- protected void setVerticalPanelTranslation(float translation) {
- mNotificationStackScroller.setVerticalPanelTranslation(translation);
+ protected void setHorizontalPanelTranslation(float translation) {
+ mNotificationStackScroller.setHorizontalPanelTranslation(translation);
mQsFrame.setTranslationX(translation);
int size = mVerticalTranslationListener.size();
for (int i = 0; i < size; i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepAction.java
index b18b79e..1999f9a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepAction.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepAction.java
@@ -47,6 +47,10 @@
return mNavigationBarView.isQuickStepSwipeUpEnabled();
}
+ protected boolean requiresStableTaskList() {
+ return true;
+ }
+
@Override
public void onGestureStart(MotionEvent event) {
try {
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 fb3157a..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,19 +185,19 @@
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,
+ @Nullable NotificationEntry entry,
NotificationVisibility visibility,
boolean removedByUser) {
StatusBarNotificationPresenter.this.onNotificationRemoved(
@@ -206,14 +208,12 @@
}
};
- 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,
@@ -223,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.
@@ -242,7 +242,7 @@
MessagingMessage.dropCache();
MessagingGroup.dropCache();
if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) {
- mEntryManager.updateNotificationsOnDensityOrFontScaleChanged();
+ updateNotificationsOnDensityOrFontScaleChanged();
} else {
mReinflateNotificationsOnUserSwitched = true;
}
@@ -258,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();
@@ -269,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()
@@ -323,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;
}
@@ -367,7 +380,7 @@
if (MULTIUSER_DEBUG) mNotificationPanelDebugText.setText("USER " + newUserId);
mCommandQueue.animateCollapsePanels();
if (mReinflateNotificationsOnUserSwitched) {
- mEntryManager.updateNotificationsOnDensityOrFontScaleChanged();
+ updateNotificationsOnDensityOrFontScaleChanged();
mReinflateNotificationsOnUserSwitched = false;
}
if (mDispatchUiModeChangeOnUserSwitched) {
@@ -381,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);
@@ -439,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/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index f65f826..5e94152 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -48,18 +48,36 @@
}
/**
- * A listener that will be notified whenever a change in battery level or power save mode
- * has occurred.
+ * A listener that will be notified whenever a change in battery level or power save mode has
+ * occurred.
*/
interface BatteryStateChangeCallback {
- default void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {}
- default void onPowerSaveChanged(boolean isPowerSave) {}
+
+ default void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
+ }
+
+ default void onPowerSaveChanged(boolean isPowerSave) {
+ }
}
/**
- * If available, get the estimated battery time remaining as a string
+ * If available, get the estimated battery time remaining as a string.
+ *
+ * @param completion A lambda that will be called with the result of fetching the estimate. The
+ * first time this method is called may need to be dispatched to a background thread. The
+ * completion is called on the main thread
*/
- default String getEstimatedTimeRemainingString() {
- return null;
+ default void getEstimatedTimeRemainingString(EstimateFetchCompletion completion) {}
+
+ /**
+ * Callback called when the estimated time remaining text is fetched.
+ */
+ public interface EstimateFetchCompletion {
+
+ /**
+ * The callback
+ * @param estimate the estimate
+ */
+ void onBatteryRemainingEstimateRetrieved(String estimate);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 6190c8f..af3c96f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -27,9 +27,12 @@
import android.os.PowerSaveState;
import android.util.Log;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.fuelgauge.BatterySaverUtils;
import com.android.settingslib.utils.PowerUtil;
+import com.android.systemui.Dependency;
import com.android.systemui.power.EnhancedEstimates;
import com.android.systemui.power.Estimate;
@@ -56,6 +59,7 @@
private final EnhancedEstimates mEstimates;
private final ArrayList<BatteryController.BatteryStateChangeCallback> mChangeCallbacks = new ArrayList<>();
+ private final ArrayList<EstimateFetchCompletion> mFetchCallbacks = new ArrayList<>();
private final PowerManager mPowerManager;
private final Handler mHandler;
private final Context mContext;
@@ -70,6 +74,7 @@
private boolean mHasReceivedBattery = false;
private Estimate mEstimate;
private long mLastEstimateTimestamp = -1;
+ private boolean mFetchingEstimate = false;
@Inject
public BatteryControllerImpl(Context context, EnhancedEstimates enhancedEstimates) {
@@ -197,20 +202,61 @@
}
@Override
- public String getEstimatedTimeRemainingString() {
- if (mEstimate == null
- || System.currentTimeMillis() > mLastEstimateTimestamp + UPDATE_GRANULARITY_MSEC) {
- updateEstimate();
+ public void getEstimatedTimeRemainingString(EstimateFetchCompletion completion) {
+ if (mEstimate != null
+ && mLastEstimateTimestamp > System.currentTimeMillis() - UPDATE_GRANULARITY_MSEC) {
+ String percentage = generateTimeRemainingString();
+ completion.onBatteryRemainingEstimateRetrieved(percentage);
+ return;
}
- // Estimates may not exist yet even if we've checked
+
+ // Need to fetch or refresh the estimate, but it may involve binder calls so offload the
+ // work
+ synchronized (mFetchCallbacks) {
+ mFetchCallbacks.add(completion);
+ }
+ updateEstimateInBackground();
+ }
+
+ @Nullable
+ private String generateTimeRemainingString() {
if (mEstimate == null) {
return null;
}
- final String percentage = NumberFormat.getPercentInstance().format((double) mLevel / 100.0);
+
+ String percentage = NumberFormat.getPercentInstance().format((double) mLevel / 100.0);
return PowerUtil.getBatteryRemainingShortStringFormatted(
mContext, mEstimate.estimateMillis);
}
+ private void updateEstimateInBackground() {
+ if (mFetchingEstimate) {
+ // Already dispatched a fetch. It will notify all listeners when finished
+ return;
+ }
+
+ mFetchingEstimate = true;
+ Dependency.get(Dependency.BG_HANDLER).post(() -> {
+ mEstimate = mEstimates.getEstimate();
+ mLastEstimateTimestamp = System.currentTimeMillis();
+ mFetchingEstimate = false;
+
+ Dependency.get(Dependency.MAIN_HANDLER).post(this::notifyEstimateFetchCallbacks);
+ });
+ }
+
+ private void notifyEstimateFetchCallbacks() {
+ String estimate = generateTimeRemainingString();
+
+ synchronized (mFetchCallbacks) {
+ for (EstimateFetchCompletion completion : mFetchCallbacks) {
+ completion.onBatteryRemainingEstimateRetrieved(estimate);
+ }
+
+ mFetchCallbacks.clear();
+ }
+ }
+
private void updateEstimate() {
mEstimate = mEstimates.getEstimate();
mLastEstimateTimestamp = System.currentTimeMillis();
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 f8912bc..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),
+ 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 31df4a3..21d3652 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -33,9 +33,9 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.NotificationTestHelper;
-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.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.StatusBarWindowController;
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 e716421..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;
@@ -102,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;
}
@@ -113,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());
@@ -142,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());
@@ -173,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.
@@ -199,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));
@@ -264,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;
}
@@ -283,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 6197341..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;
@@ -123,7 +127,7 @@
@Mock private RowInflaterTask mAsyncInflationTask;
@Mock private NotificationRowBinder mMockedRowBinder;
- private NotificationData.Entry mEntry;
+ private NotificationEntry mEntry;
private StatusBarNotification mSbn;
private TestableNotificationEntryManager mEntryManager;
private CountDownLatch mCountDownLatch;
@@ -136,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);
@@ -222,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);
@@ -259,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:
@@ -291,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();
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 afdeb62..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),
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 b66d0ab..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
@@ -53,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;
@@ -265,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);
@@ -282,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);
@@ -298,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);
@@ -314,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 79695fd..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);
@@ -244,9 +243,9 @@
@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);
@@ -267,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/QuickStepControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/QuickStepControllerTest.java
index 2805908..382dde9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/QuickStepControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/QuickStepControllerTest.java
@@ -97,6 +97,7 @@
doReturn(HIT_TARGET_NONE).when(mNavigationBarView).getDownHitTarget();
doReturn(backButton).when(mNavigationBarView).getBackButton();
doReturn(mResources).when(mNavigationBarView).getResources();
+ doReturn(mContext).when(mNavigationBarView).getContext();
mController = new QuickStepController(mContext);
mController.setComponents(mNavigationBarView);
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 a45a5c4..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
@@ -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));
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/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/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 3379b1b..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;
@@ -6735,6 +6735,39 @@
// 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 6bd3990..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;
@@ -139,13 +138,7 @@
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);
}
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 198a258..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;
@@ -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.
@@ -251,21 +253,22 @@
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 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
@@ -273,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();
@@ -340,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;
@@ -348,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,8 +374,8 @@
Trampoline trampoline,
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;
}
@@ -434,6 +437,19 @@
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,
@@ -447,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);
@@ -465,12 +482,9 @@
// Set up our bookkeeping
final ContentResolver resolver = context.getContentResolver();
- mSetupComplete =
- Settings.Secure.getIntForUser(
- resolver, Settings.Secure.USER_SETUP_COMPLETE, 0, mUserId)
- != 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;
ContentObserver setupObserver = new SetupObserver(this, mBackupHandler);
resolver.registerContentObserver(
@@ -489,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");
@@ -546,12 +580,17 @@
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 int getUserId() {
+ public @UserIdInt int getUserId() {
return mUserId;
}
@@ -567,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;
}
@@ -646,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;
}
@@ -687,10 +685,6 @@
mBackupRunning = backupRunning;
}
- public long getLastBackupPass() {
- return mLastBackupPass;
- }
-
public void setLastBackupPass(long lastBackupPass) {
mLastBackupPass = lastBackupPass;
}
@@ -699,10 +693,6 @@
return mClearDataLock;
}
- public boolean isClearingData() {
- return mClearingData;
- }
-
public void setClearingData(boolean clearingData) {
mClearingData = clearingData;
}
@@ -723,11 +713,6 @@
return mActiveRestoreSession;
}
- public void setActiveRestoreSession(
- ActiveRestoreSession activeRestoreSession) {
- mActiveRestoreSession = activeRestoreSession;
- }
-
public SparseArray<Operation> getCurrentOperations() {
return mCurrentOperations;
}
@@ -761,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;
}
@@ -873,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() {
@@ -1150,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)) {
@@ -1168,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);
}
@@ -1239,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));
}
}
}
@@ -2121,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()
@@ -2144,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);
@@ -2807,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 {
@@ -3021,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()";
@@ -3137,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()
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/internal/RunBackupReceiver.java b/services/backup/java/com/android/server/backup/internal/RunBackupReceiver.java
index 3b87724..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,62 +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.isSetupComplete()) {
- 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 enabled=" + backupManagerService.isEnabled()
- + " setupComplete=" + backupManagerService.isSetupComplete());
- }
+ 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
index 41eb966..62ae3eb 100644
--- a/services/backup/java/com/android/server/backup/internal/SetupObserver.java
+++ b/services/backup/java/com/android/server/backup/internal/SetupObserver.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -18,6 +18,7 @@
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;
@@ -53,13 +54,7 @@
*/
public void onChange(boolean selfChange) {
boolean previousSetupComplete = mUserBackupManagerService.isSetupComplete();
- boolean newSetupComplete =
- Settings.Secure.getIntForUser(
- mContext.getContentResolver(),
- Settings.Secure.USER_SETUP_COMPLETE,
- 0,
- mUserId)
- != 0;
+ boolean newSetupComplete = getSetupCompleteSettingForUser(mContext, mUserId);
boolean resolvedSetupComplete = previousSetupComplete || newSetupComplete;
mUserBackupManagerService.setSetupComplete(resolvedSetupComplete);
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 e10e0d6..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;
@@ -1366,7 +1368,8 @@
mDataConnectionNetworkType[phoneId] = networkType;
}
mPreciseDataConnectionState = new PreciseDataConnectionState(state, networkType,
- apnType, apn, linkProperties, "");
+ ApnSetting.getApnTypesBitmaskFromString(apnType), apn,
+ linkProperties, DataFailCause.NONE);
for (Record r : mRecords) {
if (r.matchPhoneStateListenerEvent(
PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)) {
@@ -1384,7 +1387,7 @@
broadcastDataConnectionStateChanged(state, isDataAllowed, apn, apnType, linkProperties,
networkCapabilities, roaming, subId);
broadcastPreciseDataConnectionStateChanged(state, networkType, apnType, apn,
- linkProperties, "");
+ linkProperties, DataFailCause.NONE);
}
public void notifyDataConnectionFailed(String apnType) {
@@ -1403,7 +1406,8 @@
synchronized (mRecords) {
mPreciseDataConnectionState = new PreciseDataConnectionState(
TelephonyManager.DATA_UNKNOWN,TelephonyManager.NETWORK_TYPE_UNKNOWN,
- apnType, "", null, "");
+ ApnSetting.getApnTypesBitmaskFromString(apnType), "", null,
+ DataFailCause.NONE);
for (Record r : mRecords) {
if (r.matchPhoneStateListenerEvent(
PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)) {
@@ -1418,7 +1422,8 @@
}
broadcastDataConnectionFailed(apnType, subId);
broadcastPreciseDataConnectionStateChanged(TelephonyManager.DATA_UNKNOWN,
- TelephonyManager.NETWORK_TYPE_UNKNOWN, apnType, "", null, "");
+ TelephonyManager.NETWORK_TYPE_UNKNOWN, apnType, "", null,
+ DataFailCause.NONE);
}
public void notifyCellLocation(Bundle cellLocation) {
@@ -1528,14 +1533,15 @@
}
}
- public void notifyPreciseDataConnectionFailed(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, null, failCause);
+ ApnSetting.getApnTypesBitmaskFromString(apnType), apn, null, failCause);
for (Record r : mRecords) {
if (r.matchPhoneStateListenerEvent(
PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)) {
@@ -1928,9 +1934,8 @@
}
private void broadcastPreciseDataConnectionStateChanged(int state, int networkType,
- String apnType, String apn,
- 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);
@@ -1939,7 +1944,7 @@
if (linkProperties != null) {
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 ed39d83..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);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index bc21610..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;
@@ -328,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;
@@ -2429,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
@@ -2454,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;
}
@@ -5322,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) {
@@ -14345,6 +14359,7 @@
case BLUETOOTH_UID:
case NFC_UID:
case SE_UID:
+ case NETWORK_STACK_UID:
isCallerSystem = true;
break;
default:
@@ -19687,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/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/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..24f2ef9 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);
@@ -452,9 +474,12 @@
// Send errors after the dialog is dismissed.
mHandler.postDelayed(() -> {
try {
- mCurrentAuthSession.mClientReceiver.onError(error, message);
- mCurrentAuthSession.mState = STATE_AUTH_IDLE;
- mCurrentAuthSession = null;
+ if (mCurrentAuthSession != null) {
+ mCurrentAuthSession.mClientReceiver.onError(error,
+ message);
+ mCurrentAuthSession.mState = STATE_AUTH_IDLE;
+ mCurrentAuthSession = null;
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
@@ -579,8 +604,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 +629,7 @@
modality |= pair.getKey();
}
- if (!mContinuing) {
+ if (!continuing) {
mStatusBarService.showBiometricDialog(mCurrentAuthSession.mBundle,
mInternalReceiver, modality, requireConfirmation, userId);
mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
@@ -706,7 +733,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 +753,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 +779,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 78b3c15..d57431e 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -17,6 +17,12 @@
package com.android.server.display;
import android.annotation.Nullable;
+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.PackageManager;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
@@ -27,6 +33,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
+import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
import android.util.EventLog;
@@ -34,6 +41,7 @@
import android.util.Slog;
import android.util.TimeUtils;
+import com.android.internal.os.BackgroundThread;
import com.android.server.EventLogTags;
import java.io.PrintWriter;
@@ -41,7 +49,6 @@
class AutomaticBrightnessController {
private static final String TAG = "AutomaticBrightnessController";
- private static final boolean DEBUG = false;
private static final boolean DEBUG_PRETEND_LIGHT_SENSOR_ABSENT = false;
// If true, enables the use of the screen auto-brightness adjustment setting.
@@ -56,16 +63,11 @@
// 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;
+ private static final int MSG_UPDATE_FOREGROUND_APP = 4;
+ private static final int MSG_UPDATE_FOREGROUND_APP_SYNC = 5;
// Length of the ambient light horizon used to calculate the long term estimate of ambient
// light.
@@ -126,6 +128,15 @@
private final HysteresisLevels mAmbientBrightnessThresholds;
private final HysteresisLevels mScreenBrightnessThresholds;
+ 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.
@@ -192,13 +203,26 @@
private float mShortTermModelAnchor;
private float SHORT_TERM_MODEL_THRESHOLD_RATIO = 0.6f;
+ // Context-sensitive brightness configurations require keeping track of the foreground app's
+ // 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 String mForegroundAppPackageName;
+ private String mPendingForegroundAppPackageName;
+ private @ApplicationInfo.Category int mForegroundAppCategory;
+ private @ApplicationInfo.Category int mPendingForegroundAppCategory;
+ private TaskStackListenerImpl mTaskStackListener;
+ private IActivityTaskManager mActivityTaskManager;
+ private PackageManager mPackageManager;
+
public AutomaticBrightnessController(Callbacks callbacks, Looper looper,
SensorManager sensorManager, BrightnessMappingStrategy mapper,
int lightSensorWarmUpTime, int brightnessMin, int brightnessMax, float dozeScaleFactor,
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;
@@ -216,6 +240,7 @@
mWeightingIntercept = AMBIENT_LIGHT_LONG_HORIZON_MILLIS;
mAmbientBrightnessThresholds = ambientBrightnessThresholds;
mScreenBrightnessThresholds = screenBrightnessThresholds;
+ mShortTermModelTimeout = shortTermModelTimeout;
mShortTermModelValid = true;
mShortTermModelAnchor = -1;
@@ -226,6 +251,31 @@
if (!DEBUG_PRETEND_LIGHT_SENSOR_ABSENT) {
mLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
}
+
+ mActivityTaskManager = ActivityTaskManager.getService();
+ mPackageManager = packageManager;
+ mTaskStackListener = new TaskStackListenerImpl();
+ mForegroundAppPackageName = null;
+ mPendingForegroundAppPackageName = null;
+ mForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED;
+ mPendingForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED;
+ }
+
+ /**
+ * Enable/disable logging.
+ *
+ * @param loggingEnabled
+ * Whether logging should be on/off.
+ *
+ * @return Whether the method succeeded or not.
+ */
+ public boolean setLoggingEnabled(boolean loggingEnabled) {
+ if (mLoggingEnabled == loggingEnabled) {
+ return false;
+ }
+ mBrightnessMapper.setLoggingEnabled(loggingEnabled);
+ mLoggingEnabled = loggingEnabled;
+ return true;
}
public int getAutomaticScreenBrightness() {
@@ -290,12 +340,12 @@
}
final int oldPolicy = mDisplayPolicy;
mDisplayPolicy = policy;
- if (DEBUG) {
+ if (mLoggingEnabled) {
Slog.d(TAG, "Display policy transitioning from " + oldPolicy + " to " + policy);
}
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);
}
@@ -317,7 +367,7 @@
mBrightnessMapper.addUserDataPoint(mAmbientLux, brightness);
mShortTermModelValid = true;
mShortTermModelAnchor = mAmbientLux;
- if (DEBUG) {
+ if (mLoggingEnabled) {
Slog.d(TAG, "ShortTermModel: anchor=" + mShortTermModelAnchor);
}
return true;
@@ -330,7 +380,7 @@
}
private void invalidateShortTermModel() {
- if (DEBUG) {
+ if (mLoggingEnabled) {
Slog.d(TAG, "ShortTermModel: invalidate user data");
}
mShortTermModelValid = false;
@@ -377,13 +427,17 @@
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(" mShortTermModelValid=" + mShortTermModelValid);
+ pw.println(" mForegroundAppPackageName=" + mForegroundAppPackageName);
+ pw.println(" mPendingForegroundAppPackageName=" + mPendingForegroundAppPackageName);
+ pw.println(" mForegroundAppCategory=" + mForegroundAppCategory);
+ pw.println(" mPendingForegroundAppCategory=" + mPendingForegroundAppCategory);
pw.println();
mBrightnessMapper.dump(pw);
@@ -399,6 +453,7 @@
mLightSensorEnabled = true;
mLightSensorEnableTime = SystemClock.uptimeMillis();
mCurrentLightSensorRate = mInitialLightSensorRate;
+ registerForegroundAppUpdater();
mSensorManager.registerListener(mLightSensorListener, mLightSensor,
mCurrentLightSensorRate * 1000, mHandler);
return true;
@@ -411,6 +466,7 @@
mAmbientLightRingBuffer.clear();
mCurrentLightSensorRate = -1;
mHandler.removeMessages(MSG_UPDATE_AMBIENT_LUX);
+ unregisterForegroundAppUpdater();
mSensorManager.unregisterListener(mLightSensorListener);
}
return false;
@@ -441,7 +497,7 @@
private void adjustLightSensorRate(int lightSensorRate) {
// if the light sensor rate changed, update the sensor listener
if (lightSensorRate != mCurrentLightSensorRate) {
- if (DEBUG) {
+ if (mLoggingEnabled) {
Slog.d(TAG, "adjustLightSensorRate: " +
"previousRate=" + mCurrentLightSensorRate + ", " +
"currentRate=" + lightSensorRate);
@@ -458,7 +514,7 @@
}
private void setAmbientLux(float lux) {
- if (DEBUG) {
+ if (mLoggingEnabled) {
Slog.d(TAG, "setAmbientLux(" + lux + ")");
}
if (lux < 0) {
@@ -476,7 +532,7 @@
final float maxAmbientLux =
mShortTermModelAnchor + mShortTermModelAnchor * SHORT_TERM_MODEL_THRESHOLD_RATIO;
if (minAmbientLux < mAmbientLux && mAmbientLux < maxAmbientLux) {
- if (DEBUG) {
+ if (mLoggingEnabled) {
Slog.d(TAG, "ShortTermModel: re-validate user data, ambient lux is " +
minAmbientLux + " < " + mAmbientLux + " < " + maxAmbientLux);
}
@@ -490,7 +546,7 @@
}
private float calculateAmbientLux(long now, long horizon) {
- if (DEBUG) {
+ if (mLoggingEnabled) {
Slog.d(TAG, "calculateAmbientLux(" + now + ", " + horizon + ")");
}
final int N = mAmbientLightRingBuffer.size();
@@ -509,7 +565,7 @@
break;
}
}
- if (DEBUG) {
+ if (mLoggingEnabled) {
Slog.d(TAG, "calculateAmbientLux: selected endIndex=" + endIndex + ", point=(" +
mAmbientLightRingBuffer.getTime(endIndex) + ", " +
mAmbientLightRingBuffer.getLux(endIndex) + ")");
@@ -527,7 +583,7 @@
final long startTime = eventTime - now;
float weight = calculateWeight(startTime, endTime);
float lux = mAmbientLightRingBuffer.getLux(i);
- if (DEBUG) {
+ if (mLoggingEnabled) {
Slog.d(TAG, "calculateAmbientLux: [" + startTime + ", " + endTime + "]: " +
"lux=" + lux + ", " +
"weight=" + weight);
@@ -536,7 +592,7 @@
sum += lux * weight;
endTime = startTime;
}
- if (DEBUG) {
+ if (mLoggingEnabled) {
Slog.d(TAG, "calculateAmbientLux: " +
"totalWeight=" + totalWeight + ", " +
"newAmbientLux=" + (sum / totalWeight));
@@ -591,7 +647,7 @@
final long timeWhenSensorWarmedUp =
mLightSensorWarmUpTimeConfig + mLightSensorEnableTime;
if (time < timeWhenSensorWarmedUp) {
- if (DEBUG) {
+ if (mLoggingEnabled) {
Slog.d(TAG, "updateAmbientLux: Sensor not ready yet: " +
"time=" + time + ", " +
"timeWhenSensorWarmedUp=" + timeWhenSensorWarmedUp);
@@ -602,7 +658,7 @@
}
setAmbientLux(calculateAmbientLux(time, AMBIENT_LIGHT_SHORT_HORIZON_MILLIS));
mAmbientLuxValid = true;
- if (DEBUG) {
+ if (mLoggingEnabled) {
Slog.d(TAG, "updateAmbientLux: Initializing: " +
"mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", " +
"mAmbientLux=" + mAmbientLux);
@@ -630,10 +686,10 @@
&& fastAmbientLux <= mAmbientDarkeningThreshold
&& nextDarkenTransition <= time)) {
setAmbientLux(fastAmbientLux);
- if (DEBUG) {
+ if (mLoggingEnabled) {
Slog.d(TAG, "updateAmbientLux: "
+ ((fastAmbientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": "
- + "mAmbientBrighteningThreshold=" + mAmbientBrighteningThreshold + ", "
+ + "mBrighteningLuxThreshold=" + mAmbientBrighteningThreshold + ", "
+ "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", "
+ "mAmbientLux=" + mAmbientLux);
}
@@ -650,7 +706,7 @@
// weighted ambient lux or not.
nextTransitionTime =
nextTransitionTime > time ? nextTransitionTime : time + mNormalLightSensorRate;
- if (DEBUG) {
+ if (mLoggingEnabled) {
Slog.d(TAG, "updateAmbientLux: Scheduling ambient lux update for " +
nextTransitionTime + TimeUtils.formatUptime(nextTransitionTime));
}
@@ -662,7 +718,8 @@
return;
}
- float value = mBrightnessMapper.getBrightness(mAmbientLux);
+ float value = mBrightnessMapper.getBrightness(mAmbientLux, mForegroundAppPackageName,
+ mForegroundAppCategory);
int newScreenAutoBrightness =
clampScreenBrightness(Math.round(value * PowerManager.BRIGHTNESS_ON));
@@ -673,7 +730,7 @@
if (mScreenAutoBrightness != -1
&& newScreenAutoBrightness > mScreenDarkeningThreshold
&& newScreenAutoBrightness < mScreenBrighteningThreshold) {
- if (DEBUG) {
+ if (mLoggingEnabled) {
Slog.d(TAG, "ignoring newScreenAutoBrightness: " + mScreenDarkeningThreshold
+ " < " + newScreenAutoBrightness + " < " + mScreenBrighteningThreshold);
}
@@ -681,8 +738,7 @@
}
if (mScreenAutoBrightness != newScreenAutoBrightness) {
-
- if (DEBUG) {
+ if (mLoggingEnabled) {
Slog.d(TAG, "updateAutoBrightness: " +
"mScreenAutoBrightness=" + mScreenAutoBrightness + ", " +
"newScreenAutoBrightness=" + newScreenAutoBrightness);
@@ -729,7 +785,7 @@
if (mBrightnessAdjustmentSamplePending) {
mBrightnessAdjustmentSamplePending = false;
if (mAmbientLuxValid && mScreenAutoBrightness >= 0) {
- if (DEBUG) {
+ if (mLoggingEnabled) {
Slog.d(TAG, "Auto-brightness adjustment changed by user: " +
"lux=" + mAmbientLux + ", " +
"brightness=" + mScreenAutoBrightness + ", " +
@@ -745,6 +801,83 @@
}
}
+ // Register a TaskStackListener to call back to us onTaskStackChanged, so we can update the
+ // foreground app's package name and category and correct the brightness accordingly.
+ private void registerForegroundAppUpdater() {
+ try {
+ mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
+ // This will not get called until the foreground app changes for the first time, so
+ // 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.
+ }
+ }
+
+ private void unregisterForegroundAppUpdater() {
+ try {
+ mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
+ } catch (RemoteException e) {
+ // Nothing to do.
+ }
+ mForegroundAppPackageName = null;
+ mForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED;
+ }
+
+ // 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() {
+ public void run() {
+ try {
+ // The foreground app is the top activity of the focused tasks stack.
+ final StackInfo info = mActivityTaskManager.getFocusedStackInfo();
+ if (info == null || info.topActivity == null) {
+ return;
+ }
+ final String packageName = info.topActivity.getPackageName();
+ // If the app didn't change, there's nothing to do. Otherwise, we have to
+ // update the category and re-apply the brightness correction.
+ if (mForegroundAppPackageName != null
+ && mForegroundAppPackageName.equals(packageName)) {
+ return;
+ }
+ mPendingForegroundAppPackageName = packageName;
+ 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
+ }
+ }
+ });
+ }
+
+ private void updateForegroundAppSync() {
+ if (mLoggingEnabled) {
+ Slog.d(TAG, "Updating foreground app: packageName=" + mPendingForegroundAppPackageName
+ + ", category=" + mPendingForegroundAppCategory);
+ }
+ mForegroundAppPackageName = mPendingForegroundAppPackageName;
+ mPendingForegroundAppPackageName = null;
+ mForegroundAppCategory = mPendingForegroundAppCategory;
+ mPendingForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED;
+ updateAutoBrightness(true /* sendUpdate */);
+ }
+
private final class AutomaticBrightnessHandler extends Handler {
public AutomaticBrightnessHandler(Looper looper) {
super(looper, null, true /*async*/);
@@ -764,6 +897,14 @@
case MSG_INVALIDATE_SHORT_TERM_MODEL:
invalidateShortTermModel();
break;
+
+ case MSG_UPDATE_FOREGROUND_APP:
+ updateForegroundApp();
+ break;
+
+ case MSG_UPDATE_FOREGROUND_APP_SYNC:
+ updateForegroundAppSync();
+ break;
}
}
}
@@ -784,6 +925,15 @@
}
};
+ // Call back whenever the tasks stack changes, which includes tasks being created, removed, and
+ // moving to top.
+ class TaskStackListenerImpl extends TaskStackListener {
+ @Override
+ public void onTaskStackChanged() {
+ mHandler.sendEmptyMessage(MSG_UPDATE_FOREGROUND_APP);
+ }
+ }
+
/** Callbacks to request updates to the display's power state. */
interface Callbacks {
void updateBrightness();
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index 76c191d..9fce644 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -17,9 +17,11 @@
package com.android.server.display;
import android.annotation.Nullable;
+import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.hardware.display.BrightnessConfiguration;
+import android.hardware.display.BrightnessCorrection;
import android.os.PowerManager;
import android.util.MathUtils;
import android.util.Pair;
@@ -42,11 +44,12 @@
*/
public abstract class BrightnessMappingStrategy {
private static final String TAG = "BrightnessMappingStrategy";
- private static final boolean DEBUG = false;
private static final float LUX_GRAD_SMOOTHING = 0.25f;
private static final float MAX_GRAD = 1.0f;
+ protected boolean mLoggingEnabled;
+
private static final Plog PLOG = Plog.createSystemPlog(TAG);
@Nullable
@@ -161,6 +164,22 @@
}
/**
+ * Enable/disable logging.
+ *
+ * @param loggingEnabled
+ * Whether logging should be on/off.
+ *
+ * @return Whether the method succeeded or not.
+ */
+ public boolean setLoggingEnabled(boolean loggingEnabled) {
+ if (mLoggingEnabled == loggingEnabled) {
+ return false;
+ }
+ mLoggingEnabled = loggingEnabled;
+ return true;
+ }
+
+ /**
* Sets the {@link BrightnessConfiguration}.
*
* @param config The new configuration. If {@code null} is passed, the default configuration is
@@ -170,15 +189,33 @@
public abstract boolean setBrightnessConfiguration(@Nullable BrightnessConfiguration config);
/**
- * Returns the desired brightness of the display based on the current ambient lux.
+ * Returns the desired brightness of the display based on the current ambient lux, including
+ * any context-related corrections.
*
* The returned brightness will be in the range [0, 1.0], where 1.0 is the display at max
* brightness and 0 is the display at minimum brightness.
*
* @param lux The current ambient brightness in lux.
+ * @param packageName the foreground app package name.
+ * @param category the foreground app package category.
* @return The desired brightness of the display normalized to the range [0, 1.0].
*/
- public abstract float getBrightness(float lux);
+ public abstract float getBrightness(float lux, String packageName,
+ @ApplicationInfo.Category int category);
+
+ /**
+ * Returns the desired brightness of the display based on the current ambient lux.
+ *
+ * The returned brightness wil be in the range [0, 1.0], where 1.0 is the display at max
+ * brightness and 0 is the display at minimum brightness.
+ *
+ * @param lux The current ambient brightness in lux.
+ *
+ * @return The desired brightness of the display normalized to the range [0, 1.0].
+ */
+ public float getBrightness(float lux) {
+ return getBrightness(lux, null /* packageName */, ApplicationInfo.CATEGORY_UNDEFINED);
+ }
/**
* Returns the current auto-brightness adjustment.
@@ -239,13 +276,13 @@
public abstract void dump(PrintWriter pw);
- private static float normalizeAbsoluteBrightness(int brightness) {
+ protected float normalizeAbsoluteBrightness(int brightness) {
brightness = MathUtils.constrain(brightness,
PowerManager.BRIGHTNESS_OFF, PowerManager.BRIGHTNESS_ON);
return (float) brightness / PowerManager.BRIGHTNESS_ON;
}
- private static Pair<float[], float[]> insertControlPoint(
+ private Pair<float[], float[]> insertControlPoint(
float[] luxLevels, float[] brightnessLevels, float lux, float brightness) {
final int idx = findInsertionPoint(luxLevels, lux);
final float[] newLuxLevels;
@@ -278,7 +315,7 @@
* This assumes that {@code arr} is sorted. If all values in {@code arr} are greater
* than val, then it will return the length of arr as the insertion point.
*/
- private static int findInsertionPoint(float[] arr, float val) {
+ private int findInsertionPoint(float[] arr, float val) {
for (int i = 0; i < arr.length; i++) {
if (val <= arr[i]) {
return i;
@@ -287,8 +324,8 @@
return arr.length;
}
- private static void smoothCurve(float[] lux, float[] brightness, int idx) {
- if (DEBUG) {
+ private void smoothCurve(float[] lux, float[] brightness, int idx) {
+ if (mLoggingEnabled) {
PLOG.logCurve("unsmoothed curve", lux, brightness);
}
float prevLux = lux[idx];
@@ -323,19 +360,19 @@
prevBrightness = newBrightness;
brightness[i] = newBrightness;
}
- if (DEBUG) {
+ if (mLoggingEnabled) {
PLOG.logCurve("smoothed curve", lux, brightness);
}
}
- private static float permissibleRatio(float currLux, float prevLux) {
+ private float permissibleRatio(float currLux, float prevLux) {
return MathUtils.exp(MAX_GRAD
* (MathUtils.log(currLux + LUX_GRAD_SMOOTHING)
- MathUtils.log(prevLux + LUX_GRAD_SMOOTHING)));
}
- private static float inferAutoBrightnessAdjustment(float maxGamma,
- float desiredBrightness, float currentBrightness) {
+ protected float inferAutoBrightnessAdjustment(float maxGamma, float desiredBrightness,
+ float currentBrightness) {
float adjustment = 0;
float gamma = Float.NaN;
// Extreme edge cases: use a simpler heuristic, as proper gamma correction around the edges
@@ -355,7 +392,7 @@
adjustment = -MathUtils.log(gamma) / MathUtils.log(maxGamma);
}
adjustment = MathUtils.constrain(adjustment, -1, +1);
- if (DEBUG) {
+ if (mLoggingEnabled) {
Slog.d(TAG, "inferAutoBrightnessAdjustment: " + maxGamma + "^" + -adjustment + "=" +
MathUtils.pow(maxGamma, -adjustment) + " == " + gamma);
Slog.d(TAG, "inferAutoBrightnessAdjustment: " + currentBrightness + "^" + gamma + "=" +
@@ -364,16 +401,16 @@
return adjustment;
}
- private static Pair<float[], float[]> getAdjustedCurve(float[] lux, float[] brightness,
+ protected Pair<float[], float[]> getAdjustedCurve(float[] lux, float[] brightness,
float userLux, float userBrightness, float adjustment, float maxGamma) {
float[] newLux = lux;
float[] newBrightness = Arrays.copyOf(brightness, brightness.length);
- if (DEBUG) {
+ if (mLoggingEnabled) {
PLOG.logCurve("unadjusted curve", newLux, newBrightness);
}
adjustment = MathUtils.constrain(adjustment, -1, 1);
float gamma = MathUtils.pow(maxGamma, -adjustment);
- if (DEBUG) {
+ if (mLoggingEnabled) {
Slog.d(TAG, "getAdjustedCurve: " + maxGamma + "^" + -adjustment + "=" +
MathUtils.pow(maxGamma, -adjustment) + " == " + gamma);
}
@@ -382,7 +419,7 @@
newBrightness[i] = MathUtils.pow(newBrightness[i], gamma);
}
}
- if (DEBUG) {
+ if (mLoggingEnabled) {
PLOG.logCurve("gamma adjusted curve", newLux, newBrightness);
}
if (userLux != -1) {
@@ -390,7 +427,7 @@
userBrightness);
newLux = curve.first;
newBrightness = curve.second;
- if (DEBUG) {
+ if (mLoggingEnabled) {
PLOG.logCurve("gamma and user adjusted curve", newLux, newBrightness);
// This is done for comparison.
curve = insertControlPoint(lux, brightness, userLux, userBrightness);
@@ -440,7 +477,7 @@
mAutoBrightnessAdjustment = 0;
mUserLux = -1;
mUserBrightness = -1;
- if (DEBUG) {
+ if (mLoggingEnabled) {
PLOG.start("simple mapping strategy");
}
computeSpline();
@@ -452,7 +489,8 @@
}
@Override
- public float getBrightness(float lux) {
+ public float getBrightness(float lux, String packageName,
+ @ApplicationInfo.Category int category) {
return mSpline.interpolate(lux);
}
@@ -467,7 +505,7 @@
if (adjustment == mAutoBrightnessAdjustment) {
return false;
}
- if (DEBUG) {
+ if (mLoggingEnabled) {
Slog.d(TAG, "setAutoBrightnessAdjustment: " + mAutoBrightnessAdjustment + " => " +
adjustment);
PLOG.start("auto-brightness adjustment");
@@ -485,7 +523,7 @@
@Override
public void addUserDataPoint(float lux, float brightness) {
float unadjustedBrightness = getUnadjustedBrightness(lux);
- if (DEBUG){
+ if (mLoggingEnabled) {
Slog.d(TAG, "addUserDataPoint: (" + lux + "," + brightness + ")");
PLOG.start("add user data point")
.logPoint("user data point", lux, brightness)
@@ -494,7 +532,7 @@
float adjustment = inferAutoBrightnessAdjustment(mMaxGamma,
brightness /* desiredBrightness */,
unadjustedBrightness /* currentBrightness */);
- if (DEBUG) {
+ if (mLoggingEnabled) {
Slog.d(TAG, "addUserDataPoint: " + mAutoBrightnessAdjustment + " => " +
adjustment);
}
@@ -507,7 +545,7 @@
@Override
public void clearUserDataPoints() {
if (mUserLux != -1) {
- if (DEBUG) {
+ if (mLoggingEnabled) {
Slog.d(TAG, "clearUserDataPoints: " + mAutoBrightnessAdjustment + " => 0");
PLOG.start("clear user data points")
.logPoint("user data point", mUserLux, mUserBrightness);
@@ -614,7 +652,7 @@
mBacklightToNitsSpline = Spline.createSpline(normalizedBacklight, nits);
mDefaultConfig = config;
- if (DEBUG) {
+ if (mLoggingEnabled) {
PLOG.start("physical mapping strategy");
}
mConfig = config;
@@ -629,7 +667,7 @@
if (config.equals(mConfig)) {
return false;
}
- if (DEBUG) {
+ if (mLoggingEnabled) {
PLOG.start("brightness configuration");
}
mConfig = config;
@@ -638,9 +676,17 @@
}
@Override
- public float getBrightness(float lux) {
+ public float getBrightness(float lux, String packageName,
+ @ApplicationInfo.Category int category) {
float nits = mBrightnessSpline.interpolate(lux);
float backlight = mNitsToBacklightSpline.interpolate(nits);
+ // Correct the brightness according to the current application and its category, but
+ // only if no user data point is set (as this will oevrride the user setting).
+ if (mUserLux == -1) {
+ backlight = correctBrightness(backlight, packageName, category);
+ } else if (mLoggingEnabled) {
+ Slog.d(TAG, "user point set, correction not applied");
+ }
return backlight;
}
@@ -655,7 +701,7 @@
if (adjustment == mAutoBrightnessAdjustment) {
return false;
}
- if (DEBUG) {
+ if (mLoggingEnabled) {
Slog.d(TAG, "setAutoBrightnessAdjustment: " + mAutoBrightnessAdjustment + " => " +
adjustment);
PLOG.start("auto-brightness adjustment");
@@ -673,7 +719,7 @@
@Override
public void addUserDataPoint(float lux, float brightness) {
float unadjustedBrightness = getUnadjustedBrightness(lux);
- if (DEBUG){
+ if (mLoggingEnabled) {
Slog.d(TAG, "addUserDataPoint: (" + lux + "," + brightness + ")");
PLOG.start("add user data point")
.logPoint("user data point", lux, brightness)
@@ -682,7 +728,7 @@
float adjustment = inferAutoBrightnessAdjustment(mMaxGamma,
brightness /* desiredBrightness */,
unadjustedBrightness /* currentBrightness */);
- if (DEBUG) {
+ if (mLoggingEnabled) {
Slog.d(TAG, "addUserDataPoint: " + mAutoBrightnessAdjustment + " => " +
adjustment);
}
@@ -695,7 +741,7 @@
@Override
public void clearUserDataPoints() {
if (mUserLux != -1) {
- if (DEBUG) {
+ if (mLoggingEnabled) {
Slog.d(TAG, "clearUserDataPoints: " + mAutoBrightnessAdjustment + " => 0");
PLOG.start("clear user data points")
.logPoint("user data point", mUserLux, mUserBrightness);
@@ -758,5 +804,21 @@
Spline spline = Spline.createSpline(curve.first, curve.second);
return mNitsToBacklightSpline.interpolate(spline.interpolate(lux));
}
+
+ private float correctBrightness(float brightness, String packageName, int category) {
+ if (packageName != null) {
+ BrightnessCorrection correction = mConfig.getCorrectionByPackageName(packageName);
+ if (correction != null) {
+ return correction.apply(brightness);
+ }
+ }
+ if (category != ApplicationInfo.CATEGORY_UNDEFINED) {
+ BrightnessCorrection correction = mConfig.getCorrectionByCategory(category);
+ if (correction != null) {
+ return correction.apply(brightness);
+ }
+ }
+ return brightness;
+ }
}
}
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 0a1a9a2..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,
@@ -2144,6 +2110,14 @@
mContext.getPackageName());
}
+ void setAutoBrightnessLoggingEnabled(boolean enabled) {
+ if (mDisplayPowerController != null) {
+ synchronized (mSyncRoot) {
+ mDisplayPowerController.setAutoBrightnessLoggingEnabled(enabled);
+ }
+ }
+ }
+
private boolean validatePackageName(int uid, String packageName) {
if (packageName != null) {
String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid);
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index 27cad1e..abbfc7b 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -17,14 +17,9 @@
package com.android.server.display;
import android.content.Intent;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.os.ShellCallback;
import android.os.ShellCommand;
-import android.util.Slog;
import java.io.PrintWriter;
-import java.lang.NumberFormatException;
class DisplayManagerShellCommand extends ShellCommand {
private static final String TAG = "DisplayManagerShellCommand";
@@ -46,6 +41,10 @@
return setBrightness();
case "reset-brightness-configuration":
return resetBrightnessConfiguration();
+ case "ab-logging-enable":
+ return setAutoBrightnessLoggingEnabled(true);
+ case "ab-logging-disable":
+ return setAutoBrightnessLoggingEnabled(false);
default:
return handleDefaultCommands(cmd);
}
@@ -62,6 +61,10 @@
pw.println(" Sets the current brightness to BRIGHTNESS (a number between 0 and 1).");
pw.println(" reset-brightness-configuration");
pw.println(" Reset the brightness to its default configuration.");
+ pw.println(" ab-logging-enable");
+ pw.println(" Enable auto-brightness logging.");
+ pw.println(" ab-logging-disable");
+ pw.println(" Disable auto-brightness logging.");
pw.println();
Intent.printIntentArgsHelp(pw , "");
}
@@ -89,4 +92,9 @@
mService.resetBrightnessConfiguration();
return 0;
}
+
+ private int setAutoBrightnessLoggingEnabled(boolean enabled) {
+ mService.setAutoBrightnessLoggingEnabled(enabled);
+ return 0;
+ }
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 249270b..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;
}
@@ -1836,4 +1839,10 @@
mHandler.sendMessage(msg);
}
}
+
+ void setAutoBrightnessLoggingEnabled(boolean enabled) {
+ if (mAutomaticBrightnessController != null) {
+ mAutomaticBrightnessController.setLoggingEnabled(enabled);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/display/PersistentDataStore.java b/services/core/java/com/android/server/display/PersistentDataStore.java
index 89cef62..9aec43b 100644
--- a/services/core/java/com/android/server/display/PersistentDataStore.java
+++ b/services/core/java/com/android/server/display/PersistentDataStore.java
@@ -16,13 +16,6 @@
package com.android.server.display;
-import com.android.internal.util.FastXmlSerializer;
-import com.android.internal.util.XmlUtils;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
import android.annotation.Nullable;
import android.graphics.Point;
import android.hardware.display.BrightnessConfiguration;
@@ -30,13 +23,20 @@
import android.util.AtomicFile;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.Pair;
import android.util.SparseLongArray;
import android.util.TimeUtils;
import android.util.Xml;
import android.view.Display;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
+
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
@@ -50,12 +50,9 @@
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
import java.util.Objects;
-import libcore.io.IoUtils;
-
/**
* Manages persistent state recorded by the display manager service as an XML file.
* Caller must acquire lock on the data store before accessing it.
@@ -110,14 +107,9 @@
private static final String TAG_BRIGHTNESS_CONFIGURATIONS = "brightness-configurations";
private static final String TAG_BRIGHTNESS_CONFIGURATION = "brightness-configuration";
- private static final String TAG_BRIGHTNESS_CURVE = "brightness-curve";
- private static final String TAG_BRIGHTNESS_POINT = "brightness-point";
private static final String ATTR_USER_SERIAL = "user-serial";
private static final String ATTR_PACKAGE_NAME = "package-name";
private static final String ATTR_TIME_STAMP = "timestamp";
- private static final String ATTR_LUX = "lux";
- private static final String ATTR_NITS = "nits";
- private static final String ATTR_DESCRIPTION = "description";
// Remembered Wifi display devices.
private ArrayList<WifiDisplay> mRememberedWifiDisplays = new ArrayList<WifiDisplay>();
@@ -646,7 +638,8 @@
}
try {
- BrightnessConfiguration config = loadConfigurationFromXml(parser);
+ BrightnessConfiguration config =
+ BrightnessConfiguration.loadFromXml(parser);
if (userSerial >= 0 && config != null) {
mConfigurations.put(userSerial, config);
if (timeStamp != -1) {
@@ -663,56 +656,6 @@
}
}
- private static BrightnessConfiguration loadConfigurationFromXml(XmlPullParser parser)
- throws IOException, XmlPullParserException {
- final int outerDepth = parser.getDepth();
- String description = null;
- Pair<float[], float[]> curve = null;
- while (XmlUtils.nextElementWithin(parser, outerDepth)) {
- if (TAG_BRIGHTNESS_CURVE.equals(parser.getName())) {
- description = parser.getAttributeValue(null, ATTR_DESCRIPTION);
- curve = loadCurveFromXml(parser);
- }
- }
- if (curve == null) {
- return null;
- }
- final BrightnessConfiguration.Builder builder = new BrightnessConfiguration.Builder(
- curve.first, curve.second);
- builder.setDescription(description);
- return builder.build();
- }
-
- private static Pair<float[], float[]> loadCurveFromXml(XmlPullParser parser)
- throws IOException, XmlPullParserException {
- final int outerDepth = parser.getDepth();
- List<Float> luxLevels = new ArrayList<>();
- List<Float> nitLevels = new ArrayList<>();
- while (XmlUtils.nextElementWithin(parser, outerDepth)) {
- if (TAG_BRIGHTNESS_POINT.equals(parser.getName())) {
- luxLevels.add(loadFloat(parser.getAttributeValue(null, ATTR_LUX)));
- nitLevels.add(loadFloat(parser.getAttributeValue(null, ATTR_NITS)));
- }
- }
- final int N = luxLevels.size();
- float[] lux = new float[N];
- float[] nits = new float[N];
- for (int i = 0; i < N; i++) {
- lux[i] = luxLevels.get(i);
- nits[i] = nitLevels.get(i);
- }
- return Pair.create(lux, nits);
- }
-
- private static float loadFloat(String val) {
- try {
- return Float.parseFloat(val);
- } catch (NullPointerException | NumberFormatException e) {
- Slog.e(TAG, "Failed to parse float loading brightness config", e);
- return Float.NEGATIVE_INFINITY;
- }
- }
-
public void saveToXml(XmlSerializer serializer) throws IOException {
for (int i = 0; i < mConfigurations.size(); i++) {
final int userSerial = mConfigurations.keyAt(i);
@@ -728,27 +671,11 @@
if (timestamp != -1) {
serializer.attribute(null, ATTR_TIME_STAMP, Long.toString(timestamp));
}
- saveConfigurationToXml(serializer, config);
+ config.saveToXml(serializer);
serializer.endTag(null, TAG_BRIGHTNESS_CONFIGURATION);
}
}
- private static void saveConfigurationToXml(XmlSerializer serializer,
- BrightnessConfiguration config) throws IOException {
- serializer.startTag(null, TAG_BRIGHTNESS_CURVE);
- if (config.getDescription() != null) {
- serializer.attribute(null, ATTR_DESCRIPTION, config.getDescription());
- }
- final Pair<float[], float[]> curve = config.getCurve();
- for (int i = 0; i < curve.first.length; i++) {
- serializer.startTag(null, TAG_BRIGHTNESS_POINT);
- serializer.attribute(null, ATTR_LUX, Float.toString(curve.first[i]));
- serializer.attribute(null, ATTR_NITS, Float.toString(curve.second[i]));
- serializer.endTag(null, TAG_BRIGHTNESS_POINT);
- }
- serializer.endTag(null, TAG_BRIGHTNESS_CURVE);
- }
-
public void dump(final PrintWriter pw, final String prefix) {
for (int i = 0; i < mConfigurations.size(); i++) {
final int userSerial = mConfigurations.keyAt(i);
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 1f0f94a..6f5a196 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -256,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";
@@ -273,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";
@@ -300,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.
@@ -310,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 9690ba8..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
@@ -105,29 +152,17 @@
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
@@ -271,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;
}
@@ -281,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;
}
@@ -296,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;
@@ -306,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;
@@ -338,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) {
@@ -354,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 "
@@ -370,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) {
@@ -382,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() {
@@ -450,7 +520,7 @@
return;
}
- if (setSystemAudioMode(false)) {
+ if (checkSupportAndSetSystemAudioMode(false)) {
// send <Set System Audio Mode> [“Off”]
mService.sendCecCommand(
HdmiCecMessageBuilder.buildSetSystemAudioMode(
@@ -473,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));
@@ -481,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;
}
@@ -491,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 3420b26..903045d 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -140,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.
*/
@@ -413,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);
@@ -592,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);
@@ -634,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);
@@ -998,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);
@@ -1646,6 +1663,9 @@
}
HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
if (device == null) {
+ device = audioSystem();
+ }
+ if (device == null) {
Slog.w(TAG, "Local device not available");
return;
}
@@ -1825,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;
}
@@ -2090,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..df28f30 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,
@@ -1785,15 +1760,14 @@
}
// Native callback
- private void notifyFocusChanged(IBinder token) {
- if (mFocusedWindow != token) {
- if (mFocusedWindowHasCapture) {
- setPointerCapture(false);
- }
- if (token instanceof IWindow) {
- mFocusedWindow = (IWindow) token;
- }
+ private void notifyFocusChanged(IBinder oldToken, IBinder newToken) {
+ if (mFocusedWindow.asBinder() == newToken) {
+ Log.w(TAG, "notifyFocusChanged called with unchanged mFocusedWindow=" + mFocusedWindow);
+ return;
}
+
+ setPointerCapture(false);
+ mFocusedWindow = IWindow.Stub.asInterface(newToken);
}
// 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 052d579..ce0e72b 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -40,20 +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;
@@ -73,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;
@@ -97,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;
@@ -295,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) {
@@ -308,6 +322,7 @@
user.destroySessionsForUserLocked(userId);
}
}
+ mSession2TokensPerUser.remove(userId);
updateUser();
}
}
@@ -353,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.
@@ -362,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);
}
@@ -415,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.
}
@@ -501,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);
}
@@ -515,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);
@@ -525,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);
}
@@ -722,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
@@ -876,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();
@@ -904,7 +935,17 @@
if (DEBUG) {
Log.d(TAG, "Session2 is created " + sessionToken);
}
- // TODO: Keep the session.
+ 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);
}
@@ -932,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();
@@ -1930,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 fe89be6..2dc5437 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -8572,7 +8572,7 @@
* match one in a trusted source, and should be done separately.
*/
private boolean canSkipForcedApkVerification(String apkPath) {
- if (!PackageManagerServiceUtils.isLegacyApkVerityMode()) {
+ if (!PackageManagerServiceUtils.isLegacyApkVerityEnabled()) {
return VerityUtils.hasFsverity(apkPath);
}
@@ -13562,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 =
@@ -15618,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-
@@ -15634,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;
}
@@ -16866,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/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/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/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 42121ca..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;
@@ -7012,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/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index cc57b93..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);
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/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..64120076 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,
};
@@ -252,7 +244,7 @@
const sp<IBinder>& token,
const std::string& reason);
virtual void notifyInputChannelBroken(const sp<IBinder>& token);
- virtual void notifyFocusChanged(const sp<IBinder>& token);
+ virtual void notifyFocusChanged(const sp<IBinder>& oldToken, const sp<IBinder>& newToken);
virtual bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags);
virtual void getDispatcherConfiguration(InputDispatcherConfiguration* outConfig);
virtual void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags);
@@ -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();
@@ -718,7 +738,8 @@
}
}
-void NativeInputManager::notifyFocusChanged(const sp<IBinder>& token) {
+void NativeInputManager::notifyFocusChanged(const sp<IBinder>& oldToken,
+ const sp<IBinder>& newToken) {
#if DEBUG_INPUT_DISPATCHER_POLICY
ALOGD("notifyFocusChanged");
#endif
@@ -726,12 +747,11 @@
JNIEnv* env = jniEnv();
- jobject tokenObj = javaObjectForIBinder(env, token);
- if (tokenObj) {
- env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyFocusChanged,
- tokenObj);
- checkAndClearExceptionFromCallback(env, "notifyFocusChanged");
- }
+ jobject oldTokenObj = javaObjectForIBinder(env, oldToken);
+ jobject newTokenObj = javaObjectForIBinder(env, newToken);
+ env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyFocusChanged,
+ oldTokenObj, newTokenObj);
+ checkAndClearExceptionFromCallback(env, "notifyFocusChanged");
}
void NativeInputManager::getDispatcherConfiguration(InputDispatcherConfiguration* outConfig) {
@@ -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",
@@ -1765,7 +1762,7 @@
"notifyInputChannelBroken", "(Landroid/os/IBinder;)V");
GET_METHOD_ID(gServiceClassInfo.notifyFocusChanged, clazz,
- "notifyFocusChanged", "(Landroid/os/IBinder;)V");
+ "notifyFocusChanged", "(Landroid/os/IBinder;Landroid/os/IBinder;)V");
GET_METHOD_ID(gServiceClassInfo.notifyANR, clazz,
"notifyANR",
@@ -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 b682def..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;
@@ -27,7 +28,6 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.robolectric.Shadows.shadowOf;
import static org.testng.Assert.expectThrows;
@@ -201,7 +201,7 @@
backupManagerService.stopServiceForUser(mUserOneId);
verify(mUserOneService).tearDownService();
- verifyNoMoreInteractions(mUserTwoService);
+ verify(mUserTwoService, never()).tearDownService();
}
/** Test that the service unregisters users when stopped. */
@@ -1542,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/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
index b56b5e6..b754356 100644
--- a/services/robotests/backup/src/com/android/server/backup/internal/SetupObserverTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/internal/SetupObserverTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
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 6a10ff4..2cc338c 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -70,7 +70,7 @@
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;
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/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/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/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/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 8373899..57a1826 100644
--- a/telephony/java/android/telephony/PreciseDataConnectionState.java
+++ b/telephony/java/android/telephony/PreciseDataConnectionState.java
@@ -16,10 +16,12 @@
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.data.ApnSetting;
import java.util.Objects;
@@ -31,7 +33,7 @@
* <ul>
* <li>Data connection state.
* <li>Network type of the connection.
- * <li>APN type.
+ * <li>APN types.
* <li>APN.
* <li>The properties of the network link.
* <li>Data connection fail cause.
@@ -39,14 +41,15 @@
*
* @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 LinkProperties mLinkProperties = null;
- private String mFailCause = "";
/**
* Constructor
@@ -54,11 +57,14 @@
* @hide
*/
@UnsupportedAppUsage
- public PreciseDataConnectionState(int state, int networkType, String apnType, String apn,
- 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;
mLinkProperties = linkProperties;
mFailCause = failCause;
@@ -74,73 +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();
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 the properties of the network link.
+ * Get the properties of the network link {@link LinkProperties}.
+ * @hide
*/
@UnsupportedAppUsage
public LinkProperties getDataConnectionLinkProperties() {
@@ -148,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;
}
@@ -164,10 +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.writeParcelable(mLinkProperties, flags);
- out.writeString(mFailCause);
+ out.writeInt(mFailCause);
}
public static final Parcelable.Creator<PreciseDataConnectionState> CREATOR
@@ -184,56 +168,23 @@
@Override
public int hashCode() {
- return Objects.hash(mState, mNetworkType, mAPNType, mAPN, mLinkProperties, mFailCause);
+ 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 (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
@@ -242,10 +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(", 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 8dfdb2f..b48a1ce 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -76,8 +76,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telecom.ITelecomService;
import com.android.internal.telephony.CellNetworkScanResult;
-import com.android.internal.telephony.IAns;
import com.android.internal.telephony.INumberVerificationCallback;
+import com.android.internal.telephony.IOns;
import com.android.internal.telephony.IPhoneSubInfo;
import com.android.internal.telephony.ITelephony;
import com.android.internal.telephony.ITelephonyRegistry;
@@ -4585,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;
@@ -4642,8 +4651,8 @@
return ITelephonyRegistry.Stub.asInterface(ServiceManager.getService("telephony.registry"));
}
- private IAns getIAns() {
- return IAns.Stub.asInterface(ServiceManager.getService("ians"));
+ private IOns getIOns() {
+ return IOns.Stub.asInterface(ServiceManager.getService("ions"));
}
//
@@ -5849,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();
@@ -9442,10 +9456,10 @@
}
/**
- * Enable or disable AlternativeNetworkService.
+ * Enable or disable OpportunisticNetworkService.
*
* This method should be called to enable or disable
- * AlternativeNetwork service on the device.
+ * OpportunisticNetwork service on the device.
*
* <p>
* Requires Permission:
@@ -9456,25 +9470,25 @@
* @hide
*/
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- public boolean setAlternativeNetworkState(boolean enable) {
+ public boolean setOpportunisticNetworkState(boolean enable) {
String pkgForDebug = mContext != null ? mContext.getOpPackageName() : "<unknown>";
boolean ret = false;
try {
- IAns iAlternativeNetworkService = getIAns();
- if (iAlternativeNetworkService != null) {
- ret = iAlternativeNetworkService.setEnable(enable, pkgForDebug);
+ IOns iOpportunisticNetworkService = getIOns();
+ if (iOpportunisticNetworkService != null) {
+ ret = iOpportunisticNetworkService.setEnable(enable, pkgForDebug);
}
} catch (RemoteException ex) {
- Rlog.e(TAG, "enableAlternativeNetwork RemoteException", ex);
+ Rlog.e(TAG, "enableOpportunisticNetwork RemoteException", ex);
}
return ret;
}
/**
- * is AlternativeNetworkService enabled
+ * is OpportunisticNetworkService enabled
*
- * This method should be called to determine if the AlternativeNetworkService is
+ * This method should be called to determine if the OpportunisticNetworkService is
* enabled
*
* <p>
@@ -9483,17 +9497,17 @@
* @hide
*/
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
- public boolean isAlternativeNetworkEnabled() {
+ public boolean isOpportunisticNetworkEnabled() {
String pkgForDebug = mContext != null ? mContext.getOpPackageName() : "<unknown>";
boolean isEnabled = false;
try {
- IAns iAlternativeNetworkService = getIAns();
- if (iAlternativeNetworkService != null) {
- isEnabled = iAlternativeNetworkService.isEnabled(pkgForDebug);
+ IOns iOpportunisticNetworkService = getIOns();
+ if (iOpportunisticNetworkService != null) {
+ isEnabled = iOpportunisticNetworkService.isEnabled(pkgForDebug);
}
} catch (RemoteException ex) {
- Rlog.e(TAG, "enableAlternativeNetwork RemoteException", ex);
+ Rlog.e(TAG, "enableOpportunisticNetwork RemoteException", ex);
}
return isEnabled;
@@ -9848,9 +9862,9 @@
public boolean setPreferredOpportunisticDataSubscription(int subId) {
String pkgForDebug = mContext != null ? mContext.getOpPackageName() : "<unknown>";
try {
- IAns iAlternativeNetworkService = getIAns();
- if (iAlternativeNetworkService != null) {
- return iAlternativeNetworkService.setPreferredData(subId, pkgForDebug);
+ IOns iOpportunisticNetworkService = getIOns();
+ if (iOpportunisticNetworkService != null) {
+ return iOpportunisticNetworkService.setPreferredData(subId, pkgForDebug);
}
} catch (RemoteException ex) {
Rlog.e(TAG, "setPreferredData RemoteException", ex);
@@ -9872,9 +9886,9 @@
String pkgForDebug = mContext != null ? mContext.getOpPackageName() : "<unknown>";
int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
try {
- IAns iAlternativeNetworkService = getIAns();
- if (iAlternativeNetworkService != null) {
- subId = iAlternativeNetworkService.getPreferredData(pkgForDebug);
+ IOns iOpportunisticNetworkService = getIOns();
+ if (iOpportunisticNetworkService != null) {
+ subId = iOpportunisticNetworkService.getPreferredData(pkgForDebug);
}
} catch (RemoteException ex) {
Rlog.e(TAG, "getPreferredData RemoteException", ex);
@@ -9885,8 +9899,8 @@
/**
* Update availability of a list of networks in the current location.
*
- * This api should be called to inform AlternativeNetwork Service about the availability
- * of a network at the current location. This information will be used by AlternativeNetwork
+ * This api should be called to inform OpportunisticNetwork Service about the availability
+ * of a network at the current location. This information will be used by OpportunisticNetwork
* service to decide to attach to the network opportunistically. If an empty list is passed,
* it is assumed that no network is available.
* Requires that the calling app has carrier privileges on both primary and
@@ -9901,9 +9915,9 @@
String pkgForDebug = mContext != null ? mContext.getOpPackageName() : "<unknown>";
boolean ret = false;
try {
- IAns iAlternativeNetworkService = getIAns();
- if (iAlternativeNetworkService != null) {
- ret = iAlternativeNetworkService.updateAvailableNetworks(availableNetworks,
+ IOns iOpportunisticNetworkService = getIOns();
+ if (iOpportunisticNetworkService != null) {
+ ret = iOpportunisticNetworkService.updateAvailableNetworks(availableNetworks,
pkgForDebug);
}
} catch (RemoteException ex) {
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/RcsParticipant.java b/telephony/java/android/telephony/ims/RcsParticipant.java
index 70500aa..f678ec7e 100644
--- a/telephony/java/android/telephony/ims/RcsParticipant.java
+++ b/telephony/java/android/telephony/ims/RcsParticipant.java
@@ -15,22 +15,111 @@
*/
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;
+
/**
- * Returns the row id of this participant.
+ * Constructor for {@link com.android.internal.telephony.ims.RcsMessageStoreController}
+ * to create instances of participants. This is not meant to be part of the SDK.
*
- * TODO(sahinc) implement
+ * @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 12345;
+ return mId;
}
public static final Creator<RcsParticipant> CREATOR = new Creator<RcsParticipant>() {
@@ -46,6 +135,9 @@
};
protected RcsParticipant(Parcel in) {
+ mId = in.readInt();
+ mCanonicalAddress = in.readString();
+ mAlias = in.readString();
}
@Override
@@ -55,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/aidl/IRcs.aidl b/telephony/java/android/telephony/ims/aidl/IRcs.aidl
index 9badac5..0c958ba 100644
--- a/telephony/java/android/telephony/ims/aidl/IRcs.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IRcs.aidl
@@ -37,8 +37,13 @@
Rcs1To1Thread createRcs1To1Thread(in RcsParticipant participant);
- RcsParticipant createRcsParticipant(String canonicalAddress);
-
// 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/IAns.aidl b/telephony/java/com/android/internal/telephony/IOns.aidl
similarity index 92%
rename from telephony/java/com/android/internal/telephony/IAns.aidl
rename to telephony/java/com/android/internal/telephony/IOns.aidl
index 98bcd41..d6779f1 100755
--- a/telephony/java/com/android/internal/telephony/IAns.aidl
+++ b/telephony/java/com/android/internal/telephony/IOns.aidl
@@ -18,13 +18,13 @@
import android.telephony.AvailableNetworkInfo;
-interface IAns {
+interface IOns {
/**
- * Enable or disable Alternative Network service.
+ * Enable or disable Opportunistic Network service.
*
* This method should be called to enable or disable
- * AlternativeNetwork service on the device.
+ * OpportunisticNetwork service on the device.
*
* <p>
* Requires Permission:
@@ -38,9 +38,9 @@
boolean setEnable(boolean enable, String callingPackage);
/**
- * is Alternative Network service enabled
+ * is Opportunistic Network service enabled
*
- * This method should be called to determine if the Alternative Network service is enabled
+ * This method should be called to determine if the Opportunistic Network service is enabled
*
* <p>
* Requires Permission:
@@ -84,7 +84,7 @@
* Update availability of a list of networks in the current location.
*
* This api should be called if the caller is aware of the availability of a network
- * at the current location. This information will be used by AlternativeNetwork service
+ * at the current location. This information will be used by OpportunisticNetwork service
* to decide to attach to the network. If an empty list is passed,
* it is assumed that no network is available.
* Requires that the calling app has carrier privileges on both primary and
diff --git a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 02a6f31..5632c63 100644
--- a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -68,7 +68,7 @@
int backgroundCallState);
void notifyDisconnectCause(int disconnectCause, int preciseDisconnectCause);
void notifyPreciseDataConnectionFailed(String apnType, String apn,
- String failCause);
+ 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/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/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/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/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/aapt2/Android.bp b/tools/aapt2/Android.bp
index c42a888..0580df6 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -58,6 +58,7 @@
"libprotobuf-cpp-lite",
"libz",
],
+ stl: "libc++_static",
group_static_libs: true,
}
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/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));
}
/**