summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp1
-rw-r--r--api/current.txt5
-rwxr-xr-xapi/system-current.txt9
-rw-r--r--core/java/android/app/AppOpsManager.java25
-rw-r--r--core/java/android/content/pm/PackageInstaller.java7
-rw-r--r--core/proto/android/app/enums.proto4
-rw-r--r--core/res/AndroidManifest.xml19
-rw-r--r--core/res/res/values/strings.xml5
-rw-r--r--core/tests/coretests/src/android/widget/EditorCursorDragTest.java6
-rw-r--r--core/tests/coretests/src/android/widget/EditorTouchStateTest.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java50
-rw-r--r--packages/Shell/AndroidManifest.xml2
-rw-r--r--packages/SystemUI/res/values/strings.xml22
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java79
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt2
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java8
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java3
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java8
-rw-r--r--services/people/java/com/android/server/people/data/DataManager.java31
-rw-r--r--services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java58
-rw-r--r--services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java59
-rw-r--r--services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java406
-rw-r--r--services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java81
-rw-r--r--services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java25
-rw-r--r--services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java406
-rw-r--r--telephony/java/android/telephony/SmsManager.java2
-rw-r--r--tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java44
29 files changed, 1291 insertions, 127 deletions
diff --git a/Android.bp b/Android.bp
index b0e0b35a1f76..7f4cd849ab77 100644
--- a/Android.bp
+++ b/Android.bp
@@ -722,6 +722,7 @@ filegroup {
srcs: [
"core/java/android/annotation/StringDef.java",
"core/java/android/net/annotations/PolicyDirection.java",
+ "core/java/com/android/internal/util/HexDump.java",
"core/java/com/android/internal/util/IState.java",
"core/java/com/android/internal/util/State.java",
"core/java/com/android/internal/util/StateMachine.java",
diff --git a/api/current.txt b/api/current.txt
index ff74ce8b43dc..ea9da1513528 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -9,7 +9,6 @@ package android {
ctor public Manifest.permission();
field public static final String ACCEPT_HANDOVER = "android.permission.ACCEPT_HANDOVER";
field public static final String ACCESS_BACKGROUND_LOCATION = "android.permission.ACCESS_BACKGROUND_LOCATION";
- field public static final String ACCESS_CALL_AUDIO = "android.permission.ACCESS_CALL_AUDIO";
field public static final String ACCESS_CHECKIN_PROPERTIES = "android.permission.ACCESS_CHECKIN_PROPERTIES";
field public static final String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
field public static final String ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION";
@@ -47774,7 +47773,7 @@ package android.telephony {
method public String createAppSpecificSmsToken(android.app.PendingIntent);
method @Nullable public String createAppSpecificSmsTokenWithPackageInfo(@Nullable String, @NonNull android.app.PendingIntent);
method public java.util.ArrayList<java.lang.String> divideMessage(String);
- method @Deprecated public void downloadMultimediaMessage(android.content.Context, String, android.net.Uri, android.os.Bundle, android.app.PendingIntent);
+ method public void downloadMultimediaMessage(android.content.Context, String, android.net.Uri, android.os.Bundle, android.app.PendingIntent);
method @NonNull public android.os.Bundle getCarrierConfigValues();
method public static android.telephony.SmsManager getDefault();
method public static int getDefaultSmsSubscriptionId();
@@ -47784,7 +47783,7 @@ package android.telephony {
method public int getSubscriptionId();
method public void injectSmsPdu(byte[], String, android.app.PendingIntent);
method public void sendDataMessage(String, String, short, byte[], android.app.PendingIntent, android.app.PendingIntent);
- method @Deprecated public void sendMultimediaMessage(android.content.Context, android.net.Uri, String, android.os.Bundle, android.app.PendingIntent);
+ method public void sendMultimediaMessage(android.content.Context, android.net.Uri, String, android.os.Bundle, android.app.PendingIntent);
method public void sendMultipartTextMessage(String, String, java.util.ArrayList<java.lang.String>, java.util.ArrayList<android.app.PendingIntent>, java.util.ArrayList<android.app.PendingIntent>);
method public void sendMultipartTextMessage(@NonNull String, @Nullable String, @NonNull java.util.List<java.lang.String>, @Nullable java.util.List<android.app.PendingIntent>, @Nullable java.util.List<android.app.PendingIntent>, long);
method public void sendMultipartTextMessage(@NonNull String, @Nullable String, @NonNull java.util.List<java.lang.String>, @Nullable java.util.List<android.app.PendingIntent>, @Nullable java.util.List<android.app.PendingIntent>, @NonNull String, @Nullable String);
diff --git a/api/system-current.txt b/api/system-current.txt
index 7e25382f06d9..b9153d96a56a 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -368,7 +368,6 @@ package android.app {
method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setUidMode(@NonNull String, int, int);
field public static final String OPSTR_ACCEPT_HANDOVER = "android:accept_handover";
field public static final String OPSTR_ACCESS_ACCESSIBILITY = "android:access_accessibility";
- field public static final String OPSTR_ACCESS_CALL_AUDIO = "android:access_call_audio";
field public static final String OPSTR_ACCESS_NOTIFICATIONS = "android:access_notifications";
field public static final String OPSTR_ACTIVATE_VPN = "android:activate_vpn";
field public static final String OPSTR_ASSIST_SCREENSHOT = "android:assist_screenshot";
@@ -2030,10 +2029,10 @@ package android.content.pm {
}
public static class PackageInstaller.Session implements java.io.Closeable {
- method public void addFile(int, @NonNull String, long, @NonNull byte[], @Nullable byte[]);
+ method @RequiresPermission("com.android.permission.USE_INSTALLER_V2") public void addFile(int, @NonNull String, long, @NonNull byte[], @Nullable byte[]);
method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void commitTransferred(@NonNull android.content.IntentSender);
- method @Nullable public android.content.pm.DataLoaderParams getDataLoaderParams();
- method public void removeFile(int, @NonNull String);
+ method @Nullable @RequiresPermission("com.android.permission.USE_INSTALLER_V2") public android.content.pm.DataLoaderParams getDataLoaderParams();
+ method @RequiresPermission("com.android.permission.USE_INSTALLER_V2") public void removeFile(int, @NonNull String);
}
public static class PackageInstaller.SessionInfo implements android.os.Parcelable {
@@ -2054,7 +2053,7 @@ package android.content.pm {
public static class PackageInstaller.SessionParams implements android.os.Parcelable {
method @RequiresPermission(android.Manifest.permission.ALLOCATE_AGGRESSIVE) public void setAllocateAggressive(boolean);
method @Deprecated public void setAllowDowngrade(boolean);
- method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setDataLoaderParams(@NonNull android.content.pm.DataLoaderParams);
+ method @RequiresPermission(allOf={android.Manifest.permission.INSTALL_PACKAGES, "com.android.permission.USE_INSTALLER_V2"}) public void setDataLoaderParams(@NonNull android.content.pm.DataLoaderParams);
method public void setDontKillApp(boolean);
method public void setEnableRollback(boolean);
method public void setEnableRollback(boolean, int);
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index d4749bd8f330..a5dcefcf3ab7 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1088,8 +1088,9 @@ public class AppOpsManager {
public static final int OP_ACTIVATE_PLATFORM_VPN = AppProtoEnums.APP_OP_ACTIVATE_PLATFORM_VPN;
/** @hide */
public static final int OP_LOADER_USAGE_STATS = AppProtoEnums.APP_OP_LOADER_USAGE_STATS;
- /** @hide Access telephony call audio */
- public static final int OP_ACCESS_CALL_AUDIO = AppProtoEnums.APP_OP_ACCESS_CALL_AUDIO;
+
+ // App op deprecated/removed.
+ private static final int OP_DEPRECATED_1 = AppProtoEnums.APP_OP_DEPRECATED_1;
/** @hide Auto-revoke app permissions if app is unused for an extended period */
public static final int OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED =
@@ -1396,9 +1397,6 @@ public class AppOpsManager {
@SystemApi
public static final String OPSTR_MANAGE_EXTERNAL_STORAGE =
"android:manage_external_storage";
- /** @hide Access telephony call audio */
- @SystemApi
- public static final String OPSTR_ACCESS_CALL_AUDIO = "android:access_call_audio";
/** @hide Auto-revoke app permissions if app is unused for an extended period */
@SystemApi
@@ -1498,7 +1496,6 @@ public class AppOpsManager {
OP_MANAGE_EXTERNAL_STORAGE,
OP_INTERACT_ACROSS_PROFILES,
OP_LOADER_USAGE_STATS,
- OP_ACCESS_CALL_AUDIO,
OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED,
OP_AUTO_REVOKE_MANAGED_BY_INSTALLER,
};
@@ -1608,7 +1605,7 @@ public class AppOpsManager {
OP_INTERACT_ACROSS_PROFILES, //INTERACT_ACROSS_PROFILES
OP_ACTIVATE_PLATFORM_VPN, // ACTIVATE_PLATFORM_VPN
OP_LOADER_USAGE_STATS, // LOADER_USAGE_STATS
- OP_ACCESS_CALL_AUDIO, // ACCESS_CALL_AUDIO
+ OP_DEPRECATED_1, // deprecated
OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, //AUTO_REVOKE_PERMISSIONS_IF_UNUSED
OP_AUTO_REVOKE_MANAGED_BY_INSTALLER, //OP_AUTO_REVOKE_MANAGED_BY_INSTALLER
};
@@ -1713,7 +1710,7 @@ public class AppOpsManager {
OPSTR_INTERACT_ACROSS_PROFILES,
OPSTR_ACTIVATE_PLATFORM_VPN,
OPSTR_LOADER_USAGE_STATS,
- OPSTR_ACCESS_CALL_AUDIO,
+ "", // deprecated
OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED,
OPSTR_AUTO_REVOKE_MANAGED_BY_INSTALLER,
};
@@ -1819,7 +1816,7 @@ public class AppOpsManager {
"INTERACT_ACROSS_PROFILES",
"ACTIVATE_PLATFORM_VPN",
"LOADER_USAGE_STATS",
- "ACCESS_CALL_AUDIO",
+ "deprecated",
"AUTO_REVOKE_PERMISSIONS_IF_UNUSED",
"AUTO_REVOKE_MANAGED_BY_INSTALLER",
};
@@ -1926,7 +1923,7 @@ public class AppOpsManager {
android.Manifest.permission.INTERACT_ACROSS_PROFILES,
null, // no permission for OP_ACTIVATE_PLATFORM_VPN
android.Manifest.permission.LOADER_USAGE_STATS,
- Manifest.permission.ACCESS_CALL_AUDIO,
+ null, // deprecated operation
null, // no permission for OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED
null, // no permission for OP_AUTO_REVOKE_MANAGED_BY_INSTALLER
};
@@ -2033,7 +2030,7 @@ public class AppOpsManager {
null, // INTERACT_ACROSS_PROFILES
null, // ACTIVATE_PLATFORM_VPN
null, // LOADER_USAGE_STATS
- null, // ACCESS_CALL_AUDIO
+ null, // deprecated operation
null, // AUTO_REVOKE_PERMISSIONS_IF_UNUSED
null, // AUTO_REVOKE_MANAGED_BY_INSTALLER
};
@@ -2139,7 +2136,7 @@ public class AppOpsManager {
null, // INTERACT_ACROSS_PROFILES
null, // ACTIVATE_PLATFORM_VPN
null, // LOADER_USAGE_STATS
- null, // ACCESS_CALL_AUDIO
+ null, // deprecated operation
null, // AUTO_REVOKE_PERMISSIONS_IF_UNUSED
null, // AUTO_REVOKE_MANAGED_BY_INSTALLER
};
@@ -2244,7 +2241,7 @@ public class AppOpsManager {
AppOpsManager.MODE_DEFAULT, // INTERACT_ACROSS_PROFILES
AppOpsManager.MODE_IGNORED, // ACTIVATE_PLATFORM_VPN
AppOpsManager.MODE_DEFAULT, // LOADER_USAGE_STATS
- AppOpsManager.MODE_DEFAULT, // ACCESS_CALL_AUDIO
+ AppOpsManager.MODE_IGNORED, // deprecated operation
AppOpsManager.MODE_DEFAULT, // OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED
AppOpsManager.MODE_ALLOWED, // OP_AUTO_REVOKE_MANAGED_BY_INSTALLER
};
@@ -2353,7 +2350,7 @@ public class AppOpsManager {
false, // INTERACT_ACROSS_PROFILES
false, // ACTIVATE_PLATFORM_VPN
false, // LOADER_USAGE_STATS
- false, // ACCESS_CALL_AUDIO
+ false, // deprecated operation
false, // AUTO_REVOKE_PERMISSIONS_IF_UNUSED
false, // AUTO_REVOKE_MANAGED_BY_INSTALLER
};
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 50bee854c027..1e0b2e358e17 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -1118,6 +1118,7 @@ public class PackageInstaller {
* {@hide}
*/
@SystemApi
+ @RequiresPermission(android.Manifest.permission.USE_INSTALLER_V2)
public @Nullable DataLoaderParams getDataLoaderParams() {
try {
DataLoaderParamsParcel data = mSession.getDataLoaderParams();
@@ -1157,6 +1158,7 @@ public class PackageInstaller {
* {@hide}
*/
@SystemApi
+ @RequiresPermission(android.Manifest.permission.USE_INSTALLER_V2)
public void addFile(@FileLocation int location, @NonNull String name, long lengthBytes,
@NonNull byte[] metadata, @Nullable byte[] signature) {
try {
@@ -1180,6 +1182,7 @@ public class PackageInstaller {
* {@hide}
*/
@SystemApi
+ @RequiresPermission(android.Manifest.permission.USE_INSTALLER_V2)
public void removeFile(@FileLocation int location, @NonNull String name) {
try {
mSession.removeFile(location, name);
@@ -1927,7 +1930,9 @@ public class PackageInstaller {
* {@hide}
*/
@SystemApi
- @RequiresPermission(Manifest.permission.INSTALL_PACKAGES)
+ @RequiresPermission(allOf = {
+ Manifest.permission.INSTALL_PACKAGES,
+ Manifest.permission.USE_INSTALLER_V2})
public void setDataLoaderParams(@NonNull DataLoaderParams dataLoaderParams) {
this.dataLoaderParams = dataLoaderParams;
}
diff --git a/core/proto/android/app/enums.proto b/core/proto/android/app/enums.proto
index 42437d5b44a0..563ef145b79c 100644
--- a/core/proto/android/app/enums.proto
+++ b/core/proto/android/app/enums.proto
@@ -203,9 +203,7 @@ enum AppOpEnum {
APP_OP_INTERACT_ACROSS_PROFILES = 93;
APP_OP_ACTIVATE_PLATFORM_VPN = 94;
APP_OP_LOADER_USAGE_STATS = 95;
- APP_OP_ACCESS_CALL_AUDIO = 96;
+ APP_OP_DEPRECATED_1 = 96 [deprecated = true];
APP_OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED = 97;
APP_OP_AUTO_REVOKE_MANAGED_BY_INSTALLER = 98;
}
-
-
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 84d9ce288069..eae614546dfb 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1213,15 +1213,6 @@
android:description="@string/permdesc_acceptHandovers"
android:protectionLevel="dangerous" />
- <!-- Allows an application assigned to the Dialer role to be granted access to the telephony
- call audio streams, both TX and RX.
- <p>Protection level: signature|appop
- -->
- <permission android:name="android.permission.ACCESS_CALL_AUDIO"
- android.label="@string/permlab_accessCallAudio"
- android:description="@string/permdesc_accessCallAudio"
- android:protectionLevel="signature|appop" />
-
<!-- ====================================================================== -->
<!-- Permissions for accessing the device microphone -->
<!-- ====================================================================== -->
@@ -3654,6 +3645,16 @@
<permission android:name="com.android.permission.INSTALL_EXISTING_PACKAGES"
android:protectionLevel="signature|privileged" />
+ <!-- Allows an application to use the package installer v2 APIs.
+ <p>The package installer v2 APIs are still a work in progress and we're
+ currently validating they work in all scenarios.
+ <p>Not for use by third-party applications.
+ TODO(b/152310230): remove this permission once the APIs are confirmed to be sufficient.
+ @hide
+ -->
+ <permission android:name="com.android.permission.USE_INSTALLER_V2"
+ android:protectionLevel="signature|verifier" />
+
<!-- @SystemApi @TestApi Allows an application to clear user data.
<p>Not for use by third-party applications
@hide
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 06f776013233..5b880365657c 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5482,11 +5482,6 @@
<!-- Error message. This text lets the user know that their current personal apps can't open this specific content. [CHAR LIMIT=NONE] -->
<string name="resolver_no_personal_apps_available_resolve">No personal apps can open this content</string>
- <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE] -->
- <string name="permlab_accessCallAudio">Record or play audio in telephony calls</string>
- <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE] -->
- <string name="permdesc_accessCallAudio">Allows this app, when assigned as default dialer application, to record or play audio in telephony calls.</string>
-
<!-- Icc depersonalization related strings -->
<!-- Label text for PIN entry widget on SIM Network Depersonalization panel [CHAR LIMIT=none] -->
<string name="PERSOSUBSTATE_SIM_NETWORK_ENTRY">SIM network unlock PIN</string>
diff --git a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
index 1b5ce8fd4ce5..9a93dbf67d33 100644
--- a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
+++ b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
@@ -37,6 +37,7 @@ import static org.junit.Assert.assertTrue;
import android.app.Activity;
import android.app.Instrumentation;
import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
import android.text.Layout;
import android.text.Spannable;
import android.text.SpannableString;
@@ -48,7 +49,7 @@ import android.view.MotionEvent;
import android.view.View;
import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
+import androidx.test.filters.MediumTest;
import androidx.test.filters.Suppress;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
@@ -67,7 +68,8 @@ import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
@RunWith(AndroidJUnit4.class)
-@SmallTest
+@MediumTest
+@Presubmit
public class EditorCursorDragTest {
private static final String LOG_TAG = EditorCursorDragTest.class.getSimpleName();
diff --git a/core/tests/coretests/src/android/widget/EditorTouchStateTest.java b/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
index 3dc001d68a02..ec75e40f1334 100644
--- a/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
+++ b/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
@@ -22,6 +22,7 @@ import static junit.framework.Assert.assertTrue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
+import android.platform.test.annotations.Presubmit;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
@@ -36,6 +37,7 @@ import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
@SmallTest
+@Presubmit
public class EditorTouchStateTest {
private EditorTouchState mTouchState;
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index 8bf48e59165e..c713d7813a54 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -17,6 +17,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
@@ -36,7 +37,10 @@ import com.android.settingslib.R;
import java.util.List;
-public class WifiStatusTracker extends ConnectivityManager.NetworkCallback {
+/**
+ * Track status of Wi-Fi for the Sys UI.
+ */
+public class WifiStatusTracker {
private final Context mContext;
private final WifiNetworkScoreCache mWifiNetworkScoreCache;
private final WifiManager mWifiManager;
@@ -55,8 +59,9 @@ public class WifiStatusTracker extends ConnectivityManager.NetworkCallback {
.clearCapabilities()
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build();
- private final ConnectivityManager.NetworkCallback mNetworkCallback = new ConnectivityManager
- .NetworkCallback() {
+ private final NetworkCallback mNetworkCallback = new NetworkCallback() {
+ // Note: onCapabilitiesChanged is guaranteed to be called "immediately" after onAvailable
+ // and onLinkPropertiesChanged.
@Override
public void onCapabilitiesChanged(
Network network, NetworkCapabilities networkCapabilities) {
@@ -64,11 +69,35 @@ public class WifiStatusTracker extends ConnectivityManager.NetworkCallback {
mCallback.run();
}
};
+ private final NetworkCallback mDefaultNetworkCallback = new NetworkCallback() {
+ @Override
+ public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
+ // network is now the default network, and its capabilities are nc.
+ // This method will always be called immediately after the network becomes the
+ // default, in addition to any time the capabilities change while the network is
+ // the default.
+ mDefaultNetwork = network;
+ mDefaultNetworkCapabilities = nc;
+ updateStatusLabel();
+ mCallback.run();
+ }
+ @Override
+ public void onLost(Network network) {
+ // The system no longer has a default network.
+ mDefaultNetwork = null;
+ mDefaultNetworkCapabilities = null;
+ updateStatusLabel();
+ mCallback.run();
+ }
+ };
+ private Network mDefaultNetwork = null;
+ private NetworkCapabilities mDefaultNetworkCapabilities = null;
private final Runnable mCallback;
private WifiInfo mWifiInfo;
public boolean enabled;
public boolean isCaptivePortal;
+ public boolean isDefaultNetwork;
public int state;
public boolean connected;
public String ssid;
@@ -94,11 +123,13 @@ public class WifiStatusTracker extends ConnectivityManager.NetworkCallback {
mWifiNetworkScoreCache.registerListener(mCacheListener);
mConnectivityManager.registerNetworkCallback(
mNetworkRequest, mNetworkCallback, mHandler);
+ mConnectivityManager.registerDefaultNetworkCallback(mDefaultNetworkCallback, mHandler);
} else {
mNetworkScoreManager.unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI,
mWifiNetworkScoreCache);
mWifiNetworkScoreCache.unregisterListener();
mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
+ mConnectivityManager.unregisterNetworkCallback(mDefaultNetworkCallback);
}
}
@@ -154,8 +185,17 @@ public class WifiStatusTracker extends ConnectivityManager.NetworkCallback {
}
private void updateStatusLabel() {
- final NetworkCapabilities networkCapabilities
- = mConnectivityManager.getNetworkCapabilities(mWifiManager.getCurrentNetwork());
+ NetworkCapabilities networkCapabilities;
+ final Network currentWifiNetwork = mWifiManager.getCurrentNetwork();
+ if (currentWifiNetwork != null && currentWifiNetwork.equals(mDefaultNetwork)) {
+ // Wifi is connected and the default network.
+ isDefaultNetwork = true;
+ networkCapabilities = mDefaultNetworkCapabilities;
+ } else {
+ isDefaultNetwork = false;
+ networkCapabilities = mConnectivityManager.getNetworkCapabilities(
+ mWifiManager.getCurrentNetwork());
+ }
isCaptivePortal = false;
if (networkCapabilities != null) {
if (networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) {
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index c7fb00a8130c..4771c4139a5b 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -81,6 +81,8 @@
<uses-permission android:name="android.permission.READ_INPUT_STATE" />
<uses-permission android:name="android.permission.SET_ORIENTATION" />
<uses-permission android:name="android.permission.INSTALL_PACKAGES" />
+ <!-- TODO(b/152310230): remove once APIs are confirmed to be sufficient -->
+ <uses-permission android:name="com.android.permission.USE_INSTALLER_V2" />
<uses-permission android:name="android.permission.MOVE_PACKAGE" />
<uses-permission android:name="android.permission.CLEAR_APP_USER_DATA" />
<uses-permission android:name="android.permission.CLEAR_APP_CACHE" />
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index c8c35c704297..8a3a16e9a6cf 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -374,9 +374,31 @@
<string name="biometric_dialog_wrong_password">Wrong password</string>
<!-- Error string shown when the user enters too many incorrect attempts [CHAR LIMIT=120]-->
<string name="biometric_dialog_credential_too_many_attempts">Too many incorrect attempts.\nTry again in <xliff:g id="number">%d</xliff:g> seconds.</string>
+
<!-- Error string shown when the user enters an incorrect PIN/pattern/password and it counts towards the max attempts before the data on the device is wiped. [CHAR LIMIT=NONE]-->
<string name="biometric_dialog_credential_attempts_before_wipe">Try again. Attempt <xliff:g id="attempts" example="1">%1$d</xliff:g> of <xliff:g id="max_attempts" example="3">%2$d</xliff:g>.</string>
+ <!-- Title of a dialog shown when the user only has one attempt left to provide the correct PIN/pattern/password before the device, one of its users, or a work profile is wiped. [CHAR LIMIT=NONE] -->
+ <string name="biometric_dialog_last_attempt_before_wipe_dialog_title">Your data will be deleted</string>
+ <!-- Content of a dialog shown when the user only has one attempt left to provide the correct lock pattern before the device is wiped. [CHAR LIMIT=NONE] -->
+ <string name="biometric_dialog_last_pattern_attempt_before_wipe_device">If you enter an incorrect pattern on the next attempt, this device\u2019s data will be deleted.</string>
+ <!-- Content of a dialog shown when the user only has one attempt left to provide the correct PIN before the device is wiped. [CHAR LIMIT=NONE] -->
+ <string name="biometric_dialog_last_pin_attempt_before_wipe_device">If you enter an incorrect PIN on the next attempt, this device\u2019s data will be deleted.</string>
+ <!-- Content of a dialog shown when the user only has one attempt left to provide the correct password before the device is wiped. [CHAR LIMIT=NONE] -->
+ <string name="biometric_dialog_last_password_attempt_before_wipe_device">If you enter an incorrect password on the next attempt, this device\u2019s data will be deleted.</string>
+ <!-- Content of a dialog shown when the user only has one attempt left to provide the correct lock pattern before the user is removed. [CHAR LIMIT=NONE] -->
+ <string name="biometric_dialog_last_pattern_attempt_before_wipe_user">If you enter an incorrect pattern on the next attempt, this user will be deleted.</string>
+ <!-- Content of a dialog shown when the user only has one attempt left to provide the correct PIN before the user is removed. [CHAR LIMIT=NONE] -->
+ <string name="biometric_dialog_last_pin_attempt_before_wipe_user">If you enter an incorrect PIN on the next attempt, this user will be deleted.</string>
+ <!-- Content of a dialog shown when the user only has one attempt left to provide the correct password before the user is removed. [CHAR LIMIT=NONE] -->
+ <string name="biometric_dialog_last_password_attempt_before_wipe_user">If you enter an incorrect password on the next attempt, this user will be deleted.</string>
+ <!-- Content of a dialog shown when the user only has one attempt left to provide the correct pattern before the work profile is removed. [CHAR LIMIT=NONE] -->
+ <string name="biometric_dialog_last_pattern_attempt_before_wipe_profile">If you enter an incorrect pattern on the next attempt, your work profile and its data will be deleted.</string>
+ <!-- Content of a dialog shown when the user only has one attempt left to provide the correct PIN before the work profile is removed. [CHAR LIMIT=NONE] -->
+ <string name="biometric_dialog_last_pin_attempt_before_wipe_profile">If you enter an incorrect PIN on the next attempt, your work profile and its data will be deleted.</string>
+ <!-- Content of a dialog shown when the user only has one attempt left to provide the correct password before the work profile is removed. [CHAR LIMIT=NONE] -->
+ <string name="biometric_dialog_last_password_attempt_before_wipe_profile">If you enter an incorrect password on the next attempt, your work profile and its data will be deleted.</string>
+
<!-- Content of a dialog shown when the user has failed to provide the device lock too many times and the device is wiped. [CHAR LIMIT=NONE] -->
<string name="biometric_dialog_failed_attempts_now_wiping_device">Too many incorrect attempts. This device\u2019s data will be deleted.</string>
<!-- Content of a dialog shown when the user has failed to provide the user lock too many times and the user is removed. [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
index 13f3c0fce5c2..b006bc1351a3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
@@ -347,21 +347,35 @@ public abstract class AuthCredentialView extends LinearLayout {
showError(message);
}
- // Only show popup dialog before wipe.
+ // Only show dialog if <=1 attempts are left before wiping.
final int remainingAttempts = maxAttempts - numAttempts;
- if (remainingAttempts <= 0) {
- showNowWipingMessage();
- mContainerView.animateAway(AuthDialogCallback.DISMISSED_ERROR);
+ if (remainingAttempts == 1) {
+ showLastAttemptBeforeWipeDialog();
+ } else if (remainingAttempts <= 0) {
+ showNowWipingDialog();
}
return true;
}
- private void showNowWipingMessage() {
+ private void showLastAttemptBeforeWipeDialog() {
+ final AlertDialog alertDialog = new AlertDialog.Builder(mContext)
+ .setTitle(R.string.biometric_dialog_last_attempt_before_wipe_dialog_title)
+ .setMessage(
+ getLastAttemptBeforeWipeMessageRes(getUserTypeForWipe(), mCredentialType))
+ .setPositiveButton(android.R.string.ok, null)
+ .create();
+ alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
+ alertDialog.show();
+ }
+
+ private void showNowWipingDialog() {
final AlertDialog alertDialog = new AlertDialog.Builder(mContext)
.setMessage(getNowWipingMessageRes(getUserTypeForWipe()))
.setPositiveButton(R.string.biometric_dialog_now_wiping_dialog_dismiss, null)
+ .setOnDismissListener(
+ dialog -> mContainerView.animateAway(AuthDialogCallback.DISMISSED_ERROR))
.create();
- alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+ alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
alertDialog.show();
}
@@ -377,6 +391,59 @@ public abstract class AuthCredentialView extends LinearLayout {
}
}
+ private static @StringRes int getLastAttemptBeforeWipeMessageRes(
+ @UserType int userType, @Utils.CredentialType int credentialType) {
+ switch (userType) {
+ case USER_TYPE_PRIMARY:
+ return getLastAttemptBeforeWipeDeviceMessageRes(credentialType);
+ case USER_TYPE_MANAGED_PROFILE:
+ return getLastAttemptBeforeWipeProfileMessageRes(credentialType);
+ case USER_TYPE_SECONDARY:
+ return getLastAttemptBeforeWipeUserMessageRes(credentialType);
+ default:
+ throw new IllegalArgumentException("Unrecognized user type:" + userType);
+ }
+ }
+
+ private static @StringRes int getLastAttemptBeforeWipeDeviceMessageRes(
+ @Utils.CredentialType int credentialType) {
+ switch (credentialType) {
+ case Utils.CREDENTIAL_PIN:
+ return R.string.biometric_dialog_last_pin_attempt_before_wipe_device;
+ case Utils.CREDENTIAL_PATTERN:
+ return R.string.biometric_dialog_last_pattern_attempt_before_wipe_device;
+ case Utils.CREDENTIAL_PASSWORD:
+ default:
+ return R.string.biometric_dialog_last_password_attempt_before_wipe_device;
+ }
+ }
+
+ private static @StringRes int getLastAttemptBeforeWipeProfileMessageRes(
+ @Utils.CredentialType int credentialType) {
+ switch (credentialType) {
+ case Utils.CREDENTIAL_PIN:
+ return R.string.biometric_dialog_last_pin_attempt_before_wipe_profile;
+ case Utils.CREDENTIAL_PATTERN:
+ return R.string.biometric_dialog_last_pattern_attempt_before_wipe_profile;
+ case Utils.CREDENTIAL_PASSWORD:
+ default:
+ return R.string.biometric_dialog_last_password_attempt_before_wipe_profile;
+ }
+ }
+
+ private static @StringRes int getLastAttemptBeforeWipeUserMessageRes(
+ @Utils.CredentialType int credentialType) {
+ switch (credentialType) {
+ case Utils.CREDENTIAL_PIN:
+ return R.string.biometric_dialog_last_pin_attempt_before_wipe_user;
+ case Utils.CREDENTIAL_PATTERN:
+ return R.string.biometric_dialog_last_pattern_attempt_before_wipe_user;
+ case Utils.CREDENTIAL_PASSWORD:
+ default:
+ return R.string.biometric_dialog_last_password_attempt_before_wipe_user;
+ }
+ }
+
private static @StringRes int getNowWipingMessageRes(@UserType int userType) {
switch (userType) {
case USER_TYPE_PRIMARY:
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index 6c49c82acdc0..118fcbb20f26 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -252,10 +252,17 @@ class ControlsControllerImpl @Inject constructor (
it.controlId in favoritesForComponentKeys
)
}
+ val removedControls = mutableListOf<ControlStatus>()
+ Favorites.getStructuresForComponent(componentName).forEach { st ->
+ st.controls.forEach {
+ if (it.controlId in removed) {
+ val r = createRemovedStatus(componentName, it, st.structure)
+ removedControls.add(r)
+ }
+ }
+ }
val loadData = createLoadDataObject(
- Favorites.getControlsForComponent(componentName)
- .filter { it.controlId in removed }
- .map { createRemovedStatus(componentName, it) } +
+ removedControls +
controlsWithFavorite,
favoritesForComponentKeys
)
@@ -266,17 +273,15 @@ class ControlsControllerImpl @Inject constructor (
override fun error(message: String) {
loadCanceller = null
executor.execute {
- val loadData = Favorites.getControlsForComponent(componentName)
- .let { controls ->
- val keys = controls.map { it.controlId }
- createLoadDataObject(
- controls.map {
- createRemovedStatus(componentName, it, false)
- },
- keys,
- true
- )
- }
+ val controls = Favorites.getStructuresForComponent(componentName)
+ .flatMap { st ->
+ st.controls.map {
+ createRemovedStatus(componentName, it, st.structure,
+ false)
+ }
+ }
+ val keys = controls.map { it.control.controlId }
+ val loadData = createLoadDataObject(controls, keys, true)
dataCallback.accept(loadData)
}
}
@@ -372,6 +377,7 @@ class ControlsControllerImpl @Inject constructor (
private fun createRemovedStatus(
componentName: ComponentName,
controlInfo: ControlInfo,
+ structure: CharSequence,
setRemoved: Boolean = true
): ControlStatus {
val intent = Intent(Intent.ACTION_MAIN).apply {
@@ -384,6 +390,8 @@ class ControlsControllerImpl @Inject constructor (
0)
val control = Control.StatelessBuilder(controlInfo.controlId, pendingIntent)
.setTitle(controlInfo.controlTitle)
+ .setSubtitle(controlInfo.controlSubtitle)
+ .setStructure(structure)
.setDeviceType(controlInfo.deviceType)
.build()
return ControlStatus(control, componentName, true, setRemoved)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
index 7c963869ed47..c2fc18fe21a1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
@@ -37,10 +37,10 @@ import java.util.Objects;
public class WifiSignalController extends
SignalController<WifiSignalController.WifiState, SignalController.IconGroup> {
- private final boolean mHasMobileData;
+ private final boolean mHasMobileDataFeature;
private final WifiStatusTracker mWifiTracker;
- public WifiSignalController(Context context, boolean hasMobileData,
+ public WifiSignalController(Context context, boolean hasMobileDataFeature,
CallbackHandler callbackHandler, NetworkControllerImpl networkController,
WifiManager wifiManager) {
super("WifiSignalController", context, NetworkCapabilities.TRANSPORT_WIFI,
@@ -52,7 +52,7 @@ public class WifiSignalController extends
mWifiTracker = new WifiStatusTracker(mContext, wifiManager, networkScoreManager,
connectivityManager, this::handleStatusUpdated);
mWifiTracker.setListening(true);
- mHasMobileData = hasMobileData;
+ mHasMobileDataFeature = hasMobileDataFeature;
if (wifiManager != null) {
wifiManager.registerTrafficStateCallback(context.getMainExecutor(),
new WifiTrafficStateCallback());
@@ -85,9 +85,10 @@ public class WifiSignalController extends
// only show wifi in the cluster if connected or if wifi-only
boolean visibleWhenEnabled = mContext.getResources().getBoolean(
R.bool.config_showWifiIndicatorWhenEnabled);
- boolean wifiVisible = mCurrentState.enabled
- && ((mCurrentState.connected && mCurrentState.inetCondition == 1)
- || !mHasMobileData || visibleWhenEnabled);
+ boolean wifiVisible = mCurrentState.enabled && (
+ (mCurrentState.connected && mCurrentState.inetCondition == 1)
+ || !mHasMobileDataFeature || mWifiTracker.isDefaultNetwork
+ || visibleWhenEnabled);
String wifiDesc = mCurrentState.connected ? mCurrentState.ssid : null;
boolean ssidPresent = wifiVisible && mCurrentState.ssid != null;
String contentDescription = getTextIfExists(getContentDescription()).toString();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
index d5a654dc2b6f..eb4d438600d8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
@@ -307,6 +307,7 @@ class ControlsControllerImplTest : SysuiTestCase() {
assertEquals(1, controls.size)
val controlStatus = controls[0]
assertEquals(TEST_CONTROL_ID, controlStatus.control.controlId)
+ assertEquals(TEST_STRUCTURE_INFO.structure, controlStatus.control.structure)
assertTrue(controlStatus.favorite)
assertTrue(controlStatus.removed)
@@ -337,6 +338,7 @@ class ControlsControllerImplTest : SysuiTestCase() {
assertEquals(1, controls.size)
val controlStatus = controls[0]
assertEquals(TEST_CONTROL_ID, controlStatus.control.controlId)
+ assertEquals(TEST_STRUCTURE_INFO.structure, controlStatus.control.structure)
assertTrue(controlStatus.favorite)
assertFalse(controlStatus.removed)
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 2ff3d2a3a938..8ff7ea9d61dd 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -490,6 +490,14 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
throw new SecurityException("User restriction prevents installing");
}
+ if (params.dataLoaderParams != null
+ && mContext.checkCallingOrSelfPermission(Manifest.permission.USE_INSTALLER_V2)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("You need the "
+ + "com.android.permission.USE_INSTALLER_V2 permission "
+ + "to use a data loader");
+ }
+
String requestedInstallerPackageName = params.installerPackageName != null
? params.installerPackageName : installerPackageName;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 348a4cb77163..42ed20654a2c 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -2479,12 +2479,14 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@Override
public DataLoaderParamsParcel getDataLoaderParams() {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.USE_INSTALLER_V2, null);
return params.dataLoaderParams != null ? params.dataLoaderParams.getData() : null;
}
@Override
public void addFile(int location, String name, long lengthBytes, byte[] metadata,
byte[] signature) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.USE_INSTALLER_V2, null);
if (!isDataLoaderInstallation()) {
throw new IllegalStateException(
"Cannot add files to non-data loader installation session.");
@@ -2517,6 +2519,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@Override
public void removeFile(int location, String name) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.USE_INSTALLER_V2, null);
if (!isDataLoaderInstallation()) {
throw new IllegalStateException(
"Cannot add files to non-data loader installation session.");
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
index b051bab71f12..8ff2a1b6364b 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
@@ -37,9 +37,9 @@ class TimeZoneDetectorShellCommand extends ShellCommand {
}
switch (cmd) {
- case "suggestTelephonyTimeZone":
+ case "suggest_telephony_time_zone":
return runSuggestTelephonyTimeZone();
- case "suggestManualTimeZone":
+ case "suggest_manual_time_zone":
return runSuggestManualTimeZone();
default: {
return handleDefaultCommands(cmd);
@@ -105,9 +105,9 @@ class TimeZoneDetectorShellCommand extends ShellCommand {
pw.println("Time Zone Detector (time_zone_detector) commands:");
pw.println(" help");
pw.println(" Print this help text.");
- pw.println(" suggestTelephonyTimeZone");
+ pw.println(" suggest_telephony_time_zone");
pw.println(" --suggestion <telephony suggestion opts>");
- pw.println(" suggestManualTimeZone");
+ pw.println(" suggest_manual_time_zone");
pw.println(" --suggestion <manual suggestion opts>");
pw.println();
ManualTimeZoneSuggestion.printCommandLineOpts(pw);
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index ae8d5743668a..136ee91dd685 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -26,6 +26,7 @@ import android.app.NotificationManager;
import android.app.Person;
import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetEvent;
+import android.app.usage.UsageEvents;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -70,6 +71,7 @@ import com.android.server.notification.NotificationManagerInternal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@@ -237,6 +239,27 @@ public class DataManager {
eventHistory.addEvent(new Event(System.currentTimeMillis(), eventType));
}
+ /**
+ * Queries events for moving app to foreground between {@code startTime} and {@code endTime}.
+ */
+ @NonNull
+ public List<UsageEvents.Event> queryAppMovingToForegroundEvents(@UserIdInt int callingUserId,
+ long startTime, long endTime) {
+ return UsageStatsQueryHelper.queryAppMovingToForegroundEvents(callingUserId, startTime,
+ endTime);
+ }
+
+ /**
+ * Queries launch counts of apps within {@code packageNameFilter} between {@code startTime}
+ * and {@code endTime}.
+ */
+ @NonNull
+ public Map<String, Integer> queryAppLaunchCount(@UserIdInt int callingUserId, long startTime,
+ long endTime, Set<String> packageNameFilter) {
+ return UsageStatsQueryHelper.queryAppLaunchCount(callingUserId, startTime, endTime,
+ packageNameFilter);
+ }
+
/** Prunes the data for the specified user. */
public void pruneDataForUser(@UserIdInt int userId, @NonNull CancellationSignal signal) {
UserData userData = getUnlockedUserData(userId);
@@ -382,7 +405,13 @@ public class DataManager {
}
}
- private int mimeTypeToShareEventType(String mimeType) {
+ /**
+ * Converts {@code mimeType} to {@link Event.EventType}.
+ */
+ public int mimeTypeToShareEventType(String mimeType) {
+ if (mimeType == null) {
+ return Event.TYPE_SHARE_OTHER;
+ }
if (mimeType.startsWith("text/")) {
return Event.TYPE_SHARE_TEXT;
} else if (mimeType.startsWith("image/")) {
diff --git a/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java b/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java
index 72f1abb70e34..6e6fea93c803 100644
--- a/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java
+++ b/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java
@@ -19,6 +19,8 @@ package com.android.server.people.data;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.usage.UsageEvents;
+import android.app.usage.UsageStats;
+import android.app.usage.UsageStatsManager;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
import android.content.LocusId;
@@ -27,7 +29,10 @@ import android.util.ArrayMap;
import com.android.server.LocalServices;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.function.Function;
/** A helper class that queries {@link UsageStatsManagerInternal}. */
@@ -46,7 +51,7 @@ class UsageStatsQueryHelper {
*/
UsageStatsQueryHelper(@UserIdInt int userId,
Function<String, PackageData> packageDataGetter) {
- mUsageStatsManagerInternal = LocalServices.getService(UsageStatsManagerInternal.class);
+ mUsageStatsManagerInternal = getUsageStatsManagerInternal();
mUserId = userId;
mPackageDataGetter = packageDataGetter;
}
@@ -106,6 +111,53 @@ class UsageStatsQueryHelper {
return mLastEventTimestamp;
}
+ /**
+ * Queries {@link UsageStatsManagerInternal} events for moving app to foreground between
+ * {@code startTime} and {@code endTime}.
+ *
+ * @return a list containing events moving app to foreground.
+ */
+ static List<UsageEvents.Event> queryAppMovingToForegroundEvents(@UserIdInt int userId,
+ long startTime, long endTime) {
+ List<UsageEvents.Event> res = new ArrayList<>();
+ UsageEvents usageEvents = getUsageStatsManagerInternal().queryEventsForUser(userId,
+ startTime, endTime,
+ UsageEvents.HIDE_SHORTCUT_EVENTS | UsageEvents.HIDE_LOCUS_EVENTS);
+ if (usageEvents == null) {
+ return res;
+ }
+ while (usageEvents.hasNextEvent()) {
+ UsageEvents.Event e = new UsageEvents.Event();
+ usageEvents.getNextEvent(e);
+ if (e.getEventType() == UsageEvents.Event.ACTIVITY_RESUMED) {
+ res.add(e);
+ }
+ }
+ return res;
+ }
+
+ /**
+ * Queries {@link UsageStatsManagerInternal} for launch count of apps within {@code
+ * packageNameFilter} between {@code startTime} and {@code endTime}.obfuscateInstantApps
+ *
+ * @return a map which keys are package names and values are app launch counts.
+ */
+ static Map<String, Integer> queryAppLaunchCount(@UserIdInt int userId, long startTime,
+ long endTime, Set<String> packageNameFilter) {
+ List<UsageStats> stats = getUsageStatsManagerInternal().queryUsageStatsForUser(userId,
+ UsageStatsManager.INTERVAL_BEST, startTime, endTime,
+ /* obfuscateInstantApps= */ false);
+ Map<String, Integer> aggregatedStats = new ArrayMap<>();
+ for (UsageStats stat : stats) {
+ String packageName = stat.getPackageName();
+ if (packageNameFilter.contains(packageName)) {
+ aggregatedStats.put(packageName,
+ aggregatedStats.getOrDefault(packageName, 0) + stat.getAppLaunchCount());
+ }
+ }
+ return aggregatedStats;
+ }
+
private void onInAppConversationEnded(@NonNull PackageData packageData,
@NonNull UsageEvents.Event endEvent) {
ComponentName activityName =
@@ -138,4 +190,8 @@ class UsageStatsQueryHelper {
EventStore.CATEGORY_LOCUS_ID_BASED, locusId.getId());
eventHistory.addEvent(event);
}
+
+ private static UsageStatsManagerInternal getUsageStatsManagerInternal() {
+ return LocalServices.getService(UsageStatsManagerInternal.class);
+ }
}
diff --git a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
index 8e5d75be12b7..d09d0b379769 100644
--- a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
+++ b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
@@ -27,13 +27,11 @@ import android.app.prediction.AppTargetId;
import android.content.IntentFilter;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager.ShareShortcutInfo;
-import android.util.Range;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ChooserActivity;
import com.android.server.people.data.ConversationInfo;
import com.android.server.people.data.DataManager;
-import com.android.server.people.data.Event;
import com.android.server.people.data.EventHistory;
import com.android.server.people.data.PackageData;
@@ -42,6 +40,9 @@ import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
+/**
+ * Predictor that predicts the {@link AppTarget} the user is most likely to open on share sheet.
+ */
class ShareTargetPredictor extends AppTargetPredictor {
private final IntentFilter mIntentFilter;
@@ -66,7 +67,9 @@ class ShareTargetPredictor extends AppTargetPredictor {
@Override
void predictTargets() {
List<ShareTarget> shareTargets = getDirectShareTargets();
- rankTargets(shareTargets);
+ SharesheetModelScorer.computeScore(shareTargets, getShareEventType(mIntentFilter),
+ System.currentTimeMillis());
+ Collections.sort(shareTargets, (t1, t2) -> -Float.compare(t1.getScore(), t2.getScore()));
List<AppTarget> res = new ArrayList<>();
for (int i = 0; i < Math.min(getPredictionContext().getPredictedTargetCount(),
shareTargets.size()); i++) {
@@ -80,36 +83,16 @@ class ShareTargetPredictor extends AppTargetPredictor {
@Override
void sortTargets(List<AppTarget> targets, Consumer<List<AppTarget>> callback) {
List<ShareTarget> shareTargets = getAppShareTargets(targets);
- rankTargets(shareTargets);
+ SharesheetModelScorer.computeScoreForAppShare(shareTargets,
+ getShareEventType(mIntentFilter), getPredictionContext().getPredictedTargetCount(),
+ System.currentTimeMillis(), getDataManager(),
+ mCallingUserId);
+ Collections.sort(shareTargets, (t1, t2) -> -Float.compare(t1.getScore(), t2.getScore()));
List<AppTarget> appTargetList = new ArrayList<>();
shareTargets.forEach(t -> appTargetList.add(t.getAppTarget()));
callback.accept(appTargetList);
}
- private void rankTargets(List<ShareTarget> shareTargets) {
- // Rank targets based on recency of sharing history only for the moment.
- // TODO: Take more factors into ranking, e.g. frequency, mime type, foreground app.
- Collections.sort(shareTargets, (t1, t2) -> {
- if (t1.getEventHistory() == null) {
- return 1;
- }
- if (t2.getEventHistory() == null) {
- return -1;
- }
- Range<Long> timeSlot1 = t1.getEventHistory().getEventIndex(
- Event.SHARE_EVENT_TYPES).getMostRecentActiveTimeSlot();
- Range<Long> timeSlot2 = t2.getEventHistory().getEventIndex(
- Event.SHARE_EVENT_TYPES).getMostRecentActiveTimeSlot();
- if (timeSlot1 == null) {
- return 1;
- } else if (timeSlot2 == null) {
- return -1;
- } else {
- return -Long.compare(timeSlot1.getUpper(), timeSlot2.getUpper());
- }
- });
- }
-
private List<ShareTarget> getDirectShareTargets() {
List<ShareTarget> shareTargets = new ArrayList<>();
List<ShareShortcutInfo> shareShortcuts =
@@ -153,6 +136,11 @@ class ShareTargetPredictor extends AppTargetPredictor {
return shareTargets;
}
+ private int getShareEventType(IntentFilter intentFilter) {
+ String mimeType = intentFilter != null ? intentFilter.getDataType(0) : null;
+ return getDataManager().mimeTypeToShareEventType(mimeType);
+ }
+
@VisibleForTesting
static class ShareTarget {
@@ -162,13 +150,16 @@ class ShareTargetPredictor extends AppTargetPredictor {
private final EventHistory mEventHistory;
@Nullable
private final ConversationInfo mConversationInfo;
+ private float mScore;
- private ShareTarget(@NonNull AppTarget appTarget,
+ @VisibleForTesting
+ ShareTarget(@NonNull AppTarget appTarget,
@Nullable EventHistory eventHistory,
@Nullable ConversationInfo conversationInfo) {
mAppTarget = appTarget;
mEventHistory = eventHistory;
mConversationInfo = conversationInfo;
+ mScore = 0f;
}
@NonNull
@@ -188,5 +179,15 @@ class ShareTargetPredictor extends AppTargetPredictor {
ConversationInfo getConversationInfo() {
return mConversationInfo;
}
+
+ @VisibleForTesting
+ float getScore() {
+ return mScore;
+ }
+
+ @VisibleForTesting
+ void setScore(float score) {
+ mScore = score;
+ }
}
}
diff --git a/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java b/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java
new file mode 100644
index 000000000000..0ac5724210da
--- /dev/null
+++ b/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java
@@ -0,0 +1,406 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.people.prediction;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.usage.UsageEvents;
+import android.util.ArrayMap;
+import android.util.Pair;
+import android.util.Range;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.ChooserActivity;
+import com.android.server.people.data.DataManager;
+import com.android.server.people.data.Event;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.PriorityQueue;
+import java.util.concurrent.TimeUnit;
+
+/** Ranking scorer for Sharesheet targets. */
+class SharesheetModelScorer {
+
+ private static final String TAG = "SharesheetModelScorer";
+ private static final boolean DEBUG = false;
+ private static final Integer RECENCY_SCORE_COUNT = 6;
+ private static final float RECENCY_INITIAL_BASE_SCORE = 0.4F;
+ private static final float RECENCY_SCORE_INITIAL_DECAY = 0.05F;
+ private static final float RECENCY_SCORE_SUBSEQUENT_DECAY = 0.02F;
+ private static final long ONE_MONTH_WINDOW = TimeUnit.DAYS.toMillis(30);
+ private static final long FOREGROUND_APP_PROMO_TIME_WINDOW = TimeUnit.MINUTES.toMillis(10);
+ private static final float FREQUENTLY_USED_APP_SCORE_DECAY = 0.9F;
+ @VisibleForTesting
+ static final float FOREGROUND_APP_WEIGHT = 0F;
+ @VisibleForTesting
+ static final String CHOOSER_ACTIVITY = ChooserActivity.class.getSimpleName();
+
+ // Keep constructor private to avoid class being instantiated.
+ private SharesheetModelScorer() {
+ }
+
+ /**
+ * Computes each target's recency, frequency and frequency of the same {@code shareEventType}
+ * based on past sharing history. Update {@link ShareTargetPredictor.ShareTargetScore}.
+ */
+ static void computeScore(List<ShareTargetPredictor.ShareTarget> shareTargets,
+ int shareEventType, long now) {
+ if (shareTargets.isEmpty()) {
+ return;
+ }
+ float totalFreqScore = 0f;
+ int freqScoreCount = 0;
+ float totalMimeFreqScore = 0f;
+ int mimeFreqScoreCount = 0;
+ // Top of this heap has lowest rank.
+ PriorityQueue<Pair<ShareTargetRankingScore, Range<Long>>> recencyMinHeap =
+ new PriorityQueue<>(RECENCY_SCORE_COUNT,
+ Comparator.comparingLong(p -> p.second.getUpper()));
+ List<ShareTargetRankingScore> scoreList = new ArrayList<>(shareTargets.size());
+ for (ShareTargetPredictor.ShareTarget target : shareTargets) {
+ ShareTargetRankingScore shareTargetScore = new ShareTargetRankingScore();
+ scoreList.add(shareTargetScore);
+ if (target.getEventHistory() == null) {
+ continue;
+ }
+ // Counts frequency
+ List<Range<Long>> timeSlots = target.getEventHistory().getEventIndex(
+ Event.SHARE_EVENT_TYPES).getActiveTimeSlots();
+ if (!timeSlots.isEmpty()) {
+ for (Range<Long> timeSlot : timeSlots) {
+ shareTargetScore.incrementFrequencyScore(
+ getFreqDecayedOnElapsedTime(now - timeSlot.getLower()));
+ }
+ totalFreqScore += shareTargetScore.getFrequencyScore();
+ freqScoreCount++;
+ }
+ // Counts frequency for sharing same mime type
+ List<Range<Long>> timeSlotsOfSameType = target.getEventHistory().getEventIndex(
+ shareEventType).getActiveTimeSlots();
+ if (!timeSlotsOfSameType.isEmpty()) {
+ for (Range<Long> timeSlot : timeSlotsOfSameType) {
+ shareTargetScore.incrementMimeFrequencyScore(
+ getFreqDecayedOnElapsedTime(now - timeSlot.getLower()));
+ }
+ totalMimeFreqScore += shareTargetScore.getMimeFrequencyScore();
+ mimeFreqScoreCount++;
+ }
+ // Records most recent targets
+ Range<Long> mostRecentTimeSlot = target.getEventHistory().getEventIndex(
+ Event.SHARE_EVENT_TYPES).getMostRecentActiveTimeSlot();
+ if (mostRecentTimeSlot == null) {
+ continue;
+ }
+ if (recencyMinHeap.size() < RECENCY_SCORE_COUNT
+ || mostRecentTimeSlot.getUpper() > recencyMinHeap.peek().second.getUpper()) {
+ if (recencyMinHeap.size() == RECENCY_SCORE_COUNT) {
+ recencyMinHeap.poll();
+ }
+ recencyMinHeap.offer(new Pair(shareTargetScore, mostRecentTimeSlot));
+ }
+ }
+ // Calculates recency score
+ while (!recencyMinHeap.isEmpty()) {
+ float recencyScore = RECENCY_INITIAL_BASE_SCORE;
+ if (recencyMinHeap.size() > 1) {
+ recencyScore = RECENCY_INITIAL_BASE_SCORE - RECENCY_SCORE_INITIAL_DECAY
+ - RECENCY_SCORE_SUBSEQUENT_DECAY * (recencyMinHeap.size() - 2);
+ }
+ recencyMinHeap.poll().first.setRecencyScore(recencyScore);
+ }
+
+ Float avgFreq = freqScoreCount != 0 ? totalFreqScore / freqScoreCount : 0f;
+ Float avgMimeFreq = mimeFreqScoreCount != 0 ? totalMimeFreqScore / mimeFreqScoreCount : 0f;
+ for (int i = 0; i < scoreList.size(); i++) {
+ ShareTargetPredictor.ShareTarget target = shareTargets.get(i);
+ ShareTargetRankingScore targetScore = scoreList.get(i);
+ // Normalizes freq and mimeFreq score
+ targetScore.setFrequencyScore(normalizeFreqScore(
+ avgFreq.equals(0f) ? 0f : targetScore.getFrequencyScore() / avgFreq));
+ targetScore.setMimeFrequencyScore(normalizeMimeFreqScore(avgMimeFreq.equals(0f) ? 0f
+ : targetScore.getMimeFrequencyScore() / avgMimeFreq));
+ // Calculates total score
+ targetScore.setTotalScore(
+ probOR(probOR(targetScore.getRecencyScore(), targetScore.getFrequencyScore()),
+ targetScore.getMimeFrequencyScore()));
+ target.setScore(targetScore.getTotalScore());
+
+ if (DEBUG) {
+ Slog.d(TAG, String.format(
+ "SharesheetModel: packageName: %s, className: %s, shortcutId: %s, "
+ + "recency:%.2f, freq_all:%.2f, freq_mime:%.2f, total:%.2f",
+ target.getAppTarget().getPackageName(),
+ target.getAppTarget().getClassName(),
+ target.getAppTarget().getShortcutInfo() != null
+ ? target.getAppTarget().getShortcutInfo().getId() : null,
+ targetScore.getRecencyScore(),
+ targetScore.getFrequencyScore(),
+ targetScore.getMimeFrequencyScore(),
+ targetScore.getTotalScore()));
+ }
+ }
+ }
+
+ /**
+ * Computes ranking score for app sharing. Update {@link ShareTargetPredictor.ShareTargetScore}.
+ */
+ static void computeScoreForAppShare(List<ShareTargetPredictor.ShareTarget> shareTargets,
+ int shareEventType, int targetsLimit, long now, @NonNull DataManager dataManager,
+ @UserIdInt int callingUserId) {
+ computeScore(shareTargets, shareEventType, now);
+ postProcess(shareTargets, targetsLimit, dataManager, callingUserId);
+ }
+
+ private static void postProcess(List<ShareTargetPredictor.ShareTarget> shareTargets,
+ int targetsLimit, @NonNull DataManager dataManager, @UserIdInt int callingUserId) {
+ // Populates a map which key is package name and value is list of shareTargets descended
+ // on total score.
+ Map<String, List<ShareTargetPredictor.ShareTarget>> shareTargetMap = new ArrayMap<>();
+ for (ShareTargetPredictor.ShareTarget shareTarget : shareTargets) {
+ String packageName = shareTarget.getAppTarget().getPackageName();
+ shareTargetMap.computeIfAbsent(packageName, key -> new ArrayList<>());
+ List<ShareTargetPredictor.ShareTarget> targetsList = shareTargetMap.get(packageName);
+ int index = 0;
+ while (index < targetsList.size()) {
+ if (shareTarget.getScore() > targetsList.get(index).getScore()) {
+ break;
+ }
+ index++;
+ }
+ targetsList.add(index, shareTarget);
+ }
+ promoteForegroundApp(shareTargetMap, dataManager, callingUserId);
+ promoteFrequentlyUsedApps(shareTargetMap, targetsLimit, dataManager, callingUserId);
+ }
+
+ /**
+ * Promotes frequently used sharing apps, if recommended apps based on sharing history have not
+ * reached the limit (e.g. user did not share any content in last couple weeks)
+ */
+ private static void promoteFrequentlyUsedApps(
+ Map<String, List<ShareTargetPredictor.ShareTarget>> shareTargetMap, int targetsLimit,
+ @NonNull DataManager dataManager, @UserIdInt int callingUserId) {
+ int validPredictionNum = 0;
+ float minValidScore = 1f;
+ for (List<ShareTargetPredictor.ShareTarget> targets : shareTargetMap.values()) {
+ for (ShareTargetPredictor.ShareTarget target : targets) {
+ if (target.getScore() > 0f) {
+ validPredictionNum++;
+ minValidScore = Math.min(target.getScore(), minValidScore);
+ }
+ }
+ }
+ // Skips if recommended apps based on sharing history have already reached the limit.
+ if (validPredictionNum >= targetsLimit) {
+ return;
+ }
+ long now = System.currentTimeMillis();
+ Map<String, Integer> appLaunchCountsMap = dataManager.queryAppLaunchCount(
+ callingUserId, now - ONE_MONTH_WINDOW, now, shareTargetMap.keySet());
+ List<Pair<String, Integer>> appLaunchCounts = new ArrayList<>();
+ for (Map.Entry<String, Integer> entry : appLaunchCountsMap.entrySet()) {
+ if (entry.getValue() > 0) {
+ appLaunchCounts.add(new Pair(entry.getKey(), entry.getValue()));
+ }
+ }
+ Collections.sort(appLaunchCounts, (p1, p2) -> -Integer.compare(p1.second, p2.second));
+ for (Pair<String, Integer> entry : appLaunchCounts) {
+ if (!shareTargetMap.containsKey(entry.first)) {
+ continue;
+ }
+ ShareTargetPredictor.ShareTarget target = shareTargetMap.get(entry.first).get(0);
+ if (target.getScore() > 0f) {
+ continue;
+ }
+ minValidScore *= FREQUENTLY_USED_APP_SCORE_DECAY;
+ target.setScore(minValidScore);
+ if (DEBUG) {
+ Slog.d(TAG, String.format(
+ "SharesheetModel: promoteFrequentUsedApps packageName: %s, className: %s,"
+ + " total:%.2f",
+ target.getAppTarget().getPackageName(),
+ target.getAppTarget().getClassName(),
+ target.getScore()));
+ }
+ validPredictionNum++;
+ if (validPredictionNum == targetsLimit) {
+ return;
+ }
+ }
+ }
+
+ /**
+ * Promotes the foreground app just prior to source sharing app. Share often occurs between
+ * two apps the user is switching.
+ */
+ private static void promoteForegroundApp(
+ Map<String, List<ShareTargetPredictor.ShareTarget>> shareTargetMap,
+ @NonNull DataManager dataManager, @UserIdInt int callingUserId) {
+ String sharingForegroundApp = findSharingForegroundApp(shareTargetMap, dataManager,
+ callingUserId);
+ if (sharingForegroundApp != null) {
+ ShareTargetPredictor.ShareTarget target = shareTargetMap.get(sharingForegroundApp).get(
+ 0);
+ target.setScore(probOR(target.getScore(), FOREGROUND_APP_WEIGHT));
+ if (DEBUG) {
+ Slog.d(TAG, String.format(
+ "SharesheetModel: promoteForegroundApp packageName: %s, className: %s, "
+ + "total:%.2f",
+ target.getAppTarget().getPackageName(),
+ target.getAppTarget().getClassName(),
+ target.getScore()));
+ }
+ }
+ }
+
+ /**
+ * Find the foreground app just prior to source sharing app from usageStatsManager. Returns null
+ * if it is not available.
+ */
+ @Nullable
+ private static String findSharingForegroundApp(
+ Map<String, List<ShareTargetPredictor.ShareTarget>> shareTargetMap,
+ @NonNull DataManager dataManager, @UserIdInt int callingUserId) {
+ String sharingForegroundApp = null;
+ long now = System.currentTimeMillis();
+ List<UsageEvents.Event> events = dataManager.queryAppMovingToForegroundEvents(
+ callingUserId, now - FOREGROUND_APP_PROMO_TIME_WINDOW, now);
+ String sourceApp = null;
+ for (int i = events.size() - 1; i >= 0; i--) {
+ String className = events.get(i).getClassName();
+ String packageName = events.get(i).getPackageName();
+ if (packageName == null || (className != null && className.contains(CHOOSER_ACTIVITY))
+ || packageName.contains(CHOOSER_ACTIVITY)) {
+ continue;
+ }
+ if (sourceApp == null) {
+ sourceApp = packageName;
+ } else if (!packageName.equals(sourceApp) && shareTargetMap.containsKey(packageName)) {
+ sharingForegroundApp = packageName;
+ break;
+ }
+ }
+ return sharingForegroundApp;
+ }
+
+ /**
+ * Probabilistic OR (also known as the algebraic sum). If a <= 1 and b <= 1, the result will be
+ * <= 1.0.
+ */
+ private static float probOR(float a, float b) {
+ return 1f - (1f - a) * (1f - b);
+ }
+
+ /** Counts frequency of share targets. Decays frequency for old shares. */
+ private static float getFreqDecayedOnElapsedTime(long elapsedTimeMillis) {
+ Duration duration = Duration.ofMillis(elapsedTimeMillis);
+ if (duration.compareTo(Duration.ofDays(1)) <= 0) {
+ return 1.0f;
+ } else if (duration.compareTo(Duration.ofDays(3)) <= 0) {
+ return 0.9f;
+ } else if (duration.compareTo(Duration.ofDays(7)) <= 0) {
+ return 0.8f;
+ } else if (duration.compareTo(Duration.ofDays(14)) <= 0) {
+ return 0.7f;
+ } else {
+ return 0.6f;
+ }
+ }
+
+ /** Normalizes frequency score. */
+ private static float normalizeFreqScore(double freqRatio) {
+ if (freqRatio >= 2.5) {
+ return 0.2f;
+ } else if (freqRatio >= 1.5) {
+ return 0.15f;
+ } else if (freqRatio >= 1.0) {
+ return 0.1f;
+ } else if (freqRatio >= 0.75) {
+ return 0.05f;
+ } else {
+ return 0f;
+ }
+ }
+
+ /** Normalizes mimetype-specific frequency score. */
+ private static float normalizeMimeFreqScore(double freqRatio) {
+ if (freqRatio >= 2.0) {
+ return 0.2f;
+ } else if (freqRatio >= 1.2) {
+ return 0.15f;
+ } else if (freqRatio > 0.0) {
+ return 0.1f;
+ } else {
+ return 0f;
+ }
+ }
+
+ private static class ShareTargetRankingScore {
+
+ private float mRecencyScore = 0f;
+ private float mFrequencyScore = 0f;
+ private float mMimeFrequencyScore = 0f;
+ private float mTotalScore = 0f;
+
+ float getTotalScore() {
+ return mTotalScore;
+ }
+
+ void setTotalScore(float totalScore) {
+ mTotalScore = totalScore;
+ }
+
+ float getRecencyScore() {
+ return mRecencyScore;
+ }
+
+ void setRecencyScore(float recencyScore) {
+ mRecencyScore = recencyScore;
+ }
+
+ float getFrequencyScore() {
+ return mFrequencyScore;
+ }
+
+ void setFrequencyScore(float frequencyScore) {
+ mFrequencyScore = frequencyScore;
+ }
+
+ void incrementFrequencyScore(float incremental) {
+ mFrequencyScore += incremental;
+ }
+
+ float getMimeFrequencyScore() {
+ return mMimeFrequencyScore;
+ }
+
+ void setMimeFrequencyScore(float mimeFrequencyScore) {
+ mMimeFrequencyScore = mimeFrequencyScore;
+ }
+
+ void incrementMimeFrequencyScore(float incremental) {
+ mMimeFrequencyScore += incremental;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java
index 7934d33f907d..03d9ad51e6c5 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java
@@ -21,15 +21,16 @@ import static com.android.server.people.data.TestUtils.timestamp;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.usage.UsageEvents;
+import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManagerInternal;
import android.content.Context;
import android.content.LocusId;
@@ -50,6 +51,7 @@ import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Predicate;
@@ -58,7 +60,8 @@ import java.util.function.Predicate;
public final class UsageStatsQueryHelperTest {
private static final int USER_ID_PRIMARY = 0;
- private static final String PKG_NAME = "pkg";
+ private static final String PKG_NAME_1 = "pkg_1";
+ private static final String PKG_NAME_2 = "pkg_2";
private static final String ACTIVITY_NAME = "TestActivity";
private static final String SHORTCUT_ID = "abc";
private static final LocusId LOCUS_ID_1 = new LocusId("locus_1");
@@ -80,7 +83,7 @@ public final class UsageStatsQueryHelperTest {
File testDir = new File(ctx.getCacheDir(), "testdir");
ScheduledExecutorService scheduledExecutorService = new MockScheduledExecutorService();
- mPackageData = new TestPackageData(PKG_NAME, USER_ID_PRIMARY, pkg -> false, pkg -> false,
+ mPackageData = new TestPackageData(PKG_NAME_1, USER_ID_PRIMARY, pkg -> false, pkg -> false,
scheduledExecutorService, testDir);
mPackageData.mConversationStore.mConversationInfo = new ConversationInfo.Builder()
.setShortcutId(SHORTCUT_ID)
@@ -173,10 +176,72 @@ public final class UsageStatsQueryHelperTest {
assertEquals(createInAppConversationEvent(130_000L, 30), events.get(2));
}
+ @Test
+ public void testQueryAppMovingToForegroundEvents() {
+ addUsageEvents(
+ createShortcutInvocationEvent(100_000L),
+ createActivityResumedEvent(110_000L),
+ createActivityStoppedEvent(120_000L),
+ createActivityResumedEvent(130_000L));
+
+ List<UsageEvents.Event> events = mHelper.queryAppMovingToForegroundEvents(USER_ID_PRIMARY,
+ 90_000L,
+ 200_000L);
+
+ assertEquals(2, events.size());
+ assertEquals(UsageEvents.Event.ACTIVITY_RESUMED, events.get(0).getEventType());
+ assertEquals(110_000L, events.get(0).getTimeStamp());
+ assertEquals(UsageEvents.Event.ACTIVITY_RESUMED, events.get(1).getEventType());
+ assertEquals(130_000L, events.get(1).getTimeStamp());
+ }
+
+ @Test
+ public void testQueryAppLaunchCount() {
+
+ UsageStats packageStats1 = createUsageStats(PKG_NAME_1, 2);
+ UsageStats packageStats2 = createUsageStats(PKG_NAME_1, 3);
+ UsageStats packageStats3 = createUsageStats(PKG_NAME_2, 1);
+ when(mUsageStatsManagerInternal.queryUsageStatsForUser(anyInt(), anyInt(), anyLong(),
+ anyLong(), anyBoolean())).thenReturn(
+ List.of(packageStats1, packageStats2, packageStats3));
+
+ Map<String, Integer> appLaunchCounts = mHelper.queryAppLaunchCount(USER_ID_PRIMARY, 90_000L,
+ 200_000L, Set.of(PKG_NAME_1, PKG_NAME_2));
+
+ assertEquals(2, appLaunchCounts.size());
+ assertEquals(5, (long) appLaunchCounts.get(PKG_NAME_1));
+ assertEquals(1, (long) appLaunchCounts.get(PKG_NAME_2));
+ }
+
+ @Test
+ public void testQueryAppLaunchCount_packageNameFiltered() {
+
+ UsageStats packageStats1 = createUsageStats(PKG_NAME_1, 2);
+ UsageStats packageStats2 = createUsageStats(PKG_NAME_1, 3);
+ UsageStats packageStats3 = createUsageStats(PKG_NAME_2, 1);
+ when(mUsageStatsManagerInternal.queryUsageStatsForUser(anyInt(), anyInt(), anyLong(),
+ anyLong(), anyBoolean())).thenReturn(
+ List.of(packageStats1, packageStats2, packageStats3));
+
+ Map<String, Integer> appLaunchCounts = mHelper.queryAppLaunchCount(USER_ID_PRIMARY, 90_000L,
+ 200_000L,
+ Set.of(PKG_NAME_1));
+
+ assertEquals(1, appLaunchCounts.size());
+ assertEquals(5, (long) appLaunchCounts.get(PKG_NAME_1));
+ }
+
private void addUsageEvents(UsageEvents.Event... events) {
UsageEvents usageEvents = new UsageEvents(Arrays.asList(events), new String[]{});
when(mUsageStatsManagerInternal.queryEventsForUser(anyInt(), anyLong(), anyLong(),
- eq(UsageEvents.SHOW_ALL_EVENT_DATA))).thenReturn(usageEvents);
+ anyInt())).thenReturn(usageEvents);
+ }
+
+ private static UsageStats createUsageStats(String packageName, int launchCount) {
+ UsageStats packageStats = new UsageStats();
+ packageStats.mPackageName = packageName;
+ packageStats.mAppLaunchCount = launchCount;
+ return packageStats;
}
private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
@@ -203,9 +268,15 @@ public final class UsageStatsQueryHelperTest {
return e;
}
+ private static UsageEvents.Event createActivityResumedEvent(long timestamp) {
+ UsageEvents.Event e = createUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, timestamp);
+ e.mClass = ACTIVITY_NAME;
+ return e;
+ }
+
private static UsageEvents.Event createUsageEvent(int eventType, long timestamp) {
UsageEvents.Event e = new UsageEvents.Event(eventType, timestamp);
- e.mPackage = PKG_NAME;
+ e.mPackage = PKG_NAME_1;
return e;
}
diff --git a/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java b/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java
index c6cd34732acf..1480627b9b9f 100644
--- a/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java
@@ -127,6 +127,9 @@ public final class ShareTargetPredictorTest {
when(mEventHistory1.getEventIndex(anySet())).thenReturn(mEventIndex1);
when(mEventHistory2.getEventIndex(anySet())).thenReturn(mEventIndex2);
when(mEventHistory3.getEventIndex(anySet())).thenReturn(mEventIndex3);
+ when(mEventHistory1.getEventIndex(anyInt())).thenReturn(mEventIndex1);
+ when(mEventHistory2.getEventIndex(anyInt())).thenReturn(mEventIndex2);
+ when(mEventHistory3.getEventIndex(anyInt())).thenReturn(mEventIndex3);
when(mEventIndex1.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(1L, 2L));
when(mEventIndex2.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(2L, 3L));
when(mEventIndex3.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(3L, 4L));
@@ -183,6 +186,12 @@ public final class ShareTargetPredictorTest {
when(mEventHistory4.getEventIndex(anySet())).thenReturn(mEventIndex4);
when(mEventHistory5.getEventIndex(anySet())).thenReturn(mEventIndex5);
when(mEventHistory6.getEventIndex(anySet())).thenReturn(mEventIndex6);
+ when(mEventHistory1.getEventIndex(anyInt())).thenReturn(mEventIndex1);
+ when(mEventHistory2.getEventIndex(anyInt())).thenReturn(mEventIndex2);
+ when(mEventHistory3.getEventIndex(anyInt())).thenReturn(mEventIndex3);
+ when(mEventHistory4.getEventIndex(anyInt())).thenReturn(mEventIndex4);
+ when(mEventHistory5.getEventIndex(anyInt())).thenReturn(mEventIndex5);
+ when(mEventHistory6.getEventIndex(anyInt())).thenReturn(mEventIndex6);
when(mEventIndex1.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(1L, 2L));
when(mEventIndex2.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(2L, 3L));
when(mEventIndex3.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(3L, 4L));
@@ -220,19 +229,19 @@ public final class ShareTargetPredictorTest {
@Test
public void testSortTargets() {
AppTarget appTarget1 = new AppTarget.Builder(
- new AppTargetId("cls1#pkg1"), PACKAGE_1, UserHandle.of(USER_ID))
+ new AppTargetId("cls1#pkg1"), PACKAGE_1, UserHandle.of(USER_ID))
.setClassName(CLASS_1)
.build();
AppTarget appTarget2 = new AppTarget.Builder(
- new AppTargetId("cls2#pkg1"), PACKAGE_1, UserHandle.of(USER_ID))
+ new AppTargetId("cls2#pkg1"), PACKAGE_1, UserHandle.of(USER_ID))
.setClassName(CLASS_2)
.build();
AppTarget appTarget3 = new AppTarget.Builder(
- new AppTargetId("cls1#pkg2"), PACKAGE_2, UserHandle.of(USER_ID))
+ new AppTargetId("cls1#pkg2"), PACKAGE_2, UserHandle.of(USER_ID))
.setClassName(CLASS_1)
.build();
AppTarget appTarget4 = new AppTarget.Builder(
- new AppTargetId("cls2#pkg2"), PACKAGE_2, UserHandle.of(USER_ID))
+ new AppTargetId("cls2#pkg2"), PACKAGE_2, UserHandle.of(USER_ID))
.setClassName(CLASS_2)
.build();
AppTarget appTarget5 = new AppTarget.Builder(
@@ -251,6 +260,10 @@ public final class ShareTargetPredictorTest {
when(mEventHistory2.getEventIndex(anySet())).thenReturn(mEventIndex2);
when(mEventHistory3.getEventIndex(anySet())).thenReturn(mEventIndex3);
when(mEventHistory4.getEventIndex(anySet())).thenReturn(mEventIndex4);
+ when(mEventHistory1.getEventIndex(anyInt())).thenReturn(mEventIndex1);
+ when(mEventHistory2.getEventIndex(anyInt())).thenReturn(mEventIndex2);
+ when(mEventHistory3.getEventIndex(anyInt())).thenReturn(mEventIndex3);
+ when(mEventHistory4.getEventIndex(anyInt())).thenReturn(mEventIndex4);
when(mEventIndex1.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(1L, 2L));
when(mEventIndex2.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(2L, 3L));
when(mEventIndex3.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(3L, 4L));
@@ -265,14 +278,14 @@ public final class ShareTargetPredictorTest {
appTarget4, appTarget3, appTarget2, appTarget1, appTarget5);
}
- private ShareShortcutInfo buildShareShortcut(
+ private static ShareShortcutInfo buildShareShortcut(
String packageName, String className, String shortcutId) {
ShortcutInfo shortcutInfo = buildShortcut(packageName, shortcutId);
ComponentName componentName = new ComponentName(packageName, className);
return new ShareShortcutInfo(shortcutInfo, componentName);
}
- private ShortcutInfo buildShortcut(String packageName, String shortcutId) {
+ private static ShortcutInfo buildShortcut(String packageName, String shortcutId) {
Context mockContext = mock(Context.class);
when(mockContext.getPackageName()).thenReturn(packageName);
when(mockContext.getUserId()).thenReturn(USER_ID);
diff --git a/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java b/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java
new file mode 100644
index 000000000000..9d96d6b7d861
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java
@@ -0,0 +1,406 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.people.prediction;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anySet;
+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 android.app.prediction.AppTarget;
+import android.app.prediction.AppTargetId;
+import android.app.usage.UsageEvents;
+import android.os.UserHandle;
+import android.util.Range;
+
+import com.android.server.people.data.DataManager;
+import com.android.server.people.data.Event;
+import com.android.server.people.data.EventHistory;
+import com.android.server.people.data.EventIndex;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.Map;
+
+@RunWith(JUnit4.class)
+public final class SharesheetModelScorerTest {
+
+ private static final int USER_ID = 0;
+ private static final String PACKAGE_1 = "pkg1";
+ private static final String PACKAGE_2 = "pkg2";
+ private static final String PACKAGE_3 = "pkg3";
+ private static final String CLASS_1 = "cls1";
+ private static final String CLASS_2 = "cls2";
+ private static final double DELTA = 1e-6;
+ private static final long NOW = System.currentTimeMillis();
+ private static final Range<Long> WITHIN_ONE_DAY = new Range(
+ NOW - Duration.ofHours(23).toMillis(),
+ NOW - Duration.ofHours(22).toMillis());
+ private static final Range<Long> TWO_DAYS_AGO = new Range(
+ NOW - Duration.ofHours(50).toMillis(),
+ NOW - Duration.ofHours(49).toMillis());
+ private static final Range<Long> FIVE_DAYS_AGO = new Range(
+ NOW - Duration.ofDays(6).toMillis(),
+ NOW - Duration.ofDays(5).toMillis());
+ private static final Range<Long> EIGHT_DAYS_AGO = new Range(
+ NOW - Duration.ofDays(9).toMillis(),
+ NOW - Duration.ofDays(8).toMillis());
+ private static final Range<Long> TWELVE_DAYS_AGO = new Range(
+ NOW - Duration.ofDays(13).toMillis(),
+ NOW - Duration.ofDays(12).toMillis());
+ private static final Range<Long> TWENTY_DAYS_AGO = new Range(
+ NOW - Duration.ofDays(21).toMillis(),
+ NOW - Duration.ofDays(20).toMillis());
+ private static final Range<Long> FOUR_WEEKS_AGO = new Range(
+ NOW - Duration.ofDays(29).toMillis(),
+ NOW - Duration.ofDays(28).toMillis());
+
+ @Mock
+ private DataManager mDataManager;
+ @Mock
+ private EventHistory mEventHistory1;
+ @Mock
+ private EventHistory mEventHistory2;
+ @Mock
+ private EventHistory mEventHistory3;
+ @Mock
+ private EventHistory mEventHistory4;
+ @Mock
+ private EventHistory mEventHistory5;
+ @Mock
+ private EventIndex mEventIndex1;
+ @Mock
+ private EventIndex mEventIndex2;
+ @Mock
+ private EventIndex mEventIndex3;
+ @Mock
+ private EventIndex mEventIndex4;
+ @Mock
+ private EventIndex mEventIndex5;
+ @Mock
+ private EventIndex mEventIndex6;
+ @Mock
+ private EventIndex mEventIndex7;
+ @Mock
+ private EventIndex mEventIndex8;
+ @Mock
+ private EventIndex mEventIndex9;
+ @Mock
+ private EventIndex mEventIndex10;
+
+ private ShareTargetPredictor.ShareTarget mShareTarget1;
+ private ShareTargetPredictor.ShareTarget mShareTarget2;
+ private ShareTargetPredictor.ShareTarget mShareTarget3;
+ private ShareTargetPredictor.ShareTarget mShareTarget4;
+ private ShareTargetPredictor.ShareTarget mShareTarget5;
+ private ShareTargetPredictor.ShareTarget mShareTarget6;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mShareTarget1 = new ShareTargetPredictor.ShareTarget(
+ new AppTarget.Builder(
+ new AppTargetId("cls1#pkg1"), PACKAGE_1, UserHandle.of(USER_ID))
+ .setClassName(CLASS_1).build(),
+ mEventHistory1, null);
+ mShareTarget2 = new ShareTargetPredictor.ShareTarget(
+ new AppTarget.Builder(new AppTargetId("cls2#pkg1"), PACKAGE_1,
+ UserHandle.of(USER_ID)).setClassName(CLASS_2).build(),
+ mEventHistory2, null);
+ mShareTarget3 = new ShareTargetPredictor.ShareTarget(
+ new AppTarget.Builder(
+ new AppTargetId("cls1#pkg2"), PACKAGE_2, UserHandle.of(USER_ID))
+ .setClassName(CLASS_1).build(),
+ mEventHistory3, null);
+ mShareTarget4 = new ShareTargetPredictor.ShareTarget(
+ new AppTarget.Builder(
+ new AppTargetId("cls2#pkg2"), PACKAGE_2, UserHandle.of(USER_ID))
+ .setClassName(CLASS_2).build(),
+ mEventHistory4, null);
+ mShareTarget5 = new ShareTargetPredictor.ShareTarget(
+ new AppTarget.Builder(
+ new AppTargetId("cls1#pkg3"), PACKAGE_3, UserHandle.of(USER_ID))
+ .setClassName(CLASS_1).build(),
+ mEventHistory5, null);
+ mShareTarget6 = new ShareTargetPredictor.ShareTarget(
+ new AppTarget.Builder(
+ new AppTargetId("cls2#pkg3"), PACKAGE_3, UserHandle.of(USER_ID))
+ .setClassName(CLASS_2).build(),
+ null, null);
+ }
+
+ @Test
+ public void testComputeScore() {
+ // Frequency and recency
+ when(mEventHistory1.getEventIndex(anySet())).thenReturn(mEventIndex1);
+ when(mEventHistory2.getEventIndex(anySet())).thenReturn(mEventIndex2);
+ when(mEventHistory3.getEventIndex(anySet())).thenReturn(mEventIndex3);
+ when(mEventHistory4.getEventIndex(anySet())).thenReturn(mEventIndex4);
+ when(mEventHistory5.getEventIndex(anySet())).thenReturn(mEventIndex5);
+
+ when(mEventIndex1.getActiveTimeSlots()).thenReturn(
+ List.of(WITHIN_ONE_DAY, TWO_DAYS_AGO, FIVE_DAYS_AGO));
+ when(mEventIndex2.getActiveTimeSlots()).thenReturn(List.of(TWO_DAYS_AGO, TWELVE_DAYS_AGO));
+ when(mEventIndex3.getActiveTimeSlots()).thenReturn(List.of(FIVE_DAYS_AGO, TWENTY_DAYS_AGO));
+ when(mEventIndex4.getActiveTimeSlots()).thenReturn(
+ List.of(EIGHT_DAYS_AGO, TWELVE_DAYS_AGO, FOUR_WEEKS_AGO));
+ when(mEventIndex5.getActiveTimeSlots()).thenReturn(List.of());
+
+ when(mEventIndex1.getMostRecentActiveTimeSlot()).thenReturn(WITHIN_ONE_DAY);
+ when(mEventIndex2.getMostRecentActiveTimeSlot()).thenReturn(TWO_DAYS_AGO);
+ when(mEventIndex3.getMostRecentActiveTimeSlot()).thenReturn(FIVE_DAYS_AGO);
+ when(mEventIndex4.getMostRecentActiveTimeSlot()).thenReturn(EIGHT_DAYS_AGO);
+ when(mEventIndex5.getMostRecentActiveTimeSlot()).thenReturn(null);
+
+ // Frequency of the same mime type
+ when(mEventHistory1.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex6);
+ when(mEventHistory2.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex7);
+ when(mEventHistory3.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex8);
+ when(mEventHistory4.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex9);
+ when(mEventHistory5.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex10);
+
+ when(mEventIndex6.getActiveTimeSlots()).thenReturn(List.of(TWO_DAYS_AGO));
+ when(mEventIndex7.getActiveTimeSlots()).thenReturn(List.of(TWO_DAYS_AGO, TWELVE_DAYS_AGO));
+ when(mEventIndex8.getActiveTimeSlots()).thenReturn(List.of());
+ when(mEventIndex9.getActiveTimeSlots()).thenReturn(List.of(EIGHT_DAYS_AGO));
+ when(mEventIndex10.getActiveTimeSlots()).thenReturn(List.of());
+
+ SharesheetModelScorer.computeScore(
+ List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
+ mShareTarget6),
+ Event.TYPE_SHARE_TEXT,
+ NOW);
+
+ // Verification
+ assertEquals(0.514f, mShareTarget1.getScore(), DELTA);
+ assertEquals(0.475125f, mShareTarget2.getScore(), DELTA);
+ assertEquals(0.33f, mShareTarget3.getScore(), DELTA);
+ assertEquals(0.4411f, mShareTarget4.getScore(), DELTA);
+ assertEquals(0f, mShareTarget5.getScore(), DELTA);
+ assertEquals(0f, mShareTarget6.getScore(), DELTA);
+ }
+
+ @Test
+ public void testComputeScoreForAppShare() {
+ // Frequency and recency
+ when(mEventHistory1.getEventIndex(anySet())).thenReturn(mEventIndex1);
+ when(mEventHistory2.getEventIndex(anySet())).thenReturn(mEventIndex2);
+ when(mEventHistory3.getEventIndex(anySet())).thenReturn(mEventIndex3);
+ when(mEventHistory4.getEventIndex(anySet())).thenReturn(mEventIndex4);
+ when(mEventHistory5.getEventIndex(anySet())).thenReturn(mEventIndex5);
+
+ when(mEventIndex1.getActiveTimeSlots()).thenReturn(
+ List.of(WITHIN_ONE_DAY, TWO_DAYS_AGO, FIVE_DAYS_AGO));
+ when(mEventIndex2.getActiveTimeSlots()).thenReturn(List.of(TWO_DAYS_AGO, TWELVE_DAYS_AGO));
+ when(mEventIndex3.getActiveTimeSlots()).thenReturn(List.of(FIVE_DAYS_AGO, TWENTY_DAYS_AGO));
+ when(mEventIndex4.getActiveTimeSlots()).thenReturn(
+ List.of(EIGHT_DAYS_AGO, TWELVE_DAYS_AGO, FOUR_WEEKS_AGO));
+ when(mEventIndex5.getActiveTimeSlots()).thenReturn(List.of());
+
+ when(mEventIndex1.getMostRecentActiveTimeSlot()).thenReturn(WITHIN_ONE_DAY);
+ when(mEventIndex2.getMostRecentActiveTimeSlot()).thenReturn(TWO_DAYS_AGO);
+ when(mEventIndex3.getMostRecentActiveTimeSlot()).thenReturn(FIVE_DAYS_AGO);
+ when(mEventIndex4.getMostRecentActiveTimeSlot()).thenReturn(EIGHT_DAYS_AGO);
+ when(mEventIndex5.getMostRecentActiveTimeSlot()).thenReturn(null);
+
+ // Frequency of the same mime type
+ when(mEventHistory1.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex6);
+ when(mEventHistory2.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex7);
+ when(mEventHistory3.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex8);
+ when(mEventHistory4.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex9);
+ when(mEventHistory5.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex10);
+
+ when(mEventIndex6.getActiveTimeSlots()).thenReturn(List.of(TWO_DAYS_AGO));
+ when(mEventIndex7.getActiveTimeSlots()).thenReturn(List.of(TWO_DAYS_AGO, TWELVE_DAYS_AGO));
+ when(mEventIndex8.getActiveTimeSlots()).thenReturn(List.of());
+ when(mEventIndex9.getActiveTimeSlots()).thenReturn(List.of(EIGHT_DAYS_AGO));
+ when(mEventIndex10.getActiveTimeSlots()).thenReturn(List.of());
+
+ SharesheetModelScorer.computeScoreForAppShare(
+ List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
+ mShareTarget6),
+ Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID);
+
+ // Verification
+ assertEquals(0.514f, mShareTarget1.getScore(), DELTA);
+ assertEquals(0.475125f, mShareTarget2.getScore(), DELTA);
+ assertEquals(0.33f, mShareTarget3.getScore(), DELTA);
+ assertEquals(0.4411f, mShareTarget4.getScore(), DELTA);
+ assertEquals(0f, mShareTarget5.getScore(), DELTA);
+ assertEquals(0f, mShareTarget6.getScore(), DELTA);
+ }
+
+ @Test
+ public void testComputeScoreForAppShare_promoteFrequentlyUsedApps() {
+ when(mEventHistory1.getEventIndex(anySet())).thenReturn(mEventIndex1);
+ when(mEventHistory2.getEventIndex(anySet())).thenReturn(mEventIndex2);
+ when(mEventHistory3.getEventIndex(anySet())).thenReturn(mEventIndex3);
+ when(mEventHistory4.getEventIndex(anySet())).thenReturn(mEventIndex4);
+ when(mEventHistory5.getEventIndex(anySet())).thenReturn(mEventIndex5);
+ when(mEventHistory1.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex6);
+ when(mEventHistory2.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex7);
+ when(mEventHistory3.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex8);
+ when(mEventHistory4.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex9);
+ when(mEventHistory5.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex10);
+ when(mDataManager.queryAppLaunchCount(anyInt(), anyLong(), anyLong(), anySet()))
+ .thenReturn(
+ Map.of(PACKAGE_1, 1,
+ PACKAGE_2, 2,
+ PACKAGE_3, 3));
+
+ SharesheetModelScorer.computeScoreForAppShare(
+ List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
+ mShareTarget6),
+ Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID);
+
+ verify(mDataManager, times(1)).queryAppLaunchCount(anyInt(), anyLong(), anyLong(),
+ anySet());
+ assertEquals(0.9f, mShareTarget5.getScore(), DELTA);
+ assertEquals(0.81f, mShareTarget3.getScore(), DELTA);
+ assertEquals(0.729f, mShareTarget1.getScore(), DELTA);
+ assertEquals(0f, mShareTarget2.getScore(), DELTA);
+ assertEquals(0f, mShareTarget4.getScore(), DELTA);
+ assertEquals(0f, mShareTarget6.getScore(), DELTA);
+ }
+
+ @Test
+ public void testComputeScoreForAppShare_skipPromoteFrequentlyUsedAppsWhenReachesLimit() {
+ when(mEventHistory1.getEventIndex(anySet())).thenReturn(mEventIndex1);
+ when(mEventHistory2.getEventIndex(anySet())).thenReturn(mEventIndex2);
+ when(mEventHistory3.getEventIndex(anySet())).thenReturn(mEventIndex3);
+ when(mEventHistory4.getEventIndex(anySet())).thenReturn(mEventIndex4);
+ when(mEventHistory5.getEventIndex(anySet())).thenReturn(mEventIndex5);
+ when(mEventHistory1.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex6);
+ when(mEventHistory2.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex7);
+ when(mEventHistory3.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex8);
+ when(mEventHistory4.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex9);
+ when(mEventHistory5.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex10);
+ when(mEventIndex1.getMostRecentActiveTimeSlot()).thenReturn(WITHIN_ONE_DAY);
+ when(mEventIndex2.getMostRecentActiveTimeSlot()).thenReturn(TWO_DAYS_AGO);
+ when(mEventIndex3.getMostRecentActiveTimeSlot()).thenReturn(FIVE_DAYS_AGO);
+ when(mEventIndex4.getMostRecentActiveTimeSlot()).thenReturn(EIGHT_DAYS_AGO);
+ when(mEventIndex5.getMostRecentActiveTimeSlot()).thenReturn(null);
+ when(mDataManager.queryAppLaunchCount(anyInt(), anyLong(), anyLong(), anySet()))
+ .thenReturn(
+ Map.of(PACKAGE_1, 1,
+ PACKAGE_2, 2,
+ PACKAGE_3, 3));
+
+ SharesheetModelScorer.computeScoreForAppShare(
+ List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
+ mShareTarget6),
+ Event.TYPE_SHARE_TEXT, 4, NOW, mDataManager, USER_ID);
+
+ verify(mDataManager, never()).queryAppLaunchCount(anyInt(), anyLong(), anyLong(), anySet());
+ assertEquals(0.4f, mShareTarget1.getScore(), DELTA);
+ assertEquals(0.35f, mShareTarget2.getScore(), DELTA);
+ assertEquals(0.33f, mShareTarget3.getScore(), DELTA);
+ assertEquals(0.31f, mShareTarget4.getScore(), DELTA);
+ assertEquals(0f, mShareTarget5.getScore(), DELTA);
+ assertEquals(0f, mShareTarget6.getScore(), DELTA);
+ }
+
+ @Test
+ public void testComputeScoreForAppShare_promoteForegroundApp() {
+ when(mEventHistory1.getEventIndex(anySet())).thenReturn(mEventIndex1);
+ when(mEventHistory2.getEventIndex(anySet())).thenReturn(mEventIndex2);
+ when(mEventHistory3.getEventIndex(anySet())).thenReturn(mEventIndex3);
+ when(mEventHistory4.getEventIndex(anySet())).thenReturn(mEventIndex4);
+ when(mEventHistory5.getEventIndex(anySet())).thenReturn(mEventIndex5);
+ when(mEventHistory1.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex6);
+ when(mEventHistory2.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex7);
+ when(mEventHistory3.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex8);
+ when(mEventHistory4.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex9);
+ when(mEventHistory5.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex10);
+ when(mDataManager.queryAppMovingToForegroundEvents(anyInt(), anyLong(),
+ anyLong())).thenReturn(
+ List.of(createUsageEvent(PACKAGE_2),
+ createUsageEvent(PACKAGE_3),
+ createUsageEvent(SharesheetModelScorer.CHOOSER_ACTIVITY),
+ createUsageEvent(PACKAGE_3),
+ createUsageEvent(PACKAGE_3))
+ );
+
+ SharesheetModelScorer.computeScoreForAppShare(
+ List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
+ mShareTarget6),
+ Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID);
+
+ verify(mDataManager, times(1)).queryAppMovingToForegroundEvents(anyInt(), anyLong(),
+ anyLong());
+ assertEquals(0f, mShareTarget1.getScore(), DELTA);
+ assertEquals(0f, mShareTarget2.getScore(), DELTA);
+ assertEquals(SharesheetModelScorer.FOREGROUND_APP_WEIGHT, mShareTarget3.getScore(), DELTA);
+ assertEquals(0f, mShareTarget4.getScore(), DELTA);
+ assertEquals(0f, mShareTarget5.getScore(), DELTA);
+ assertEquals(0f, mShareTarget6.getScore(), DELTA);
+ }
+
+ @Test
+ public void testComputeScoreForAppShare_skipPromoteForegroundAppWhenNoValidForegroundApp() {
+ when(mEventHistory1.getEventIndex(anySet())).thenReturn(mEventIndex1);
+ when(mEventHistory2.getEventIndex(anySet())).thenReturn(mEventIndex2);
+ when(mEventHistory3.getEventIndex(anySet())).thenReturn(mEventIndex3);
+ when(mEventHistory4.getEventIndex(anySet())).thenReturn(mEventIndex4);
+ when(mEventHistory5.getEventIndex(anySet())).thenReturn(mEventIndex5);
+ when(mEventHistory1.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex6);
+ when(mEventHistory2.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex7);
+ when(mEventHistory3.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex8);
+ when(mEventHistory4.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex9);
+ when(mEventHistory5.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex10);
+ when(mDataManager.queryAppMovingToForegroundEvents(anyInt(), anyLong(),
+ anyLong())).thenReturn(
+ List.of(createUsageEvent(PACKAGE_3),
+ createUsageEvent(PACKAGE_3),
+ createUsageEvent(SharesheetModelScorer.CHOOSER_ACTIVITY),
+ createUsageEvent(PACKAGE_3),
+ createUsageEvent(PACKAGE_3))
+ );
+
+ SharesheetModelScorer.computeScoreForAppShare(
+ List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
+ mShareTarget6),
+ Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID);
+
+ verify(mDataManager, times(1)).queryAppMovingToForegroundEvents(anyInt(), anyLong(),
+ anyLong());
+ assertEquals(0f, mShareTarget1.getScore(), DELTA);
+ assertEquals(0f, mShareTarget2.getScore(), DELTA);
+ assertEquals(0f, mShareTarget3.getScore(), DELTA);
+ assertEquals(0f, mShareTarget4.getScore(), DELTA);
+ assertEquals(0f, mShareTarget5.getScore(), DELTA);
+ assertEquals(0f, mShareTarget6.getScore(), DELTA);
+ }
+
+ private static UsageEvents.Event createUsageEvent(String packageName) {
+ UsageEvents.Event e = new UsageEvents.Event();
+ e.mPackage = packageName;
+ return e;
+ }
+}
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index c1447465f53f..991375c5bc73 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -2520,7 +2520,6 @@ public final class SmsManager {
* @param sentIntent if not NULL this <code>PendingIntent</code> is
* broadcast when the message is successfully sent, or failed
* @throws IllegalArgumentException if contentUri is empty
- * @deprecated use {@link MmsManager#sendMultimediaMessage} instead.
*/
public void sendMultimediaMessage(Context context, Uri contentUri, String locationUrl,
Bundle configOverrides, PendingIntent sentIntent) {
@@ -2555,7 +2554,6 @@ public final class SmsManager {
* @param downloadedIntent if not NULL this <code>PendingIntent</code> is
* broadcast when the message is downloaded, or the download is failed
* @throws IllegalArgumentException if locationUrl or contentUri is empty
- * @deprecated use {@link MmsManager#downloadMultimediaMessage} instead.
*/
public void downloadMultimediaMessage(Context context, String locationUrl, Uri contentUri,
Bundle configOverrides, PendingIntent downloadedIntent) {
diff --git a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
index f444b77b738e..0ad30391d1a8 100644
--- a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
+++ b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
@@ -46,6 +46,7 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
@@ -72,6 +73,7 @@ public class AppLaunch extends InstrumentationTestCase {
private static final String KEY_REQUIRED_ACCOUNTS = "required_accounts";
private static final String KEY_APPS = "apps";
private static final String KEY_IORAP_TRIAL_LAUNCH = "iorap_trial_launch";
+ private static final String KEY_IORAP_COMPILER_FILTERS = "iorap_compiler_filters";
private static final String KEY_TRIAL_LAUNCH = "trial_launch";
private static final String KEY_LAUNCH_ITERATIONS = "launch_iterations";
private static final String KEY_LAUNCH_ORDER = "launch_order";
@@ -153,6 +155,7 @@ public class AppLaunch extends InstrumentationTestCase {
private BufferedWriter mBufferedWriter = null;
private boolean mSimplePerfAppOnly = false;
private String[] mCompilerFilters = null;
+ private List<String> mIorapCompilerFilters = null;
private String mLastAppName = "";
private boolean mCycleCleanUp = false;
private boolean mTraceAll = false;
@@ -618,6 +621,24 @@ public class AppLaunch extends InstrumentationTestCase {
return reason;
}
+ private boolean shouldIncludeIorap(String compilerFilter) {
+ if (!mIorapTrialLaunch) {
+ return false;
+ }
+
+ // No iorap compiler filters specified: treat all compiler filters as ok.
+ if (mIorapCompilerFilters == null) {
+ return true;
+ }
+
+ // iorap compiler filters specified: the compilerFilter must be in the whitelist.
+ if (mIorapCompilerFilters.indexOf(compilerFilter) != -1) {
+ return true;
+ }
+
+ return false;
+ }
+
/**
* If launch order is "cyclic" then apps will be launched one after the
* other for each iteration count.
@@ -632,7 +653,7 @@ public class AppLaunch extends InstrumentationTestCase {
mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, TRIAL_LAUNCH, /*iorapEnabled*/false));
}
}
- if (mIorapTrialLaunch) {
+ if (shouldIncludeIorap(compilerFilter)) {
for (int launchCount = 0; launchCount < IORAP_TRIAL_LAUNCH_ITERATIONS; ++launchCount) {
for (String app : mNameToResultKey.keySet()) {
String reason = makeReasonForIorapTrialLaunch(launchCount);
@@ -646,14 +667,16 @@ public class AppLaunch extends InstrumentationTestCase {
for (int launchCount = 0; launchCount < mLaunchIterations; launchCount++) {
for (String app : mNameToResultKey.keySet()) {
mLaunchOrderList.add(new LaunchOrder(app, compilerFilter,
- String.format(LAUNCH_ITERATION, launchCount), mIorapTrialLaunch));
+ String.format(LAUNCH_ITERATION, launchCount),
+ shouldIncludeIorap(compilerFilter)));
}
}
if (mTraceDirectoryStr != null && !mTraceDirectoryStr.isEmpty()) {
for (int traceCount = 0; traceCount < mTraceLaunchCount; traceCount++) {
for (String app : mNameToResultKey.keySet()) {
mLaunchOrderList.add(new LaunchOrder(app, compilerFilter,
- String.format(TRACE_ITERATION, traceCount), mIorapTrialLaunch));
+ String.format(TRACE_ITERATION, traceCount),
+ shouldIncludeIorap(compilerFilter)));
}
}
}
@@ -664,7 +687,7 @@ public class AppLaunch extends InstrumentationTestCase {
if (mTrialLaunch) {
mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, TRIAL_LAUNCH, /*iorapEnabled*/false));
}
- if (mIorapTrialLaunch) {
+ if (shouldIncludeIorap(compilerFilter)) {
for (int launchCount = 0; launchCount < IORAP_TRIAL_LAUNCH_ITERATIONS; ++launchCount) {
String reason = makeReasonForIorapTrialLaunch(launchCount);
mLaunchOrderList.add(
@@ -675,12 +698,14 @@ public class AppLaunch extends InstrumentationTestCase {
}
for (int launchCount = 0; launchCount < mLaunchIterations; launchCount++) {
mLaunchOrderList.add(new LaunchOrder(app, compilerFilter,
- String.format(LAUNCH_ITERATION, launchCount), mIorapTrialLaunch));
+ String.format(LAUNCH_ITERATION, launchCount),
+ shouldIncludeIorap(compilerFilter)));
}
if (mTraceDirectoryStr != null && !mTraceDirectoryStr.isEmpty()) {
for (int traceCount = 0; traceCount < mTraceLaunchCount; traceCount++) {
mLaunchOrderList.add(new LaunchOrder(app, compilerFilter,
- String.format(TRACE_ITERATION, traceCount), mIorapTrialLaunch));
+ String.format(TRACE_ITERATION, traceCount),
+ shouldIncludeIorap(compilerFilter)));
}
}
}
@@ -822,6 +847,13 @@ public class AppLaunch extends InstrumentationTestCase {
mCompilerFilters = new String[1];
}
+ String iorapCompilerFilterList = args.getString(KEY_IORAP_COMPILER_FILTERS);
+ if (iorapCompilerFilterList != null) {
+ // Passing in iorap compiler filters implies an iorap trial launch.
+ mIorapTrialLaunch = true;
+ mIorapCompilerFilters = Arrays.asList(iorapCompilerFilterList.split("\\|"));
+ }
+
// Pre-populate the results map to avoid null checks.
for (String app : mNameToLaunchTime.keySet()) {
HashMap<String, List<AppLaunchResult>> map = new HashMap<>();