Merge "[adbwifi] Add Settings metrics for adb wireless."
diff --git a/StubLibraries.bp b/StubLibraries.bp
index d4db737..9b9311f 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -190,7 +190,6 @@
droidstubs {
name: "module-lib-api",
defaults: ["metalava-api-stubs-default"],
- libs: ["framework-all"],
arg_files: ["core/res/AndroidManifest.xml"],
args: metalava_framework_docs_args + module_libs,
check_api: {
@@ -222,7 +221,6 @@
droidstubs {
name: "module-lib-api-stubs-docs",
defaults: ["metalava-api-stubs-default"],
- libs: ["framework-all"],
arg_files: ["core/res/AndroidManifest.xml"],
args: metalava_framework_docs_args + priv_apps + module_libs,
}
@@ -299,12 +297,12 @@
],
libs: [
"stub-annotations",
- "framework-all",
],
static_libs: [
"private-stub-annotations-jar",
],
defaults: ["framework-stubs-default"],
+ sdk_version: "core_current",
}
/////////////////////////////////////////////////////////////////////
diff --git a/apct-tests/perftests/core/src/android/view/CutoutSpecificationBenchmark.java b/apct-tests/perftests/core/src/android/view/CutoutSpecificationBenchmark.java
new file mode 100644
index 0000000..14282bf
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/view/CutoutSpecificationBenchmark.java
@@ -0,0 +1,240 @@
+/*
+ * 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 android.view;
+
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.PathParser;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class CutoutSpecificationBenchmark {
+ private static final String TAG = "CutoutSpecificationBenchmark";
+
+ private static final String BOTTOM_MARKER = "@bottom";
+ private static final String DP_MARKER = "@dp";
+ private static final String RIGHT_MARKER = "@right";
+ private static final String LEFT_MARKER = "@left";
+
+ private static final String DOUBLE_CUTOUT_SPEC = "M 0,0\n"
+ + "L -72, 0\n"
+ + "L -69.9940446283, 20.0595537175\n"
+ + "C -69.1582133885, 28.4178661152 -65.2, 32.0 -56.8, 32.0\n"
+ + "L 56.8, 32.0\n"
+ + "C 65.2, 32.0 69.1582133885, 28.4178661152 69.9940446283, 20.0595537175\n"
+ + "L 72, 0\n"
+ + "Z\n"
+ + "@bottom\n"
+ + "M 0,0\n"
+ + "L -72, 0\n"
+ + "L -69.9940446283, -20.0595537175\n"
+ + "C -69.1582133885, -28.4178661152 -65.2, -32.0 -56.8, -32.0\n"
+ + "L 56.8, -32.0\n"
+ + "C 65.2, -32.0 69.1582133885, -28.4178661152 69.9940446283, -20.0595537175\n"
+ + "L 72, 0\n"
+ + "Z\n"
+ + "@dp";
+ @Rule
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ private Context mContext;
+ private DisplayMetrics mDisplayMetrics;
+
+ /**
+ * Setup the necessary member field used by test methods.
+ */
+ @Before
+ public void setUp() {
+ mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ mDisplayMetrics = new DisplayMetrics();
+ mContext.getDisplay().getRealMetrics(mDisplayMetrics);
+ }
+
+
+ private static void toRectAndAddToRegion(Path p, Region inoutRegion, Rect inoutRect) {
+ final RectF rectF = new RectF();
+ p.computeBounds(rectF, false /* unused */);
+ rectF.round(inoutRect);
+ inoutRegion.op(inoutRect, Region.Op.UNION);
+ }
+
+ private static void oldMethodParsingSpec(String spec, int displayWidth, int displayHeight,
+ float density) {
+ Path p = null;
+ Rect boundTop = null;
+ Rect boundBottom = null;
+ Rect safeInset = new Rect();
+ String bottomSpec = null;
+ if (!TextUtils.isEmpty(spec)) {
+ spec = spec.trim();
+ final float offsetX;
+ if (spec.endsWith(RIGHT_MARKER)) {
+ offsetX = displayWidth;
+ spec = spec.substring(0, spec.length() - RIGHT_MARKER.length()).trim();
+ } else if (spec.endsWith(LEFT_MARKER)) {
+ offsetX = 0;
+ spec = spec.substring(0, spec.length() - LEFT_MARKER.length()).trim();
+ } else {
+ offsetX = displayWidth / 2f;
+ }
+ final boolean inDp = spec.endsWith(DP_MARKER);
+ if (inDp) {
+ spec = spec.substring(0, spec.length() - DP_MARKER.length());
+ }
+
+ if (spec.contains(BOTTOM_MARKER)) {
+ String[] splits = spec.split(BOTTOM_MARKER, 2);
+ spec = splits[0].trim();
+ bottomSpec = splits[1].trim();
+ }
+
+ final Matrix m = new Matrix();
+ final Region r = Region.obtain();
+ if (!spec.isEmpty()) {
+ try {
+ p = PathParser.createPathFromPathData(spec);
+ } catch (Throwable e) {
+ Log.wtf(TAG, "Could not inflate cutout: ", e);
+ }
+
+ if (p != null) {
+ if (inDp) {
+ m.postScale(density, density);
+ }
+ m.postTranslate(offsetX, 0);
+ p.transform(m);
+
+ boundTop = new Rect();
+ toRectAndAddToRegion(p, r, boundTop);
+ safeInset.top = boundTop.bottom;
+ }
+ }
+
+ if (bottomSpec != null) {
+ int bottomInset = 0;
+ Path bottomPath = null;
+ try {
+ bottomPath = PathParser.createPathFromPathData(bottomSpec);
+ } catch (Throwable e) {
+ Log.wtf(TAG, "Could not inflate bottom cutout: ", e);
+ }
+
+ if (bottomPath != null) {
+ // Keep top transform
+ m.postTranslate(0, displayHeight);
+ bottomPath.transform(m);
+ p.addPath(bottomPath);
+ boundBottom = new Rect();
+ toRectAndAddToRegion(bottomPath, r, boundBottom);
+ bottomInset = displayHeight - boundBottom.top;
+ }
+ safeInset.bottom = bottomInset;
+ }
+ }
+ }
+
+ @Test
+ public void parseByOldMethodForDoubleCutout() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ oldMethodParsingSpec(DOUBLE_CUTOUT_SPEC, mDisplayMetrics.widthPixels,
+ mDisplayMetrics.heightPixels, mDisplayMetrics.density);
+ }
+ }
+
+ @Test
+ public void parseByNewMethodForDoubleCutout() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ new CutoutSpecification.Parser(mDisplayMetrics.density,
+ mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels)
+ .parse(DOUBLE_CUTOUT_SPEC);
+ }
+ }
+
+ @Test
+ public void parseLongEdgeCutout() {
+ final String spec = "M 0,0\n"
+ + "H 48\n"
+ + "V 48\n"
+ + "H -48\n"
+ + "Z\n"
+ + "@left\n"
+ + "@center_vertical\n"
+ + "M 0,0\n"
+ + "H 48\n"
+ + "V 48\n"
+ + "H -48\n"
+ + "Z\n"
+ + "@left\n"
+ + "@center_vertical\n"
+ + "M 0,0\n"
+ + "H -48\n"
+ + "V 48\n"
+ + "H 48\n"
+ + "Z\n"
+ + "@right\n"
+ + "@dp";
+
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ new CutoutSpecification.Parser(mDisplayMetrics.density,
+ mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels).parse(spec);
+ }
+ }
+
+ @Test
+ public void parseShortEdgeCutout() {
+ final String spec = "M 0,0\n"
+ + "H 48\n"
+ + "V 48\n"
+ + "H -48\n"
+ + "Z\n"
+ + "@bottom\n"
+ + "M 0,0\n"
+ + "H 48\n"
+ + "V -48\n"
+ + "H -48\n"
+ + "Z\n"
+ + "@dp";
+
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ new CutoutSpecification.Parser(mDisplayMetrics.density,
+ mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels).parse(spec);
+ }
+ }
+}
diff --git a/apex/statsd/framework/Android.bp b/apex/statsd/framework/Android.bp
index d515a85..80def47 100644
--- a/apex/statsd/framework/Android.bp
+++ b/apex/statsd/framework/Android.bp
@@ -12,11 +12,27 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+genrule {
+ name: "statslog-statsd-java-gen",
+ tools: ["stats-log-api-gen"],
+ cmd: "$(location stats-log-api-gen) --java $(out) --module statsd" +
+ " --javaPackage com.android.internal.util --javaClass StatsdStatsLog",
+ out: ["com/android/internal/util/StatsdStatsLog.java"],
+}
+
+java_library_static {
+ name: "statslog-statsd",
+ srcs: [
+ ":statslog-statsd-java-gen",
+ ],
+}
+
filegroup {
name: "framework-statsd-sources",
srcs: [
"java/**/*.java",
":statsd_java_aidl",
+ ":statslog-statsd-java-gen",
],
}
diff --git a/apex/statsd/framework/java/android/app/StatsManager.java b/apex/statsd/framework/java/android/app/StatsManager.java
index 411482b..526d17f 100644
--- a/apex/statsd/framework/java/android/app/StatsManager.java
+++ b/apex/statsd/framework/java/android/app/StatsManager.java
@@ -32,7 +32,7 @@
import android.os.RemoteException;
import android.os.StatsFrameworkInitializer;
import android.util.AndroidException;
-import android.util.Slog;
+import android.util.Log;
import android.util.StatsEvent;
import android.util.StatsEventParcel;
@@ -155,7 +155,7 @@
// can throw IllegalArgumentException
service.addConfiguration(configKey, config, mContext.getOpPackageName());
} catch (RemoteException e) {
- Slog.e(TAG, "Failed to connect to statsmanager when adding configuration");
+ Log.e(TAG, "Failed to connect to statsmanager when adding configuration");
throw new StatsUnavailableException("could not connect", e);
} catch (SecurityException e) {
throw new StatsUnavailableException(e.getMessage(), e);
@@ -191,7 +191,7 @@
IStatsManagerService service = getIStatsManagerServiceLocked();
service.removeConfiguration(configKey, mContext.getOpPackageName());
} catch (RemoteException e) {
- Slog.e(TAG, "Failed to connect to statsmanager when removing configuration");
+ Log.e(TAG, "Failed to connect to statsmanager when removing configuration");
throw new StatsUnavailableException("could not connect", e);
} catch (SecurityException e) {
throw new StatsUnavailableException(e.getMessage(), e);
@@ -258,7 +258,7 @@
mContext.getOpPackageName());
}
} catch (RemoteException e) {
- Slog.e(TAG, "Failed to connect to statsmanager when adding broadcast subscriber",
+ Log.e(TAG, "Failed to connect to statsmanager when adding broadcast subscriber",
e);
throw new StatsUnavailableException("could not connect", e);
} catch (SecurityException e) {
@@ -311,7 +311,7 @@
}
} catch (RemoteException e) {
- Slog.e(TAG, "Failed to connect to statsmanager when registering data listener.");
+ Log.e(TAG, "Failed to connect to statsmanager when registering data listener.");
throw new StatsUnavailableException("could not connect", e);
} catch (SecurityException e) {
throw new StatsUnavailableException(e.getMessage(), e);
@@ -348,7 +348,7 @@
}
} catch (RemoteException e) {
- Slog.e(TAG, "Failed to connect to statsmanager "
+ Log.e(TAG, "Failed to connect to statsmanager "
+ "when registering active configs listener.");
throw new StatsUnavailableException("could not connect", e);
} catch (SecurityException e) {
@@ -387,7 +387,7 @@
IStatsManagerService service = getIStatsManagerServiceLocked();
return service.getData(configKey, mContext.getOpPackageName());
} catch (RemoteException e) {
- Slog.e(TAG, "Failed to connect to statsmanager when getting data");
+ Log.e(TAG, "Failed to connect to statsmanager when getting data");
throw new StatsUnavailableException("could not connect", e);
} catch (SecurityException e) {
throw new StatsUnavailableException(e.getMessage(), e);
@@ -424,7 +424,7 @@
IStatsManagerService service = getIStatsManagerServiceLocked();
return service.getMetadata(mContext.getOpPackageName());
} catch (RemoteException e) {
- Slog.e(TAG, "Failed to connect to statsmanager when getting metadata");
+ Log.e(TAG, "Failed to connect to statsmanager when getting metadata");
throw new StatsUnavailableException("could not connect", e);
} catch (SecurityException e) {
throw new StatsUnavailableException(e.getMessage(), e);
@@ -464,7 +464,7 @@
return service.getRegisteredExperimentIds();
} catch (RemoteException e) {
if (DEBUG) {
- Slog.d(TAG,
+ Log.d(TAG,
"Failed to connect to StatsManagerService when getting "
+ "registered experiment IDs");
}
@@ -555,7 +555,7 @@
try {
resultReceiver.pullFinished(atomTag, success, parcels);
} catch (RemoteException e) {
- Slog.w(TAG, "StatsPullResultReceiver failed for tag " + mAtomId);
+ Log.w(TAG, "StatsPullResultReceiver failed for tag " + mAtomId);
}
});
} finally {
diff --git a/apex/statsd/framework/java/android/os/StatsDimensionsValue.java b/apex/statsd/framework/java/android/os/StatsDimensionsValue.java
index 71d4359..35273da 100644
--- a/apex/statsd/framework/java/android/os/StatsDimensionsValue.java
+++ b/apex/statsd/framework/java/android/os/StatsDimensionsValue.java
@@ -16,7 +16,7 @@
package android.os;
import android.annotation.SystemApi;
-import android.util.Slog;
+import android.util.Log;
import java.util.ArrayList;
import java.util.List;
@@ -129,7 +129,7 @@
mValue = values;
break;
default:
- Slog.w(TAG, "StatsDimensionsValueParcel contains bad valueType: " + mValueType);
+ Log.w(TAG, "StatsDimensionsValueParcel contains bad valueType: " + mValueType);
mValue = null;
break;
}
@@ -155,7 +155,7 @@
try {
if (mValueType == STRING_VALUE_TYPE) return (String) mValue;
} catch (ClassCastException e) {
- Slog.w(TAG, "Failed to successfully get value", e);
+ Log.w(TAG, "Failed to successfully get value", e);
}
return null;
}
@@ -169,7 +169,7 @@
try {
if (mValueType == INT_VALUE_TYPE) return (Integer) mValue;
} catch (ClassCastException e) {
- Slog.w(TAG, "Failed to successfully get value", e);
+ Log.w(TAG, "Failed to successfully get value", e);
}
return 0;
}
@@ -183,7 +183,7 @@
try {
if (mValueType == LONG_VALUE_TYPE) return (Long) mValue;
} catch (ClassCastException e) {
- Slog.w(TAG, "Failed to successfully get value", e);
+ Log.w(TAG, "Failed to successfully get value", e);
}
return 0;
}
@@ -198,7 +198,7 @@
try {
if (mValueType == BOOLEAN_VALUE_TYPE) return (Boolean) mValue;
} catch (ClassCastException e) {
- Slog.w(TAG, "Failed to successfully get value", e);
+ Log.w(TAG, "Failed to successfully get value", e);
}
return false;
}
@@ -212,7 +212,7 @@
try {
if (mValueType == FLOAT_VALUE_TYPE) return (Float) mValue;
} catch (ClassCastException e) {
- Slog.w(TAG, "Failed to successfully get value", e);
+ Log.w(TAG, "Failed to successfully get value", e);
}
return 0;
}
@@ -238,7 +238,7 @@
}
return copy;
} catch (ClassCastException e) {
- Slog.w(TAG, "Failed to successfully get value", e);
+ Log.w(TAG, "Failed to successfully get value", e);
return null;
}
}
@@ -297,7 +297,7 @@
}
return sb.toString();
} catch (ClassCastException e) {
- Slog.w(TAG, "Failed to successfully get value", e);
+ Log.w(TAG, "Failed to successfully get value", e);
}
return "";
}
@@ -357,11 +357,11 @@
return true;
}
default:
- Slog.w(TAG, "readValue of an impossible type " + valueType);
+ Log.w(TAG, "readValue of an impossible type " + valueType);
return false;
}
} catch (ClassCastException e) {
- Slog.w(TAG, "writeValue cast failed", e);
+ Log.w(TAG, "writeValue cast failed", e);
return false;
}
}
@@ -388,7 +388,7 @@
return values;
}
default:
- Slog.w(TAG, "readValue of an impossible type " + valueType);
+ Log.w(TAG, "readValue of an impossible type " + valueType);
return null;
}
}
diff --git a/apex/statsd/framework/java/android/util/StatsLog.java b/apex/statsd/framework/java/android/util/StatsLog.java
index e7659d8..511bc01 100644
--- a/apex/statsd/framework/java/android/util/StatsLog.java
+++ b/apex/statsd/framework/java/android/util/StatsLog.java
@@ -26,10 +26,10 @@
import android.content.Context;
import android.os.IStatsd;
import android.os.RemoteException;
-import android.os.ServiceManager;
+import android.os.StatsFrameworkInitializer;
import android.util.proto.ProtoOutputStream;
-import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.StatsdStatsLog;
/**
* StatsLog provides an API for developers to send events to statsd. The events can be used to
@@ -59,17 +59,17 @@
IStatsd service = getIStatsdLocked();
if (service == null) {
if (DEBUG) {
- Slog.d(TAG, "Failed to find statsd when logging start");
+ Log.d(TAG, "Failed to find statsd when logging start");
}
return false;
}
service.sendAppBreadcrumbAtom(label,
- FrameworkStatsLog.APP_BREADCRUMB_REPORTED__STATE__START);
+ StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__START);
return true;
} catch (RemoteException e) {
sService = null;
if (DEBUG) {
- Slog.d(TAG, "Failed to connect to statsd when logging start");
+ Log.d(TAG, "Failed to connect to statsd when logging start");
}
return false;
}
@@ -88,17 +88,17 @@
IStatsd service = getIStatsdLocked();
if (service == null) {
if (DEBUG) {
- Slog.d(TAG, "Failed to find statsd when logging stop");
+ Log.d(TAG, "Failed to find statsd when logging stop");
}
return false;
}
service.sendAppBreadcrumbAtom(
- label, FrameworkStatsLog.APP_BREADCRUMB_REPORTED__STATE__STOP);
+ label, StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__STOP);
return true;
} catch (RemoteException e) {
sService = null;
if (DEBUG) {
- Slog.d(TAG, "Failed to connect to statsd when logging stop");
+ Log.d(TAG, "Failed to connect to statsd when logging stop");
}
return false;
}
@@ -117,17 +117,17 @@
IStatsd service = getIStatsdLocked();
if (service == null) {
if (DEBUG) {
- Slog.d(TAG, "Failed to find statsd when logging event");
+ Log.d(TAG, "Failed to find statsd when logging event");
}
return false;
}
service.sendAppBreadcrumbAtom(
- label, FrameworkStatsLog.APP_BREADCRUMB_REPORTED__STATE__UNSPECIFIED);
+ label, StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__UNSPECIFIED);
return true;
} catch (RemoteException e) {
sService = null;
if (DEBUG) {
- Slog.d(TAG, "Failed to connect to statsd when logging event");
+ Log.d(TAG, "Failed to connect to statsd when logging event");
}
return false;
}
@@ -162,7 +162,7 @@
| EXPERIMENT_IDS_FIELD_ID,
id);
}
- FrameworkStatsLog.write(FrameworkStatsLog.BINARY_PUSH_STATE_CHANGED,
+ StatsdStatsLog.write(StatsdStatsLog.BINARY_PUSH_STATE_CHANGED,
trainName,
trainVersionCode,
(options & IStatsd.FLAG_REQUIRE_STAGING) > 0,
@@ -180,7 +180,10 @@
if (sService != null) {
return sService;
}
- sService = IStatsd.Stub.asInterface(ServiceManager.getService("stats"));
+ sService = IStatsd.Stub.asInterface(StatsFrameworkInitializer
+ .getStatsServiceManager()
+ .getStatsdServiceRegisterer()
+ .get());
return sService;
}
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 5e2dbf3..4477754 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -127,7 +127,7 @@
WallClockTimeShifted wall_clock_time_shifted = 45 [(module) = "framework"];
AnomalyDetected anomaly_detected = 46;
AppBreadcrumbReported app_breadcrumb_reported =
- 47 [(allow_from_any_uid) = true, (module) = "framework"];
+ 47 [(allow_from_any_uid) = true, (module) = "statsd"];
AppStartOccurred app_start_occurred = 48 [(module) = "framework"];
AppStartCanceled app_start_canceled = 49 [(module) = "framework"];
AppStartFullyDrawn app_start_fully_drawn = 50 [(module) = "framework"];
@@ -188,7 +188,7 @@
ServiceStateChanged service_state_changed = 99 [(module) = "framework"];
ServiceLaunchReported service_launch_reported = 100 [(module) = "framework"];
FlagFlipUpdateOccurred flag_flip_update_occurred = 101 [(module) = "framework"];
- BinaryPushStateChanged binary_push_state_changed = 102 [(module) = "framework"];
+ BinaryPushStateChanged binary_push_state_changed = 102 [(module) = "statsd"];
DevicePolicyEvent device_policy_event = 103 [(module) = "framework"];
DocsUIFileOperationCanceledReported docs_ui_file_op_canceled = 104 [(module) = "docsui"];
DocsUIFileOperationCopyMoveModeReported docs_ui_file_op_copy_move_mode_reported =
@@ -391,6 +391,7 @@
WifiHealthStatReported wifi_health_stat_reported = 251 [(module) = "wifi"];
WifiFailureStatReported wifi_failure_stat_reported = 252 [(module) = "wifi"];
WifiConnectionResultReported wifi_connection_result_reported = 253 [(module) = "wifi"];
+ AppFreezeChanged app_freeze_changed = 254 [(module) = "framework"];
SdkExtensionStatus sdk_extension_status = 354;
}
@@ -8366,3 +8367,28 @@
// "Failed" here can mean a symbol that wasn't meant to be visible was, or the other way around.
optional int32 failed_call_symbol = 3;
}
+
+/**
+ * Logs when an app is frozen or unfrozen.
+ *
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java
+ */
+message AppFreezeChanged {
+ // The type of event.
+ enum Action {
+ UNKNOWN = 0;
+ FREEZE_APP = 1;
+ UNFREEZE_APP = 2;
+ }
+ optional Action action = 1;
+
+ // Pid of the process being frozen.
+ optional int32 pid = 2;
+
+ // Name of the process being frozen.
+ optional string process_name = 3;
+
+ // Time since last unfrozen.
+ optional int64 time_unfrozen_millis = 4;
+}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 7599791..9cec514 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -9462,16 +9462,6 @@
* {@link android.os.Build.VERSION_CODES#M} the app-op matching the permission is set to
* {@link android.app.AppOpsManager#MODE_IGNORED}, but the permission stays granted.
*
- * NOTE: Starting from Android R, location-related permissions cannot be granted by the
- * admin: Calling this method with {@link #PERMISSION_GRANT_STATE_GRANTED} for any of the
- * following permissions will return false:
- *
- * <ul>
- * <li>{@code ACCESS_FINE_LOCATION}</li>
- * <li>{@code ACCESS_BACKGROUND_LOCATION}</li>
- * <li>{@code ACCESS_COARSE_LOCATION}</li>
- * </ul>
- *
* @param admin Which profile or device owner this request is associated with.
* @param packageName The application to grant or revoke a permission to.
* @param permission The permission to grant or revoke.
diff --git a/core/java/android/content/pm/parsing/AndroidPackage.java b/core/java/android/content/pm/parsing/AndroidPackage.java
index fbe5a48..da17ff3 100644
--- a/core/java/android/content/pm/parsing/AndroidPackage.java
+++ b/core/java/android/content/pm/parsing/AndroidPackage.java
@@ -286,6 +286,8 @@
List<String> getQueriesPackages();
+ Set<String> getQueriesProviders();
+
String getRealPackage();
// TODO(b/135203078): Rename to getRequiredFeatures? Somewhat ambiguous whether "Req" is
diff --git a/core/java/android/content/pm/parsing/ApkParseUtils.java b/core/java/android/content/pm/parsing/ApkParseUtils.java
index 5c8c9a4..548d82a 100644
--- a/core/java/android/content/pm/parsing/ApkParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkParseUtils.java
@@ -96,6 +96,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.StringTokenizer;
/** @hide */
public class ApkParseUtils {
@@ -1817,6 +1818,25 @@
);
}
parsingPackage.addQueriesPackage(packageName.intern());
+ } else if (parser.getName().equals("provider")) {
+ final TypedArray sa = res.obtainAttributes(parser,
+ R.styleable.AndroidManifestQueriesProvider);
+ try {
+ final String authorities =
+ sa.getString(R.styleable.AndroidManifestQueriesProvider_authorities);
+ if (TextUtils.isEmpty(authorities)) {
+ return parseInput.error(
+ PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+ "Authority missing from provider tag."
+ );
+ }
+ StringTokenizer authoritiesTokenizer = new StringTokenizer(authorities, ";");
+ while (authoritiesTokenizer.hasMoreElements()) {
+ parsingPackage.addQueriesProvider(authoritiesTokenizer.nextToken());
+ }
+ } finally {
+ sa.recycle();
+ }
}
}
return parseInput.success(parsingPackage);
diff --git a/core/java/android/content/pm/parsing/PackageImpl.java b/core/java/android/content/pm/parsing/PackageImpl.java
index fe8307c..0df9500 100644
--- a/core/java/android/content/pm/parsing/PackageImpl.java
+++ b/core/java/android/content/pm/parsing/PackageImpl.java
@@ -216,6 +216,9 @@
private ArrayList<String> queriesPackages;
@Nullable
+ private ArraySet<String> queriesProviders;
+
+ @Nullable
private ArrayMap<String, ComponentParseUtils.ParsedProcess> processes;
private String[] splitClassLoaderNames;
@@ -957,6 +960,12 @@
}
@Override
+ public ParsingPackage addQueriesProvider(String authority) {
+ this.queriesProviders = ArrayUtils.add(this.queriesProviders, authority);
+ return this;
+ }
+
+ @Override
public PackageImpl setProcesses(ArrayMap<String, ComponentParseUtils.ParsedProcess> processes) {
this.processes = processes;
return this;
@@ -2975,6 +2984,11 @@
return queriesPackages;
}
+ @Override
+ public Set<String> getQueriesProviders() {
+ return queriesProviders;
+ }
+
private static void internStringArrayList(List<String> list) {
if (list != null) {
final int N = list.size();
diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java
index 9ddcc09..a2fe064 100644
--- a/core/java/android/content/pm/parsing/ParsingPackage.java
+++ b/core/java/android/content/pm/parsing/ParsingPackage.java
@@ -100,6 +100,8 @@
ParsingPackage addQueriesPackage(String packageName);
+ ParsingPackage addQueriesProvider(String authority);
+
ParsingPackage setProcesses(ArrayMap<String, ComponentParseUtils.ParsedProcess> processes);
ParsingPackage asSplit(
diff --git a/core/java/android/view/CutoutSpecification.java b/core/java/android/view/CutoutSpecification.java
new file mode 100644
index 0000000..d21a952
--- /dev/null
+++ b/core/java/android/view/CutoutSpecification.java
@@ -0,0 +1,486 @@
+/*
+ * 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 android.view;
+
+import static android.view.Gravity.BOTTOM;
+import static android.view.Gravity.LEFT;
+import static android.view.Gravity.RIGHT;
+import static android.view.Gravity.TOP;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Insets;
+import android.graphics.Matrix;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.PathParser;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * In order to accept the cutout specification for all of edges in devices, the specification
+ * parsing method is extracted from
+ * {@link android.view.DisplayCutout#fromResourcesRectApproximation(Resources, int, int)} to be
+ * the specified class for parsing the specification.
+ * BNF definition:
+ * <ul>
+ * <li>Cutouts Specification = ([Cutout Delimiter],Cutout Specification) {...}, [Dp] ; </li>
+ * <li>Cutout Specification = [Vertical Position], (SVG Path Element), [Horizontal Position]
+ * [Bind Cutout] ;</li>
+ * <li>Vertical Position = "@bottom" | "@center_vertical" ;</li>
+ * <li>Horizontal Position = "@left" | "@right" ;</li>
+ * <li>Bind Cutout = "@bind_left_cutout" | "@bind_right_cutout" ;</li>
+ * <li>Cutout Delimiter = "@cutout" ;</li>
+ * <li>Dp = "@dp"</li>
+ * </ul>
+ *
+ * <ul>
+ * <li>Vertical position is top by default if there is neither "@bottom" nor "@center_vertical"
+ * </li>
+ * <li>Horizontal position is center horizontal by default if there is neither "@left" nor
+ * "@right".</li>
+ * <li>@bottom make the cutout piece bind to bottom edge.</li>
+ * <li>both of @bind_left_cutout and @bind_right_cutout are use to claim the cutout belong to
+ * left or right edge cutout.</li>
+ * </ul>
+ *
+ * @hide
+ */
+@VisibleForTesting(visibility = PACKAGE)
+public class CutoutSpecification {
+ private static final String TAG = "CutoutSpecification";
+ private static final boolean DEBUG = false;
+
+ private static final int MINIMAL_ACCEPTABLE_PATH_LENGTH = "H1V1Z".length();
+
+ private static final char MARKER_START_CHAR = '@';
+ private static final String DP_MARKER = MARKER_START_CHAR + "dp";
+
+ private static final String BOTTOM_MARKER = MARKER_START_CHAR + "bottom";
+ private static final String RIGHT_MARKER = MARKER_START_CHAR + "right";
+ private static final String LEFT_MARKER = MARKER_START_CHAR + "left";
+ private static final String CUTOUT_MARKER = MARKER_START_CHAR + "cutout";
+ private static final String CENTER_VERTICAL_MARKER = MARKER_START_CHAR + "center_vertical";
+
+ /* By default, it's top bound cutout. That's why TOP_BOUND_CUTOUT_MARKER is not defined */
+ private static final String BIND_RIGHT_CUTOUT_MARKER = MARKER_START_CHAR + "bind_right_cutout";
+ private static final String BIND_LEFT_CUTOUT_MARKER = MARKER_START_CHAR + "bind_left_cutout";
+
+ private final Path mPath;
+ private final Rect mLeftBound;
+ private final Rect mTopBound;
+ private final Rect mRightBound;
+ private final Rect mBottomBound;
+ private final Insets mInsets;
+
+ private CutoutSpecification(@NonNull Parser parser) {
+ mPath = parser.mPath;
+ mLeftBound = parser.mLeftBound;
+ mTopBound = parser.mTopBound;
+ mRightBound = parser.mRightBound;
+ mBottomBound = parser.mBottomBound;
+ mInsets = parser.mInsets;
+
+ if (DEBUG) {
+ Log.d(TAG, String.format(Locale.ENGLISH,
+ "left cutout = %s, top cutout = %s, right cutout = %s, bottom cutout = %s",
+ mLeftBound != null ? mLeftBound.toString() : "",
+ mTopBound != null ? mTopBound.toString() : "",
+ mRightBound != null ? mRightBound.toString() : "",
+ mBottomBound != null ? mBottomBound.toString() : ""));
+ }
+ }
+
+ @VisibleForTesting(visibility = PACKAGE)
+ @Nullable
+ public Path getPath() {
+ return mPath;
+ }
+
+ @VisibleForTesting(visibility = PACKAGE)
+ @Nullable
+ public Rect getLeftBound() {
+ return mLeftBound;
+ }
+
+ @VisibleForTesting(visibility = PACKAGE)
+ @Nullable
+ public Rect getTopBound() {
+ return mTopBound;
+ }
+
+ @VisibleForTesting(visibility = PACKAGE)
+ @Nullable
+ public Rect getRightBound() {
+ return mRightBound;
+ }
+
+ @VisibleForTesting(visibility = PACKAGE)
+ @Nullable
+ public Rect getBottomBound() {
+ return mBottomBound;
+ }
+
+ /**
+ * To count the safe inset according to the cutout bounds and waterfall inset.
+ *
+ * @return the safe inset.
+ */
+ @VisibleForTesting(visibility = PACKAGE)
+ @NonNull
+ public Rect getSafeInset() {
+ return mInsets.toRect();
+ }
+
+ private static int decideWhichEdge(boolean isTopEdgeShortEdge,
+ boolean isShortEdge, boolean isStart) {
+ return (isTopEdgeShortEdge)
+ ? ((isShortEdge) ? (isStart ? TOP : BOTTOM) : (isStart ? LEFT : RIGHT))
+ : ((isShortEdge) ? (isStart ? LEFT : RIGHT) : (isStart ? TOP : BOTTOM));
+ }
+
+ /**
+ * The CutoutSpecification Parser.
+ */
+ @VisibleForTesting(visibility = PACKAGE)
+ public static class Parser {
+ private final boolean mIsShortEdgeOnTop;
+ private final float mDensity;
+ private final int mDisplayWidth;
+ private final int mDisplayHeight;
+ private final Matrix mMatrix;
+ private Insets mInsets;
+ private int mSafeInsetLeft;
+ private int mSafeInsetTop;
+ private int mSafeInsetRight;
+ private int mSafeInsetBottom;
+
+ private final Rect mTmpRect = new Rect();
+ private final RectF mTmpRectF = new RectF();
+
+ private boolean mInDp;
+
+ private Path mPath;
+ private Rect mLeftBound;
+ private Rect mTopBound;
+ private Rect mRightBound;
+ private Rect mBottomBound;
+
+ private boolean mPositionFromLeft = false;
+ private boolean mPositionFromRight = false;
+ private boolean mPositionFromBottom = false;
+ private boolean mPositionFromCenterVertical = false;
+
+ private boolean mBindLeftCutout = false;
+ private boolean mBindRightCutout = false;
+ private boolean mBindBottomCutout = false;
+
+ private boolean mIsTouchShortEdgeStart;
+ private boolean mIsTouchShortEdgeEnd;
+ private boolean mIsCloserToStartSide;
+
+ /**
+ * The constructor of the CutoutSpecification parser to parse the specification of cutout.
+ * @param density the display density.
+ * @param displayWidth the display width.
+ * @param displayHeight the display height.
+ */
+ @VisibleForTesting(visibility = PACKAGE)
+ public Parser(float density, int displayWidth, int displayHeight) {
+ mDensity = density;
+ mDisplayWidth = displayWidth;
+ mDisplayHeight = displayHeight;
+ mMatrix = new Matrix();
+ mIsShortEdgeOnTop = mDisplayWidth < mDisplayHeight;
+ }
+
+ private void computeBoundsRectAndAddToRegion(Path p, Region inoutRegion, Rect inoutRect) {
+ mTmpRectF.setEmpty();
+ p.computeBounds(mTmpRectF, false /* unused */);
+ mTmpRectF.round(inoutRect);
+ inoutRegion.op(inoutRect, Region.Op.UNION);
+ }
+
+ private void resetStatus(StringBuilder sb) {
+ sb.setLength(0);
+ mPositionFromBottom = false;
+ mPositionFromLeft = false;
+ mPositionFromRight = false;
+ mPositionFromCenterVertical = false;
+
+ mBindLeftCutout = false;
+ mBindRightCutout = false;
+ mBindBottomCutout = false;
+ }
+
+ private void translateMatrix() {
+ final float offsetX;
+ if (mPositionFromRight) {
+ offsetX = mDisplayWidth;
+ } else if (mPositionFromLeft) {
+ offsetX = 0;
+ } else {
+ offsetX = mDisplayWidth / 2f;
+ }
+
+ final float offsetY;
+ if (mPositionFromBottom) {
+ offsetY = mDisplayHeight;
+ } else if (mPositionFromCenterVertical) {
+ offsetY = mDisplayHeight / 2f;
+ } else {
+ offsetY = 0;
+ }
+
+ mMatrix.reset();
+ if (mInDp) {
+ mMatrix.postScale(mDensity, mDensity);
+ }
+ mMatrix.postTranslate(offsetX, offsetY);
+ }
+
+ private int computeSafeInsets(int gravity, Rect rect) {
+ if (gravity == LEFT && rect.right > 0 && rect.right < mDisplayWidth) {
+ return rect.right;
+ } else if (gravity == TOP && rect.bottom > 0 && rect.bottom < mDisplayHeight) {
+ return rect.bottom;
+ } else if (gravity == RIGHT && rect.left > 0 && rect.left < mDisplayWidth) {
+ return mDisplayWidth - rect.left;
+ } else if (gravity == BOTTOM && rect.top > 0 && rect.top < mDisplayHeight) {
+ return mDisplayHeight - rect.top;
+ }
+ return 0;
+ }
+
+ private void setSafeInset(int gravity, int inset) {
+ if (gravity == LEFT) {
+ mSafeInsetLeft = inset;
+ } else if (gravity == TOP) {
+ mSafeInsetTop = inset;
+ } else if (gravity == RIGHT) {
+ mSafeInsetRight = inset;
+ } else if (gravity == BOTTOM) {
+ mSafeInsetBottom = inset;
+ }
+ }
+
+ private int getSafeInset(int gravity) {
+ if (gravity == LEFT) {
+ return mSafeInsetLeft;
+ } else if (gravity == TOP) {
+ return mSafeInsetTop;
+ } else if (gravity == RIGHT) {
+ return mSafeInsetRight;
+ } else if (gravity == BOTTOM) {
+ return mSafeInsetBottom;
+ }
+ return 0;
+ }
+
+ @NonNull
+ private Rect onSetEdgeCutout(boolean isStart, boolean isShortEdge, @NonNull Rect rect) {
+ final int gravity;
+ if (isShortEdge) {
+ gravity = decideWhichEdge(mIsShortEdgeOnTop, true, isStart);
+ } else {
+ if (mIsTouchShortEdgeStart && mIsTouchShortEdgeEnd) {
+ gravity = decideWhichEdge(mIsShortEdgeOnTop, false, isStart);
+ } else if (mIsTouchShortEdgeStart || mIsTouchShortEdgeEnd) {
+ gravity = decideWhichEdge(mIsShortEdgeOnTop, true,
+ mIsCloserToStartSide);
+ } else {
+ gravity = decideWhichEdge(mIsShortEdgeOnTop, isShortEdge, isStart);
+ }
+ }
+
+ int oldSafeInset = getSafeInset(gravity);
+ int newSafeInset = computeSafeInsets(gravity, rect);
+ if (oldSafeInset < newSafeInset) {
+ setSafeInset(gravity, newSafeInset);
+ }
+
+ return new Rect(rect);
+ }
+
+ private void setEdgeCutout(@NonNull Path newPath) {
+ if (mBindRightCutout && mRightBound == null) {
+ mRightBound = onSetEdgeCutout(false, !mIsShortEdgeOnTop, mTmpRect);
+ } else if (mBindLeftCutout && mLeftBound == null) {
+ mLeftBound = onSetEdgeCutout(true, !mIsShortEdgeOnTop, mTmpRect);
+ } else if (mBindBottomCutout && mBottomBound == null) {
+ mBottomBound = onSetEdgeCutout(false, mIsShortEdgeOnTop, mTmpRect);
+ } else if (!(mBindBottomCutout || mBindLeftCutout || mBindRightCutout)
+ && mTopBound == null) {
+ mTopBound = onSetEdgeCutout(true, mIsShortEdgeOnTop, mTmpRect);
+ } else {
+ return;
+ }
+
+ if (mPath != null) {
+ mPath.addPath(newPath);
+ } else {
+ mPath = newPath;
+ }
+ }
+
+ private void parseSvgPathSpec(Region region, String spec) {
+ if (TextUtils.length(spec) < MINIMAL_ACCEPTABLE_PATH_LENGTH) {
+ Log.e(TAG, "According to SVG definition, it shouldn't happen");
+ return;
+ }
+ spec.trim();
+ translateMatrix();
+
+ final Path newPath = PathParser.createPathFromPathData(spec);
+ newPath.transform(mMatrix);
+ computeBoundsRectAndAddToRegion(newPath, region, mTmpRect);
+
+ if (DEBUG) {
+ Log.d(TAG, String.format(Locale.ENGLISH,
+ "hasLeft = %b, hasRight = %b, hasBottom = %b, hasCenterVertical = %b",
+ mPositionFromLeft, mPositionFromRight, mPositionFromBottom,
+ mPositionFromCenterVertical));
+ Log.d(TAG, "region = " + region);
+ Log.d(TAG, "spec = \"" + spec + "\" rect = " + mTmpRect + " newPath = " + newPath);
+ }
+
+ if (mTmpRect.isEmpty()) {
+ return;
+ }
+
+ if (mIsShortEdgeOnTop) {
+ mIsTouchShortEdgeStart = mTmpRect.top <= 0;
+ mIsTouchShortEdgeEnd = mTmpRect.bottom >= mDisplayHeight;
+ mIsCloserToStartSide = mTmpRect.centerY() < mDisplayHeight / 2;
+ } else {
+ mIsTouchShortEdgeStart = mTmpRect.left <= 0;
+ mIsTouchShortEdgeEnd = mTmpRect.right >= mDisplayWidth;
+ mIsCloserToStartSide = mTmpRect.centerX() < mDisplayWidth / 2;
+ }
+
+ setEdgeCutout(newPath);
+ }
+
+ private void parseSpecWithoutDp(@NonNull String specWithoutDp) {
+ Region region = Region.obtain();
+ StringBuilder sb = null;
+ int currentIndex = 0;
+ int lastIndex = 0;
+ while ((currentIndex = specWithoutDp.indexOf(MARKER_START_CHAR, lastIndex)) != -1) {
+ if (sb == null) {
+ sb = new StringBuilder(specWithoutDp.length());
+ }
+ sb.append(specWithoutDp, lastIndex, currentIndex);
+
+ if (specWithoutDp.startsWith(LEFT_MARKER, currentIndex)) {
+ if (!mPositionFromRight) {
+ mPositionFromLeft = true;
+ }
+ currentIndex += LEFT_MARKER.length();
+ } else if (specWithoutDp.startsWith(RIGHT_MARKER, currentIndex)) {
+ if (!mPositionFromLeft) {
+ mPositionFromRight = true;
+ }
+ currentIndex += RIGHT_MARKER.length();
+ } else if (specWithoutDp.startsWith(BOTTOM_MARKER, currentIndex)) {
+ if (!mPositionFromCenterVertical) {
+ parseSvgPathSpec(region, sb.toString());
+ }
+ currentIndex += BOTTOM_MARKER.length();
+
+ /* prepare to parse the rest path */
+ resetStatus(sb);
+ mBindBottomCutout = true;
+ mPositionFromBottom = true;
+ } else if (specWithoutDp.startsWith(CENTER_VERTICAL_MARKER, currentIndex)) {
+ if (!mPositionFromBottom) {
+ parseSvgPathSpec(region, sb.toString());
+ }
+ currentIndex += CENTER_VERTICAL_MARKER.length();
+
+ /* prepare to parse the rest path */
+ resetStatus(sb);
+ mPositionFromCenterVertical = true;
+ } else if (specWithoutDp.startsWith(CUTOUT_MARKER, currentIndex)) {
+ parseSvgPathSpec(region, sb.toString());
+ currentIndex += CUTOUT_MARKER.length();
+
+ /* prepare to parse the rest path */
+ resetStatus(sb);
+ } else if (specWithoutDp.startsWith(BIND_LEFT_CUTOUT_MARKER, currentIndex)) {
+ if (!mBindBottomCutout && !mBindRightCutout) {
+ mBindLeftCutout = true;
+ }
+ currentIndex += BIND_LEFT_CUTOUT_MARKER.length();
+ } else if (specWithoutDp.startsWith(BIND_RIGHT_CUTOUT_MARKER, currentIndex)) {
+ if (!mBindBottomCutout && !mBindLeftCutout) {
+ mBindRightCutout = true;
+ }
+ currentIndex += BIND_RIGHT_CUTOUT_MARKER.length();
+ } else {
+ currentIndex += 1;
+ }
+
+ lastIndex = currentIndex;
+ }
+
+ if (sb == null) {
+ parseSvgPathSpec(region, specWithoutDp);
+ } else {
+ sb.append(specWithoutDp, lastIndex, specWithoutDp.length());
+ parseSvgPathSpec(region, sb.toString());
+ }
+
+ region.recycle();
+ }
+
+ /**
+ * To parse specification string as the CutoutSpecification.
+ *
+ * @param originalSpec the specification string
+ * @return the CutoutSpecification instance
+ */
+ @VisibleForTesting(visibility = PACKAGE)
+ public CutoutSpecification parse(@NonNull String originalSpec) {
+ Objects.requireNonNull(originalSpec);
+
+ int dpIndex = originalSpec.lastIndexOf(DP_MARKER);
+ mInDp = (dpIndex != -1);
+ final String spec;
+ if (dpIndex != -1) {
+ spec = originalSpec.substring(0, dpIndex)
+ + originalSpec.substring(dpIndex + DP_MARKER.length());
+ } else {
+ spec = originalSpec;
+ }
+
+ parseSpecWithoutDp(spec);
+
+ mInsets = Insets.of(mSafeInsetLeft, mSafeInsetTop, mSafeInsetRight, mSafeInsetBottom);
+ return new CutoutSpecification(this);
+ }
+ }
+}
diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java
index d433591..31fc161 100644
--- a/core/java/android/view/DisplayCutout.java
+++ b/core/java/android/view/DisplayCutout.java
@@ -31,18 +31,12 @@
import android.annotation.Nullable;
import android.content.res.Resources;
import android.graphics.Insets;
-import android.graphics.Matrix;
import android.graphics.Path;
import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.Region;
-import android.graphics.Region.Op;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
-import android.util.Log;
import android.util.Pair;
-import android.util.PathParser;
import android.util.proto.ProtoOutputStream;
import com.android.internal.R;
@@ -63,10 +57,6 @@
public final class DisplayCutout {
private static final String TAG = "DisplayCutout";
- private static final String BOTTOM_MARKER = "@bottom";
- private static final String DP_MARKER = "@dp";
- private static final String RIGHT_MARKER = "@right";
- private static final String LEFT_MARKER = "@left";
/**
* Category for overlays that allow emulating a display cutout on devices that don't have
@@ -703,77 +693,16 @@
}
}
- Path p = null;
- Rect boundTop = null;
- Rect boundBottom = null;
- Rect safeInset = new Rect();
- String bottomSpec = null;
- if (!TextUtils.isEmpty(spec)) {
- spec = spec.trim();
- final float offsetX;
- if (spec.endsWith(RIGHT_MARKER)) {
- offsetX = displayWidth;
- spec = spec.substring(0, spec.length() - RIGHT_MARKER.length()).trim();
- } else if (spec.endsWith(LEFT_MARKER)) {
- offsetX = 0;
- spec = spec.substring(0, spec.length() - LEFT_MARKER.length()).trim();
- } else {
- offsetX = displayWidth / 2f;
- }
- final boolean inDp = spec.endsWith(DP_MARKER);
- if (inDp) {
- spec = spec.substring(0, spec.length() - DP_MARKER.length());
- }
+ spec = spec.trim();
- if (spec.contains(BOTTOM_MARKER)) {
- String[] splits = spec.split(BOTTOM_MARKER, 2);
- spec = splits[0].trim();
- bottomSpec = splits[1].trim();
- }
+ CutoutSpecification cutoutSpec = new CutoutSpecification.Parser(density,
+ displayWidth, displayHeight).parse(spec);
+ Rect safeInset = cutoutSpec.getSafeInset();
+ final Rect boundLeft = cutoutSpec.getLeftBound();
+ final Rect boundTop = cutoutSpec.getTopBound();
+ final Rect boundRight = cutoutSpec.getRightBound();
+ final Rect boundBottom = cutoutSpec.getBottomBound();
- final Matrix m = new Matrix();
- final Region r = Region.obtain();
- if (!spec.isEmpty()) {
- try {
- p = PathParser.createPathFromPathData(spec);
- } catch (Throwable e) {
- Log.wtf(TAG, "Could not inflate cutout: ", e);
- }
-
- if (p != null) {
- if (inDp) {
- m.postScale(density, density);
- }
- m.postTranslate(offsetX, 0);
- p.transform(m);
-
- boundTop = new Rect();
- toRectAndAddToRegion(p, r, boundTop);
- safeInset.top = boundTop.bottom;
- }
- }
-
- if (bottomSpec != null) {
- int bottomInset = 0;
- Path bottomPath = null;
- try {
- bottomPath = PathParser.createPathFromPathData(bottomSpec);
- } catch (Throwable e) {
- Log.wtf(TAG, "Could not inflate bottom cutout: ", e);
- }
-
- if (bottomPath != null) {
- // Keep top transform
- m.postTranslate(0, displayHeight);
- bottomPath.transform(m);
- p.addPath(bottomPath);
- boundBottom = new Rect();
- toRectAndAddToRegion(bottomPath, r, boundBottom);
- bottomInset = displayHeight - boundBottom.top;
- }
- safeInset.bottom = bottomInset;
- }
- }
if (!waterfallInsets.equals(Insets.NONE)) {
safeInset.set(
@@ -784,9 +713,9 @@
}
final DisplayCutout cutout = new DisplayCutout(
- safeInset, waterfallInsets, null /* boundLeft */, boundTop,
- null /* boundRight */, boundBottom, false /* copyArguments */);
- final Pair<Path, DisplayCutout> result = new Pair<>(p, cutout);
+ safeInset, waterfallInsets, boundLeft, boundTop,
+ boundRight, boundBottom, false /* copyArguments */);
+ final Pair<Path, DisplayCutout> result = new Pair<>(cutoutSpec.getPath(), cutout);
synchronized (CACHE_LOCK) {
sCachedSpec = spec;
sCachedDisplayWidth = displayWidth;
@@ -798,14 +727,6 @@
return result;
}
- private static void toRectAndAddToRegion(Path p, Region inoutRegion, Rect inoutRect) {
- final RectF rectF = new RectF();
- p.computeBounds(rectF, false /* unused */);
- rectF.round(inoutRect);
- inoutRegion.op(inoutRect, Op.UNION);
- }
-
-
private static Insets loadWaterfallInset(Resources res) {
return Insets.of(
res.getDimensionPixelSize(R.dimen.waterfall_display_left_edge_size),
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 76e7e19..8959d6f 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -360,7 +360,6 @@
"android_view_RenderNode.cpp",
"android_util_PathParser.cpp",
- "android/graphics/AnimatedImageDrawable.cpp",
"android/graphics/Bitmap.cpp",
"android/graphics/BitmapFactory.cpp",
"android/graphics/ByteBufferStreamAdaptor.cpp",
@@ -436,6 +435,7 @@
"android_view_TextureLayer.cpp",
"android_view_ThreadedRenderer.cpp",
+ "android/graphics/AnimatedImageDrawable.cpp",
"android/graphics/BitmapRegionDecoder.cpp",
"android/graphics/GIFMovie.cpp",
"android/graphics/Movie.cpp",
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index b22e186..c66261b 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -2056,6 +2056,9 @@
<attr name="name" />
</declare-styleable>
<declare-styleable name="AndroidManifestQueriesIntent" parent="AndroidManifestQueries" />
+ <declare-styleable name="AndroidManifestQueriesProvider" parent="AndroidManifestQueries" >
+ <attr name="authorities" />
+ </declare-styleable>
<!-- The <code>static-library</code> tag declares that this apk is providing itself
diff --git a/core/tests/coretests/src/android/view/CutoutSpecificationTest.java b/core/tests/coretests/src/android/view/CutoutSpecificationTest.java
new file mode 100644
index 0000000..1f831bb
--- /dev/null
+++ b/core/tests/coretests/src/android/view/CutoutSpecificationTest.java
@@ -0,0 +1,262 @@
+/*
+ * 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 android.view;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.graphics.Rect;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class CutoutSpecificationTest {
+ private static final String WITHOUT_BIND_CUTOUT_SPECIFICATION = "M 0,0\n"
+ + "h 48\n"
+ + "v 48\n"
+ + "h -48\n"
+ + "z\n"
+ + "@left\n"
+ + "@center_vertical\n"
+ + "M 0,0\n"
+ + "h 48\n"
+ + "v 48\n"
+ + "h -48\n"
+ + "z\n"
+ + "@left\n"
+ + "@center_vertical\n"
+ + "M 0,0\n"
+ + "h -48\n"
+ + "v 48\n"
+ + "h 48\n"
+ + "z\n"
+ + "@right\n"
+ + "@dp";
+ private static final String WITH_BIND_CUTOUT_SPECIFICATION = "M 0,0\n"
+ + "h 48\n"
+ + "v 48\n"
+ + "h -48\n"
+ + "z\n"
+ + "@left\n"
+ + "@center_vertical\n"
+ + "M 0,0\n"
+ + "h 48\n"
+ + "v 48\n"
+ + "h -48\n"
+ + "z\n"
+ + "@left\n"
+ + "@bind_left_cutout\n"
+ + "@center_vertical\n"
+ + "M 0,0\n"
+ + "h -48\n"
+ + "v 48\n"
+ + "h 48\n"
+ + "z\n"
+ + "@right\n"
+ + "@bind_right_cutout\n"
+ + "@dp";
+ private static final String CORNER_CUTOUT_SPECIFICATION = "M 0,0\n"
+ + "h 1\n"
+ + "v 1\n"
+ + "h -1\n"
+ + "z\n"
+ + "@left\n"
+ + "@cutout\n"
+ + "M 0, 0\n"
+ + "h -2\n"
+ + "v 2\n"
+ + "h 2\n"
+ + "z\n"
+ + "@right\n"
+ + "@bind_right_cutout\n"
+ + "@cutout\n"
+ + "M 0, 200\n"
+ + "h 3\n"
+ + "v -3\n"
+ + "h -3\n"
+ + "z\n"
+ + "@left\n"
+ + "@bind_left_cutout\n"
+ + "@bottom\n"
+ + "M 0, 0\n"
+ + "h -4\n"
+ + "v -4\n"
+ + "h 4\n"
+ + "z\n"
+ + "@right\n"
+ + "@dp";
+
+ private CutoutSpecification.Parser mParser;
+
+ /**
+ * Setup the necessary member field used by test methods.
+ */
+ @Before
+ public void setUp() {
+ mParser = new CutoutSpecification.Parser(3.5f, 1080, 1920);
+ }
+
+ @Test
+ public void parse_nullString_shouldTriggerException() {
+ assertThrows(NullPointerException.class, () -> mParser.parse(null));
+ }
+
+ @Test
+ public void parse_emptyString_pathShouldBeNull() {
+ CutoutSpecification cutoutSpecification = mParser.parse("");
+ assertThat(cutoutSpecification.getPath()).isNull();
+ }
+
+ @Test
+ public void parse_withoutBindMarker_shouldHaveNoLeftBound() {
+ CutoutSpecification cutoutSpecification = mParser.parse(WITHOUT_BIND_CUTOUT_SPECIFICATION);
+ assertThat(cutoutSpecification.getLeftBound()).isNull();
+ }
+
+ @Test
+ public void parse_withoutBindMarker_shouldHaveNoRightBound() {
+ CutoutSpecification cutoutSpecification = mParser.parse(WITHOUT_BIND_CUTOUT_SPECIFICATION);
+ assertThat(cutoutSpecification.getRightBound()).isNull();
+ }
+
+ @Test
+ public void parse_withBindMarker_shouldHaveLeftBound() {
+ CutoutSpecification cutoutSpecification = mParser.parse(WITH_BIND_CUTOUT_SPECIFICATION);
+ assertThat(cutoutSpecification.getLeftBound()).isEqualTo(new Rect(0, 960, 168, 1128));
+ }
+
+ @Test
+ public void parse_withBindMarker_shouldHaveRightBound() {
+ CutoutSpecification cutoutSpecification = mParser.parse(WITH_BIND_CUTOUT_SPECIFICATION);
+ assertThat(cutoutSpecification.getRightBound()).isEqualTo(new Rect(912, 960, 1080, 1128));
+ }
+
+ @Test
+ public void parse_tallCutout_shouldBeDone() {
+ CutoutSpecification cutoutSpecification = mParser.parse("M 0,0\n"
+ + "L -48, 0\n"
+ + "L -44.3940446283, 36.0595537175\n"
+ + "C -43.5582133885, 44.4178661152 -39.6, 48.0 -31.2, 48.0\n"
+ + "L 31.2, 48.0\n"
+ + "C 39.6, 48.0 43.5582133885, 44.4178661152 44.3940446283, 36.0595537175\n"
+ + "L 48, 0\n"
+ + "Z\n"
+ + "@dp");
+
+ assertThat(cutoutSpecification.getTopBound().height()).isEqualTo(168);
+ }
+
+ @Test
+ public void parse_wideCutout_shouldBeDone() {
+ CutoutSpecification cutoutSpecification = mParser.parse("M 0,0\n"
+ + "L -72, 0\n"
+ + "L -69.9940446283, 20.0595537175\n"
+ + "C -69.1582133885, 28.4178661152 -65.2, 32.0 -56.8, 32.0\n"
+ + "L 56.8, 32.0\n"
+ + "C 65.2, 32.0 69.1582133885, 28.4178661152 69.9940446283, 20.0595537175\n"
+ + "L 72, 0\n"
+ + "Z\n"
+ + "@dp");
+
+ assertThat(cutoutSpecification.getTopBound().width()).isEqualTo(504);
+ }
+
+ @Test
+ public void parse_narrowCutout_shouldBeDone() {
+ CutoutSpecification cutoutSpecification = mParser.parse("M 0,0\n"
+ + "L -24, 0\n"
+ + "L -21.9940446283, 20.0595537175\n"
+ + "C -21.1582133885, 28.4178661152 -17.2, 32.0 -8.8, 32.0\n"
+ + "L 8.8, 32.0\n"
+ + "C 17.2, 32.0 21.1582133885, 28.4178661152 21.9940446283, 20.0595537175\n"
+ + "L 24, 0\n"
+ + "Z\n"
+ + "@dp");
+
+ assertThat(cutoutSpecification.getTopBound().width()).isEqualTo(168);
+ }
+
+ @Test
+ public void parse_doubleCutout_shouldBeDone() {
+ CutoutSpecification cutoutSpecification = mParser.parse("M 0,0\n"
+ + "L -72, 0\n"
+ + "L -69.9940446283, 20.0595537175\n"
+ + "C -69.1582133885, 28.4178661152 -65.2, 32.0 -56.8, 32.0\n"
+ + "L 56.8, 32.0\n"
+ + "C 65.2, 32.0 69.1582133885, 28.4178661152 69.9940446283, 20.0595537175\n"
+ + "L 72, 0\n"
+ + "Z\n"
+ + "@bottom\n"
+ + "M 0,0\n"
+ + "L -72, 0\n"
+ + "L -69.9940446283, -20.0595537175\n"
+ + "C -69.1582133885, -28.4178661152 -65.2, -32.0 -56.8, -32.0\n"
+ + "L 56.8, -32.0\n"
+ + "C 65.2, -32.0 69.1582133885, -28.4178661152 69.9940446283, -20"
+ + ".0595537175\n"
+ + "L 72, 0\n"
+ + "Z\n"
+ + "@dp");
+
+ assertThat(cutoutSpecification.getTopBound().height()).isEqualTo(112);
+ }
+
+ @Test
+ public void parse_cornerCutout_shouldBeDone() {
+ CutoutSpecification cutoutSpecification = mParser.parse("M 0,0\n"
+ + "L -48, 0\n"
+ + "C -48,48 -48,48 0,48\n"
+ + "Z\n"
+ + "@dp\n"
+ + "@right");
+
+ assertThat(cutoutSpecification.getTopBound().height()).isEqualTo(168);
+ }
+
+ @Test
+ public void parse_holeCutout_shouldBeDone() {
+ CutoutSpecification cutoutSpecification = mParser.parse("M 20.0,20.0\n"
+ + "h 136\n"
+ + "v 136\n"
+ + "h -136\n"
+ + "Z\n"
+ + "@left");
+
+ assertThat(cutoutSpecification.getSafeInset()).isEqualTo(new Rect(0, 156, 0, 0));
+ }
+
+ @Test
+ public void getSafeInset_shortEdgeIsTopBottom_shouldMatchExpectedInset() {
+ CutoutSpecification cutoutSpecification =
+ new CutoutSpecification.Parser(2f, 200, 400)
+ .parse(CORNER_CUTOUT_SPECIFICATION);
+
+ assertThat(cutoutSpecification.getSafeInset())
+ .isEqualTo(new Rect(0, 4, 0, 8));
+ }
+
+ @Test
+ public void getSafeInset_shortEdgeIsLeftRight_shouldMatchExpectedInset() {
+ CutoutSpecification cutoutSpecification =
+ new CutoutSpecification.Parser(2f, 400, 200)
+ .parse(CORNER_CUTOUT_SPECIFICATION);
+
+ assertThat(cutoutSpecification.getSafeInset())
+ .isEqualTo(new Rect(6, 0, 8, 0));
+ }
+}
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 301d1af..debb38b2 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -171,7 +171,6 @@
"renderthread/RenderTask.cpp",
"renderthread/TimeLord.cpp",
"hwui/AnimatedImageDrawable.cpp",
- "hwui/AnimatedImageThread.cpp",
"hwui/Bitmap.cpp",
"hwui/Canvas.cpp",
"hwui/ImageDecoder.cpp",
@@ -213,6 +212,7 @@
android: {
srcs: [
+ "hwui/AnimatedImageThread.cpp",
"pipeline/skia/ATraceMemoryDump.cpp",
"pipeline/skia/GLFunctorDrawable.cpp",
"pipeline/skia/LayerDrawable.cpp",
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.cpp b/libs/hwui/hwui/AnimatedImageDrawable.cpp
index 4544bea..638de85 100644
--- a/libs/hwui/hwui/AnimatedImageDrawable.cpp
+++ b/libs/hwui/hwui/AnimatedImageDrawable.cpp
@@ -15,7 +15,9 @@
*/
#include "AnimatedImageDrawable.h"
+#ifdef __ANDROID__ // Layoutlib does not support AnimatedImageThread
#include "AnimatedImageThread.h"
+#endif
#include "utils/TraceUtils.h"
@@ -160,8 +162,10 @@
} else if (starting) {
// The image has animated, and now is being reset. Queue up the first
// frame, but keep showing the current frame until the first is ready.
+#ifdef __ANDROID__ // Layoutlib does not support AnimatedImageThread
auto& thread = uirenderer::AnimatedImageThread::getInstance();
mNextSnapshot = thread.reset(sk_ref_sp(this));
+#endif
}
bool finalFrame = false;
@@ -187,8 +191,10 @@
}
if (mRunning && !mNextSnapshot.valid()) {
+#ifdef __ANDROID__ // Layoutlib does not support AnimatedImageThread
auto& thread = uirenderer::AnimatedImageThread::getInstance();
mNextSnapshot = thread.decodeNextFrame(sk_ref_sp(this));
+#endif
}
if (!drawDirectly) {
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 767b67b..d237975 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -1457,6 +1457,9 @@
private int mRw2JpgFromRawOffset;
private boolean mIsSupportedFile;
private boolean mModified;
+ // XMP data can be contained as either part of the EXIF data (tag number 700), or as a
+ // separate data marker (a separate MARKER_APP1).
+ private boolean mXmpIsFromSeparateMarker;
// Pattern to check non zero timestamp
private static final Pattern sNonZeroTimePattern = Pattern.compile(".*[1-9].*");
@@ -2837,10 +2840,12 @@
final long offset = start + IDENTIFIER_XMP_APP1.length;
final byte[] value = Arrays.copyOfRange(bytes,
IDENTIFIER_XMP_APP1.length, bytes.length);
-
+ // TODO: check if ignoring separate XMP data when tag 700 already exists is
+ // valid.
if (getAttribute(TAG_XMP) == null) {
mAttributes[IFD_TYPE_PRIMARY].put(TAG_XMP, new ExifAttribute(
IFD_FORMAT_BYTE, value.length, offset, value));
+ mXmpIsFromSeparateMarker = true;
}
}
break;
@@ -3445,11 +3450,24 @@
}
dataOutputStream.writeByte(MARKER_SOI);
+ // Remove XMP data if it is from a separate marker (IDENTIFIER_XMP_APP1, not
+ // IDENTIFIER_EXIF_APP1)
+ // Will re-add it later after the rest of the file is written
+ ExifAttribute xmpAttribute = null;
+ if (getAttribute(TAG_XMP) != null && mXmpIsFromSeparateMarker) {
+ xmpAttribute = (ExifAttribute) mAttributes[IFD_TYPE_PRIMARY].remove(TAG_XMP);
+ }
+
// Write EXIF APP1 segment
dataOutputStream.writeByte(MARKER);
dataOutputStream.writeByte(MARKER_APP1);
writeExifSegment(dataOutputStream);
+ // Re-add previously removed XMP data.
+ if (xmpAttribute != null) {
+ mAttributes[IFD_TYPE_PRIMARY].put(TAG_XMP, xmpAttribute);
+ }
+
byte[] bytes = new byte[4096];
while (true) {
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index b1f930d..2c1fdab 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -247,13 +247,23 @@
Objects.requireNonNull(packageName, "packageName must not be null");
Objects.requireNonNull(route, "route must not be null");
+ boolean transferred = false;
+ //TODO: instead of release all controllers, add an API to specify controllers that
+ // should be released (or is the system controller).
for (RoutingController controller : getRoutingControllers(packageName)) {
- if (controller.getSessionInfo().getTransferrableRoutes().contains(route.getId())) {
+ if (!transferred && controller.getSessionInfo().getTransferrableRoutes()
+ .contains(route.getId())) {
controller.transferToRoute(route);
- return;
+ transferred = true;
+ } else if (!controller.getSessionInfo().isSystemSession()) {
+ controller.release();
}
}
+ if (transferred) {
+ return;
+ }
+
Client client;
synchronized (sLock) {
client = mClient;
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 870c1b4..486c0c2 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -266,8 +266,12 @@
* playback after the session has been stopped. If your app is started in
* this way an {@link Intent#ACTION_MEDIA_BUTTON} intent will be sent via
* the pending intent.
+ * <p>
+ * The pending intent is recommended to be explicit to follow the security recommendation of
+ * {@link PendingIntent#getActivity}.
*
* @param mbr The {@link PendingIntent} to send the media button event to.
+ * @see PendingIntent#getActivity
*/
public void setMediaButtonReceiver(@Nullable PendingIntent mbr) {
try {
diff --git a/packages/SystemUI/docs/QS-QQS.png b/packages/SystemUI/docs/QS-QQS.png
new file mode 100644
index 0000000..02de479
--- /dev/null
+++ b/packages/SystemUI/docs/QS-QQS.png
Binary files differ
diff --git a/packages/SystemUI/docs/qs-tiles.md b/packages/SystemUI/docs/qs-tiles.md
new file mode 100644
index 0000000..b48ba67
--- /dev/null
+++ b/packages/SystemUI/docs/qs-tiles.md
@@ -0,0 +1,377 @@
+# Quick Settings Tiles (almost all there is to know about them)
+
+[TOC]
+
+## About this document
+
+This document is a more or less comprehensive summary of the state and infrastructure used by Quick Settings tiles. It provides descriptions about the lifecycle of a tile, how to create new tiles and how SystemUI manages and displays tiles, among other topics.
+
+## What are Quick Settings Tiles?
+
+Quick Settings (from now on, QS) is the expanded panel that contains shortcuts for the user to toggle many settings. This is opened by expanding the notification drawer twice (or once when phone is locked). Quick Quick Settings (QQS) is the smaller panel that appears on top of the notifications before expanding twice and contains some of the toggles with no text.
+
+Each of these toggles that appear either in QS or QQS are called Quick Settings Tiles (or tiles for short). They allow the user to enable or disable settings quickly and sometimes provides access to more comprehensive settings pages.
+
+The following image shows QQS on the left and QS on the right, with the tiles highlighted.
+
+
+
+QS Tiles usually depend on one or more Controllers that bind the tile with the necessary service. Controllers are obtained by the backend and used for communication between the user and the device.
+
+### A note on multi-user support
+
+All the classes described in this document that live inside SystemUI are only instantiated in the process of user 0. The different controllers that back the QS Tiles (also instantiated just in user 0) are user aware and provide an illusion of different instances for different users.
+
+For an example on this, see [`RotationLockController`](/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java). This controller for the `RotationLockTile` listens to changes in all users.
+
+## What are tiles made of?
+
+### Tile backend
+
+QS Tiles are composed of the following backend classes.
+
+* [`QSTile`](/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java): Interface providing common behavior for all Tiles. This class also contains some useful utility classes needed for the tiles.
+ * `Icon`: Defines the basic interface for an icon as used by the tiles.
+ * `State`: Encapsulates the state of the Tile in order to communicate between the backend and the UI.
+* [`QSTileImpl`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java): Abstract implementation of `QSTile`, providing basic common behavior for all tiles. Also implements extensions for different types of `Icon`. All tiles currently defined in SystemUI subclass from this implementation.
+* [`SystemUI/src/com/android/systemui/qs/tiles`](/packages/SystemUI/src/com/android/systemui/qs/tiles): Each tile from SystemUI is defined here by a class that extends `QSTileImpl`. These implementations connect to corresponding controllers. The controllers serve two purposes:
+ * track the state of the device and notify the tile when a change has occurred (for example, bluetooth connected to a device)
+ * accept actions from the tiles to modify the state of the phone (for example, enablind and disabling wifi).
+* [`CustomTile`](/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java): Equivalent to the tiles in the previous item, but used for 3rd party tiles. In depth information to be found in [`CustomTile`](#customtile)
+
+All the elements in SystemUI that work with tiles operate on `QSTile` or the interfaces defined in it. However, all the current implementations of tiles in SystemUI subclass from `QSTileImpl`, as it takes care of many common situations. Throughout this document, we will focus on `QSTileImpl` as examples of tiles.
+
+The interfaces in `QSTile` as well as other interfaces described in this document can be used to implement plugins to add additional tiles or different behavior. For more information, see [plugins.md](plugins.md)
+
+#### Tile State
+
+Each tile has an associated `State` object that is used to communicate information to the corresponding view. The base class `State` has (among others) the following fields:
+
+* **`state`**: one of `Tile#STATE_UNAVAILABLE`, `Tile#STATE_ACTIVE`, `Tile#STATE_INACTIVE`.
+* **`icon`**; icon to display. It may depend on the current state.
+* **`label`**: usually the name of the tile.
+* **`secondaryLabel`**: text to display in a second line. Usually extra state information.
+* **`contentDescription`**
+* **`expandedAccessibilityClassName`**: usually `Switch.class.getName()` for boolean Tiles. This will make screen readers read the current state of the tile as well as the new state when it's toggled. For this, the Tile has to use `BooleanState`.
+* **`handlesLongClick`**: whether the Tile will handle long click. If it won't, it should be set to `false` so it will not be announced for accessibility.
+
+Setting any of these fields during `QSTileImpl#handleUpdateState` will update the UI after it.
+
+Additionally. `BooleanState` has a `value` boolean field that usually would be set to `state == Tile#STATE_ACTIVE`. This is used by accessibility services along with `expandedAccessibilityClassName`.
+
+#### SystemUI tiles
+
+Each tile defined in SystemUI extends `QSTileImpl`. This abstract class implements some common functions and leaves others to be implemented by each tile, in particular those that determine how to handle different events (refresh, click, etc.).
+
+For more information on how to implement a tile in SystemUI, see [Implementing a SystemUI tile](#implementing-a-systemui-tile).
+
+### Tile views
+
+Each Tile has a couple of associated views for displaying it in QS and QQS. These views are updated after the backend updates the `State` using `QSTileImpl#handleUpdateState`.
+
+* **[`com.android.systemui.plugins.qs.QSTileView`](/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java)**: Abstract class that provides basic Tile functionality. These allows external [Factories](#qsfactory) to create Tiles.
+* **[`QSTileBaseView`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java)**: Implementation of `QSTileView` used in QQS that takes care of most of the features of the view:
+ * Holding the icon
+ * Background color and shape
+ * Ripple
+ * Click listening
+* **[`QSTileView`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java)**: Extends `QSTileBaseView`to add label support. Used in QS.
+* **[`QSIconView`](/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSIconView.java)**
+* **[`QSIconViewImpl`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java)**
+
+#### QSIconView and QSIconViewImpl
+
+`QSIconView` is an interface that define the basic actions that icons have to respond to. Its base implementation in SystemUI is `QSIconViewImpl` and it and its subclasses are used by all QS tiles.
+
+This `ViewGroup` is a container for the icon used in each tile. It has methods to apply the current `State` of the tile, modifying the icon (color and animations). Classes that inherit from this can add other details that are modified when the `State` changes.
+
+Each `QSTileImpl` can specify that they use a particular implementation of this class when creating an icon.
+
+### How are the backend and the views related?
+
+The backend of the tiles (all the implementations of `QSTileImpl`) communicate with the views by using a `State`. The backend populates the state, and then the view maps the state to a visual representation.
+
+It's important to notice that the state of the tile (internal or visual) is not directly modified by a user action like clicking on the tile. Instead, acting on a tile produces internal state changes on the device, and those trigger the changes on the tile state and UI.
+
+When a container for tiles (`QuickQSPanel` or `QSPanel`) has to display tiles, they create a [`TileRecord`](/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java). This associates the corresponding `QSTile` with its `QSTileView`, doing the following:
+
+* Create the corresponding `QSTileView` to display in that container.
+* Create a callback for `QSTile` to call when its state changes. Note that a single tile will normally have up to two callbacks: one for QS and one for QQS.
+
+#### Life of a tile click
+
+This is a brief run-down of what happens when a user clicks on a tile. Internal changes on the device (for example, changes from Settings) will trigger this process starting in step 3. Throughout this section, we assume that we are dealing with a `QSTileImpl`.
+
+1. User clicks on tile. The following calls happen in sequence:
+ 1. `QSTileBaseView#onClickListener`.
+ 2. `QSTile#click`.
+ 3. `QSTileImpl#handleClick`. This last call sets the new state for the device by using the associated controller.
+2. State in the device changes. This is normally outside of SystemUI's control.
+3. Controller receives a callback (or `Intent`) indicating the change in the device. The following calls happen:
+ 1. `QSTileImpl#refreshState`, maybe passing an object with necessary information regarding the new state.
+ 2. `QSTileImpl#handleRefreshState`
+4. `QSTileImpl#handleUpdateState` is called to update the state with the new information. This information can be obtained both from the `Object` passed to `refreshState` as well as from the controller.
+5. If the state has changed (in at least one element), `QSTileImpl#handleStateChanged` is called. This will trigger a call to all the associated `QSTile.Callback#onStateChanged`, passing the new `State`.
+6. `QSTileView#onStateChanged` is called and this calls `QSTileView#handleStateChanged`. This method maps the state into the view:
+ * The tile is rippled and the color changes to match the new state.
+ * `QSIconView.setIcon` is called to apply the correct state to the icon and the correct icon to the view.
+ * If the tile is a `QSTileView` (in expanded QS), the labels are changed.
+
+## Third party tiles (TileService)
+
+A third party tile is any Quick Settings tile that is provided by an app (that's not SystemUI). This is implemented by developers subclassing [`TileService`](/core/java/android/service/quicksettings/TileService.java) and interacting with its API.
+
+### API classes
+
+The classes that define the public API are in [core/java/android/service/quicksettings](core/java/android/service/quicksettings).
+
+#### Tile
+
+Parcelable class used to communicate information about the state between the external app and SystemUI. The class supports the following fields:
+
+* Label
+* Subtitle
+* Icon
+* State (`Tile#STATE_ACTIVE`, `Tile#STATE_INACTIVE`, `Tile#STATE_UNAVAILABLE`)
+* Content description
+
+Additionally, it provides a method to notify SystemUI that the information may have changed and the tile should be refreshed.
+
+#### TileService
+
+This is an abstract Service that needs to be implemented by the developer. The Service manifest must have the permission `android.permission.BIND_QUICK_SETTINGS_TILE` and must respond to the action `android.service.quicksettings.action.QS_TILE`. This will allow SystemUI to find the available tiles and display them to the user.
+
+The implementer is responsible for creating the methods that will respond to the following calls from SystemUI:
+
+* **`onTileAdded`**: called when the tile is added to QS.
+* **`onTileRemoved`**: called when the tile is removed from QS.
+* **`onStartListening`**: called when QS is opened and the tile is showing. This marks the start of the window when calling `getQSTile` is safe and will provide the correct object.
+* **`onStopListening`**: called when QS is closed or the tile is no longer visible by the user. This marks the end of the window described in `onStartListening`.
+* **`onClick`**: called when the user clicks on the tile.
+
+Additionally, the following final methods are provided:
+
+* ```java
+ public final Tile getQsTile()
+ ```
+
+ Provides the tile object that can be modified. This should only be called in the window between `onStartListening` and `onStopListening`.
+
+* ```java
+ public final boolean isLocked()
+
+ public final boolean isSecure()
+ ```
+
+ Provide information about the secure state of the device. This can be used by the tile to accept or reject actions on the tile.
+
+* ```java
+ public final void unlockAndRun(Runnable)
+ ```
+
+ May prompt the user to unlock the device if locked. Once the device is unlocked, it runs the given `Runnable`.
+
+* ```java
+ public final void showDialog(Dialog)
+ ```
+
+ Shows the provided dialog.
+
+##### Binding
+
+When the Service is bound, a callback Binder is provided by SystemUI for all the callbacks, as well as an identifier token (`Binder`). This token is used in the callbacks to identify this `TileService` and match it to the corresponding tile.
+
+The tiles are bound once immediately on creation. After that, the tile is bound whenever it should start listening. When the panels are closed, and the tile is set to stop listening, it will be unbound after a delay of `TileServiceManager#UNBIND_DELAY` (30s), if it's not set to listening again.
+
+##### Active tile
+
+A `TileService` can be declared as an active tile by adding specific meta-data to its manifest (see [TileService#META_DATA_ACTIVE_TILE](https://developer.android.com/reference/android/service/quicksettings/TileService#META_DATA_ACTIVE_TILE)). In this case, it won't receive a call of `onStartListening` when QS is opened. Instead, the tile must request listening status by making a call to `TileService#requestListeningState` with its component name. This will initiate a window that will last until the tile is updated.
+
+The tile will also be granted listening status if it's clicked by the user.
+
+### SystemUI classes
+
+The following sections describe the classes that live in SystemUI to support third party tiles. These classes live in [SystemUI/src/com/android/systemui/qs/external](/packages/SystemUI/src/com/android/systemui/qs/external/)
+
+#### CustomTile
+
+This class is an subclass of `QSTileImpl` to be used with third party tiles. It provides similar behavior to SystemUI tiles as well as handling exclusive behavior like lifting default icons and labels from the application manifest.
+
+#### TileServices
+
+This class is the central controller for all tile services that are currently in Quick Settings as well as provides the support for starting new ones. It is also an implementation of the `Binder` that receives all calls from current `TileService` components and dispatches them to SystemUI or the corresponding `CustomTile`.
+
+Whenever a binder call is made to this class, it matches the corresponding token assigned to the `TileService` with the `ComponentName` and verifies that the call comes from the right UID to prevent spoofing.
+
+As this class is the only one that's aware of every `TileService` that's currently bound, it is also in charge of requesting some to be unbound whenever there is a low memory situation.
+
+#### TileLifecycleManager
+
+This class is in charge of binding and unbinding to a particular `TileService` when necessary, as well as sending the corresponding binder calls. It does not decide whether the tile should be bound or unbound, unless it's requested to process a message. It additionally handles errors in the `Binder` as well as changes in the corresponding component (like updates and enable/disable).
+
+The class has a queue that stores requests while the service is not bound, to be processed as soon as the service is bound.
+
+Each `TileService` gets assigned an exclusive `TileLifecycleManager` when its corresponding tile is added to the set of current ones and kept as long as the tile is available to the user.
+
+#### TileServiceManager
+
+Each instance of this class is an intermediary between the `TileServices` controller and a `TileLifecycleManager` corresponding to a particular `TileService`.
+
+This class handles management of the service, including:
+
+* Deciding when to bind and unbind, requesting it to the `TileLifecycleManager`.
+* Relaying messages to the `TileService` through the `TileLifecycleManager`.
+* Determining the service's bind priority (to deal with OOM situations).
+* Detecting when the package/component has been removed in order to remove the tile and references to it.
+
+## How are tiles created/instantiated?
+
+This section describes the classes that aid in the creation of each tile as well as the complete lifecycle of a tile. First we describe two important interfaces/classes.
+
+### QSTileHost
+
+This class keeps track of the tiles selected by the current user (backed in the Secure Setting `sysui_qs_tiles`) to be displayed in Quick Settings. Whenever the value of this setting changes (or on device start), the whole list of tiles is read. This is compared with the current tiles, destroying unnecessary ones and creating needed ones.
+
+It additionally provides a point of communication between the tiles and the StatusBar, for example to open it and collapse it. And a way for the StatusBar service to add tiles (only works for `CustomTile`).
+
+#### Tile specs
+
+Each single tile is identified by a spec, which is a unique String for that type of tile. The current tiles are stored as a Setting string of comma separated values of these specs. Additionally, the default tiles (that appear on a fresh system) configuration value is stored likewise.
+
+SystemUI tile specs are usually a single simple word identifying the tile (like `wifi` or `battery`). Custom tile specs are always a string of the form `custom(...)` where the ellipsis is a flattened String representing the `ComponentName` for the corresponding `TileService`.
+
+### QSFactory
+
+This interface provides a way of creating tiles and views from a spec. It can be used in plugins to provide different definitions for tiles.
+
+In SystemUI there is only one implementation of this factory and that is the default factory (`QSFactoryImpl`) in `QSTileHost`.
+
+#### QSFactoryImpl
+
+This class implements two methods as specified in the `QSFactory` interface:
+
+* ```java
+ public QSTile createTile(String)
+ ```
+
+ Creates a tile (backend) from a given spec. The factory has providers for all of the SystemUI tiles, returning one when the correct spec is used.
+
+ If the spec is not recognized but it has the `custom(` prefix, the factory tries to create a `CustomTile` for the component in the spec. This could fail (the component is not a valid `TileService` or is not enabled) and will be detected later when the tile is polled to determine if it's available.
+
+* ```java
+ public QSTileView createTileView(QSTile, boolean)
+ ```
+
+ Creates a view for the corresponding `QSTile`. The second parameter determines if the view that is created should be a collapsed one (for using in QQS) or not (for using in QS).
+
+### Lifecycle of a Tile
+
+We describe first the parts of the lifecycle that are common to SystemUI tiles and third party tiles. Following that, there will be a section with the steps that are exclusive to third party tiles.
+
+1. The tile is added through the QS customizer by the user. This will immediately save the new list of tile specs to the Secure Setting `sysui_qs_tiles`. This step could also happend if `StatusBar` adds tiles (either through adb, or through its service interface as with the `DevelopmentTiles`).
+2. This triggers a "setting changed" that is caught by `QSTileHost`. This class processes the new value of the setting and finds out that there is a new spec in the list. Alternatively, when the device is booted, all tiles in the setting are considered as "new".
+3. `QSTileHost` calls all the available `QSFactory` classes that it has registered in order to find the first one that will be able to create a tile with that spec. Assume that `QSFactoryImpl` managed to create the tile, which is some implementation of `QSTile` (either a SystemUI subclass of `QSTileImpl` or a `CustomTile`). If the tile is available, it's stored in a map and things proceed forward.
+4. `QSTileHost` calls its callbacks indicating that the tiles have changed. In particular, `QSPanel` and `QuickQSPanel` receive this call with the full list of tiles. We will focus on these two classes.
+5. For each tile in this list, a `QSTileView` is created (collapsed or expanded) and attached to a `TileRecord` containing the tile backend and the view. Additionally:
+ * a callback is attached to the tile to communicate between the backend and the view or the panel.
+ * the click listeners in the tile are attached to those of the view.
+6. The tile view is added to the corresponding layout.
+
+When the tile is removed from the list of current tiles, all these classes are properly disposed including removing the callbacks and making sure that the backends remove themselves from the controllers they were listening to.
+
+#### Lifecycle of a CustomTile
+
+In step 3 of the previous process, when a `CustomTile` is created, additional steps are taken to ensure the proper binding to the service as described in [Third party tiles (TileService)](#third-party-tiles-tileservice).
+
+1. The `CustomTile` obtains the `TileServices` class from the `QSTileHost` and request the creation of a `TileServiceManager` with its token. As the spec for the `CustomTile` contains the `ComponentName` of the associated service, this can be used to bind to it.
+2. The `TileServiceManager` creates its own `TileLifecycleManager` to take care of binding to the service.
+3. `TileServices` creates maps between the token, the `CustomTile`, the `TileServiceManager`, the token and the `ComponentName`.
+
+## Implementing a tile
+
+This section describes necessary and recommended steps when implementing a Quick Settings tile. Some of them are optional and depend on the requirements of the tile.
+
+### Implementing a SystemUI tile
+
+1. Create a class (preferably in [`SystemUI/src/com/android/systemui/qs/tiles`](/packages/SystemUI/src/com/android/systemui/qs/tiles)) implementing `QSTileImpl` with a particular type of `State` as a parameter.
+2. Create an injectable constructor taking a `QSHost` and whichever classes are needed for the tile's operation. Normally this would be other SystemUI controllers.
+3. Implement the methods described in [Abstract methods in QSTileImpl](#abstract-methods-in-qstileimpl). Look at other tiles for help. Some considerations to have in mind:
+ * If the tile will not support long click (like the `FlashlightTile`), set `state.handlesLongClick` to `false` (maybe in `newTileState`).
+ * Changes to the tile state (either from controllers or from clicks) should call `refreshState`.
+ * Use only `handleUpdateState` to modify the values of the state to the new ones. This can be done by polling controllers or through the `arg` parameter.
+ * If the controller is not a `CallbackController`, respond to `handleSetListening` by attaching/dettaching from controllers.
+ * Implement `isAvailable` so the tile will not be created when it's not necessary.
+4. In `QSFactoryImpl`:
+ * Inject a `Provider` for the tile created before.
+ * Add a case to the `switch` with a unique String spec for the chosen tile.
+5. In [SystemUI/res/values/config.xml](/packages/SystemUI/res/values/config.xml), modify `quick_settings_tiles_stock` and add the spec defined in the previous step. If necessary, add it also to `quick_settings_tiles_default`. The first one contains a list of all the tiles that SystemUI knows how to create (to show to the user in the customization screen). The second one contains only the default tiles that the user will experience on a fresh boot or after they reset their tiles.
+
+#### Abstract methods in QSTileImpl
+
+Following are methods that need to be implemented when creating a new SystemUI tile. `TState` is a type variable of type `State`.
+
+* ```java
+ public TState newTileState()
+ ```
+
+ Creates a new `State` for this tile to use. Each time the state changes, it is copied into a new one and the corresponding fields are modified. The framework provides `State`, `BooleanState` (has an on and off state and provides this as a content description), `SignalState` (`BooleanState` with `activityIn` and `activityOut`), and `SlashState` (can be rotated or slashed through).
+
+ If a tile has special behavior (no long click, no ripple), it can be set in its state here.
+
+* ```java
+ public void handleSetListening(boolean)
+ ```
+
+ Initiates or terminates listening behavior, like listening to Callbacks from controllers. This gets triggered when QS is expanded or collapsed (i.e., when the tile is visible and actionable). Most tiles (like `WifiTile`) do not implement this. Instead, Tiles are LifecycleOwner and are marked as `RESUMED` or `DESTROYED` in `QSTileImpl#handleListening` and handled as part of the lifecycle of [CallbackController](/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackController.java)
+
+* ```java
+ public QSIconView createTileView(Context)
+ ```
+
+ Allows a Tile to use a `QSIconView` different from `QSIconViewImpl` (see [Tile views](#tile-views)), which is the default defined in `QSTileImpl`
+
+* ```java
+ public Intent getLongClickIntent()
+ ```
+
+ Determines the `Intent` launched when the Tile is long pressed.
+
+* ```java
+ protected void handleClick()
+
+ protected void handleSecondaryClick()
+
+ protected void handleLongClick()
+ ```
+
+ Handles what to do when the Tile is clicked. In general, a Tile will make calls to its controller here and maybe update its state immediately (by calling `QSTileImpl#refreshState`). A Tile can also decide to ignore the click here, if it's `Tile#STATE_UNAVAILABLE`.
+
+ By default long click redirects to click and long click launches the intent defined in `getLongClickIntent`.
+
+* ```java
+ protected void handleUpdateState(TState, Object)
+ ```
+
+ Updates the `State` of the Tile based on the state of the device as provided by the respective controller. It will be called every time the Tile becomes visible, is interacted with or `QSTileImpl#refreshState` is called. After this is done, the updated state will be reflected in the UI.
+
+* ```java
+ public int getMetricsCategory()
+ ```
+
+ Identifier for this Tile, as defined in [proto/src/metrics_constants/metrics_constants.proto](/proto/src/metrics_constants/metrics_constants.proto). This is used to log events related to this Tile.
+
+* ```java
+ public boolean isAvailable()
+ ```
+
+ Determines if a Tile is available to be used (for example, disable `WifiTile` in devices with no Wifi support). If this is false, the Tile will be destroyed upon creation.
+
+* ```java
+ public CharSequence getTileLabel()
+ ```
+
+ Provides a default label for this Tile. Used by the QS Panel customizer to show a name next to each available tile.
+
+### Implementing a third party tile
+
+For information about this, use the Android Developer documentation for [TileService](https://developer.android.com/reference/android/service/quicksettings/TileService).
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index 53a23b8..0ec739fe 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -30,6 +30,7 @@
import android.view.LayoutInflater;
import com.android.internal.logging.MetricsLogger;
+import com.android.internal.util.NotificationMessagingUtil;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.dagger.qualifiers.Background;
@@ -191,6 +192,12 @@
return new AlwaysOnDisplayPolicy(context);
}
+ /***/
+ @Provides
+ public NotificationMessagingUtil provideNotificationMessagingUtil(Context context) {
+ return new NotificationMessagingUtil(context);
+ }
+
/** */
@Provides
public ViewMediatorCallback providesViewMediatorCallback(KeyguardViewMediator viewMediator) {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 7b54199..f068d9c 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -40,6 +40,7 @@
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
import com.android.systemui.statusbar.notification.people.PeopleHubModule;
+import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
import com.android.systemui.statusbar.phone.KeyguardLiftController;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -68,7 +69,9 @@
NotificationsModule.class,
PeopleHubModule.class,
},
- subcomponents = {StatusBarComponent.class, NotificationRowComponent.class})
+ subcomponents = {StatusBarComponent.class,
+ NotificationRowComponent.class,
+ ExpandableNotificationRowComponent.class})
public abstract class SystemUIModule {
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index e1b61c6..e559694 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -109,10 +109,31 @@
private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
+ /**
+ * Provides a new {@link TState} of the appropriate type to use between this tile and the
+ * corresponding view.
+ *
+ * @return new state to use by the tile.
+ */
public abstract TState newTileState();
+ /**
+ * Handles clicks by the user.
+ *
+ * Calls to the controller should be made here to set the new state of the device.
+ */
abstract protected void handleClick();
+ /**
+ * Update state of the tile based on device state
+ *
+ * Called whenever the state of the tile needs to be updated, either after user
+ * interaction or from callbacks from the controller. It populates {@code state} with the
+ * information to display to the user.
+ *
+ * @param state {@link TState} to populate with information to display
+ * @param arg additional arguments needed to populate {@code state}
+ */
abstract protected void handleUpdateState(TState state, Object arg);
/**
@@ -177,6 +198,12 @@
return mHost;
}
+ /**
+ * Return the {@link QSIconView} to be used by this tile's view.
+ *
+ * @param context view context for the view
+ * @return icon view for this tile
+ */
public QSIconView createTileView(Context context) {
return new QSIconViewImpl(context);
}
@@ -298,11 +325,20 @@
mCallbacks.clear();
}
+ /**
+ * Handles secondary click on the tile.
+ *
+ * Defaults to {@link QSTileImpl#handleClick}
+ */
protected void handleSecondaryClick() {
// Default to normal click.
handleClick();
}
+ /**
+ * Handles long click on the tile by launching the {@link Intent} defined in
+ * {@link QSTileImpl#getLongClickIntent}
+ */
protected void handleLongClick() {
if (mQSSettingsPanelOption == QSSettingsPanel.USE_DETAIL) {
showDetail(true);
@@ -312,6 +348,11 @@
getLongClickIntent(), 0);
}
+ /**
+ * Returns an intent to be launched when the tile is long pressed.
+ *
+ * @return the intent to launch
+ */
public abstract Intent getLongClickIntent();
protected void handleRefreshState(Object arg) {
@@ -427,6 +468,10 @@
}
}
+ /**
+ * Provides a default label for the tile.
+ * @return default label for the tile.
+ */
public abstract CharSequence getTileLabel();
public static int getColorForState(Context context, int state) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 41c1b7b..006d40d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -69,6 +69,7 @@
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
import com.android.systemui.statusbar.notification.row.NotificationGuts;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;
@@ -153,6 +154,7 @@
private NotificationEntry parent; // our parent (if we're in a group)
private ExpandableNotificationRow row; // the outer expanded view
+ private ExpandableNotificationRowController mRowController;
private int mCachedContrastColor = COLOR_INVALID;
private int mCachedContrastColorIsFor = COLOR_INVALID;
@@ -424,6 +426,14 @@
this.row = row;
}
+ public ExpandableNotificationRowController getRowController() {
+ return mRowController;
+ }
+
+ public void setRowController(ExpandableNotificationRowController controller) {
+ mRowController = controller;
+ }
+
@Nullable
public List<NotificationEntry> getChildren() {
if (row == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index ecf62db..e8a62e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.notification.collection.inflation;
import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
-import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
import android.annotation.Nullable;
@@ -40,19 +39,19 @@
import com.android.systemui.statusbar.notification.NotificationClicker;
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
import com.android.systemui.statusbar.notification.row.NotifBindPipeline;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder;
import com.android.systemui.statusbar.notification.row.RowContentBindParams;
import com.android.systemui.statusbar.notification.row.RowContentBindStage;
import com.android.systemui.statusbar.notification.row.RowInflaterTask;
+import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
import java.util.Objects;
@@ -67,35 +66,28 @@
private static final String TAG = "NotificationViewManager";
- private final NotificationGroupManager mGroupManager;
- private final NotificationGutsManager mGutsManager;
private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
private final Context mContext;
private final NotifBindPipeline mNotifBindPipeline;
private final RowContentBindStage mRowContentBindStage;
private final NotificationMessagingUtil mMessagingUtil;
- private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger =
- this::logNotificationExpansion;
private final NotificationRemoteInputManager mNotificationRemoteInputManager;
private final NotificationLockscreenUserManager mNotificationLockscreenUserManager;
- private final boolean mAllowLongPress;
- private final KeyguardBypassController mKeyguardBypassController;
- private final StatusBarStateController mStatusBarStateController;
private NotificationPresenter mPresenter;
private NotificationListContainer mListContainer;
- private HeadsUpManager mHeadsUpManager;
private NotificationRowContentBinder.InflationCallback mInflationCallback;
- private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener;
private BindRowCallback mBindRowCallback;
private NotificationClicker mNotificationClicker;
private final Provider<RowInflaterTask> mRowInflaterTaskProvider;
- private final NotificationLogger mNotificationLogger;
+ private final ExpandableNotificationRowComponent.Builder
+ mExpandableNotificationRowComponentBuilder;
@Inject
public NotificationRowBinderImpl(
Context context,
+ NotificationMessagingUtil notificationMessagingUtil,
NotificationRemoteInputManager notificationRemoteInputManager,
NotificationLockscreenUserManager notificationLockscreenUserManager,
NotifBindPipeline notifBindPipeline,
@@ -107,21 +99,16 @@
NotificationGutsManager notificationGutsManager,
NotificationInterruptionStateProvider notificationInterruptionStateProvider,
Provider<RowInflaterTask> rowInflaterTaskProvider,
- NotificationLogger logger) {
+ ExpandableNotificationRowComponent.Builder expandableNotificationRowComponentBuilder) {
mContext = context;
mNotifBindPipeline = notifBindPipeline;
mRowContentBindStage = rowContentBindStage;
- mMessagingUtil = new NotificationMessagingUtil(context);
+ mMessagingUtil = notificationMessagingUtil;
mNotificationRemoteInputManager = notificationRemoteInputManager;
mNotificationLockscreenUserManager = notificationLockscreenUserManager;
- mAllowLongPress = allowLongPress;
- mKeyguardBypassController = keyguardBypassController;
- mStatusBarStateController = statusBarStateController;
- mGroupManager = notificationGroupManager;
- mGutsManager = notificationGutsManager;
mNotificationInterruptionStateProvider = notificationInterruptionStateProvider;
mRowInflaterTaskProvider = rowInflaterTaskProvider;
- mNotificationLogger = logger;
+ mExpandableNotificationRowComponentBuilder = expandableNotificationRowComponentBuilder;
}
/**
@@ -129,13 +116,10 @@
*/
public void setUpWithPresenter(NotificationPresenter presenter,
NotificationListContainer listContainer,
- HeadsUpManager headsUpManager,
BindRowCallback bindRowCallback) {
mPresenter = presenter;
mListContainer = listContainer;
- mHeadsUpManager = headsUpManager;
mBindRowCallback = bindRowCallback;
- mOnAppOpsClickListener = mGutsManager::openGuts;
}
public void setInflationCallback(NotificationRowContentBinder.InflationCallback callback) {
@@ -150,9 +134,7 @@
* Inflates the views for the given entry (possibly asynchronously).
*/
@Override
- public void inflateViews(
- NotificationEntry entry,
- Runnable onDismissRunnable)
+ public void inflateViews(NotificationEntry entry, Runnable onDismissRunnable)
throws InflationException {
ViewGroup parent = mListContainer.getViewParentForNotification(entry);
PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext,
@@ -163,12 +145,26 @@
entry.updateIcons(mContext, sbn);
entry.reset();
updateNotification(entry, pmUser, sbn, entry.getRow());
- entry.getRow().setOnDismissRunnable(onDismissRunnable);
+ entry.getRowController().setOnDismissRunnable(onDismissRunnable);
} else {
entry.createIcons(mContext, sbn);
mRowInflaterTaskProvider.get().inflate(mContext, parent, entry,
row -> {
- bindRow(entry, pmUser, sbn, row, onDismissRunnable);
+ // Setup the controller for the view.
+ ExpandableNotificationRowComponent component =
+ mExpandableNotificationRowComponentBuilder
+ .expandableNotificationRow(row)
+ .notificationEntry(entry)
+ .onDismissRunnable(onDismissRunnable)
+ .inflationCallback(mInflationCallback)
+ .rowContentBindStage(mRowContentBindStage)
+ .onExpandClickListener(mPresenter)
+ .build();
+ ExpandableNotificationRowController rowController =
+ component.getExpandableNotificationRowController();
+ rowController.init();
+ entry.setRowController(rowController);
+ bindRow(entry, pmUser, sbn, row);
updateNotification(entry, pmUser, sbn, row);
});
}
@@ -176,55 +172,12 @@
//TODO: This method associates a row with an entry, but eventually needs to not do that
private void bindRow(NotificationEntry entry, PackageManager pmUser,
- StatusBarNotification sbn, ExpandableNotificationRow row,
- Runnable onDismissRunnable) {
- // Get the app name.
- // Note that Notification.Builder#bindHeaderAppName has similar logic
- // but since this field is used in the guts, it must be accurate.
- // Therefore we will only show the application label, or, failing that, the
- // package name. No substitutions.
- final String pkg = sbn.getPackageName();
- String appname = pkg;
- try {
- final ApplicationInfo info = pmUser.getApplicationInfo(pkg,
- PackageManager.MATCH_UNINSTALLED_PACKAGES
- | PackageManager.MATCH_DISABLED_COMPONENTS);
- if (info != null) {
- appname = String.valueOf(pmUser.getApplicationLabel(info));
- }
- } catch (PackageManager.NameNotFoundException e) {
- // Do nothing
- }
-
- row.initialize(
- appname,
- sbn.getKey(),
- mExpansionLogger,
- mKeyguardBypassController,
- mGroupManager,
- mHeadsUpManager,
- mRowContentBindStage,
- mPresenter);
-
- // TODO: Either move these into ExpandableNotificationRow#initialize or out of row entirely
- row.setStatusBarStateController(mStatusBarStateController);
- row.setAppOpsOnClickListener(mOnAppOpsClickListener);
- if (mAllowLongPress) {
- row.setLongPressListener(mGutsManager::openGuts);
- }
+ StatusBarNotification sbn, ExpandableNotificationRow row) {
mListContainer.bindRow(row);
mNotificationRemoteInputManager.bindRow(row);
-
- row.setOnDismissRunnable(onDismissRunnable);
- row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
- if (ENABLE_REMOTE_INPUT) {
- row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
- }
-
entry.setRow(row);
row.setEntry(entry);
mNotifBindPipeline.manageRow(entry, row);
-
mBindRowCallback.onBindRow(entry, pmUser, sbn, row);
}
@@ -307,10 +260,6 @@
Objects.requireNonNull(mNotificationClicker).register(row, sbn);
}
- private void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
- mNotificationLogger.onExpansionChanged(key, userAction, expanded);
- }
-
/** Callback for when a row is bound to an entry. */
public interface BindRowCallback {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index 254b64f..3e0bcbb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -90,7 +90,6 @@
notificationRowBinder.setUpWithPresenter(
presenter,
listContainer,
- headsUpManager,
bindRowCallback)
if (featureFlags.isNewNotifPipelineEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 50a2037..3eac229 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -33,16 +33,14 @@
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
-import com.android.systemui.Dependency;
+import com.android.systemui.Gefingerpoken;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.notification.FakeShadowView;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
-import com.android.systemui.statusbar.phone.DoubleTapHelper;
/**
* Base class for both {@link ExpandableNotificationRow} and {@link NotificationShelf}
@@ -94,14 +92,12 @@
private static final Interpolator ACTIVATE_INVERSE_ALPHA_INTERPOLATOR
= new PathInterpolator(0, 0, 0.5f, 1);
private int mTintedRippleColor;
- protected int mNormalRippleColor;
- private final AccessibilityManager mAccessibilityManager;
- private final DoubleTapHelper mDoubleTapHelper;
+ private int mNormalRippleColor;
+ private Gefingerpoken mTouchHandler;
private boolean mDimmed;
- protected int mBgTint = NO_COLOR;
- private float mBgAlpha = 1f;
+ int mBgTint = NO_COLOR;
/**
* Flag to indicate that the notification has been touched once and the second touch will
@@ -116,7 +112,7 @@
private Interpolator mCurrentAppearInterpolator;
private Interpolator mCurrentAlphaInterpolator;
- protected NotificationBackgroundView mBackgroundNormal;
+ NotificationBackgroundView mBackgroundNormal;
private NotificationBackgroundView mBackgroundDimmed;
private ObjectAnimator mBackgroundAnimator;
private RectF mAppearAnimationRect = new RectF();
@@ -130,7 +126,6 @@
private boolean mLastInSection;
private boolean mFirstInSection;
private boolean mIsBelowSpeedBump;
- private final FalsingManager mFalsingManager;
private float mNormalBackgroundVisibilityAmount;
private float mDimmedBackgroundFadeInAmount = -1;
@@ -154,38 +149,25 @@
*/
private boolean mNeedsDimming;
private int mDimmedAlpha;
- private boolean mBlockNextTouch;
private boolean mIsHeadsUpAnimation;
private int mHeadsUpAddStartLocation;
private float mHeadsUpLocation;
private boolean mIsAppearing;
private boolean mDismissed;
private boolean mRefocusOnDismiss;
+ private OnDimmedListener mOnDimmedListener;
+ private AccessibilityManager mAccessibilityManager;
public ActivatableNotificationView(Context context, AttributeSet attrs) {
super(context, attrs);
mSlowOutFastInInterpolator = new PathInterpolator(0.8f, 0.0f, 0.6f, 1.0f);
mSlowOutLinearInInterpolator = new PathInterpolator(0.8f, 0.0f, 1.0f, 1.0f);
- mFalsingManager = Dependency.get(FalsingManager.class); // TODO: inject into a controller.
setClipChildren(false);
setClipToPadding(false);
updateColors();
- mAccessibilityManager = AccessibilityManager.getInstance(mContext);
-
- mDoubleTapHelper = new DoubleTapHelper(this, (active) -> {
- if (active) {
- makeActive();
- } else {
- makeInactive(true /* animate */);
- }
- }, super::performClick, this::handleSlideBack, mFalsingManager::onNotificationDoubleTap);
initDimens();
}
- public FalsingManager getFalsingManager() {
- return mFalsingManager;
- }
-
private void updateColors() {
mNormalColor = mContext.getColor(R.color.notification_material_background_color);
mTintedRippleColor = mContext.getColor(
@@ -236,32 +218,15 @@
mBackgroundDimmed.setCustomBackground(R.drawable.notification_material_bg_dim);
}
- private final Runnable mTapTimeoutRunnable = new Runnable() {
- @Override
- public void run() {
- makeInactive(true /* animate */);
- }
- };
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
- if (mNeedsDimming && ev.getActionMasked() == MotionEvent.ACTION_DOWN
- && disallowSingleClick(ev) && !isTouchExplorationEnabled()) {
- if (!mActivated) {
- return true;
- } else if (!mDoubleTapHelper.isWithinDoubleTapSlop(ev)) {
- mBlockNextTouch = true;
- makeInactive(true /* animate */);
- return true;
- }
+ if (mTouchHandler != null && mTouchHandler.onInterceptTouchEvent(ev)) {
+ return true;
}
return super.onInterceptTouchEvent(ev);
}
- private boolean isTouchExplorationEnabled() {
- return mAccessibilityManager.isTouchExplorationEnabled();
- }
-
protected boolean disallowSingleClick(MotionEvent ev) {
return false;
}
@@ -270,25 +235,6 @@
return false;
}
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- boolean result;
- if (mBlockNextTouch) {
- mBlockNextTouch = false;
- return false;
- }
- if (mNeedsDimming && !isTouchExplorationEnabled() && isInteractive()) {
- boolean wasActivated = mActivated;
- result = handleTouchEventDimmed(event);
- if (wasActivated && result && event.getAction() == MotionEvent.ACTION_UP) {
- removeCallbacks(mTapTimeoutRunnable);
- }
- } else {
- result = super.onTouchEvent(event);
- }
- return result;
- }
-
/**
* @return whether this view is interactive and can be double tapped
*/
@@ -313,28 +259,11 @@
}
}
- public void setRippleAllowed(boolean allowed) {
+ void setRippleAllowed(boolean allowed) {
mBackgroundNormal.setPressedAllowed(allowed);
}
- private boolean handleTouchEventDimmed(MotionEvent event) {
- if (mNeedsDimming && !mDimmed) {
- // We're actually dimmed, but our content isn't dimmable, let's ensure we have a ripple
- super.onTouchEvent(event);
- }
- return mDoubleTapHelper.onTouchEvent(event, getActualHeight());
- }
-
- @Override
- public boolean performClick() {
- if (!mNeedsDimming || isTouchExplorationEnabled()) {
- return super.performClick();
- }
- return false;
- }
-
- private void makeActive() {
- mFalsingManager.onNotificationActive();
+ void makeActive() {
startActivateAnimation(false /* reverse */);
mActivated = true;
if (mOnActivatedListener != null) {
@@ -388,19 +317,25 @@
mBackgroundNormal.animate()
.alpha(reverse ? 0f : 1f)
.setInterpolator(alphaInterpolator)
- .setUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float animatedFraction = animation.getAnimatedFraction();
- if (reverse) {
- animatedFraction = 1.0f - animatedFraction;
- }
- setNormalBackgroundVisibilityAmount(animatedFraction);
+ .setUpdateListener(animation -> {
+ float animatedFraction = animation.getAnimatedFraction();
+ if (reverse) {
+ animatedFraction = 1.0f - animatedFraction;
}
+ setNormalBackgroundVisibilityAmount(animatedFraction);
})
.setDuration(ACTIVATE_ANIMATION_LENGTH);
}
+ @Override
+ public boolean performClick() {
+ if (!mNeedsDimming || (mAccessibilityManager != null
+ && mAccessibilityManager.isTouchExplorationEnabled())) {
+ return super.performClick();
+ }
+ return false;
+ }
+
/**
* Cancels the hotspot and makes the notification inactive.
*/
@@ -418,11 +353,13 @@
if (mOnActivatedListener != null) {
mOnActivatedListener.onActivationReset(this);
}
- removeCallbacks(mTapTimeoutRunnable);
}
public void setDimmed(boolean dimmed, boolean fade) {
mNeedsDimming = dimmed;
+ if (mOnDimmedListener != null) {
+ mOnDimmedListener.onSetDimmed(dimmed);
+ }
dimmed &= isDimmable();
if (mDimmed != dimmed) {
mDimmed = dimmed;
@@ -439,13 +376,17 @@
return true;
}
+ public boolean isDimmed() {
+ return mDimmed;
+ }
+
private void updateOutlineAlpha() {
float alpha = NotificationStackScrollLayout.BACKGROUND_ALPHA_DIMMED;
alpha = (alpha + (1.0f - alpha) * mNormalBackgroundVisibilityAmount);
setOutlineAlpha(alpha);
}
- public void setNormalBackgroundVisibilityAmount(float normalBackgroundVisibilityAmount) {
+ private void setNormalBackgroundVisibilityAmount(float normalBackgroundVisibilityAmount) {
mNormalBackgroundVisibilityAmount = normalBackgroundVisibilityAmount;
updateOutlineAlpha();
}
@@ -473,14 +414,14 @@
/**
* Sets the tint color of the background
*/
- public void setTintColor(int color) {
+ protected void setTintColor(int color) {
setTintColor(color, false);
}
/**
* Sets the tint color of the background
*/
- public void setTintColor(int color, boolean animated) {
+ void setTintColor(int color, boolean animated) {
if (color != mBgTint) {
mBgTint = color;
updateBackgroundTint(animated);
@@ -562,13 +503,10 @@
mStartTint = mCurrentBackgroundTint;
mTargetTint = color;
mBackgroundColorAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
- mBackgroundColorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- int newColor = NotificationUtils.interpolateColors(mStartTint, mTargetTint,
- animation.getAnimatedFraction());
- setBackgroundTintColor(newColor);
- }
+ mBackgroundColorAnimator.addUpdateListener(animation -> {
+ int newColor = NotificationUtils.interpolateColors(mStartTint, mTargetTint,
+ animation.getAnimatedFraction());
+ setBackgroundTintColor(newColor);
});
mBackgroundColorAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
mBackgroundColorAnimator.setInterpolator(Interpolators.LINEAR);
@@ -643,11 +581,11 @@
}
protected void updateBackgroundAlpha(float transformationAmount) {
- mBgAlpha = isChildInGroup() && mDimmed ? transformationAmount : 1f;
+ float bgAlpha = isChildInGroup() && mDimmed ? transformationAmount : 1f;
if (mDimmedBackgroundFadeInAmount != -1) {
- mBgAlpha *= mDimmedBackgroundFadeInAmount;
+ bgAlpha *= mDimmedBackgroundFadeInAmount;
}
- mBackgroundDimmed.setAlpha(mBgAlpha);
+ mBackgroundDimmed.setAlpha(bgAlpha);
}
protected void resetBackgroundAlpha() {
@@ -671,7 +609,6 @@
mBackgroundDimmed.setVisibility(View.INVISIBLE);
mBackgroundNormal.setVisibility(View.VISIBLE);
mBackgroundNormal.setAlpha(1f);
- removeCallbacks(mTapTimeoutRunnable);
// make in inactive to avoid it sticking around active
makeInactive(false /* animate */);
}
@@ -783,14 +720,11 @@
mAppearAnimator.setInterpolator(Interpolators.LINEAR);
mAppearAnimator.setDuration(
(long) (duration * Math.abs(mAppearAnimationFraction - targetValue)));
- mAppearAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- mAppearAnimationFraction = (float) animation.getAnimatedValue();
- updateAppearAnimationAlpha();
- updateAppearRect();
- invalidate();
- }
+ mAppearAnimator.addUpdateListener(animation -> {
+ mAppearAnimationFraction = (float) animation.getAnimatedValue();
+ updateAppearAnimationAlpha();
+ updateAppearRect();
+ invalidate();
});
if (animationListener != null) {
mAppearAnimator.addListener(animationListener);
@@ -921,7 +855,7 @@
getCurrentBackgroundRadiusBottom());
}
- protected void applyBackgroundRoundness(float topRadius, float bottomRadius) {
+ private void applyBackgroundRoundness(float topRadius, float bottomRadius) {
mBackgroundDimmed.setRoundness(topRadius, bottomRadius);
mBackgroundNormal.setRoundness(topRadius, bottomRadius);
}
@@ -963,7 +897,7 @@
}
}
- protected int getRippleColor() {
+ private int getRippleColor() {
if (mBgTint != 0) {
return mTintedRippleColor;
} else {
@@ -1010,10 +944,6 @@
mOnActivatedListener = onActivatedListener;
}
- public boolean hasSameBgColor(ActivatableNotificationView otherView) {
- return calculateBgColor() == otherView.calculateBgColor();
- }
-
@Override
public void setFakeShadowIntensity(float shadowIntensity, float outlineAlpha, int shadowYEnd,
int outlineTranslation) {
@@ -1071,8 +1001,24 @@
return mRefocusOnDismiss || isAccessibilityFocused();
}
+ void setTouchHandler(Gefingerpoken touchHandler) {
+ mTouchHandler = touchHandler;
+ }
+
+ void setOnDimmedListener(OnDimmedListener onDimmedListener) {
+ mOnDimmedListener = onDimmedListener;
+ }
+
+ public void setAccessibilityManager(AccessibilityManager accessibilityManager) {
+ mAccessibilityManager = accessibilityManager;
+ }
+
public interface OnActivatedListener {
void onActivated(ActivatableNotificationView view);
void onActivationReset(ActivatableNotificationView view);
}
+
+ interface OnDimmedListener {
+ void onSetDimmed(boolean dimmed);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java
index 18993ff..8465658 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java
@@ -16,9 +16,13 @@
package com.android.systemui.statusbar.notification.row;
+import android.view.MotionEvent;
+import android.view.View;
import android.view.accessibility.AccessibilityManager;
+import com.android.systemui.Gefingerpoken;
import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.statusbar.phone.DoubleTapHelper;
import javax.inject.Inject;
@@ -27,21 +31,102 @@
*/
public class ActivatableNotificationViewController {
private final ActivatableNotificationView mView;
+ private final ExpandableOutlineViewController mExpandableOutlineViewController;
private final AccessibilityManager mAccessibilityManager;
private final FalsingManager mFalsingManager;
+ private DoubleTapHelper mDoubleTapHelper;
+ private boolean mNeedsDimming;
+
+ private TouchHandler mTouchHandler = new TouchHandler();
@Inject
public ActivatableNotificationViewController(ActivatableNotificationView view,
+ ExpandableOutlineViewController expandableOutlineViewController,
AccessibilityManager accessibilityManager, FalsingManager falsingManager) {
mView = view;
+ mExpandableOutlineViewController = expandableOutlineViewController;
mAccessibilityManager = accessibilityManager;
mFalsingManager = falsingManager;
+
+ mView.setOnActivatedListener(new ActivatableNotificationView.OnActivatedListener() {
+ @Override
+ public void onActivated(ActivatableNotificationView view) {
+ mFalsingManager.onNotificationActive();
+ }
+
+ @Override
+ public void onActivationReset(ActivatableNotificationView view) {
+ }
+ });
}
/**
* Initialize the controller, setting up handlers and other behavior.
*/
public void init() {
+ mExpandableOutlineViewController.init();
+ mDoubleTapHelper = new DoubleTapHelper(mView, (active) -> {
+ if (active) {
+ mView.makeActive();
+ mFalsingManager.onNotificationActive();
+ } else {
+ mView.makeInactive(true /* animate */);
+ }
+ }, mView::performClick, mView::handleSlideBack, mFalsingManager::onNotificationDoubleTap);
+ mView.setOnTouchListener(mTouchHandler);
+ mView.setTouchHandler(mTouchHandler);
+ mView.setOnDimmedListener(dimmed -> {
+ mNeedsDimming = dimmed;
+ });
+ mView.setAccessibilityManager(mAccessibilityManager);
+ }
+ class TouchHandler implements Gefingerpoken, View.OnTouchListener {
+ private boolean mBlockNextTouch;
+
+ @Override
+ public boolean onTouch(View v, MotionEvent ev) {
+ boolean result;
+ if (mBlockNextTouch) {
+ mBlockNextTouch = false;
+ return true;
+ }
+ if (mNeedsDimming && !mAccessibilityManager.isTouchExplorationEnabled()
+ && mView.isInteractive()) {
+ if (mNeedsDimming && !mView.isDimmed()) {
+ // We're actually dimmed, but our content isn't dimmable,
+ // let's ensure we have a ripple
+ return false;
+ }
+ result = mDoubleTapHelper.onTouchEvent(ev, mView.getActualHeight());
+ } else {
+ return false;
+ }
+ return result;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (mNeedsDimming && ev.getActionMasked() == MotionEvent.ACTION_DOWN
+ && mView.disallowSingleClick(ev)
+ && !mAccessibilityManager.isTouchExplorationEnabled()) {
+ if (!mView.isActivated()) {
+ return true;
+ } else if (!mDoubleTapHelper.isWithinDoubleTapSlop(ev)) {
+ mBlockNextTouch = true;
+ mView.makeInactive(true /* animate */);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Use {@link #onTouch(View, MotionEvent) instead}.
+ */
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ return false;
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index c34bba7..55173182 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -42,7 +42,6 @@
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
-import android.os.SystemClock;
import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
import android.util.AttributeSet;
@@ -72,11 +71,11 @@
import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
+import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarIconView;
@@ -199,6 +198,7 @@
private NotificationGuts mGuts;
private NotificationEntry mEntry;
private String mAppName;
+ private FalsingManager mFalsingManager;
/**
* Whether or not the notification is using the heads up view and should peek from the top.
@@ -1087,20 +1087,6 @@
}
@Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mEntry.setInitializationTime(SystemClock.elapsedRealtime());
- Dependency.get(PluginManager.class).addPluginListener(this,
- NotificationMenuRowPlugin.class, false /* Allow multiple */);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- Dependency.get(PluginManager.class).removePluginListener(this);
- }
-
- @Override
public void onPluginConnected(NotificationMenuRowPlugin plugin, Context pluginContext) {
boolean existed = mMenuRow != null && mMenuRow.getMenuView() != null;
if (existed) {
@@ -1439,7 +1425,7 @@
return mIsBlockingHelperShowing && mNotificationTranslationFinished;
}
- public void setOnDismissRunnable(Runnable onDismissRunnable) {
+ void setOnDismissRunnable(Runnable onDismissRunnable) {
mOnDismissRunnable = onDismissRunnable;
}
@@ -1597,7 +1583,6 @@
mMenuRow = new NotificationMenuRow(mContext);
mImageResolver = new NotificationInlineImageResolver(context,
new NotificationInlineImageCache());
- mMediaManager = Dependency.get(NotificationMediaManager.class);
initDimens();
}
@@ -1612,7 +1597,11 @@
NotificationGroupManager groupManager,
HeadsUpManager headsUpManager,
RowContentBindStage rowContentBindStage,
- OnExpandClickListener onExpandClickListener) {
+ OnExpandClickListener onExpandClickListener,
+ NotificationMediaManager notificationMediaManager,
+ OnAppOpsClickListener onAppOpsClickListener,
+ FalsingManager falsingManager,
+ StatusBarStateController statusBarStateController) {
mAppName = appName;
if (mMenuRow != null && mMenuRow.getMenuView() != null) {
mMenuRow.setAppName(mAppName);
@@ -1625,9 +1614,9 @@
mHeadsUpManager = headsUpManager;
mRowContentBindStage = rowContentBindStage;
mOnExpandClickListener = onExpandClickListener;
- }
-
- public void setStatusBarStateController(StatusBarStateController statusBarStateController) {
+ mMediaManager = notificationMediaManager;
+ setAppOpsOnClickListener(onAppOpsClickListener);
+ mFalsingManager = falsingManager;
mStatusbarStateController = statusBarStateController;
}
@@ -1719,7 +1708,7 @@
return mOnAppOpsClickListener;
}
- public void setAppOpsOnClickListener(ExpandableNotificationRow.OnAppOpsClickListener l) {
+ void setAppOpsOnClickListener(ExpandableNotificationRow.OnAppOpsClickListener l) {
mOnAppOpsClickListener = v -> {
createMenu();
NotificationMenuRowPlugin provider = getProvider();
@@ -2188,7 +2177,7 @@
* @param allowChildExpansion whether a call to this method allows expanding children
*/
public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) {
- getFalsingManager().setNotificationExpanded();
+ mFalsingManager.setNotificationExpanded();
if (mIsSummaryWithChildren && !shouldShowPublic() && allowChildExpansion
&& !mChildrenContainer.showingAsLowPriority()) {
final boolean wasExpanded = mGroupManager.isGroupExpanded(mEntry.getSbn());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
new file mode 100644
index 0000000..39fab43
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -0,0 +1,161 @@
+/*
+ * 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.systemui.statusbar.notification.row;
+
+import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
+import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
+import com.android.systemui.statusbar.notification.row.dagger.AppName;
+import com.android.systemui.statusbar.notification.row.dagger.DismissRunnable;
+import com.android.systemui.statusbar.notification.row.dagger.NotificationKey;
+import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.util.time.SystemClock;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * Controller for {@link ExpandableNotificationRow}.
+ */
+@NotificationRowScope
+public class ExpandableNotificationRowController {
+ private final ExpandableNotificationRow mView;
+ private final ActivatableNotificationViewController mActivatableNotificationViewController;
+ private final NotificationMediaManager mMediaManager;
+ private final PluginManager mPluginManager;
+ private final SystemClock mClock;
+ private final String mAppName;
+ private final String mNotificationKey;
+ private final KeyguardBypassController mKeyguardBypassController;
+ private final NotificationGroupManager mNotificationGroupManager;
+ private final RowContentBindStage mRowContentBindStage;
+ private final NotificationLogger mNotificationLogger;
+ private final HeadsUpManager mHeadsUpManager;
+ private final ExpandableNotificationRow.OnExpandClickListener mOnExpandClickListener;
+ private final StatusBarStateController mStatusBarStateController;
+ private final NotificationRowContentBinder.InflationCallback mInflationCallback;
+
+ private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger =
+ this::logNotificationExpansion;
+ private final ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener;
+ private final NotificationGutsManager mNotificationGutsManager;
+ private Runnable mOnDismissRunnable;
+ private final FalsingManager mFalsingManager;
+ private final boolean mAllowLongPress;
+
+ @Inject
+ public ExpandableNotificationRowController(ExpandableNotificationRow view,
+ ActivatableNotificationViewController activatableNotificationViewController,
+ NotificationMediaManager mediaManager, PluginManager pluginManager,
+ SystemClock clock, @AppName String appName, @NotificationKey String notificationKey,
+ KeyguardBypassController keyguardBypassController,
+ NotificationGroupManager notificationGroupManager,
+ RowContentBindStage rowContentBindStage,
+ NotificationLogger notificationLogger, HeadsUpManager headsUpManager,
+ ExpandableNotificationRow.OnExpandClickListener onExpandClickListener,
+ StatusBarStateController statusBarStateController,
+ NotificationRowContentBinder.InflationCallback inflationCallback,
+ NotificationGutsManager notificationGutsManager,
+ @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
+ @DismissRunnable Runnable onDismissRunnable, FalsingManager falsingManager) {
+ mView = view;
+ mActivatableNotificationViewController = activatableNotificationViewController;
+ mMediaManager = mediaManager;
+ mPluginManager = pluginManager;
+ mClock = clock;
+ mAppName = appName;
+ mNotificationKey = notificationKey;
+ mKeyguardBypassController = keyguardBypassController;
+ mNotificationGroupManager = notificationGroupManager;
+ mRowContentBindStage = rowContentBindStage;
+ mNotificationLogger = notificationLogger;
+ mHeadsUpManager = headsUpManager;
+ mOnExpandClickListener = onExpandClickListener;
+ mStatusBarStateController = statusBarStateController;
+ mInflationCallback = inflationCallback;
+ mNotificationGutsManager = notificationGutsManager;
+ mOnDismissRunnable = onDismissRunnable;
+ mOnAppOpsClickListener = mNotificationGutsManager::openGuts;
+ mAllowLongPress = allowLongPress;
+ mFalsingManager = falsingManager;
+ }
+
+ /**
+ * Initialize the controller.
+ */
+ public void init() {
+ mActivatableNotificationViewController.init();
+ mView.initialize(
+ mAppName,
+ mNotificationKey,
+ mExpansionLogger,
+ mKeyguardBypassController,
+ mNotificationGroupManager,
+ mHeadsUpManager,
+ mRowContentBindStage,
+ mOnExpandClickListener,
+ mMediaManager,
+ mOnAppOpsClickListener,
+ mFalsingManager,
+ mStatusBarStateController
+ );
+ mView.setOnDismissRunnable(mOnDismissRunnable);
+ mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
+ if (mAllowLongPress) {
+ mView.setLongPressListener(mNotificationGutsManager::openGuts);
+ }
+ if (ENABLE_REMOTE_INPUT) {
+ mView.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
+ }
+
+ mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ mView.getEntry().setInitializationTime(mClock.elapsedRealtime());
+ mPluginManager.addPluginListener(mView,
+ NotificationMenuRowPlugin.class, false /* Allow multiple */);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ mPluginManager.removePluginListener(mView);
+ }
+ });
+ }
+
+ private void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
+ mNotificationLogger.onExpansionChanged(key, userAction, expanded);
+ }
+
+ /** */
+ public void setOnDismissRunnable(Runnable onDismissRunnable) {
+ mOnDismissRunnable = onDismissRunnable;
+ mView.setOnDismissRunnable(onDismissRunnable);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineViewController.java
new file mode 100644
index 0000000..75c9d1e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineViewController.java
@@ -0,0 +1,41 @@
+/*
+ * 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.systemui.statusbar.notification.row;
+
+import javax.inject.Inject;
+
+/**
+ * Controller for {@link ExpandableOutlineView}.
+ */
+public class ExpandableOutlineViewController {
+ private final ExpandableOutlineView mView;
+ private final ExpandableViewController mExpandableViewController;
+
+ @Inject
+ public ExpandableOutlineViewController(ExpandableOutlineView view,
+ ExpandableViewController expandableViewController) {
+ mView = view;
+ mExpandableViewController = expandableViewController;
+ }
+
+ /**
+ * Initialize the controller.
+ */
+ public void init() {
+ mExpandableViewController.init();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableViewController.java
new file mode 100644
index 0000000..e14ca8c4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableViewController.java
@@ -0,0 +1,37 @@
+/*
+ * 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.systemui.statusbar.notification.row;
+
+import javax.inject.Inject;
+
+/**
+ * Controller for {@link ExpandableView}.
+ */
+public class ExpandableViewController {
+ private final ExpandableView mView;
+
+ @Inject
+ public ExpandableViewController(ExpandableView view) {
+ mView = view;
+ }
+
+ /**
+ * Initialize the controller.
+ */
+ public void init() {
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
index c173b4d..6feffe6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
@@ -26,7 +26,6 @@
import com.android.systemui.R;
import com.android.systemui.statusbar.InflationTask;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
import javax.inject.Inject;
@@ -37,7 +36,6 @@
private static final String TAG = "RowInflaterTask";
private static final boolean TRACE_ORIGIN = true;
- private final NotificationRowComponent.Builder mNotificationRowComponentBuilder;
private RowInflationFinishedListener mListener;
private NotificationEntry mEntry;
@@ -45,10 +43,7 @@
private Throwable mInflateOrigin;
@Inject
- public RowInflaterTask(
- NotificationRowComponent.Builder notificationRowComponentBuilder) {
- super();
- mNotificationRowComponentBuilder = notificationRowComponentBuilder;
+ public RowInflaterTask() {
}
/**
@@ -75,12 +70,6 @@
public void onInflateFinished(View view, int resid, ViewGroup parent) {
if (!mCancelled) {
try {
- // Setup the controller for the view.
- NotificationRowComponent component = mNotificationRowComponentBuilder
- .activatableNotificationView((ActivatableNotificationView) view)
- .build();
- component.getActivatableNotificationViewController().init();
-
mEntry.onInflationTaskFinished();
mListener.onInflationFinished((ExpandableNotificationRow) view);
} catch (Throwable t) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ActivatableNotificationViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ActivatableNotificationViewModule.java
new file mode 100644
index 0000000..a3dfa60
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ActivatableNotificationViewModule.java
@@ -0,0 +1,37 @@
+/*
+ * 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.systemui.statusbar.notification.row.dagger;
+
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
+import com.android.systemui.statusbar.notification.row.ExpandableOutlineView;
+import com.android.systemui.statusbar.notification.row.ExpandableView;
+
+import dagger.Binds;
+import dagger.Module;
+
+/**
+ * Module for NotificationRowComponent.
+ */
+@Module
+public interface ActivatableNotificationViewModule {
+ /** ExpandableView is provided as an instance of ActivatableNotificationView. */
+ @Binds
+ ExpandableView bindExpandableView(ActivatableNotificationView view);
+ /** ExpandableOutlineView is provided as an instance of ActivatableNotificationView. */
+ @Binds
+ ExpandableOutlineView bindExpandableOutlineView(ActivatableNotificationView view);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/AppName.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/AppName.java
new file mode 100644
index 0000000..1dbca0c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/AppName.java
@@ -0,0 +1,30 @@
+/*
+ * 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.systemui.statusbar.notification.row.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface AppName {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/DismissRunnable.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/DismissRunnable.java
new file mode 100644
index 0000000..4331142
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/DismissRunnable.java
@@ -0,0 +1,30 @@
+/*
+ * 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.systemui.statusbar.notification.row.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface DismissRunnable {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java
new file mode 100644
index 0000000..6d6d3e4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java
@@ -0,0 +1,122 @@
+/*
+ * 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.systemui.statusbar.notification.row.dagger;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.service.notification.StatusBarNotification;
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder;
+import com.android.systemui.statusbar.notification.row.RowContentBindStage;
+import com.android.systemui.statusbar.phone.StatusBar;
+
+import dagger.Binds;
+import dagger.BindsInstance;
+import dagger.Module;
+import dagger.Provides;
+import dagger.Subcomponent;
+
+/**
+ * Dagger Component for a {@link ExpandableNotificationRow}.
+ */
+@Subcomponent(modules = {ExpandableNotificationRowComponent.ExpandableNotificationRowModule.class,
+ ActivatableNotificationViewModule.class})
+@NotificationRowScope
+public interface ExpandableNotificationRowComponent {
+
+ /**
+ * Builder for {@link NotificationRowComponent}.
+ */
+ @Subcomponent.Builder
+ interface Builder {
+ // TODO: NotificationEntry contains a reference to ExpandableNotificationRow, so it
+ // should be possible to pull one from the other, but they aren't connected at the time
+ // this component is constructed.
+ @BindsInstance
+ Builder expandableNotificationRow(ExpandableNotificationRow view);
+ @BindsInstance
+ Builder notificationEntry(NotificationEntry entry);
+ @BindsInstance
+ Builder onDismissRunnable(@DismissRunnable Runnable runnable);
+ @BindsInstance
+ Builder rowContentBindStage(RowContentBindStage rowContentBindStage);
+ @BindsInstance
+ Builder inflationCallback(NotificationRowContentBinder.InflationCallback inflationCallback);
+ @BindsInstance
+ Builder onExpandClickListener(ExpandableNotificationRow.OnExpandClickListener presenter);
+ ExpandableNotificationRowComponent build();
+ }
+
+ /**
+ * Creates a ExpandableNotificationRowController.
+ */
+ @NotificationRowScope
+ ExpandableNotificationRowController getExpandableNotificationRowController();
+
+ /**
+ * Dagger Module that extracts interesting properties from an ExpandableNotificationRow.
+ */
+ @Module
+ abstract class ExpandableNotificationRowModule {
+
+ /** ExpandableNotificationRow is provided as an instance of ActivatableNotificationView. */
+ @Binds
+ abstract ActivatableNotificationView bindExpandableView(ExpandableNotificationRow view);
+
+ @Provides
+ static StatusBarNotification provideStatusBarNotification(
+ NotificationEntry notificationEntry) {
+ return notificationEntry.getSbn();
+ }
+
+ @Provides
+ @NotificationKey
+ static String provideNotificationKey(StatusBarNotification statusBarNotification) {
+ return statusBarNotification.getKey();
+ }
+
+ @Provides
+ @AppName
+ static String provideAppName(Context context, StatusBarNotification statusBarNotification) {
+ // Get the app name.
+ // Note that Notification.Builder#bindHeaderAppName has similar logic
+ // but since this field is used in the guts, it must be accurate.
+ // Therefore we will only show the application label, or, failing that, the
+ // package name. No substitutions.
+ PackageManager pmUser = StatusBar.getPackageManagerForUser(
+ context, statusBarNotification.getUser().getIdentifier());
+ final String pkg = statusBarNotification.getPackageName();
+ try {
+ final ApplicationInfo info = pmUser.getApplicationInfo(pkg,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES
+ | PackageManager.MATCH_DISABLED_COMPONENTS);
+ if (info != null) {
+ return String.valueOf(pmUser.getApplicationLabel(info));
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // Do nothing
+ }
+
+ return pkg;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationKey.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationKey.java
new file mode 100644
index 0000000..b1fff38
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationKey.java
@@ -0,0 +1,30 @@
+/*
+ * 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.systemui.statusbar.notification.row.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface NotificationKey {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowComponent.java
index f16ea7a..1f535c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowComponent.java
@@ -16,24 +16,17 @@
package com.android.systemui.statusbar.notification.row.dagger;
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController;
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-
-import javax.inject.Scope;
-
import dagger.BindsInstance;
import dagger.Subcomponent;
/**
* Dagger subcomponent for Notification related views.
*/
-@Subcomponent(modules = {})
-@NotificationRowComponent.NotificationRowScope
+@Subcomponent(modules = {ActivatableNotificationViewModule.class})
+@NotificationRowScope
public interface NotificationRowComponent {
/**
* Builder for {@link NotificationRowComponent}.
@@ -46,14 +39,6 @@
}
/**
- * Scope annotation for singleton items within the StatusBarComponent.
- */
- @Documented
- @Retention(RUNTIME)
- @Scope
- @interface NotificationRowScope {}
-
- /**
* Creates a ActivatableNotificationViewController.
*/
@NotificationRowScope
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowScope.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowScope.java
new file mode 100644
index 0000000..4555b83
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowScope.java
@@ -0,0 +1,32 @@
+/*
+ * 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.systemui.statusbar.notification.row.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Scope;
+
+/**
+ * Scope annotation for singleton items within the StatusBarComponent.
+ */
+@Documented
+@Retention(RUNTIME)
+@Scope
+public @interface NotificationRowScope {}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index 07f6936..5a0a495 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -58,10 +58,13 @@
import androidx.test.filters.SmallTest;
import com.android.internal.statusbar.NotificationVisibility;
+import com.android.internal.util.NotificationMessagingUtil;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.NotificationLifetimeExtender;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -83,12 +86,13 @@
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
import com.android.systemui.statusbar.notification.row.NotifBindPipeline;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.row.RowContentBindParams;
import com.android.systemui.statusbar.notification.row.RowContentBindStage;
import com.android.systemui.statusbar.notification.row.RowInflaterTask;
-import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
+import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
@@ -96,6 +100,7 @@
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.util.Assert;
import com.android.systemui.util.leak.LeakDetector;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.After;
import org.junit.Before;
@@ -106,6 +111,7 @@
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
import java.util.ArrayList;
import java.util.Arrays;
@@ -141,8 +147,13 @@
@Mock private NotificationEntryManagerLogger mLogger;
@Mock private FeatureFlags mFeatureFlags;
@Mock private LeakDetector mLeakDetector;
- @Mock private ActivatableNotificationViewController mActivatableNotificationViewController;
- @Mock private NotificationRowComponent.Builder mNotificationRowComponentBuilder;
+ @Mock private NotificationMediaManager mNotificationMediaManager;
+ @Mock private ExpandableNotificationRowComponent.Builder
+ mExpandableNotificationRowComponentBuilder;
+ @Mock private ExpandableNotificationRowComponent mExpandableNotificationRowComponent;
+ @Mock private FalsingManager mFalsingManager;
+ @Mock private KeyguardBypassController mKeyguardBypassController;
+ @Mock private StatusBarStateController mStatusBarStateController;
private int mId;
private NotificationEntry mEntry;
@@ -191,7 +202,6 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
mDependency.injectMockDependency(SmartReplyController.class);
- mDependency.injectMockDependency(NotificationMediaManager.class);
mCountDownLatch = new CountDownLatch(1);
@@ -207,28 +217,23 @@
mEntry.expandedIcon = mock(StatusBarIconView.class);
- when(mNotificationRowComponentBuilder.activatableNotificationView(any()))
- .thenReturn(mNotificationRowComponentBuilder);
- when(mNotificationRowComponentBuilder.build()).thenReturn(
- () -> mActivatableNotificationViewController);
-
RowContentBindStage bindStage = mock(RowContentBindStage.class);
when(bindStage.getStageParams(any())).thenReturn(new RowContentBindParams());
-
NotificationRowBinderImpl notificationRowBinder =
new NotificationRowBinderImpl(mContext,
+ new NotificationMessagingUtil(mContext),
mRemoteInputManager,
mLockscreenUserManager,
mock(NotifBindPipeline.class),
bindStage,
true, /* allowLongPress */
- mock(KeyguardBypassController.class),
- mock(StatusBarStateController.class),
+ mKeyguardBypassController,
+ mStatusBarStateController,
mGroupManager,
mGutsManager,
mNotificationInterruptionStateProvider,
- () -> new RowInflaterTask(mNotificationRowComponentBuilder),
- mock(NotificationLogger.class));
+ RowInflaterTask::new,
+ mExpandableNotificationRowComponentBuilder);
when(mFeatureFlags.isNewNotifPipelineEnabled()).thenReturn(false);
when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false);
@@ -236,7 +241,7 @@
mLogger,
mGroupManager,
new NotificationRankingManager(
- () -> mock(NotificationMediaManager.class),
+ () -> mNotificationMediaManager,
mGroupManager,
mHeadsUpManager,
mock(NotificationFilter.class),
@@ -255,13 +260,55 @@
mEntryManager.addNotificationEntryListener(mEntryListener);
mEntryManager.addNotificationRemoveInterceptor(mRemoveInterceptor);
- notificationRowBinder.setUpWithPresenter(
- mPresenter, mListContainer, mHeadsUpManager, mBindCallback);
+ notificationRowBinder.setUpWithPresenter(mPresenter, mListContainer, mBindCallback);
notificationRowBinder.setInflationCallback(mEntryManager);
notificationRowBinder.setNotificationClicker(mock(NotificationClicker.class));
setUserSentiment(
mEntry.getKey(), Ranking.USER_SENTIMENT_NEUTRAL);
+
+ ArgumentCaptor<ExpandableNotificationRow> viewCaptor =
+ ArgumentCaptor.forClass(ExpandableNotificationRow.class);
+ when(mExpandableNotificationRowComponentBuilder
+ .expandableNotificationRow(viewCaptor.capture()))
+ .thenReturn(mExpandableNotificationRowComponentBuilder);
+ when(mExpandableNotificationRowComponentBuilder
+ .notificationEntry(any()))
+ .thenReturn(mExpandableNotificationRowComponentBuilder);
+ when(mExpandableNotificationRowComponentBuilder
+ .onDismissRunnable(any()))
+ .thenReturn(mExpandableNotificationRowComponentBuilder);
+ when(mExpandableNotificationRowComponentBuilder
+ .inflationCallback(any()))
+ .thenReturn(mExpandableNotificationRowComponentBuilder);
+ when(mExpandableNotificationRowComponentBuilder
+ .onExpandClickListener(any()))
+ .thenReturn(mExpandableNotificationRowComponentBuilder);
+
+ when(mExpandableNotificationRowComponentBuilder.build())
+ .thenReturn(mExpandableNotificationRowComponent);
+ when(mExpandableNotificationRowComponent.getExpandableNotificationRowController())
+ .thenAnswer((Answer<ExpandableNotificationRowController>) invocation ->
+ new ExpandableNotificationRowController(
+ viewCaptor.getValue(),
+ mock(ActivatableNotificationViewController.class),
+ mNotificationMediaManager,
+ mock(PluginManager.class),
+ new FakeSystemClock(),
+ "FOOBAR", "FOOBAR",
+ mKeyguardBypassController,
+ mGroupManager,
+ bindStage,
+ mock(NotificationLogger.class),
+ mHeadsUpManager,
+ mPresenter,
+ mStatusBarStateController,
+ mEntryManager,
+ mGutsManager,
+ true,
+ null,
+ mFalsingManager
+ ));
}
@After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index d8cf6ed..a891810 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -194,9 +194,8 @@
@Test
public void testClickSound() throws Exception {
assertTrue("Should play sounds by default.", mGroupRow.isSoundEffectsEnabled());
- StatusBarStateController mock = mock(StatusBarStateController.class);
+ StatusBarStateController mock = mNotificationTestHelper.getStatusBarStateController();
when(mock.isDozing()).thenReturn(true);
- mGroupRow.setStatusBarStateController(mock);
mGroupRow.setSecureStateProvider(()-> false);
assertFalse("Shouldn't play sounds when dark and trusted.",
mGroupRow.isSoundEffectsEnabled());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 9b2e0c3..35b5508 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -45,6 +45,7 @@
import com.android.systemui.TestableDependency;
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.bubbles.BubblesTestActivity;
+import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -91,6 +92,7 @@
private final NotifBindPipeline mBindPipeline;
private final NotificationEntryListener mBindPipelineEntryListener;
private final RowContentBindStage mBindStage;
+ private StatusBarStateController mStatusBarStateController;
public NotificationTestHelper(Context context, TestableDependency dependency) {
mContext = context;
@@ -98,9 +100,9 @@
dependency.injectMockDependency(BubbleController.class);
dependency.injectMockDependency(NotificationShadeWindowController.class);
dependency.injectMockDependency(SmartReplyController.class);
- StatusBarStateController stateController = mock(StatusBarStateController.class);
- mGroupManager = new NotificationGroupManager(stateController);
- mHeadsUpManager = new HeadsUpManagerPhone(mContext, stateController,
+ mStatusBarStateController = mock(StatusBarStateController.class);
+ mGroupManager = new NotificationGroupManager(mStatusBarStateController);
+ mHeadsUpManager = new HeadsUpManagerPhone(mContext, mStatusBarStateController,
mock(KeyguardBypassController.class));
mHeadsUpManager.setUp(null, mGroupManager, null, null);
mGroupManager.setHeadsUpManager(mHeadsUpManager);
@@ -321,6 +323,10 @@
return notificationBuilder.build();
}
+ public StatusBarStateController getStatusBarStateController() {
+ return mStatusBarStateController;
+ }
+
private ExpandableNotificationRow generateRow(
Notification notification,
String pkg,
@@ -382,7 +388,11 @@
mGroupManager,
mHeadsUpManager,
mBindStage,
- mock(OnExpandClickListener.class));
+ mock(OnExpandClickListener.class),
+ mock(NotificationMediaManager.class),
+ mock(ExpandableNotificationRow.OnAppOpsClickListener.class),
+ mock(FalsingManager.class),
+ mStatusBarStateController);
row.setAboveShelfChangedListener(aboveShelf -> { });
mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags);
inflateAndWait(entry, mBindStage);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
index e84f14a..2d1bc78 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
@@ -30,7 +30,6 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -59,8 +58,6 @@
private ExpandableNotificationRow mFirst;
private ExpandableNotificationRow mSecond;
@Mock
- private StatusBarStateController mStatusBarStateController;
- @Mock
private KeyguardBypassController mBypassController;
@Before
@@ -150,13 +147,12 @@
createSection(mFirst, mSecond),
createSection(null, null)
});
- ExpandableNotificationRow row = new NotificationTestHelper(getContext(), mDependency)
- .createRow();
+ NotificationTestHelper testHelper = new NotificationTestHelper(getContext(), mDependency);
+ ExpandableNotificationRow row = testHelper.createRow();
NotificationEntry entry = mock(NotificationEntry.class);
when(entry.getRow()).thenReturn(row);
- when(mStatusBarStateController.isDozing()).thenReturn(true);
- row.setStatusBarStateController(mStatusBarStateController);
+ when(testHelper.getStatusBarStateController().isDozing()).thenReturn(true);
row.setHeadsUp(true);
mRoundnessManager.onHeadsUpStateChanged(entry, true);
Assert.assertEquals(1f, row.getCurrentBottomRoundness(), 0.0f);
diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp
index 285f4ab..4efe934 100644
--- a/packages/Tethering/Android.bp
+++ b/packages/Tethering/Android.bp
@@ -28,6 +28,7 @@
"netd_aidl_interface-unstable-java",
"netlink-client",
"networkstack-aidl-interfaces-unstable-java",
+ "android.hardware.tetheroffload.config-V1.0-java",
"android.hardware.tetheroffload.control-V1.0-java",
"net-utils-framework-common",
],
@@ -48,25 +49,26 @@
// Due to b/143733063, APK can't access a jni lib that is in APEX (but not in the APK).
cc_library {
name: "libtetherutilsjni",
+ sdk_version: "current",
srcs: [
"jni/android_net_util_TetheringUtils.cpp",
],
shared_libs: [
- "libcgrouprc",
- "libnativehelper_compat_libc++",
- "libvndksupport",
- ],
- static_libs: [
- "android.hardware.tetheroffload.config@1.0",
"liblog",
- "libbase",
- "libcutils",
- "libhidlbase",
- "libjsoncpp",
- "libprocessgroup",
- "libutils",
+ "libnativehelper_compat_libc++",
],
+ // We cannot use plain "libc++" here to link libc++ dynamically because it results in:
+ // java.lang.UnsatisfiedLinkError: dlopen failed: library "libc++_shared.so" not found
+ // even if "libc++" is added into jni_libs below. Adding "libc++_shared" into jni_libs doesn't
+ // build because soong complains of:
+ // module Tethering missing dependencies: libc++_shared
+ //
+ // So, link libc++ statically. This means that we also need to ensure that all the C++ libraries
+ // we depend on do not dynamically link libc++. This is currently the case, because liblog is
+ // C-only and libnativehelper_compat_libc also uses stl: "c++_static".
+ stl: "c++_static",
+
cflags: [
"-Wall",
"-Werror",
@@ -85,9 +87,8 @@
// Build system doesn't track transitive dependeicies for jni_libs, list all the dependencies
// explicitly.
jni_libs: [
- "libcgrouprc",
+ "liblog",
"libnativehelper_compat_libc++",
- "libvndksupport",
"libtetherutilsjni",
],
resource_dirs: [
diff --git a/packages/Tethering/jni/android_net_util_TetheringUtils.cpp b/packages/Tethering/jni/android_net_util_TetheringUtils.cpp
index 1cf8f98..54934406 100644
--- a/packages/Tethering/jni/android_net_util_TetheringUtils.cpp
+++ b/packages/Tethering/jni/android_net_util_TetheringUtils.cpp
@@ -16,123 +16,18 @@
#include <errno.h>
#include <error.h>
-#include <hidl/HidlSupport.h>
#include <jni.h>
#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedUtfChars.h>
-#include <linux/netfilter/nfnetlink.h>
-#include <linux/netlink.h>
#include <net/if.h>
#include <netinet/icmp6.h>
#include <sys/socket.h>
-#include <android-base/unique_fd.h>
-#include <android/hardware/tetheroffload/config/1.0/IOffloadConfig.h>
#define LOG_TAG "TetheringUtils"
-#include <utils/Log.h>
+#include <android/log.h>
namespace android {
-using hardware::hidl_handle;
-using hardware::hidl_string;
-using hardware::tetheroffload::config::V1_0::IOffloadConfig;
-
-namespace {
-
-inline const sockaddr * asSockaddr(const sockaddr_nl *nladdr) {
- return reinterpret_cast<const sockaddr *>(nladdr);
-}
-
-int conntrackSocket(unsigned groups) {
- base::unique_fd s(socket(AF_NETLINK, SOCK_DGRAM, NETLINK_NETFILTER));
- if (s.get() < 0) return -errno;
-
- const struct sockaddr_nl bind_addr = {
- .nl_family = AF_NETLINK,
- .nl_pad = 0,
- .nl_pid = 0,
- .nl_groups = groups,
- };
- if (bind(s.get(), asSockaddr(&bind_addr), sizeof(bind_addr)) != 0) {
- return -errno;
- }
-
- const struct sockaddr_nl kernel_addr = {
- .nl_family = AF_NETLINK,
- .nl_pad = 0,
- .nl_pid = 0,
- .nl_groups = groups,
- };
- if (connect(s.get(), asSockaddr(&kernel_addr), sizeof(kernel_addr)) != 0) {
- return -errno;
- }
-
- return s.release();
-}
-
-// Return a hidl_handle that owns the file descriptor owned by fd, and will
-// auto-close it (otherwise there would be double-close problems).
-//
-// Rely upon the compiler to eliminate the constexprs used for clarity.
-hidl_handle handleFromFileDescriptor(base::unique_fd fd) {
- hidl_handle h;
-
- static constexpr int kNumFds = 1;
- static constexpr int kNumInts = 0;
- native_handle_t *nh = native_handle_create(kNumFds, kNumInts);
- nh->data[0] = fd.release();
-
- static constexpr bool kTakeOwnership = true;
- h.setTo(nh, kTakeOwnership);
-
- return h;
-}
-
-} // namespace
-
-static jboolean android_net_util_configOffload(
- JNIEnv* /* env */) {
- sp<IOffloadConfig> configInterface = IOffloadConfig::getService();
- if (configInterface.get() == nullptr) {
- ALOGD("Could not find IOffloadConfig service.");
- return false;
- }
-
- // Per the IConfigOffload definition:
- //
- // fd1 A file descriptor bound to the following netlink groups
- // (NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY).
- //
- // fd2 A file descriptor bound to the following netlink groups
- // (NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY).
- base::unique_fd
- fd1(conntrackSocket(NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY)),
- fd2(conntrackSocket(NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY));
- if (fd1.get() < 0 || fd2.get() < 0) {
- ALOGE("Unable to create conntrack handles: %d/%s", errno, strerror(errno));
- return false;
- }
-
- hidl_handle h1(handleFromFileDescriptor(std::move(fd1))),
- h2(handleFromFileDescriptor(std::move(fd2)));
-
- bool rval(false);
- hidl_string msg;
- const auto status = configInterface->setHandles(h1, h2,
- [&rval, &msg](bool success, const hidl_string& errMsg) {
- rval = success;
- msg = errMsg;
- });
- if (!status.isOk() || !rval) {
- ALOGE("IOffloadConfig::setHandles() error: '%s' / '%s'",
- status.description().c_str(), msg.c_str());
- // If status is somehow not ok, make sure rval captures this too.
- rval = false;
- }
-
- return rval;
-}
-
static void android_net_util_setupRaSocket(JNIEnv *env, jobject clazz, jobject javaFd,
jint ifIndex)
{
@@ -229,7 +124,6 @@
*/
static const JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
- { "configOffload", "()Z", (void*) android_net_util_configOffload },
{ "setupRaSocket", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_util_setupRaSocket },
};
@@ -242,7 +136,7 @@
extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
JNIEnv *env;
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
- ALOGE("ERROR: GetEnv failed");
+ __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "ERROR: GetEnv failed");
return JNI_ERR;
}
diff --git a/packages/Tethering/src/android/net/util/TetheringUtils.java b/packages/Tethering/src/android/net/util/TetheringUtils.java
index fa543bd..5a6d5c1 100644
--- a/packages/Tethering/src/android/net/util/TetheringUtils.java
+++ b/packages/Tethering/src/android/net/util/TetheringUtils.java
@@ -24,14 +24,6 @@
* {@hide}
*/
public class TetheringUtils {
-
- /**
- * Offload management process need to know conntrack rules to support NAT, but it may not have
- * permission to create netlink netfilter sockets. Create two netlink netfilter sockets and
- * share them with offload management process.
- */
- public static native boolean configOffload();
-
/**
* Configures a socket for receiving ICMPv6 router solicitations and sending advertisements.
* @param fd the socket's {@link FileDescriptor}.
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
index 90b9d3f..b545717 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
@@ -18,19 +18,28 @@
import static android.net.util.TetheringUtils.uint16;
+import android.hardware.tetheroffload.config.V1_0.IOffloadConfig;
import android.hardware.tetheroffload.control.V1_0.IOffloadControl;
import android.hardware.tetheroffload.control.V1_0.ITetheringOffloadCallback;
import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate;
import android.hardware.tetheroffload.control.V1_0.NetworkProtocol;
import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent;
+import android.net.netlink.NetlinkSocket;
import android.net.util.SharedLog;
-import android.net.util.TetheringUtils;
+import android.net.util.SocketUtils;
import android.os.Handler;
+import android.os.NativeHandle;
import android.os.RemoteException;
+import android.system.ErrnoException;
+import android.system.Os;
import android.system.OsConstants;
import com.android.internal.annotations.VisibleForTesting;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.SocketAddress;
+import java.net.SocketException;
import java.util.ArrayList;
@@ -49,6 +58,10 @@
private static final String NO_INTERFACE_NAME = "";
private static final String NO_IPV4_ADDRESS = "";
private static final String NO_IPV4_GATEWAY = "";
+ // Reference kernel/uapi/linux/netfilter/nfnetlink_compat.h
+ private static final int NF_NETLINK_CONNTRACK_NEW = 1;
+ private static final int NF_NETLINK_CONNTRACK_UPDATE = 2;
+ private static final int NF_NETLINK_CONNTRACK_DESTROY = 4;
private final Handler mHandler;
private final SharedLog mLog;
@@ -121,9 +134,103 @@
return DEFAULT_TETHER_OFFLOAD_DISABLED;
}
- /** Configure offload management process. */
+ /**
+ * Offload management process need to know conntrack rules to support NAT, but it may not have
+ * permission to create netlink netfilter sockets. Create two netlink netfilter sockets and
+ * share them with offload management process.
+ */
public boolean initOffloadConfig() {
- return TetheringUtils.configOffload();
+ IOffloadConfig offloadConfig;
+ try {
+ offloadConfig = IOffloadConfig.getService();
+ } catch (RemoteException e) {
+ mLog.e("getIOffloadConfig error " + e);
+ return false;
+ }
+ if (offloadConfig == null) {
+ mLog.e("Could not find IOffloadConfig service");
+ return false;
+ }
+ // Per the IConfigOffload definition:
+ //
+ // h1 provides a file descriptor bound to the following netlink groups
+ // (NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY).
+ //
+ // h2 provides a file descriptor bound to the following netlink groups
+ // (NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY).
+ final NativeHandle h1 = createConntrackSocket(
+ NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY);
+ if (h1 == null) return false;
+
+ final NativeHandle h2 = createConntrackSocket(
+ NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY);
+ if (h2 == null) {
+ closeFdInNativeHandle(h1);
+ return false;
+ }
+
+ final CbResults results = new CbResults();
+ try {
+ offloadConfig.setHandles(h1, h2,
+ (boolean success, String errMsg) -> {
+ results.mSuccess = success;
+ results.mErrMsg = errMsg;
+ });
+ } catch (RemoteException e) {
+ record("initOffloadConfig, setHandles fail", e);
+ return false;
+ }
+ // Explicitly close FDs.
+ closeFdInNativeHandle(h1);
+ closeFdInNativeHandle(h2);
+
+ record("initOffloadConfig, setHandles results:", results);
+ return results.mSuccess;
+ }
+
+ private void closeFdInNativeHandle(final NativeHandle h) {
+ try {
+ h.close();
+ } catch (IOException | IllegalStateException e) {
+ // IllegalStateException means fd is already closed, do nothing here.
+ // Also nothing we can do if IOException.
+ }
+ }
+
+ private NativeHandle createConntrackSocket(final int groups) {
+ FileDescriptor fd;
+ try {
+ fd = NetlinkSocket.forProto(OsConstants.NETLINK_NETFILTER);
+ } catch (ErrnoException e) {
+ mLog.e("Unable to create conntrack socket " + e);
+ return null;
+ }
+
+ final SocketAddress sockAddr = SocketUtils.makeNetlinkSocketAddress(0, groups);
+ try {
+ Os.bind(fd, sockAddr);
+ } catch (ErrnoException | SocketException e) {
+ mLog.e("Unable to bind conntrack socket for groups " + groups + " error: " + e);
+ try {
+ SocketUtils.closeSocket(fd);
+ } catch (IOException ie) {
+ // Nothing we can do here
+ }
+ return null;
+ }
+ try {
+ Os.connect(fd, sockAddr);
+ } catch (ErrnoException | SocketException e) {
+ mLog.e("connect to kernel fail for groups " + groups + " error: " + e);
+ try {
+ SocketUtils.closeSocket(fd);
+ } catch (IOException ie) {
+ // Nothing we can do here
+ }
+ return null;
+ }
+
+ return new NativeHandle(fd, true);
}
/** Initialize the tethering offload HAL. */
diff --git a/packages/services/PacProcessor/src/com/android/pacprocessor/PacNative.java b/packages/services/PacProcessor/src/com/android/pacprocessor/PacNative.java
index c67fe9f..1e8109c 100644
--- a/packages/services/PacProcessor/src/com/android/pacprocessor/PacNative.java
+++ b/packages/services/PacProcessor/src/com/android/pacprocessor/PacNative.java
@@ -23,6 +23,8 @@
public class PacNative {
private static final String TAG = "PacProxy";
+ private static final PacNative sInstance = new PacNative();
+
private String mCurrentPac;
private boolean mIsActive;
@@ -39,10 +41,14 @@
System.loadLibrary("jni_pacprocessor");
}
- PacNative() {
+ private PacNative() {
}
+ public static PacNative getInstance() {
+ return sInstance;
+ }
+
public synchronized boolean startPacSupport() {
if (createV8ParserNativeLocked()) {
Log.e(TAG, "Unable to Create v8 Proxy Parser.");
diff --git a/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java b/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java
index 74391eb..b006d6e 100644
--- a/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java
+++ b/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java
@@ -31,43 +31,27 @@
public class PacService extends Service {
private static final String TAG = "PacService";
- private PacNative mPacNative;
- private ProxyServiceStub mStub;
+ private PacNative mPacNative = PacNative.getInstance();
+ private ProxyServiceStub mStub = new ProxyServiceStub();
@Override
public void onCreate() {
super.onCreate();
- if (mPacNative == null) {
- mPacNative = new PacNative();
- mStub = new ProxyServiceStub(mPacNative);
- }
+ mPacNative.startPacSupport();
}
@Override
public void onDestroy() {
+ mPacNative.stopPacSupport();
super.onDestroy();
- if (mPacNative != null) {
- mPacNative.stopPacSupport();
- mPacNative = null;
- mStub = null;
- }
}
@Override
public IBinder onBind(Intent intent) {
- if (mPacNative == null) {
- mPacNative = new PacNative();
- mStub = new ProxyServiceStub(mPacNative);
- }
return mStub;
}
- private static class ProxyServiceStub extends IProxyService.Stub {
- private final PacNative mPacNative;
-
- public ProxyServiceStub(PacNative pacNative) {
- mPacNative = pacNative;
- }
+ private class ProxyServiceStub extends IProxyService.Stub {
@Override
public String resolvePacFile(String host, String url) throws RemoteException {
@@ -102,20 +86,12 @@
@Override
public void startPacSystem() throws RemoteException {
- if (Binder.getCallingUid() != Process.SYSTEM_UID) {
- Log.e(TAG, "Only system user is allowed to call startPacSystem");
- throw new SecurityException();
- }
- mPacNative.startPacSupport();
+ //TODO: remove
}
@Override
public void stopPacSystem() throws RemoteException {
- if (Binder.getCallingUid() != Process.SYSTEM_UID) {
- Log.e(TAG, "Only system user is allowed to call stopPacSystem");
- throw new SecurityException();
- }
- mPacNative.stopPacSupport();
+ //TODO: remove
}
}
}
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index d047a3c..eec68dc 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -946,6 +946,15 @@
}
EventLog.writeEvent(EventLogTags.AM_FREEZE, pid, name);
+
+ // See above for why we're not taking mPhenotypeFlagLock here
+ if (mRandom.nextFloat() < mFreezerStatsdSampleRate) {
+ FrameworkStatsLog.write(FrameworkStatsLog.APP_FREEZE_CHANGED,
+ FrameworkStatsLog.APP_FREEZE_CHANGED__ACTION__FREEZE_APP,
+ pid,
+ name,
+ unfrozenDuration);
+ }
}
}
@@ -994,6 +1003,16 @@
}
EventLog.writeEvent(EventLogTags.AM_UNFREEZE, pid, name);
+
+ // See above for why we're not taking mPhenotypeFlagLock here
+ if (mRandom.nextFloat() < mFreezerStatsdSampleRate) {
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.APP_FREEZE_CHANGED,
+ FrameworkStatsLog.APP_FREEZE_CHANGED__ACTION__UNFREEZE_APP,
+ pid,
+ name,
+ frozenDuration);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
new file mode 100644
index 0000000..58c2707
--- /dev/null
+++ b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright 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.media;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ComponentInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
+/**
+ * Holds the media button receiver, and also provides helper methods around it.
+ */
+final class MediaButtonReceiverHolder {
+ public static final int COMPONENT_TYPE_INVALID = 0;
+ public static final int COMPONENT_TYPE_BROADCAST = 1;
+ public static final int COMPONENT_TYPE_ACTIVITY = 2;
+ public static final int COMPONENT_TYPE_SERVICE = 3;
+
+ @IntDef(value = {
+ COMPONENT_TYPE_INVALID,
+ COMPONENT_TYPE_BROADCAST,
+ COMPONENT_TYPE_ACTIVITY,
+ COMPONENT_TYPE_SERVICE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ComponentType {}
+
+ private static final String TAG = "PendingIntentHolder";
+ private static final boolean DEBUG_KEY_EVENT = MediaSessionService.DEBUG_KEY_EVENT;
+ private static final String COMPONENT_NAME_USER_ID_DELIM = ",";
+
+ private final int mUserId;
+ private final PendingIntent mPendingIntent;
+ private final ComponentName mComponentName;
+ private final String mPackageName;
+ @ComponentType
+ private final int mComponentType;
+
+ /**
+ * Unflatten from string which is previously flattened string via flattenToString().
+ * <p>
+ * It's used to store and restore media button receiver across the boot, by keeping the intent's
+ * component name to the persistent storage.
+ *
+ * @param mediaButtonReceiverInfo previously flattened string via flattenToString()
+ * @return new instance if the string was valid. {@code null} otherwise.
+ */
+ public static MediaButtonReceiverHolder unflattenFromString(
+ Context context, String mediaButtonReceiverInfo) {
+ if (TextUtils.isEmpty(mediaButtonReceiverInfo)) {
+ return null;
+ }
+ String[] tokens = mediaButtonReceiverInfo.split(COMPONENT_NAME_USER_ID_DELIM);
+ if (tokens == null || (tokens.length != 2 && tokens.length != 3)) {
+ return null;
+ }
+ ComponentName componentName = ComponentName.unflattenFromString(tokens[0]);
+ int userId = Integer.parseInt(tokens[1]);
+ // Guess component type if the OS version is updated from the older version.
+ int componentType = (tokens.length == 3)
+ ? Integer.parseInt(tokens[2])
+ : getComponentType(context, componentName);
+ return new MediaButtonReceiverHolder(userId, null, componentName, componentType);
+ }
+
+ /**
+ * Creates a new instance.
+ *
+ * @param context context
+ * @param userId userId
+ * @param pendingIntent pending intent
+ * @return Can be {@code null} if pending intent was null.
+ */
+ public static MediaButtonReceiverHolder create(Context context, int userId,
+ PendingIntent pendingIntent) {
+ if (pendingIntent == null) {
+ return null;
+ }
+ ComponentName componentName = (pendingIntent != null && pendingIntent.getIntent() != null)
+ ? pendingIntent.getIntent().getComponent() : null;
+ if (componentName != null) {
+ // Explicit intent, where component name is in the PendingIntent.
+ return new MediaButtonReceiverHolder(userId, pendingIntent, componentName,
+ getComponentType(context, componentName));
+ }
+
+ // Implicit intent, where component name isn't in the PendingIntent. Try resolve.
+ PackageManager pm = context.getPackageManager();
+ Intent intent = pendingIntent.getIntent();
+ if ((componentName = resolveImplicitServiceIntent(pm, intent)) != null) {
+ return new MediaButtonReceiverHolder(
+ userId, pendingIntent, componentName, COMPONENT_TYPE_SERVICE);
+ } else if ((componentName = resolveManifestDeclaredBroadcastReceiverIntent(pm, intent))
+ != null) {
+ return new MediaButtonReceiverHolder(
+ userId, pendingIntent, componentName, COMPONENT_TYPE_BROADCAST);
+ } else if ((componentName = resolveImplicitActivityIntent(pm, intent)) != null) {
+ return new MediaButtonReceiverHolder(
+ userId, pendingIntent, componentName, COMPONENT_TYPE_ACTIVITY);
+ }
+
+ // Failed to resolve target component for the pending intent. It's unlikely to be usable.
+ // However, the pending intent would be still used, just to follow the legacy behavior.
+ Log.w(TAG, "Unresolvable implicit intent is set, pi=" + pendingIntent);
+ String packageName = (pendingIntent != null && pendingIntent.getIntent() != null)
+ ? pendingIntent.getIntent().getPackage() : null;
+ return new MediaButtonReceiverHolder(userId, pendingIntent,
+ packageName != null ? packageName : "");
+ }
+
+ private MediaButtonReceiverHolder(int userId, PendingIntent pendingIntent,
+ ComponentName componentName, @ComponentType int componentType) {
+ mUserId = userId;
+ mPendingIntent = pendingIntent;
+ mComponentName = componentName;
+ mPackageName = componentName.getPackageName();
+ mComponentType = componentType;
+ }
+
+ private MediaButtonReceiverHolder(int userId, PendingIntent pendingIntent, String packageName) {
+ mUserId = userId;
+ mPendingIntent = pendingIntent;
+ mComponentName = null;
+ mPackageName = packageName;
+ mComponentType = COMPONENT_TYPE_INVALID;
+ }
+
+ /**
+ * @return the user id
+ */
+ public int getUserId() {
+ return mUserId;
+ }
+
+ /**
+ * @return package name that the media button receiver would be sent to.
+ */
+ @NonNull
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Sends the media key event to the media button receiver.
+ * <p>
+ * This prioritizes using use pending intent for sending media key event.
+ *
+ * @param context context to be used to call PendingIntent#send
+ * @param keyEvent keyEvent to send
+ * @param resultCode result code to be used to call PendingIntent#send
+ * Ignored if there's no valid pending intent.
+ * @param onFinishedListener callback to be used to get result of PendingIntent#send.
+ * Ignored if there's no valid pending intent.
+ * @param handler handler to be used to call onFinishedListener
+ * Ignored if there's no valid pending intent.
+ * @see PendingIntent#send(Context, int, Intent, PendingIntent.OnFinished, Handler)
+ */
+ public boolean send(Context context, KeyEvent keyEvent, String callingPackageName,
+ int resultCode, PendingIntent.OnFinished onFinishedListener, Handler handler) {
+ Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+ mediaButtonIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
+ // TODO: Find a way to also send PID/UID in secure way.
+ mediaButtonIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, callingPackageName);
+
+ if (mPendingIntent != null) {
+ if (DEBUG_KEY_EVENT) {
+ Log.d(TAG, "Sending " + keyEvent + " to the last known PendingIntent "
+ + mPendingIntent);
+ }
+ try {
+ mPendingIntent.send(
+ context, resultCode, mediaButtonIntent, onFinishedListener, handler);
+ } catch (PendingIntent.CanceledException e) {
+ Log.w(TAG, "Error sending key event to media button receiver " + mPendingIntent, e);
+ return false;
+ }
+ } else if (mComponentName != null) {
+ if (DEBUG_KEY_EVENT) {
+ Log.d(TAG, "Sending " + keyEvent + " to the restored intent "
+ + mComponentName + ", type=" + mComponentType);
+ }
+ mediaButtonIntent.setComponent(mComponentName);
+ UserHandle userHandle = UserHandle.of(mUserId);
+ try {
+ switch (mComponentType) {
+ case COMPONENT_TYPE_ACTIVITY:
+ context.startActivityAsUser(mediaButtonIntent, userHandle);
+ break;
+ case COMPONENT_TYPE_SERVICE:
+ context.startForegroundServiceAsUser(mediaButtonIntent,
+ userHandle);
+ break;
+ default:
+ // Legacy behavior for other cases.
+ context.sendBroadcastAsUser(mediaButtonIntent, userHandle);
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "Error sending media button to the restored intent "
+ + mComponentName + ", type=" + mComponentType, e);
+ return false;
+ }
+ } else {
+ // Leave log, just in case.
+ Log.e(TAG, "Shouldn't be happen -- pending intent or component name must be set");
+ return false;
+ }
+ return true;
+ }
+
+
+ @Override
+ public String toString() {
+ if (mPendingIntent != null) {
+ return "MBR {pi=" + mPendingIntent + ", type=" + mComponentType + "}";
+ }
+ return "Restored MBR {component=" + mComponentName + ", type=" + mComponentType + "}";
+ }
+
+ /**
+ * @return flattened string. Can be empty string if the MBR is created with implicit intent.
+ */
+ public String flattenToString() {
+ if (mComponentName == null) {
+ // We don't know which component would receive the key event.
+ return "";
+ }
+ return String.join(COMPONENT_NAME_USER_ID_DELIM,
+ mComponentName.toString(),
+ String.valueOf(mUserId),
+ String.valueOf(mComponentType));
+ }
+
+ /**
+ * Gets the type of the component
+ *
+ * @param context context
+ * @param componentName component name
+ * @return A component type
+ */
+ @ComponentType
+ private static int getComponentType(Context context, ComponentName componentName) {
+ if (componentName == null) {
+ return COMPONENT_TYPE_INVALID;
+ }
+ PackageManager pm = context.getPackageManager();
+ try {
+ ActivityInfo activityInfo = pm.getActivityInfo(componentName,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.GET_ACTIVITIES);
+ if (activityInfo != null) {
+ return COMPONENT_TYPE_ACTIVITY;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+ try {
+ ServiceInfo serviceInfo = pm.getServiceInfo(componentName,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.GET_SERVICES);
+ if (serviceInfo != null) {
+ return COMPONENT_TYPE_SERVICE;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+ // Pick legacy behavior for BroadcastReceiver or unknown.
+ return COMPONENT_TYPE_BROADCAST;
+ }
+
+ private static ComponentName resolveImplicitServiceIntent(PackageManager pm, Intent intent) {
+ // Flag explanations.
+ // - MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE:
+ // filter apps regardless of the phone's locked/unlocked state.
+ // - GET_SERVICES: Return service
+ return createComponentName(pm.resolveService(intent,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.GET_SERVICES));
+ }
+
+ private static ComponentName resolveManifestDeclaredBroadcastReceiverIntent(
+ PackageManager pm, Intent intent) {
+ // Flag explanations.
+ // - MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE:
+ // filter apps regardless of the phone's locked/unlocked state.
+ List<ResolveInfo> resolveInfos = pm.queryBroadcastReceivers(intent,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
+ return (resolveInfos != null && !resolveInfos.isEmpty())
+ ? createComponentName(resolveInfos.get(0)) : null;
+ }
+
+ private static ComponentName resolveImplicitActivityIntent(PackageManager pm, Intent intent) {
+ // Flag explanations.
+ // - MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE:
+ // Filter apps regardless of the phone's locked/unlocked state.
+ // - MATCH_DEFAULT_ONLY:
+ // Implicit intent receiver should be set as default. Only needed for activity.
+ // - GET_ACTIVITIES: Return activity
+ return createComponentName(pm.resolveActivity(intent,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.MATCH_DEFAULT_ONLY
+ | PackageManager.GET_ACTIVITIES));
+ }
+
+ private static ComponentName createComponentName(ResolveInfo resolveInfo) {
+ if (resolveInfo == null) {
+ return null;
+ }
+ ComponentInfo componentInfo;
+ // Code borrowed from ResolveInfo#getComponentInfo().
+ if (resolveInfo.activityInfo != null) {
+ componentInfo = resolveInfo.activityInfo;
+ } else if (resolveInfo.serviceInfo != null) {
+ componentInfo = resolveInfo.serviceInfo;
+ } else {
+ // We're not interested in content provider.
+ return null;
+ }
+ // Code borrowed from ComponentInfo#getComponentName().
+ try {
+ return new ComponentName(componentInfo.packageName, componentInfo.name);
+ } catch (IllegalArgumentException | NullPointerException e) {
+ // This may be happen if resolveActivity() end up with matching multiple activities.
+ // see PackageManager#resolveActivity().
+ return null;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
index d08fb71..dd536ec 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
@@ -411,6 +411,12 @@
mActiveConnection.dispose();
mActiveConnection = null;
setAndNotifyProviderState(null);
+ synchronized (mLock) {
+ for (RoutingSessionInfo sessionInfo : mSessionInfos) {
+ mCallback.onSessionReleased(this, sessionInfo);
+ }
+ mSessionInfos.clear();
+ }
}
}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 7bcbcd4..9f47b34 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -127,7 +127,7 @@
new ArrayList<>();
private long mFlags;
- private PendingIntent mMediaButtonReceiver;
+ private MediaButtonReceiverHolder mMediaButtonReceiverHolder;
private PendingIntent mLaunchIntent;
// TransportPerformer fields
@@ -220,8 +220,8 @@
*
* @return The pending intent set by the app or null.
*/
- public PendingIntent getMediaButtonReceiver() {
- return mMediaButtonReceiver;
+ public MediaButtonReceiverHolder getMediaButtonReceiver() {
+ return mMediaButtonReceiverHolder;
}
/**
@@ -471,7 +471,7 @@
+ ", userId=" + mUserId);
pw.println(indent + "package=" + mPackageName);
pw.println(indent + "launchIntent=" + mLaunchIntent);
- pw.println(indent + "mediaButtonReceiver=" + mMediaButtonReceiver);
+ pw.println(indent + "mediaButtonReceiver=" + mMediaButtonReceiverHolder);
pw.println(indent + "active=" + mIsActive);
pw.println(indent + "flags=" + mFlags);
pw.println(indent + "rating type=" + mRatingType);
@@ -833,12 +833,14 @@
@Override
public void setMediaButtonReceiver(PendingIntent pi) throws RemoteException {
- if ((mPolicies & SessionPolicyProvider.SESSION_POLICY_IGNORE_BUTTON_RECEIVER) == 1) {
- return;
- }
- mMediaButtonReceiver = pi;
final long token = Binder.clearCallingIdentity();
try {
+ if ((mPolicies & SessionPolicyProvider.SESSION_POLICY_IGNORE_BUTTON_RECEIVER)
+ != 0) {
+ return;
+ }
+ mMediaButtonReceiverHolder =
+ MediaButtonReceiverHolder.create(mContext, mUserId, pi);
mService.onMediaButtonReceiverChanged(MediaSessionRecord.this);
} finally {
Binder.restoreCallingIdentity(token);
@@ -1529,5 +1531,4 @@
msg.sendToTarget();
}
}
-
}
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 88b884e..7ffac06 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -18,22 +18,17 @@
import static android.os.UserHandle.USER_ALL;
-import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.INotificationManager;
import android.app.KeyguardManager;
import android.app.PendingIntent;
-import android.app.PendingIntent.CanceledException;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ParceledListSlice;
-import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.media.AudioManager;
@@ -99,7 +94,7 @@
private static final String TAG = "MediaSessionService";
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
// Leave log for key event always.
- private static final boolean DEBUG_KEY_EVENT = true;
+ static final boolean DEBUG_KEY_EVENT = true;
private static final int WAKELOCK_TIMEOUT = 5000;
private static final int MEDIA_KEY_LISTENER_TIMEOUT = 1000;
@@ -760,12 +755,6 @@
* <p>The contents of this object is guarded by {@link #mLock}.
*/
final class FullUserRecord implements MediaSessionStack.OnMediaButtonSessionChangedListener {
- public static final int COMPONENT_TYPE_INVALID = 0;
- public static final int COMPONENT_TYPE_BROADCAST = 1;
- public static final int COMPONENT_TYPE_ACTIVITY = 2;
- public static final int COMPONENT_TYPE_SERVICE = 3;
- private static final String COMPONENT_NAME_USER_ID_DELIM = ",";
-
private final int mFullUserId;
private final MediaSessionStack mPriorityStack;
private final HashMap<IBinder, OnMediaKeyEventDispatchedListenerRecord>
@@ -774,10 +763,7 @@
mOnMediaKeyEventSessionChangedListeners = new HashMap<>();
private final SparseIntArray mUidToSessionCount = new SparseIntArray();
- private PendingIntent mLastMediaButtonReceiver;
- private ComponentName mRestoredMediaButtonReceiver;
- private int mRestoredMediaButtonReceiverComponentType;
- private int mRestoredMediaButtonReceiverUserId;
+ private MediaButtonReceiverHolder mLastMediaButtonReceiverHolder;
private IOnVolumeKeyLongPressListener mOnVolumeKeyLongPressListener;
private int mOnVolumeKeyLongPressListenerUid;
@@ -794,21 +780,9 @@
// Restore the remembered media button receiver before the boot.
String mediaButtonReceiverInfo = Settings.Secure.getStringForUser(mContentResolver,
Settings.System.MEDIA_BUTTON_RECEIVER, mFullUserId);
- if (mediaButtonReceiverInfo == null) {
- return;
- }
- String[] tokens = mediaButtonReceiverInfo.split(COMPONENT_NAME_USER_ID_DELIM);
- if (tokens == null || (tokens.length != 2 && tokens.length != 3)) {
- return;
- }
- mRestoredMediaButtonReceiver = ComponentName.unflattenFromString(tokens[0]);
- mRestoredMediaButtonReceiverUserId = Integer.parseInt(tokens[1]);
- if (tokens.length == 3) {
- mRestoredMediaButtonReceiverComponentType = Integer.parseInt(tokens[2]);
- } else {
- mRestoredMediaButtonReceiverComponentType =
- getComponentType(mRestoredMediaButtonReceiver);
- }
+ mLastMediaButtonReceiverHolder =
+ MediaButtonReceiverHolder.unflattenFromString(
+ mContext, mediaButtonReceiverInfo);
}
public void destroySessionsForUserLocked(int userId) {
@@ -892,10 +866,7 @@
: mOnMediaKeyEventSessionChangedListeners.values()) {
pw.println(indent + " from " + getCallingPackageName(cr.uid));
}
- pw.println(indent + "Last MediaButtonReceiver: " + mLastMediaButtonReceiver);
- pw.println(indent + "Restored MediaButtonReceiver: " + mRestoredMediaButtonReceiver);
- pw.println(indent + "Restored MediaButtonReceiverComponentType: "
- + mRestoredMediaButtonReceiverComponentType);
+ pw.println(indent + "Last MediaButtonReceiver: " + mLastMediaButtonReceiverHolder);
mPriorityStack.dump(pw, indent);
}
@@ -924,25 +895,12 @@
return;
}
MediaSessionRecord sessionRecord = (MediaSessionRecord) record;
- PendingIntent receiver = sessionRecord.getMediaButtonReceiver();
- mLastMediaButtonReceiver = receiver;
- mRestoredMediaButtonReceiver = null;
- mRestoredMediaButtonReceiverComponentType = COMPONENT_TYPE_INVALID;
-
- String mediaButtonReceiverInfo = "";
- if (receiver != null) {
- ComponentName component = receiver.getIntent().getComponent();
- if (component != null
- && record.getPackageName().equals(component.getPackageName())) {
- String componentName = component.flattenToString();
- int componentType = getComponentType(component);
- mediaButtonReceiverInfo = String.join(COMPONENT_NAME_USER_ID_DELIM,
- componentName, String.valueOf(record.getUserId()),
- String.valueOf(componentType));
- }
- }
+ mLastMediaButtonReceiverHolder = sessionRecord.getMediaButtonReceiver();
+ String mediaButtonReceiverInfo = (mLastMediaButtonReceiverHolder == null)
+ ? "" : mLastMediaButtonReceiverHolder.flattenToString();
Settings.Secure.putStringForUser(mContentResolver,
- Settings.System.MEDIA_BUTTON_RECEIVER, mediaButtonReceiverInfo,
+ Settings.System.MEDIA_BUTTON_RECEIVER,
+ mediaButtonReceiverInfo,
mFullUserId);
}
@@ -958,15 +916,9 @@
} else {
// TODO(jaewan): Implement
}
- } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) {
- callback.onMediaKeyEventSessionChanged(
- mCurrentFullUserRecord.mLastMediaButtonReceiver
- .getIntent().getComponent().getPackageName(),
- null);
- } else if (mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) {
- callback.onMediaKeyEventSessionChanged(
- mCurrentFullUserRecord.mRestoredMediaButtonReceiver.getPackageName(),
- null);
+ } else if (mCurrentFullUserRecord.mLastMediaButtonReceiverHolder != null) {
+ String packageName = mLastMediaButtonReceiverHolder.getPackageName();
+ callback.onMediaKeyEventSessionChanged(packageName, null);
}
} catch (RemoteException e) {
Log.w(TAG, "Failed to pushAddressedPlayerChangedLocked", e);
@@ -985,35 +937,6 @@
? mGlobalPrioritySession : mPriorityStack.getMediaButtonSession();
}
- private int getComponentType(@Nullable ComponentName componentName) {
- if (componentName == null) {
- return COMPONENT_TYPE_INVALID;
- }
- PackageManager pm = mContext.getPackageManager();
- try {
- ActivityInfo activityInfo = pm.getActivityInfo(componentName,
- PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
- | PackageManager.GET_ACTIVITIES);
- if (activityInfo != null) {
- return COMPONENT_TYPE_ACTIVITY;
- }
- } catch (NameNotFoundException e) {
- }
- try {
- ServiceInfo serviceInfo = pm.getServiceInfo(componentName,
- PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
- | PackageManager.GET_SERVICES);
- if (serviceInfo != null) {
- return COMPONENT_TYPE_SERVICE;
- }
- } catch (NameNotFoundException e) {
- }
- // Pick legacy behavior for BroadcastReceiver or unknown.
- return COMPONENT_TYPE_BROADCAST;
- }
-
final class OnMediaKeyEventDispatchedListenerRecord implements IBinder.DeathRecipient {
public final IOnMediaKeyEventDispatchedListener callback;
public final int uid;
@@ -2166,79 +2089,31 @@
} catch (RemoteException e) {
Log.w(TAG, "Failed to send callback", e);
}
- } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null
- || mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) {
+ } else if (mCurrentFullUserRecord.mLastMediaButtonReceiverHolder != null) {
if (needWakeLock) {
mKeyEventReceiver.acquireWakeLockLocked();
}
- Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
- mediaButtonIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
- // TODO: Find a way to also send PID/UID in secure way.
- String callerPackageName =
+ String callingPackageName =
(asSystemService) ? mContext.getPackageName() : packageName;
- mediaButtonIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, callerPackageName);
- try {
- if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) {
- PendingIntent receiver = mCurrentFullUserRecord.mLastMediaButtonReceiver;
- if (DEBUG_KEY_EVENT) {
- Log.d(TAG, "Sending " + keyEvent
- + " to the last known PendingIntent " + receiver);
- }
- receiver.send(mContext,
- needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
- mediaButtonIntent, mKeyEventReceiver, mHandler);
- ComponentName componentName = mCurrentFullUserRecord
- .mLastMediaButtonReceiver.getIntent().getComponent();
- if (componentName != null) {
- for (FullUserRecord.OnMediaKeyEventDispatchedListenerRecord cr
- : mCurrentFullUserRecord
- .mOnMediaKeyEventDispatchedListeners.values()) {
- cr.callback.onMediaKeyEventDispatched(keyEvent,
- componentName.getPackageName(), null);
- }
- }
- } else {
- ComponentName receiver =
- mCurrentFullUserRecord.mRestoredMediaButtonReceiver;
- int componentType = mCurrentFullUserRecord
- .mRestoredMediaButtonReceiverComponentType;
- UserHandle userHandle = UserHandle.of(mCurrentFullUserRecord
- .mRestoredMediaButtonReceiverUserId);
- if (DEBUG_KEY_EVENT) {
- Log.d(TAG, "Sending " + keyEvent + " to the restored intent "
- + receiver + ", type=" + componentType);
- }
- mediaButtonIntent.setComponent(receiver);
+
+ MediaButtonReceiverHolder mediaButtonReceiverHolder =
+ mCurrentFullUserRecord.mLastMediaButtonReceiverHolder;
+
+ boolean sent = mediaButtonReceiverHolder.send(
+ mContext, keyEvent, callingPackageName,
+ needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1, mKeyEventReceiver,
+ mHandler);
+ if (sent) {
+ String pkgName = mediaButtonReceiverHolder.getPackageName();
+ for (FullUserRecord.OnMediaKeyEventDispatchedListenerRecord cr
+ : mCurrentFullUserRecord
+ .mOnMediaKeyEventDispatchedListeners.values()) {
try {
- switch (componentType) {
- case FullUserRecord.COMPONENT_TYPE_ACTIVITY:
- mContext.startActivityAsUser(mediaButtonIntent, userHandle);
- break;
- case FullUserRecord.COMPONENT_TYPE_SERVICE:
- mContext.startForegroundServiceAsUser(mediaButtonIntent,
- userHandle);
- break;
- default:
- // Legacy behavior for other cases.
- mContext.sendBroadcastAsUser(mediaButtonIntent, userHandle);
- }
- } catch (Exception e) {
- Log.w(TAG, "Error sending media button to the restored intent "
- + receiver + ", type=" + componentType, e);
- }
- for (FullUserRecord.OnMediaKeyEventDispatchedListenerRecord cr
- : mCurrentFullUserRecord
- .mOnMediaKeyEventDispatchedListeners.values()) {
- cr.callback.onMediaKeyEventDispatched(keyEvent,
- receiver.getPackageName(), null);
+ cr.callback.onMediaKeyEventDispatched(keyEvent, pkgName, null);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed notify key event dispatch, uid=" + cr.uid, e);
}
}
- } catch (CanceledException e) {
- Log.i(TAG, "Error sending key event to media button receiver "
- + mCurrentFullUserRecord.mLastMediaButtonReceiver, e);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to send callback", e);
}
}
}
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index 8c13b5b..d629b54 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -33,7 +33,6 @@
import android.content.pm.parsing.ComponentParseUtils.ParsedIntentInfo;
import android.content.pm.parsing.ComponentParseUtils.ParsedProvider;
import android.content.pm.parsing.ComponentParseUtils.ParsedService;
-import android.net.Uri;
import android.os.Binder;
import android.os.Process;
import android.os.Trace;
@@ -87,10 +86,10 @@
private final SparseSetArray<Integer> mQueriesViaPackage = new SparseSetArray<>();
/**
- * A mapping from the set of App IDs that query others via intent to the list
- * of packages that the intents resolve to.
+ * A mapping from the set of App IDs that query others via component match to the list
+ * of packages that the they resolve to.
*/
- private final SparseSetArray<Integer> mQueriesViaIntent = new SparseSetArray<>();
+ private final SparseSetArray<Integer> mQueriesViaComponent = new SparseSetArray<>();
/**
* A set of App IDs that are always queryable by any package, regardless of their manifest
@@ -206,16 +205,19 @@
}
/** Returns true if the querying package may query for the potential target package */
- private static boolean canQueryViaIntent(AndroidPackage querying,
+ private static boolean canQueryViaComponents(AndroidPackage querying,
AndroidPackage potentialTarget) {
- if (querying.getQueriesIntents() == null) {
- return false;
- }
- for (Intent intent : querying.getQueriesIntents()) {
- if (matches(intent, potentialTarget)) {
- return true;
+ if (querying.getQueriesIntents() != null) {
+ for (Intent intent : querying.getQueriesIntents()) {
+ if (matchesIntentFilters(intent, potentialTarget)) {
+ return true;
+ }
}
}
+ if (querying.getQueriesProviders() != null
+ && matchesProviders(querying.getQueriesProviders(), potentialTarget)) {
+ return true;
+ }
return false;
}
@@ -238,24 +240,25 @@
return false;
}
- private static boolean matches(Intent intent, AndroidPackage potentialTarget) {
+ private static boolean matchesProviders(
+ Set<String> queriesAuthorities, AndroidPackage potentialTarget) {
for (int p = ArrayUtils.size(potentialTarget.getProviders()) - 1; p >= 0; p--) {
ParsedProvider provider = potentialTarget.getProviders().get(p);
if (!provider.isExported()) {
continue;
}
- final Uri data = intent.getData();
- if (!"content".equalsIgnoreCase(intent.getScheme()) || data == null
- || provider.getAuthority() == null) {
- continue;
- }
- StringTokenizer authorities = new StringTokenizer(provider.getAuthority(), ";", false);
+ StringTokenizer authorities = new StringTokenizer(provider.getAuthority(), ";",
+ false);
while (authorities.hasMoreElements()) {
- if (Objects.equals(authorities.nextElement(), data.getAuthority())) {
+ if (queriesAuthorities.contains(authorities.nextToken())) {
return true;
}
}
}
+ return false;
+ }
+
+ private static boolean matchesIntentFilters(Intent intent, AndroidPackage potentialTarget) {
for (int s = ArrayUtils.size(potentialTarget.getServices()) - 1; s >= 0; s--) {
ParsedService service = potentialTarget.getServices().get(s);
if (!service.exported) {
@@ -368,8 +371,8 @@
final AndroidPackage existingPkg = existingSetting.pkg;
// let's evaluate the ability of already added packages to see this new package
if (!newIsForceQueryable) {
- if (canQueryViaIntent(existingPkg, newPkg)) {
- mQueriesViaIntent.add(existingSetting.appId, newPkgSetting.appId);
+ if (canQueryViaComponents(existingPkg, newPkg)) {
+ mQueriesViaComponent.add(existingSetting.appId, newPkgSetting.appId);
}
if (canQueryViaPackage(existingPkg, newPkg)
|| canQueryAsInstaller(existingSetting, newPkg)) {
@@ -378,8 +381,8 @@
}
// now we'll evaluate our new package's ability to see existing packages
if (!mForceQueryable.contains(existingSetting.appId)) {
- if (canQueryViaIntent(newPkg, existingPkg)) {
- mQueriesViaIntent.add(newPkgSetting.appId, existingSetting.appId);
+ if (canQueryViaComponents(newPkg, existingPkg)) {
+ mQueriesViaComponent.add(newPkgSetting.appId, existingSetting.appId);
}
if (canQueryViaPackage(newPkg, existingPkg)
|| canQueryAsInstaller(newPkgSetting, existingPkg)) {
@@ -427,9 +430,9 @@
}
}
- mQueriesViaIntent.remove(setting.appId);
- for (int i = mQueriesViaIntent.size() - 1; i >= 0; i--) {
- mQueriesViaIntent.remove(mQueriesViaIntent.keyAt(i), setting.appId);
+ mQueriesViaComponent.remove(setting.appId);
+ for (int i = mQueriesViaComponent.size() - 1; i >= 0; i--) {
+ mQueriesViaComponent.remove(mQueriesViaComponent.keyAt(i), setting.appId);
}
mQueriesViaPackage.remove(setting.appId);
for (int i = mQueriesViaPackage.size() - 1; i >= 0; i--) {
@@ -594,10 +597,10 @@
Trace.endSection();
}
try {
- Trace.beginSection("mQueriesViaIntent");
- if (mQueriesViaIntent.contains(callingAppId, targetAppId)) {
+ Trace.beginSection("mQueriesViaComponent");
+ if (mQueriesViaComponent.contains(callingAppId, targetAppId)) {
if (DEBUG_LOGGING) {
- log(callingSetting, targetPkgSetting, "queries intent");
+ log(callingSetting, targetPkgSetting, "queries component");
}
return false;
}
@@ -732,7 +735,7 @@
pw.println(" queries via package name:");
dumpQueriesMap(pw, filteringAppId, mQueriesViaPackage, " ", expandPackages);
pw.println(" queries via intent:");
- dumpQueriesMap(pw, filteringAppId, mQueriesViaIntent, " ", expandPackages);
+ dumpQueriesMap(pw, filteringAppId, mQueriesViaComponent, " ", expandPackages);
pw.println(" queryable via interaction:");
for (int user : users) {
pw.append(" User ").append(Integer.toString(user)).println(":");
diff --git a/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java b/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java
index e77839c..524ae54 100644
--- a/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java
+++ b/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java
@@ -224,6 +224,13 @@
}
/**
+ * Deletes all device-encrypted apex data snapshots for the given rollback id.
+ */
+ public void destroyApexDeSnapshots(int rollbackId) {
+ mApexManager.destroyDeSnapshots(rollbackId);
+ }
+
+ /**
* Commits the pending backups and restores for a given {@code userId} and {@code rollback}. If
* the rollback has a pending backup, it is updated with a mapping from {@code userId} to inode
* of the CE user data snapshot.
diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java
index b5da1c2..5abd9f0 100644
--- a/services/core/java/com/android/server/rollback/Rollback.java
+++ b/services/core/java/com/android/server/rollback/Rollback.java
@@ -44,7 +44,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.pm.ApexManager;
import java.io.File;
import java.io.IOException;
@@ -671,7 +670,7 @@
}
}
if (containsApex) {
- ApexManager.getInstance().destroyDeSnapshots(info.getRollbackId());
+ dataHelper.destroyApexDeSnapshots(info.getRollbackId());
}
RollbackStore.deleteRollback(this);
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index 8c54fa9..88de340 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -87,11 +87,15 @@
private static final long SYSTEM_CLOCK_PARANOIA_THRESHOLD_MILLIS = 2 * 1000;
/**
- * The number of previous telephony suggestions to keep for each ID (for use during debugging).
+ * The number of suggestions to keep. These are logged in bug reports to assist when debugging
+ * issues with detection.
*/
- private static final int KEEP_SUGGESTION_HISTORY_SIZE = 30;
+ private static final int KEEP_SUGGESTION_HISTORY_SIZE = 10;
- // A log for changes made to the system clock and why.
+ /**
+ * A log that records the decisions / decision metadata that affected the device's system clock
+ * time. This is logged in bug reports to assist with debugging issues with detection.
+ */
@NonNull
private final LocalLog mTimeChangesLog = new LocalLog(30, false /* useLocalTimestamps */);
@@ -140,7 +144,18 @@
if (!validateSuggestionTime(timeSuggestion.getUtcTime(), timeSuggestion)) {
return;
}
- mLastNetworkSuggestion.set(timeSuggestion);
+
+ // The caller submits suggestions with the best available information when there are network
+ // changes. The best available information may have been cached and if they were all stored
+ // this would lead to duplicates showing up in the suggestion history. The suggestions may
+ // be made for different reasons but there is not a significant benefit to storing the same
+ // suggestion information again. doAutoTimeDetection() should still be called: this ensures
+ // the suggestion and device state are always re-evaluated, which might produce a different
+ // detected time if, for example, the age of all suggestions are considered.
+ NetworkTimeSuggestion lastNetworkSuggestion = mLastNetworkSuggestion.get();
+ if (lastNetworkSuggestion == null || !lastNetworkSuggestion.equals(timeSuggestion)) {
+ mLastNetworkSuggestion.set(timeSuggestion);
+ }
// Now perform auto time detection. The new suggestion may be used to modify the system
// clock.
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
index 652dbe15..cc33fb0 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
@@ -161,16 +161,17 @@
public static final int TELEPHONY_SCORE_USAGE_THRESHOLD = TELEPHONY_SCORE_MEDIUM;
/**
- * The number of previous telephony suggestions to keep for each ID (for use during debugging).
+ * The number of suggestions to keep. These are logged in bug reports to assist when debugging
+ * issues with detection.
*/
- private static final int KEEP_TELEPHONY_SUGGESTION_HISTORY_SIZE = 30;
+ private static final int KEEP_SUGGESTION_HISTORY_SIZE = 10;
@NonNull
private final Callback mCallback;
/**
- * A log that records the decisions / decision metadata that affected the device's time zone
- * (for use during debugging).
+ * A log that records the decisions / decision metadata that affected the device's time zone.
+ * This is logged in bug reports to assist with debugging issues with detection.
*/
@NonNull
private final LocalLog mTimeZoneChangesLog = new LocalLog(30, false /* useLocalTimestamps */);
@@ -182,8 +183,7 @@
*/
@GuardedBy("this")
private ArrayMapWithHistory<Integer, QualifiedTelephonyTimeZoneSuggestion>
- mSuggestionBySlotIndex =
- new ArrayMapWithHistory<>(KEEP_TELEPHONY_SUGGESTION_HISTORY_SIZE);
+ mSuggestionBySlotIndex = new ArrayMapWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
/**
* Creates a new instance of {@link TimeZoneDetectorStrategyImpl}.
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 2fdcbc90..2196d89 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -3323,6 +3323,14 @@
* @return {@code true} if the top activity looks like it belongs to {@param userId}.
*/
private void taskTopActivityIsUser(Task task, @UserIdInt int userId) {
+ // TODO(b/80414790): having utilities to loop for all leaf tasks from caller vs. checking
+ // leaf tasks here.
+ if (!task.isLeafTask()) {
+ // No op if not a leaf task since we don't want to report root tasks to
+ // TaskStackListeners.
+ return;
+ }
+
// To handle the case that work app is in the task but just is not the top one.
final ActivityRecord activityRecord = task.getTopNonFinishingActivity();
final ActivityRecord resultTo = (activityRecord != null ? activityRecord.resultTo : null);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 0e500f9..9fb8e2f 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1431,18 +1431,27 @@
}
/**
- * @return whether or not there are ONLY task overlay activities in the stack.
+ * @return whether or not there are ONLY task overlay activities in the task.
* If {@param includeFinishing} is set, then don't ignore finishing activities in the
* check. If there are no task overlay activities, this call returns false.
*/
boolean onlyHasTaskOverlayActivities(boolean includeFinishing) {
- if (getChildCount() == 0) {
- return false;
+ int count = 0;
+ for (int i = getChildCount() - 1; i >= 0; i--) {
+ final ActivityRecord r = getChildAt(i).asActivityRecord();
+ if (r == null) {
+ // Has a child that is other than Activity.
+ return false;
+ }
+ if (!includeFinishing && r.finishing) {
+ continue;
+ }
+ if (!r.isTaskOverlay()) {
+ return false;
+ }
+ count++;
}
- if (includeFinishing) {
- return getActivity((r) -> r.isTaskOverlay()) != null;
- }
- return getActivity((r) -> !r.finishing && r.isTaskOverlay()) != null;
+ return count > 0;
}
private boolean autoRemoveFromRecents() {
@@ -2369,6 +2378,15 @@
return getRootTask() == this;
}
+ boolean isLeafTask() {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ if (mChildren.get(i).asTask() != null) {
+ return false;
+ }
+ }
+ return true;
+ }
+
int getDescendantTaskCount() {
final int[] currentCount = {0};
final PooledConsumer c = PooledLambda.obtainConsumer((t, count) -> { count[0]++; },
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 553ec42..96ac279 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -458,8 +458,6 @@
// A collection of user restrictions that are deprecated and should simply be ignored.
private static final Set<String> DEPRECATED_USER_RESTRICTIONS;
private static final String AB_DEVICE_KEY = "ro.build.ab_update";
- // Permissions related to location which must not be granted automatically
- private static final Set<String> LOCATION_PERMISSIONS;
static {
SECURE_SETTINGS_WHITELIST = new ArraySet<>();
@@ -504,11 +502,6 @@
DEPRECATED_USER_RESTRICTIONS = Sets.newHashSet(
UserManager.DISALLOW_ADD_MANAGED_PROFILE,
UserManager.DISALLOW_REMOVE_MANAGED_PROFILE);
-
- LOCATION_PERMISSIONS = Sets.newHashSet(
- permission.ACCESS_FINE_LOCATION,
- permission.ACCESS_BACKGROUND_LOCATION,
- permission.ACCESS_COARSE_LOCATION);
}
/**
@@ -12541,14 +12534,6 @@
true);
}
- // Prevent granting location-related permissions without user consent.
- if (LOCATION_PERMISSIONS.contains(permission)
- && grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
- && !isUnattendedManagedKioskUnchecked()) {
- callback.sendResult(null);
- return;
- }
-
if (grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
|| grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED
|| grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) {
diff --git a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
index ce5d6d9..4a686ee 100644
--- a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
@@ -19,6 +19,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
@@ -28,10 +29,12 @@
import android.content.Context;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.compat.AndroidBuildClassifier;
+import com.android.server.LocalServices;
import org.junit.Before;
import org.junit.Test;
@@ -48,6 +51,8 @@
@Mock
private PackageManager mPackageManager;
@Mock
+ private PackageManagerInternal mPackageManagerInternal;
+ @Mock
CompatChange.ChangeListener mListener1, mListener2;
PlatformCompat mPlatformCompat;
CompatConfig mCompatConfig;
@@ -60,11 +65,15 @@
when(mContext.getPackageManager()).thenReturn(mPackageManager);
when(mPackageManager.getPackageUid(eq(PACKAGE_NAME), eq(0))).thenThrow(
new PackageManager.NameNotFoundException());
+ when(mPackageManagerInternal.getPackageUid(eq(PACKAGE_NAME), eq(0), anyInt()))
+ .thenReturn(-1);
mCompatConfig = new CompatConfig(mBuildClassifier, mContext);
mPlatformCompat = new PlatformCompat(mContext, mCompatConfig);
// Assume userdebug/eng non-final build
when(mBuildClassifier.isDebuggableBuild()).thenReturn(true);
when(mBuildClassifier.isFinalBuild()).thenReturn(false);
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
index 3e3f40d..e28d13a 100644
--- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
@@ -35,7 +35,6 @@
import android.content.pm.parsing.ComponentParseUtils.ParsedActivityIntentInfo;
import android.content.pm.parsing.PackageImpl;
import android.content.pm.parsing.ParsingPackage;
-import android.net.Uri;
import android.os.Build;
import android.os.Process;
import android.util.ArrayMap;
@@ -87,6 +86,17 @@
return pkg;
}
+ private static ParsingPackage pkgQueriesProvider(String packageName,
+ String... queriesAuthorities) {
+ ParsingPackage pkg = pkg(packageName);
+ if (queriesAuthorities != null) {
+ for (String authority : queriesAuthorities) {
+ pkg.addQueriesProvider(authority);
+ }
+ }
+ return pkg;
+ }
+
private static ParsingPackage pkg(String packageName, String... queriesPackages) {
ParsingPackage pkg = pkg(packageName);
if (queriesPackages != null) {
@@ -172,8 +182,7 @@
PackageSetting target = simulateAddPackage(appsFilter,
pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_UID);
PackageSetting calling = simulateAddPackage(appsFilter,
- pkg("com.some.other.package",
- new Intent().setData(Uri.parse("content://com.some.authority"))),
+ pkgQueriesProvider("com.some.other.package", "com.some.authority"),
DUMMY_CALLING_UID);
assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
@@ -188,8 +197,7 @@
PackageSetting target = simulateAddPackage(appsFilter,
pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_UID);
PackageSetting calling = simulateAddPackage(appsFilter,
- pkg("com.some.other.package",
- new Intent().setData(Uri.parse("content://com.some.other.authority"))),
+ pkgQueriesProvider("com.some.other.package", "com.some.other.authority"),
DUMMY_CALLING_UID);
assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
@@ -205,8 +213,7 @@
pkgWithProvider("com.some.package", "com.some.authority;com.some.other.authority"),
DUMMY_TARGET_UID);
PackageSetting calling = simulateAddPackage(appsFilter,
- pkg("com.some.other.package",
- new Intent().setData(Uri.parse("content://com.some.authority"))),
+ pkgQueriesProvider("com.some.other.package", "com.some.authority"),
DUMMY_CALLING_UID);
assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
@@ -266,7 +273,7 @@
appsFilter.onSystemReady();
PackageSetting target = simulateAddPackage(appsFilter,
- pkg("com.some.package").setForceQueryable(true), DUMMY_TARGET_UID);
+ pkg("com.some.package").setForceQueryable(true), DUMMY_TARGET_UID);
PackageSetting calling = simulateAddPackage(appsFilter,
pkg("com.some.other.package"), DUMMY_CALLING_UID);
diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
index 804c1b9..1a6c6b4 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
+++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
@@ -224,21 +224,43 @@
}
@Test
- public void snapshotThenDelete() {
+ public void snapshotThenDeleteNoApex() {
Rollback rollback = new Rollback(123, new File("/test/testing"), -1, USER, INSTALLER);
PackageRollbackInfo pkgInfo1 = newPkgInfoFor(PKG_1, 12, 10, false);
- PackageRollbackInfo pkgInfo2 = newPkgInfoFor(PKG_2, 18, 12, true);
+ PackageRollbackInfo pkgInfo2 = newPkgInfoFor(PKG_2, 18, 12, false);
rollback.info.getPackages().addAll(Arrays.asList(pkgInfo1, pkgInfo2));
- int[] userIds = {12, 18};
+ int[] userIds = {111, 222};
rollback.snapshotUserData(PKG_2, userIds, mMockDataHelper);
verify(mMockDataHelper).snapshotAppData(eq(123), pkgRollbackInfoFor(PKG_2), eq(userIds));
rollback.delete(mMockDataHelper);
- verify(mMockDataHelper).destroyAppDataSnapshot(eq(123), pkgRollbackInfoFor(PKG_2), eq(12));
- verify(mMockDataHelper).destroyAppDataSnapshot(eq(123), pkgRollbackInfoFor(PKG_2), eq(18));
+ verify(mMockDataHelper).destroyAppDataSnapshot(eq(123), pkgRollbackInfoFor(PKG_2), eq(111));
+ verify(mMockDataHelper).destroyAppDataSnapshot(eq(123), pkgRollbackInfoFor(PKG_2), eq(222));
+ verify(mMockDataHelper, never()).destroyApexDeSnapshots(anyInt());
+
+ assertThat(rollback.isDeleted()).isTrue();
+ }
+
+ @Test
+ public void snapshotThenDeleteWithApex() {
+ Rollback rollback = new Rollback(123, new File("/test/testing"), -1, USER, INSTALLER);
+ PackageRollbackInfo pkgInfo1 = newPkgInfoFor(PKG_1, 12, 10, false);
+ PackageRollbackInfo pkgInfo2 = newPkgInfoFor(PKG_2, 18, 12, true);
+ rollback.info.getPackages().addAll(Arrays.asList(pkgInfo1, pkgInfo2));
+
+ int[] userIds = {111, 222};
+ rollback.snapshotUserData(PKG_2, userIds, mMockDataHelper);
+
+ verify(mMockDataHelper).snapshotAppData(eq(123), pkgRollbackInfoFor(PKG_2), eq(userIds));
+
+ rollback.delete(mMockDataHelper);
+
+ verify(mMockDataHelper, never())
+ .destroyAppDataSnapshot(anyInt(), pkgRollbackInfoFor(PKG_2), anyInt());
+ verify(mMockDataHelper).destroyApexDeSnapshots(123);
assertThat(rollback.isDeleted()).isTrue();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index ebe4ab9..a0ea729 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -29,6 +29,7 @@
import static android.view.WindowManager.TRANSIT_TASK_CLOSE;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.atLeast;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -140,6 +141,19 @@
}
@Test
+ public void testRemoveChildWithOverlayActivity() {
+ final ActivityRecord overlayActivity =
+ new ActivityBuilder(mService).setTask(mTask).build();
+ overlayActivity.setTaskOverlay(true);
+ final ActivityRecord overlayActivity2 =
+ new ActivityBuilder(mService).setTask(mTask).build();
+ overlayActivity2.setTaskOverlay(true);
+
+ mTask.removeChild(overlayActivity2, "test");
+ verify(mSupervisor, never()).removeTask(any(), anyBoolean(), anyBoolean(), any());
+ }
+
+ @Test
public void testNoCleanupMovingActivityInSameStack() {
final Task newTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build();
mActivity.reparent(newTask, 0, null /*reason*/);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 9dbaa4c..e6291a4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -95,8 +95,6 @@
@Before
public void setUp() throws Exception {
- updateDisplayFrames();
-
mWindow = spy(createWindow(null, TYPE_APPLICATION, "window"));
// We only test window frames set by DisplayPolicy, so here prevents computeFrameLw from
// changing those frames.
@@ -106,6 +104,8 @@
attrs.width = MATCH_PARENT;
attrs.height = MATCH_PARENT;
attrs.format = PixelFormat.TRANSLUCENT;
+
+ updateDisplayFrames();
}
@After
@@ -126,6 +126,7 @@
private void updateDisplayFrames() {
mFrames = createDisplayFrames();
+ mDisplayContent.mDisplayFrames = mFrames;
}
private DisplayFrames createDisplayFrames() {
@@ -180,63 +181,6 @@
}
@Test
- public void layoutWindowLw_appDrawsBars() {
- assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
-
- mWindow.mAttrs.flags = FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
- mWindow.mAttrs.setFitInsetsTypes(Type.systemBars());
- addWindow(mWindow);
-
- mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
- mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
-
- assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
- assertInsetByTopBottom(mWindow.getDisplayFrameLw(), 0, 0);
- assertInsetByTopBottom(mWindow.getParentFrame(), 0, 0);
- assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
- assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
- assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0);
- }
-
- @Test
- public void layoutWindowLw_forceAppDrawBars() {
- assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
-
- mWindow.mAttrs.privateFlags = PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
- mWindow.mAttrs.setFitInsetsTypes(Type.systemBars());
- addWindow(mWindow);
-
- mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
- mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
-
- assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
- assertInsetByTopBottom(mWindow.getDisplayFrameLw(), 0, 0);
- assertInsetByTopBottom(mWindow.getParentFrame(), 0, 0);
- assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
- assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
- assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0);
- }
-
- @Test
- public void layoutWindowLw_onlyDrawBottomBar() {
- assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
-
- mWindow.mAttrs.flags = FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
- mWindow.mAttrs.setFitInsetsTypes(Type.systemBars());
- addWindow(mWindow);
-
- mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
- mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
-
- assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
- assertInsetByTopBottom(mWindow.getDisplayFrameLw(), STATUS_BAR_HEIGHT, 0);
- assertInsetByTopBottom(mWindow.getParentFrame(), STATUS_BAR_HEIGHT, 0);
- assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
- assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
- assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0);
- }
-
- @Test
public void layoutWindowLw_fitAllSides() {
assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
@@ -273,7 +217,7 @@
}
@Test
- public void layoutWindowLw_fitMax() {
+ public void layoutWindowLw_fitInsetsIgnoringVisibility() {
assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
final InsetsState state =
@@ -295,7 +239,7 @@
}
@Test
- public void layoutWindowLw_fitNonMax() {
+ public void layoutWindowLw_fitInsetsNotIgnoringVisibility() {
assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
final InsetsState state =
@@ -398,6 +342,7 @@
mWindow.mAttrs.flags =
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+ mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
addWindow(mWindow);
mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
@@ -416,6 +361,7 @@
mWindow.mAttrs.flags =
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+ mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
addWindow(mWindow);
@@ -435,6 +381,7 @@
mWindow.mAttrs.flags =
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+ mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
mWindow.mAttrs.setFitInsetsTypes(
mWindow.mAttrs.getFitInsetsTypes() & ~Type.statusBars());
@@ -456,6 +403,7 @@
mWindow.mAttrs.flags =
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+ mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN;
mDisplayContent.getInsetsPolicy().getInsetsForDispatch(mWindow)
.getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
@@ -477,6 +425,7 @@
mWindow.mAttrs.flags =
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+ mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN;
mDisplayContent.getInsetsPolicy().getInsetsForDispatch(mWindow)
.getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
@@ -501,6 +450,7 @@
mWindow.mAttrs.flags =
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+ mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
addWindow(mWindow);
mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
@@ -521,6 +471,7 @@
mWindow.mAttrs.flags =
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+ mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
addWindow(mWindow);
mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
@@ -541,6 +492,7 @@
mWindow.mAttrs.flags =
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+ mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
mWindow.mAttrs.setFitInsetsTypes(
mWindow.mAttrs.getFitInsetsTypes() & ~Type.statusBars());
@@ -581,6 +533,7 @@
mWindow.mAttrs.flags =
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+ mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
mWindow.mAttrs.setFitInsetsTypes(
mWindow.mAttrs.getFitInsetsTypes() & ~Type.statusBars());
@@ -603,6 +556,7 @@
mWindow.mAttrs.flags =
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+ mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_RESIZE;
addWindow(mWindow);
@@ -625,6 +579,7 @@
public void layoutWindowLw_withForwardInset_SoftInputAdjustNothing() {
mWindow.mAttrs.flags =
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+ mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_NOTHING;
addWindow(mWindow);
@@ -849,7 +804,6 @@
final String prefix = "";
final InsetsState simulatedInsetsState = new InsetsState();
final DisplayFrames simulatedDisplayFrames = createDisplayFrames();
- mDisplayContent.mDisplayFrames = mFrames;
mDisplayPolicy.beginLayoutLw(mFrames, uiMode);
mDisplayContent.getInsetsStateController().onPostLayout();
mDisplayPolicy.simulateLayoutDisplay(simulatedDisplayFrames, simulatedInsetsState, uiMode);
@@ -867,6 +821,7 @@
// Exclude comparing IME insets because currently the simulated layout only focuses on the
// insets from status bar and navigation bar.
realInsetsState.removeSource(InsetsState.ITYPE_IME);
+ realInsetsState.removeSource(InsetsState.ITYPE_CAPTION_BAR);
realInsetsState.dump(prefix, new PrintWriter(realInsetsDump));
final StringWriter simulatedInsetsDump = new StringWriter();
simulatedInsetsState.dump(prefix, new PrintWriter(simulatedInsetsDump));
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index 2af118c..5aa32f8 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -391,6 +391,7 @@
manifest_action["uses-split"].Action(RequiredNameIsJavaPackage);
manifest_action["queries"]["package"].Action(RequiredNameIsJavaPackage);
manifest_action["queries"]["intent"] = intent_filter_action;
+ manifest_action["queries"]["provider"].Action(RequiredAndroidAttribute("authorities"));
// TODO: more complicated component name tag
manifest_action["key-sets"]["key-set"]["public-key"];