Merge "Remove ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED" am: 6fce89f8b6 am: 3780da605a
Change-Id: I6df8824ca6a5ba668d49e728434cc6d9cacda8a3
diff --git a/api/current.txt b/api/current.txt
index 23b5bc6..115d561 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -45251,6 +45251,7 @@
field public static final int APPTYPE_USIM = 2; // 0x2
field public static final int AUTHTYPE_EAP_AKA = 129; // 0x81
field public static final int AUTHTYPE_EAP_SIM = 128; // 0x80
+ field public static final long CALLBACK_ON_MORE_ERROR_CODE_CHANGE = 130595455L; // 0x7c8ba7fL
field public static final int CALL_STATE_IDLE = 0; // 0x0
field public static final int CALL_STATE_OFFHOOK = 2; // 0x2
field public static final int CALL_STATE_RINGING = 1; // 0x1
@@ -45320,6 +45321,8 @@
field public static final int PHONE_TYPE_NONE = 0; // 0x0
field public static final int PHONE_TYPE_SIP = 3; // 0x3
field public static final int SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION = 2; // 0x2
+ field public static final int SET_OPPORTUNISTIC_SUB_NO_OPPORTUNISTIC_SUB_AVAILABLE = 3; // 0x3
+ field public static final int SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION = 4; // 0x4
field public static final int SET_OPPORTUNISTIC_SUB_SUCCESS = 0; // 0x0
field public static final int SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED = 1; // 0x1
field public static final int SIM_STATE_ABSENT = 1; // 0x1
@@ -45336,8 +45339,14 @@
field public static final int UNKNOWN_CARRIER_ID = -1; // 0xffffffff
field public static final int UNSUPPORTED_CARD_ID = -1; // 0xffffffff
field public static final int UPDATE_AVAILABLE_NETWORKS_ABORTED = 2; // 0x2
+ field public static final int UPDATE_AVAILABLE_NETWORKS_DISABLE_MODEM_FAIL = 5; // 0x5
+ field public static final int UPDATE_AVAILABLE_NETWORKS_ENABLE_MODEM_FAIL = 6; // 0x6
field public static final int UPDATE_AVAILABLE_NETWORKS_INVALID_ARGUMENTS = 3; // 0x3
+ field public static final int UPDATE_AVAILABLE_NETWORKS_MULTIPLE_NETWORKS_NOT_SUPPORTED = 7; // 0x7
field public static final int UPDATE_AVAILABLE_NETWORKS_NO_CARRIER_PRIVILEGE = 4; // 0x4
+ field public static final int UPDATE_AVAILABLE_NETWORKS_NO_OPPORTUNISTIC_SUB_AVAILABLE = 8; // 0x8
+ field public static final int UPDATE_AVAILABLE_NETWORKS_REMOTE_SERVICE_EXCEPTION = 9; // 0x9
+ field public static final int UPDATE_AVAILABLE_NETWORKS_SERVICE_IS_DISABLED = 10; // 0xa
field public static final int UPDATE_AVAILABLE_NETWORKS_SUCCESS = 0; // 0x0
field public static final int UPDATE_AVAILABLE_NETWORKS_UNKNOWN_FAILURE = 1; // 0x1
field public static final int USSD_ERROR_SERVICE_UNAVAIL = -2; // 0xfffffffe
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index 8944ded..e12749e6 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -50,6 +50,7 @@
srcs: [
":statsd_aidl",
+ ":ICarStatsService.aidl",
"src/active_config_list.proto",
"src/statsd_config.proto",
"src/uid_data.proto",
@@ -69,6 +70,7 @@
"src/config/ConfigKey.cpp",
"src/config/ConfigListener.cpp",
"src/config/ConfigManager.cpp",
+ "src/external/CarStatsPuller.cpp",
"src/external/GpuStatsPuller.cpp",
"src/external/Perfetto.cpp",
"src/external/StatsPuller.cpp",
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 4440bf8..fced994 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -326,10 +326,11 @@
228 [(allow_from_any_uid) = true];
PerfettoUploaded perfetto_uploaded =
229 [(log_from_module) = "perfetto"];
+ VmsClientConnectionStateChanged vms_client_connection_state_changed = 230;
}
// Pulled events will start at field 10000.
- // Next: 10062
+ // Next: 10067
oneof pulled {
WifiBytesTransfer wifi_bytes_transfer = 10000;
WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001;
@@ -393,6 +394,8 @@
CoolingDevice cooling_device = 10059;
AppOps app_ops = 10060;
ProcessSystemIonHeapSize process_system_ion_heap_size = 10061;
+ VmsClientStats vms_client_stats = 10065;
+ NotificationRemoteViews notification_remote_views = 10066;
}
// DO NOT USE field numbers above 100,000 in AOSP.
@@ -3713,6 +3716,33 @@
optional Result result = 9;
}
+/**
+ * Logs when a Vehicle Maps Service client's connection state has changed
+ *
+ * Logged from:
+ * packages/services/Car/service/src/com/android/car/stats/VmsClientLog.java
+ */
+message VmsClientConnectionStateChanged {
+ // The UID of the VMS client app
+ optional int32 uid = 1 [(is_uid) = true];
+
+ enum State {
+ UNKNOWN = 0;
+ // Attempting to connect to the client
+ CONNECTING = 1;
+ // Client connection established
+ CONNECTED = 2;
+ // Client connection closed unexpectedly
+ DISCONNECTED = 3;
+ // Client connection closed by VMS
+ TERMINATED = 4;
+ // Error establishing the client connection
+ CONNECTION_ERROR = 5;
+ }
+
+ optional State state = 2;
+}
+
//////////////////////////////////////////////////////////////////////
// Pulled atoms below this line //
//////////////////////////////////////////////////////////////////////
@@ -4727,6 +4757,24 @@
optional ProcessStatsSectionProto proc_stats_section = 1;
}
+// Next Tag: 2
+message PackageRemoteViewInfoProto {
+ optional string package_name = 1;
+ // add per-package additional info here (like channels)
+}
+
+// Next Tag: 2
+message NotificationRemoteViewsProto {
+ repeated PackageRemoteViewInfoProto package_remote_view_info = 1;
+}
+
+/**
+ * Pulled from NotificationManagerService.java
+ */
+message NotificationRemoteViews {
+ optional NotificationRemoteViewsProto notification_remote_views = 1;
+}
+
message PowerProfileProto {
optional double cpu_suspend = 1;
@@ -6858,7 +6906,6 @@
// Where it was logged from.
optional Source source = 4;
-
}
/**
@@ -6894,3 +6941,32 @@
optional int64 trace_uuid_lsb = 2;
optional int64 trace_uuid_msb = 3;
}
+
+/**
+ * Pulls client metrics on data transferred via Vehicle Maps Service.
+ * Metrics are keyed by uid + layer.
+ *
+ * Pulled from:
+ * packages/services/Car/service/src/com/android/car/stats/CarStatsService.java
+ */
+message VmsClientStats {
+ // UID of the VMS client app
+ optional int32 uid = 1 [(is_uid) = true];
+
+ // VMS layer definition
+ optional int32 layer_type = 2;
+ optional int32 layer_channel = 3;
+ optional int32 layer_version = 4;
+
+ // Bytes and packets sent by the client for the layer
+ optional int64 tx_bytes = 5;
+ optional int64 tx_packets = 6;
+
+ // Bytes and packets received by the client for the layer
+ optional int64 rx_bytes = 7;
+ optional int64 rx_packets = 8;
+
+ // Bytes and packets dropped due to client error
+ optional int64 dropped_bytes = 9;
+ optional int64 dropped_packets = 10;
+}
diff --git a/cmds/statsd/src/external/CarStatsPuller.cpp b/cmds/statsd/src/external/CarStatsPuller.cpp
new file mode 100644
index 0000000..70c0456
--- /dev/null
+++ b/cmds/statsd/src/external/CarStatsPuller.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define DEBUG false
+#include "Log.h"
+
+#include <binder/IServiceManager.h>
+#include <com/android/internal/car/ICarStatsService.h>
+
+#include "CarStatsPuller.h"
+#include "logd/LogEvent.h"
+#include "stats_log_util.h"
+
+using android::binder::Status;
+using com::android::internal::car::ICarStatsService;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+static std::mutex gCarStatsMutex;
+static sp<ICarStatsService> gCarStats = nullptr;
+
+class CarStatsDeathRecipient : public android::IBinder::DeathRecipient {
+ public:
+ CarStatsDeathRecipient() = default;
+ ~CarStatsDeathRecipient() override = default;
+
+ // android::IBinder::DeathRecipient override:
+ void binderDied(const android::wp<android::IBinder>& /* who */) override {
+ ALOGE("Car service has died");
+ std::lock_guard<std::mutex> lock(gCarStatsMutex);
+ if (gCarStats) {
+ sp<IBinder> binder = IInterface::asBinder(gCarStats);
+ binder->unlinkToDeath(this);
+ gCarStats = nullptr;
+ }
+ }
+};
+
+static sp<CarStatsDeathRecipient> gDeathRecipient = new CarStatsDeathRecipient();
+
+static sp<ICarStatsService> getCarService() {
+ std::lock_guard<std::mutex> lock(gCarStatsMutex);
+ if (!gCarStats) {
+ const sp<IBinder> binder = defaultServiceManager()->checkService(String16("car_stats"));
+ if (!binder) {
+ ALOGW("Car service is unavailable");
+ return nullptr;
+ }
+ gCarStats = interface_cast<ICarStatsService>(binder);
+ binder->linkToDeath(gDeathRecipient);
+ }
+ return gCarStats;
+}
+
+CarStatsPuller::CarStatsPuller(const int tagId) : StatsPuller(tagId) {
+}
+
+bool CarStatsPuller::PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) {
+ const sp<ICarStatsService> carService = getCarService();
+ if (!carService) {
+ return false;
+ }
+
+ vector<StatsLogEventWrapper> returned_value;
+ Status status = carService->pullData(mTagId, &returned_value);
+ if (!status.isOk()) {
+ ALOGW("CarStatsPuller::pull failed for %d", mTagId);
+ return false;
+ }
+
+ data->clear();
+ for (const StatsLogEventWrapper& it : returned_value) {
+ LogEvent::createLogEvents(it, *data);
+ }
+ VLOG("CarStatsPuller::pull succeeded for %d", mTagId);
+ return true;
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/external/CarStatsPuller.h b/cmds/statsd/src/external/CarStatsPuller.h
new file mode 100644
index 0000000..ca0f1a9
--- /dev/null
+++ b/cmds/statsd/src/external/CarStatsPuller.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "StatsPuller.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+/**
+ * Pull atoms from CarService.
+ */
+class CarStatsPuller : public StatsPuller {
+public:
+ explicit CarStatsPuller(const int tagId);
+ bool PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) override;
+};
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 69e6a11..1c9d776 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -27,6 +27,7 @@
#include "../logd/LogEvent.h"
#include "../stats_log_util.h"
#include "../statscompanion_util.h"
+#include "CarStatsPuller.h"
#include "GpuStatsPuller.h"
#include "PowerStatsPuller.h"
#include "ResourceHealthManagerPuller.h"
@@ -267,6 +268,13 @@
// App ops
{android::util::APP_OPS,
{.puller = new StatsCompanionServicePuller(android::util::APP_OPS)}},
+ // VmsClientStats
+ {android::util::VMS_CLIENT_STATS,
+ {.additiveFields = {5, 6, 7, 8, 9, 10},
+ .puller = new CarStatsPuller(android::util::VMS_CLIENT_STATS)}},
+ // NotiifcationRemoteViews.
+ {android::util::NOTIFICATION_REMOTE_VIEWS,
+ {.puller = new StatsCompanionServicePuller(android::util::NOTIFICATION_REMOTE_VIEWS)}},
};
StatsPullerManager::StatsPullerManager() : mNextPullTimeNs(NO_ALARM_UPDATE) {
diff --git a/core/java/Android.bp b/core/java/Android.bp
index fb27f74..9a8e130 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -7,3 +7,8 @@
name: "IDropBoxManagerService.aidl",
srcs: ["com/android/internal/os/IDropBoxManagerService.aidl"],
}
+
+filegroup {
+ name: "ICarStatsService.aidl",
+ srcs: ["com/android/internal/car/ICarStatsService.aidl"],
+}
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index c80be8e..2d7adf3 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -563,6 +563,21 @@
* account, or the AbstractAcccountAuthenticator managing the account did so or because the
* client shares a signature with the managing AbstractAccountAuthenticator.
*
+ * <div class="caution"><p><b>Caution: </b>This method returns personal and sensitive user data.
+ * If your app accesses, collects, uses, or shares personal and sensitive data, you must clearly
+ * disclose that fact to users. For apps published on Google Play, policies protecting user data
+ * require that you do the following:</p>
+ * <ul>
+ * <li>Disclose to the user how your app accesses, collects, uses, or shares personal and
+ * sensitive data. Learn more about
+ * <a href="https://play.google.com/about/privacy-security-deception/user-data/#!#personal-sensitive">acceptable
+ * disclosure and consent</a>.</li>
+ * <li>Provide a privacy policy that describes your use of this data on- and off-device.</li>
+ * </ul>
+ * <p>To learn more, visit the
+ * <a href="https://play.google.com/about/privacy-security-deception/user-data">Google Play
+ * Policy regarding user data</a>.</p></div>
+ *
* <p>
* It is safe to call this method from the main thread.
*
@@ -649,6 +664,22 @@
* the account. For example, there are types corresponding to Google and Facebook. The exact
* string token to use will be published somewhere associated with the authenticator in
* question.
+ * </p>
+ *
+ * <div class="caution"><p><b>Caution: </b>This method returns personal and sensitive user data.
+ * If your app accesses, collects, uses, or shares personal and sensitive data, you must clearly
+ * disclose that fact to users. For apps published on Google Play, policies protecting user data
+ * require that you do the following:</p>
+ * <ul>
+ * <li>Disclose to the user how your app accesses, collects, uses, or shares personal and
+ * sensitive data. Learn more about
+ * <a href="https://play.google.com/about/privacy-security-deception/user-data/#!#personal-sensitive">acceptable
+ * disclosure and consent</a>.</li>
+ * <li>Provide a privacy policy that describes your use of this data on- and off-device.</li>
+ * </ul>
+ * <p>To learn more, visit the
+ * <a href="https://play.google.com/about/privacy-security-deception/user-data">Google Play
+ * Policy regarding user data</a>.</p></div>
*
* <p>
* It is safe to call this method from the main thread.
diff --git a/core/java/android/animation/FloatEvaluator.java b/core/java/android/animation/FloatEvaluator.java
index 9463aa1..ae90e37 100644
--- a/core/java/android/animation/FloatEvaluator.java
+++ b/core/java/android/animation/FloatEvaluator.java
@@ -24,7 +24,7 @@
/**
* This function returns the result of linearly interpolating the start and end values, with
* <code>fraction</code> representing the proportion between the start and end values. The
- * calculation is a simple parametric calculation: <code>result = x0 + t * (v1 - v0)</code>,
+ * calculation is a simple parametric calculation: <code>result = x0 + t * (x1 - x0)</code>,
* where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
* and <code>t</code> is <code>fraction</code>.
*
diff --git a/core/java/android/app/DisabledWallpaperManager.java b/core/java/android/app/DisabledWallpaperManager.java
new file mode 100644
index 0000000..5185941
--- /dev/null
+++ b/core/java/android/app/DisabledWallpaperManager.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A no-op implementation of {@link WallpaperManager}.
+ */
+final class DisabledWallpaperManager extends WallpaperManager {
+
+ private static final String TAG = DisabledWallpaperManager.class.getSimpleName();
+
+ // Don't need to worry about synchronization
+ private static DisabledWallpaperManager sInstance;
+
+ // TODO(b/138939803): STOPSHIP changed to false and/or remove it
+ private static final boolean DEBUG = true;
+
+ @NonNull
+ static DisabledWallpaperManager getInstance() {
+ if (sInstance == null) {
+ sInstance = new DisabledWallpaperManager();
+ }
+ return sInstance;
+ }
+
+ private DisabledWallpaperManager() {
+ super(null, null, null);
+ }
+
+ @Override
+ public boolean isWallpaperSupported() {
+ return false;
+ }
+
+ @Override
+ public boolean isSetWallpaperAllowed() {
+ return false;
+ }
+
+ // TODO(b/138939803): STOPSHIP methods below should not be necessary,
+ // callers should check if isWallpaperSupported(), consider removing them to keep this class
+ // simpler
+
+ private static <T> T unsupported() {
+ if (DEBUG) Log.w(TAG, "unsupported method called; returning null", new Exception());
+ return null;
+ }
+
+ private static boolean unsupportedBoolean() {
+ if (DEBUG) Log.w(TAG, "unsupported method called; returning false", new Exception());
+ return false;
+ }
+
+ @Override
+ public Drawable getDrawable() {
+ return unsupported();
+ }
+
+ @Override
+ public Drawable getBuiltInDrawable() {
+ return unsupported();
+ }
+
+ @Override
+ public Drawable getBuiltInDrawable(int which) {
+ return unsupported();
+ }
+
+ @Override
+ public Drawable getBuiltInDrawable(int outWidth, int outHeight, boolean scaleToFit,
+ float horizontalAlignment, float verticalAlignment) {
+ return unsupported();
+ }
+
+ @Override
+ public Drawable getBuiltInDrawable(int outWidth, int outHeight, boolean scaleToFit,
+ float horizontalAlignment, float verticalAlignment, int which) {
+ return unsupported();
+ }
+
+ @Override
+ public Drawable peekDrawable() {
+ return unsupported();
+ }
+
+ @Override
+ public Drawable getFastDrawable() {
+ return unsupported();
+ }
+
+ @Override
+ public Drawable peekFastDrawable() {
+ return unsupported();
+ }
+
+ @Override
+ public Bitmap getBitmap() {
+ return unsupported();
+ }
+
+ @Override
+ public Bitmap getBitmap(boolean hardware) {
+ return unsupported();
+ }
+
+ @Override
+ public Bitmap getBitmapAsUser(int userId, boolean hardware) {
+ return unsupported();
+ }
+
+ @Override
+ public ParcelFileDescriptor getWallpaperFile(int which) {
+ return unsupported();
+ }
+
+ @Override
+ public void addOnColorsChangedListener(OnColorsChangedListener listener, Handler handler) {
+ unsupported();
+ }
+
+ @Override
+ public void addOnColorsChangedListener(OnColorsChangedListener listener, Handler handler,
+ int userId) {
+ unsupported();
+ }
+
+ @Override
+ public void removeOnColorsChangedListener(OnColorsChangedListener callback) {
+ unsupported();
+ }
+
+ @Override
+ public void removeOnColorsChangedListener(OnColorsChangedListener callback, int userId) {
+ unsupported();
+ }
+
+ @Override
+ public WallpaperColors getWallpaperColors(int which) {
+ return unsupported();
+ }
+
+ @Override
+ public WallpaperColors getWallpaperColors(int which, int userId) {
+ return unsupported();
+ }
+
+ @Override
+ public ParcelFileDescriptor getWallpaperFile(int which, int userId) {
+ return unsupported();
+ }
+
+ @Override
+ public void forgetLoadedWallpaper() {
+ unsupported();
+ }
+
+ @Override
+ public WallpaperInfo getWallpaperInfo() {
+ return unsupported();
+ }
+
+ @Override
+ public WallpaperInfo getWallpaperInfo(int userId) {
+ return unsupported();
+ }
+
+ @Override
+ public int getWallpaperId(int which) {
+ return unsupported();
+ }
+
+ @Override
+ public int getWallpaperIdForUser(int which, int userId) {
+ return unsupported();
+ }
+
+ @Override
+ public Intent getCropAndSetWallpaperIntent(Uri imageUri) {
+ return unsupported();
+ }
+
+ @Override
+ public void setResource(int resid) throws IOException {
+ unsupported();
+ }
+
+ @Override
+ public int setResource(int resid, int which) throws IOException {
+ return unsupported();
+ }
+
+ @Override
+ public void setBitmap(Bitmap bitmap) throws IOException {
+ unsupported();
+ }
+
+ @Override
+ public int setBitmap(Bitmap fullImage, Rect visibleCropHint, boolean allowBackup)
+ throws IOException {
+ return unsupported();
+ }
+
+ @Override
+ public int setBitmap(Bitmap fullImage, Rect visibleCropHint, boolean allowBackup, int which)
+ throws IOException {
+ return unsupported();
+ }
+
+ @Override
+ public int setBitmap(Bitmap fullImage, Rect visibleCropHint, boolean allowBackup, int which,
+ int userId) throws IOException {
+ return unsupported();
+ }
+
+ @Override
+ public void setStream(InputStream bitmapData) throws IOException {
+ unsupported();
+ }
+
+ @Override
+ public int setStream(InputStream bitmapData, Rect visibleCropHint, boolean allowBackup)
+ throws IOException {
+ return unsupported();
+ }
+
+ @Override
+ public int setStream(InputStream bitmapData, Rect visibleCropHint, boolean allowBackup,
+ int which) throws IOException {
+ return unsupported();
+ }
+
+ @Override
+ public boolean hasResourceWallpaper(int resid) {
+ return unsupportedBoolean();
+ }
+
+ @Override
+ public int getDesiredMinimumWidth() {
+ return unsupported();
+ }
+
+ @Override
+ public int getDesiredMinimumHeight() {
+ return unsupported();
+ }
+
+ @Override
+ public void suggestDesiredDimensions(int minimumWidth, int minimumHeight) {
+ unsupported();
+ }
+
+ @Override
+ public void setDisplayPadding(Rect padding) {
+ unsupported();
+ }
+
+ @Override
+ public void setDisplayOffset(IBinder windowToken, int x, int y) {
+ unsupported();
+ }
+
+ @Override
+ public void clearWallpaper() {
+ unsupported();
+ }
+
+ @Override
+ public void clearWallpaper(int which, int userId) {
+ unsupported();
+ }
+
+ @Override
+ public boolean setWallpaperComponent(ComponentName name) {
+ return unsupportedBoolean();
+ }
+
+ @Override
+ public boolean setWallpaperComponent(ComponentName name, int userId) {
+ return unsupportedBoolean();
+ }
+
+ @Override
+ public void setWallpaperOffsets(IBinder windowToken, float xOffset, float yOffset) {
+ unsupported();
+ }
+
+ @Override
+ public void setWallpaperOffsetSteps(float xStep, float yStep) {
+ unsupported();
+ }
+
+ @Override
+ public void sendWallpaperCommand(IBinder windowToken, String action, int x, int y, int z,
+ Bundle extras) {
+ unsupported();
+ }
+
+ @Override
+ public void clearWallpaperOffsets(IBinder windowToken) {
+ unsupported();
+ }
+
+ @Override
+ public void clear() throws IOException {
+ unsupported();
+ }
+
+ @Override
+ public void clear(int which) throws IOException {
+ unsupported();
+ }
+
+ @Override
+ public boolean isWallpaperBackupEligible(int which) {
+ return unsupportedBoolean();
+ }
+}
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 9f51db8..3266898 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -202,4 +202,6 @@
void setPrivateNotificationsAllowed(boolean allow);
boolean getPrivateNotificationsAllowed();
+
+ long pullStats(long startNs, int report, boolean doAgg, out List<ParcelFileDescriptor> stats);
}
diff --git a/core/java/android/app/IUiModeManager.aidl b/core/java/android/app/IUiModeManager.aidl
index a3e0845..f5809ba 100644
--- a/core/java/android/app/IUiModeManager.aidl
+++ b/core/java/android/app/IUiModeManager.aidl
@@ -68,4 +68,9 @@
* Tells if Night mode is locked or not.
*/
boolean isNightModeLocked();
+
+ /**
+ * @hide
+ */
+ boolean setNightModeActivated(boolean active);
}
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 6f7a060..7b29bcd 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -1257,7 +1257,12 @@
return b != null ? new PendingIntent(b, in.getClassCookie(PendingIntent.class)) : null;
}
- /*package*/ PendingIntent(IIntentSender target) {
+ /**
+ * Creates a PendingIntent with the given target.
+ * @param target the backing IIntentSender
+ * @hide
+ */
+ public PendingIntent(IIntentSender target) {
mTarget = target;
}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index d8ccc62..54d6e00 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -694,11 +694,22 @@
@Override
public WallpaperManager createService(ContextImpl ctx)
throws ServiceNotFoundException {
- final IBinder b;
- if (ctx.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.P) {
- b = ServiceManager.getServiceOrThrow(Context.WALLPAPER_SERVICE);
- } else {
- b = ServiceManager.getService(Context.WALLPAPER_SERVICE);
+ final IBinder b = ServiceManager.getService(Context.WALLPAPER_SERVICE);
+ if (b == null) {
+ // There are 2 reason service can be null:
+ // 1.Device doesn't support it - that's fine
+ // 2.App is running on instant mode - should fail
+ final boolean enabled = Resources.getSystem()
+ .getBoolean(com.android.internal.R.bool.config_enableWallpaperService);
+ if (!enabled) {
+ // Life moves on...
+ return DisabledWallpaperManager.getInstance();
+ }
+ if (ctx.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.P) {
+ // Instant app
+ throw new ServiceNotFoundException(Context.WALLPAPER_SERVICE);
+ }
+ // Bad state - WallpaperManager methods will throw exception
}
IWallpaperManager service = IWallpaperManager.Stub.asInterface(b);
return new WallpaperManager(service, ctx.getOuterContext(),
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index 8324787..d8c030d 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -473,4 +473,18 @@
}
return true;
}
+
+ /**
+ * @hide*
+ */
+ public boolean setNightModeActivated(boolean active) {
+ if (mService != null) {
+ try {
+ return mService.setNightModeActivated(active);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
}
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 325a54b..d607ed6 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -510,7 +510,9 @@
/*package*/ WallpaperManager(IWallpaperManager service, Context context, Handler handler) {
mContext = context;
- initGlobals(service, context.getMainLooper());
+ if (service != null) {
+ initGlobals(service, context.getMainLooper());
+ }
}
/**
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index a17b2dd..08527c79 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -4032,9 +4032,17 @@
* Make the device lock immediately, as if the lock screen timeout has expired at the point of
* this call.
* <p>
+ * This method secures the device in response to an urgent situation, such as a lost or stolen
+ * device. After this method is called, the device must be unlocked using strong authentication
+ * (PIN, pattern, or password). This API is intended for use only by device admins.
+ * <p>
* The calling device admin must have requested {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK}
* to be able to call this method; if it has not, a security exception will be thrown.
* <p>
+ * If there's no lock type set, this method forces the device to go to sleep but doesn't lock
+ * the device. Device admins who find the device in this state can lock an otherwise-insecure
+ * device by first calling {@link #resetPassword} to set the password and then lock the device.
+ * <p>
* This method can be called on the {@link DevicePolicyManager} instance returned by
* {@link #getParentProfileInstance(ComponentName)} in order to lock the parent profile.
* <p>
@@ -4051,9 +4059,17 @@
* Make the device lock immediately, as if the lock screen timeout has expired at the point of
* this call.
* <p>
+ * This method secures the device in response to an urgent situation, such as a lost or stolen
+ * device. After this method is called, the device must be unlocked using strong authentication
+ * (PIN, pattern, or password). This API is intended for use only by device admins.
+ * <p>
* The calling device admin must have requested {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK}
* to be able to call this method; if it has not, a security exception will be thrown.
* <p>
+ * If there's no lock type set, this method forces the device to go to sleep but doesn't lock
+ * the device. Device admins who find the device in this state can lock an otherwise-insecure
+ * device by first calling {@link #resetPassword} to set the password and then lock the device.
+ * <p>
* This method can be called on the {@link DevicePolicyManager} instance returned by
* {@link #getParentProfileInstance(ComponentName)} in order to lock the parent profile.
*
@@ -8080,7 +8096,7 @@
* Sets which system features are enabled when the device runs in lock task mode. This method
* doesn't affect the features when lock task mode is inactive. Any system features not included
* in {@code flags} are implicitly disabled when calling this method. By default, only
- * {@link #LOCK_TASK_FEATURE_GLOBAL_ACTIONS} is enabled—all the other features are disabled. To
+ * {@link #LOCK_TASK_FEATURE_GLOBAL_ACTIONS} is enabled; all the other features are disabled. To
* disable the global actions dialog, call this method omitting
* {@link #LOCK_TASK_FEATURE_GLOBAL_ACTIONS}.
*
diff --git a/core/java/android/app/backup/WallpaperBackupHelper.java b/core/java/android/app/backup/WallpaperBackupHelper.java
index 36f5f96..5c0ddc1 100644
--- a/core/java/android/app/backup/WallpaperBackupHelper.java
+++ b/core/java/android/app/backup/WallpaperBackupHelper.java
@@ -85,6 +85,10 @@
*/
@Override
public void restoreEntity(BackupDataInputStream data) {
+ if (mWpm == null) {
+ Slog.w(TAG, "restoreEntity(): no wallpaper service");
+ return;
+ }
final String key = data.getKey();
if (isKeyInList(key, mKeys)) {
if (key.equals(WALLPAPER_IMAGE_KEY)) {
diff --git a/core/java/android/app/contentsuggestions/ContentSuggestionsManager.java b/core/java/android/app/contentsuggestions/ContentSuggestionsManager.java
index 1bb81b1..1e6ab41 100644
--- a/core/java/android/app/contentsuggestions/ContentSuggestionsManager.java
+++ b/core/java/android/app/contentsuggestions/ContentSuggestionsManager.java
@@ -45,6 +45,17 @@
*/
@SystemApi
public final class ContentSuggestionsManager {
+ /**
+ * Key into the extras Bundle passed to {@link #provideContextImage(int, Bundle)}.
+ * This can be used to provide the bitmap to
+ * {@link android.service.contentsuggestions.ContentSuggestionsService}.
+ * The value must be a {@link android.graphics.Bitmap} with the
+ * config {@link android.graphics.Bitmap.Config.HARDWARE}.
+ *
+ * @hide
+ */
+ public static final String EXTRA_BITMAP = "android.contentsuggestions.extra.BITMAP";
+
private static final String TAG = ContentSuggestionsManager.class.getSimpleName();
/**
@@ -70,7 +81,7 @@
* system content suggestions service.
*
* @param taskId of the task to snapshot.
- * @param imageContextRequestExtras sent with with request to provide implementation specific
+ * @param imageContextRequestExtras sent with request to provide implementation specific
* extra information.
*/
public void provideContextImage(
diff --git a/core/java/android/app/slice/SliceManager.java b/core/java/android/app/slice/SliceManager.java
index 955093d..90ecce2 100644
--- a/core/java/android/app/slice/SliceManager.java
+++ b/core/java/android/app/slice/SliceManager.java
@@ -390,6 +390,8 @@
}
Bundle extras = new Bundle();
extras.putParcelable(SliceProvider.EXTRA_INTENT, intent);
+ extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS,
+ new ArrayList<>(supportedSpecs));
final Bundle res = provider.call(SliceProvider.METHOD_MAP_INTENT, null, extras);
if (res == null) {
return null;
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 49187dc..9efaca1 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -1095,6 +1095,24 @@
}
/**
+ * Get the Bluetooth alias of the remote device.
+ * If Alias is null, get the Bluetooth name instead.
+ *
+ * @return the Bluetooth alias, or null if no alias or there was a problem
+ * @hide
+ * @see #getAlias()
+ * @see #getName()
+ */
+ @UnsupportedAppUsage(publicAlternatives = "Use {@link #getName()} instead.")
+ public String getAliasName() {
+ String name = getAlias();
+ if (name == null) {
+ name = getName();
+ }
+ return name;
+ }
+
+ /**
* Get the most recent identified battery level of this Bluetooth device
* <p>Requires {@link android.Manifest.permission#BLUETOOTH}
*
diff --git a/core/java/android/bluetooth/le/ScanFilter.java b/core/java/android/bluetooth/le/ScanFilter.java
index 038994f..7511fd0 100644
--- a/core/java/android/bluetooth/le/ScanFilter.java
+++ b/core/java/android/bluetooth/le/ScanFilter.java
@@ -671,8 +671,6 @@
/**
* Set filter on on manufacturerData. A negative manufacturerId is considered as invalid id.
- * <p>
- * Note the first two bytes of the {@code manufacturerData} is the manufacturerId.
*
* @throws IllegalArgumentException If the {@code manufacturerId} is invalid.
*/
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index e9a4762..62f08a6 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2677,6 +2677,9 @@
* that application is first launched (that is the first time it is moved
* out of the stopped state). The data contains the name of the package.
*
+ * <p>When the application is first launched, the application itself doesn't receive this
+ * broadcast.</p>
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
diff --git a/core/java/android/content/SyncStats.java b/core/java/android/content/SyncStats.java
index 03b2250e..9596a60 100644
--- a/core/java/android/content/SyncStats.java
+++ b/core/java/android/content/SyncStats.java
@@ -58,7 +58,7 @@
* attempted to update or delete a version of a resource on the server. This is expected
* to clear itself automatically once the new state is retrieved from the server,
* though it may remain until the user intervenes manually, perhaps by clearing the
- * local storage and starting over frmo scratch. This is considered a hard error.
+ * local storage and starting over from scratch. This is considered a hard error.
*/
public long numConflictDetectedExceptions;
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 1099d8b..69ce3bd 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -101,19 +101,6 @@
* <p>
* The ApiDemos project contains examples of using this API:
* <code>ApiDemos/src/com/example/android/apis/content/InstallApk*.java</code>.
- * <p>
- * On Android Q or above, an app installed notification will be posted
- * by system after a new app is installed.
- * To customize installer's notification icon, you should declare the following in the manifest
- * <application> as follows: </p>
- * <pre>
- * <meta-data android:name="com.android.packageinstaller.notification.smallIcon"
- * android:resource="@drawable/installer_notification_icon"/>
- * </pre>
- * <pre>
- * <meta-data android:name="com.android.packageinstaller.notification.color"
- * android:resource="@color/installer_notification_color"/>
- * </pre>
*/
public class PackageInstaller {
private static final String TAG = "PackageInstaller";
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 2b1b32e..56a6285 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -16,12 +16,17 @@
package android.content.pm;
+import android.annotation.IntDef;
import android.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
+import android.util.DebugUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* Per-user information.
@@ -94,6 +99,25 @@
*/
public static final int FLAG_DEMO = 0x00000200;
+ /**
+ * @hide
+ */
+ @IntDef(flag = true, prefix = "FLAG_", value = {
+ FLAG_PRIMARY,
+ FLAG_ADMIN,
+ FLAG_GUEST,
+ FLAG_RESTRICTED,
+ FLAG_INITIALIZED,
+ FLAG_MANAGED_PROFILE,
+ FLAG_DISABLED,
+ FLAG_QUIET_MODE,
+ FLAG_EPHEMERAL,
+ FLAG_DEMO
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UserInfoFlag {
+ }
+
public static final int NO_PROFILE_GROUP_ID = UserHandle.USER_NULL;
@UnsupportedAppUsage
@@ -128,6 +152,18 @@
@UnsupportedAppUsage
public boolean guestToRemove;
+ /**
+ * This is used to optimize the creation of an user, i.e. OEMs might choose to pre-create a
+ * number of users at the first boot, so the actual creation later is faster.
+ *
+ * <p>A {@code preCreated} user is not a real user yet, so it should not show up on regular
+ * user operations (other than user creation per se).
+ *
+ * <p>Once the pre-created is used to create a "real" user later on, {@code preCreate} is set to
+ * {@code false}.
+ */
+ public boolean preCreated;
+
@UnsupportedAppUsage
public UserInfo(int id, String name, int flags) {
this(id, name, null, flags);
@@ -155,6 +191,13 @@
@UnsupportedAppUsage
public boolean isGuest() {
+ return isGuest(flags);
+ }
+
+ /**
+ * Checks if the flag denotes a guest user.
+ */
+ public static boolean isGuest(@UserInfoFlag int flags) {
return (flags & FLAG_GUEST) == FLAG_GUEST;
}
@@ -165,6 +208,13 @@
@UnsupportedAppUsage
public boolean isManagedProfile() {
+ return isManagedProfile(flags);
+ }
+
+ /**
+ * Checks if the flag denotes a managed profile.
+ */
+ public static boolean isManagedProfile(@UserInfoFlag int flags) {
return (flags & FLAG_MANAGED_PROFILE) == FLAG_MANAGED_PROFILE;
}
@@ -252,6 +302,7 @@
lastLoggedInTime = orig.lastLoggedInTime;
lastLoggedInFingerprint = orig.lastLoggedInFingerprint;
partial = orig.partial;
+ preCreated = orig.preCreated;
profileGroupId = orig.profileGroupId;
restrictedProfileParentId = orig.restrictedProfileParentId;
guestToRemove = orig.guestToRemove;
@@ -268,6 +319,22 @@
return "UserInfo{" + id + ":" + name + ":" + Integer.toHexString(flags) + "}";
}
+ /** @hide */
+ public String toFullString() {
+ return "UserInfo[id=" + id
+ + ", name=" + name
+ + ", flags=" + flagsToString(flags)
+ + (preCreated ? " (pre-created)" : "")
+ + (partial ? " (partial)" : "")
+ + "]";
+ }
+
+ /** @hide */
+ public static String flagsToString(int flags) {
+ return DebugUtils.flagsToString(UserInfo.class, "FLAG_", flags);
+ }
+
+ @Override
public int describeContents() {
return 0;
}
@@ -281,9 +348,10 @@
dest.writeLong(creationTime);
dest.writeLong(lastLoggedInTime);
dest.writeString(lastLoggedInFingerprint);
- dest.writeInt(partial ? 1 : 0);
+ dest.writeBoolean(partial);
+ dest.writeBoolean(preCreated);
dest.writeInt(profileGroupId);
- dest.writeInt(guestToRemove ? 1 : 0);
+ dest.writeBoolean(guestToRemove);
dest.writeInt(restrictedProfileParentId);
dest.writeInt(profileBadge);
}
@@ -308,9 +376,10 @@
creationTime = source.readLong();
lastLoggedInTime = source.readLong();
lastLoggedInFingerprint = source.readString();
- partial = source.readInt() != 0;
+ partial = source.readBoolean();
+ preCreated = source.readBoolean();
profileGroupId = source.readInt();
- guestToRemove = source.readInt() != 0;
+ guestToRemove = source.readBoolean();
restrictedProfileParentId = source.readInt();
profileBadge = source.readInt();
}
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index b79cf65..38df3175 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -360,8 +360,9 @@
/**
* Retrieve the boolean value for the attribute at <var>index</var>.
* <p>
- * If the attribute is an integer value, this method will return whether
- * it is equal to zero. If the attribute is not a boolean or integer value,
+ * If the attribute is an integer value, this method returns false if the
+ * attribute is equal to zero, and true otherwise.
+ * If the attribute is not a boolean or integer value,
* this method will attempt to coerce it to an integer using
* {@link Integer#decode(String)} and return whether it is equal to zero.
*
diff --git a/core/java/android/net/nsd/NsdManager.java b/core/java/android/net/nsd/NsdManager.java
index 535bf67..64f20b8 100644
--- a/core/java/android/net/nsd/NsdManager.java
+++ b/core/java/android/net/nsd/NsdManager.java
@@ -49,8 +49,8 @@
* limited to a local network over Multicast DNS. DNS service discovery is described at
* http://files.dns-sd.org/draft-cheshire-dnsext-dns-sd.txt
*
- * <p> The API is asynchronous and responses to requests from an application are on listener
- * callbacks on a seperate internal thread.
+ * <p> The API is asynchronous, and responses to requests from an application are on listener
+ * callbacks on a separate internal thread.
*
* <p> There are three main operations the API supports - registration, discovery and resolution.
* <pre>
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 1eda4d9..772109a 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -244,7 +244,8 @@
public static final String BASE_OS = SystemProperties.get("ro.build.version.base_os", "");
/**
- * The user-visible security patch level.
+ * The user-visible security patch level. This value represents the date when the device
+ * most recently applied a security patch.
*/
public static final String SECURITY_PATCH = SystemProperties.get(
"ro.build.version.security_patch", "");
diff --git a/core/java/android/os/FileObserver.java b/core/java/android/os/FileObserver.java
index 4d9ebc2..5b715c0 100644
--- a/core/java/android/os/FileObserver.java
+++ b/core/java/android/os/FileObserver.java
@@ -32,7 +32,7 @@
/**
* Monitors files (using <a href="http://en.wikipedia.org/wiki/Inotify">inotify</a>)
- * to fire an event after files are accessed or changed by by any process on
+ * to fire an event after files are accessed or changed by any process on
* the device (including this one). FileObserver is an abstract class;
* subclasses must implement the event handler {@link #onEvent(int, String)}.
*
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 63641e5..c30491a 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -41,6 +41,7 @@
*/
UserInfo createUser(in String name, int flags);
+ UserInfo preCreateUser(int flags);
UserInfo createProfileForUser(in String name, int flags, int userHandle,
in String[] disallowedPackages);
UserInfo createRestrictedProfile(String name, int parentUserHandle);
@@ -53,7 +54,7 @@
void setUserIcon(int userHandle, in Bitmap icon);
ParcelFileDescriptor getUserIcon(int userHandle);
UserInfo getPrimaryUser();
- List<UserInfo> getUsers(boolean excludeDying);
+ List<UserInfo> getUsers(boolean excludePartial, boolean excludeDying, boolean excludePreCreated);
List<UserInfo> getProfiles(int userHandle, boolean enabledOnly);
int[] getProfileIds(int userId, boolean enabledOnly);
boolean canAddMoreManagedProfiles(int userHandle, boolean allowedToRemoveOne);
@@ -92,6 +93,7 @@
boolean someUserHasSeedAccount(in String accountName, in String accountType);
boolean isManagedProfile(int userId);
boolean isDemoUser(int userId);
+ boolean isPreCreated(int userId);
UserInfo createProfileForUserEvenWhenDisallowed(in String name, int flags, int userHandle,
in String[] disallowedPackages);
boolean isUserUnlockingOrUnlocked(int userId);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index c15618b..1880321 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -37,6 +37,7 @@
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.UserInfo;
+import android.content.pm.UserInfo.UserInfoFlag;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -2006,18 +2007,20 @@
/**
* Creates a user with the specified name and options. For non-admin users, default user
- * restrictions are going to be applied.
- * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+ * restrictions will be applied.
+ *
+ * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
*
* @param name the user's name
- * @param flags flags that identify the type of user and other properties.
+ * @param flags UserInfo flags that identify the type of user and other properties.
* @see UserInfo
*
- * @return the UserInfo object for the created user, or null if the user could not be created.
+ * @return the UserInfo object for the created user, or {@code null} if the user could not be
+ * created.
* @hide
*/
@UnsupportedAppUsage
- public UserInfo createUser(String name, int flags) {
+ public @Nullable UserInfo createUser(@Nullable String name, @UserInfoFlag int flags) {
UserInfo user = null;
try {
user = mService.createUser(name, flags);
@@ -2034,6 +2037,44 @@
}
/**
+ * Pre-creates a user with the specified name and options. For non-admin users, default user
+ * restrictions will be applied.
+ *
+ * <p>This method can be used by OEMs to "warm" up the user creation by pre-creating some users
+ * at the first boot, so they when the "real" user is created (for example,
+ * by {@link #createUser(String, int)} or {@link #createGuest(Context, String)}), it takes
+ * less time.
+ *
+ * <p>This method completes the majority of work necessary for user creation: it
+ * creates user data, CE and DE encryption keys, app data directories, initializes the user and
+ * grants default permissions. When pre-created users become "real" users, only then are
+ * components notified of new user creation by firing user creation broadcasts.
+ *
+ * <p>All pre-created users are removed during system upgrade.
+ *
+ * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+ *
+ * @param flags UserInfo flags that identify the type of user and other properties.
+ * @see UserInfo
+ *
+ * @return the UserInfo object for the created user, or {@code null} if the user could not be
+ * created.
+ *
+ * @throw {@link IllegalArgumentException} if {@code flags} contains
+ * {@link UserInfo#FLAG_MANAGED_PROFILE}.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ public @Nullable UserInfo preCreateUser(@UserInfoFlag int flags) {
+ try {
+ return mService.preCreateUser(flags);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Creates a guest user and configures it.
* @param context an application context
* @param name the name to set for the user
@@ -2340,6 +2381,8 @@
/**
* Return the number of users currently created on the device.
+ * <p>This API is not for use by third-party apps. It requires the {@code MANAGE_USERS}
+ * permission.</p>
*/
public int getUserCount() {
List<UserInfo> users = getUsers();
@@ -2349,15 +2392,26 @@
/**
* Returns information for all users on this device, including ones marked for deletion.
* To retrieve only users that are alive, use {@link #getUsers(boolean)}.
- * <p>
- * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+ *
* @return the list of users that exist on the device.
* @hide
*/
@UnsupportedAppUsage
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
public List<UserInfo> getUsers() {
+ return getUsers(/* excludeDying= */ false);
+ }
+
+ /**
+ * Returns information for all users on this device, based on the filtering parameters.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ public List<UserInfo> getUsers(boolean excludePartial, boolean excludeDying,
+ boolean excludePreCreated) {
try {
- return mService.getUsers(false);
+ return mService.getUsers(excludePartial, excludeDying, excludePreCreated);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -2373,16 +2427,12 @@
@SystemApi
@RequiresPermission(android.Manifest.permission.MANAGE_USERS)
public long[] getSerialNumbersOfUsers(boolean excludeDying) {
- try {
- List<UserInfo> users = mService.getUsers(excludeDying);
- long[] result = new long[users.size()];
- for (int i = 0; i < result.length; i++) {
- result[i] = users.get(i).serialNumber;
- }
- return result;
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
+ List<UserInfo> users = getUsers(excludeDying);
+ long[] result = new long[users.size()];
+ for (int i = 0; i < result.length; i++) {
+ result[i] = users.get(i).serialNumber;
}
+ return result;
}
/**
@@ -2768,11 +2818,8 @@
*/
@UnsupportedAppUsage
public @NonNull List<UserInfo> getUsers(boolean excludeDying) {
- try {
- return mService.getUsers(excludeDying);
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
+ return getUsers(/*excludePartial= */ true, excludeDying,
+ /* excludePreCreated= */ true);
}
/**
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index da4bd27..b393cdb 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7857,6 +7857,19 @@
NON_NEGATIVE_INTEGER_VALIDATOR;
/**
+ * Number of successful "Motion Sense" tap gestures to pause media.
+ * @hide
+ */
+ public static final String AWARE_TAP_PAUSE_GESTURE_COUNT = "aware_tap_pause_gesture_count";
+
+ /**
+ * Number of touch interactions to pause media when a "Motion Sense" gesture could
+ * have been used.
+ * @hide
+ */
+ public static final String AWARE_TAP_PAUSE_TOUCH_COUNT = "aware_tap_pause_touch_count";
+
+ /**
* The current night mode that has been selected by the user. Owned
* and controlled by UiModeManagerService. Constants are as per
* UiModeManager.
@@ -8937,6 +8950,14 @@
new SettingsValidators.DiscreteValueValidator(new String[] {"0", "1", "2"});
/**
+ * Current provider of proximity-based sharing services.
+ * Default value in @string/config_defaultNearbySharingComponent.
+ * No VALIDATOR as this setting will not be backed up.
+ * @hide
+ */
+ public static final String NEARBY_SHARING_COMPONENT = "nearby_sharing_component";
+
+ /**
* Controls whether aware is enabled.
* @hide
*/
@@ -8953,6 +8974,14 @@
private static final Validator AWARE_LOCK_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
/**
+ * Controls whether tap gesture is enabled.
+ * @hide
+ */
+ public static final String TAP_GESTURE = "tap_gesture";
+
+ private static final Validator TAP_GESTURE_VALIDATOR = BOOLEAN_VALIDATOR;
+
+ /**
* This are the settings to be backed up.
*
* NOTE: Settings are backed up and restored in the order they appear
@@ -9034,8 +9063,6 @@
DOZE_PICK_UP_GESTURE,
DOZE_DOUBLE_TAP_GESTURE,
DOZE_TAP_SCREEN_GESTURE,
- DOZE_WAKE_LOCK_SCREEN_GESTURE,
- DOZE_WAKE_DISPLAY_GESTURE,
NFC_PAYMENT_DEFAULT_COMPONENT,
AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN,
FACE_UNLOCK_KEYGUARD_ENABLED,
@@ -9043,9 +9070,6 @@
FACE_UNLOCK_DISMISSES_KEYGUARD,
FACE_UNLOCK_APP_ENABLED,
FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
- ASSIST_GESTURE_ENABLED,
- ASSIST_GESTURE_SILENCE_ALERTS_ENABLED,
- ASSIST_GESTURE_WAKE_ENABLED,
VR_DISPLAY_MODE,
NOTIFICATION_BADGING,
NOTIFICATION_DISMISS_RTL,
@@ -9078,12 +9102,9 @@
TRUST_AGENTS_EXTEND_UNLOCK,
UI_NIGHT_MODE,
LOCK_SCREEN_WHEN_TRUST_LOST,
- SKIP_GESTURE,
SKIP_DIRECTION,
- SILENCE_GESTURE,
THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
NAVIGATION_MODE,
- AWARE_ENABLED,
SKIP_GESTURE_COUNT,
SKIP_TOUCH_COUNT,
SILENCE_ALARMS_GESTURE_COUNT,
@@ -9094,7 +9115,9 @@
SILENCE_TIMER_TOUCH_COUNT,
DARK_MODE_DIALOG_SEEN,
GLOBAL_ACTIONS_PANEL_ENABLED,
- AWARE_LOCK_ENABLED
+ AWARE_LOCK_ENABLED,
+ AWARE_TAP_PAUSE_GESTURE_COUNT,
+ AWARE_TAP_PAUSE_TOUCH_COUNT
};
/**
@@ -9289,6 +9312,9 @@
VALIDATORS.put(UI_NIGHT_MODE, UI_NIGHT_MODE_VALIDATOR);
VALIDATORS.put(GLOBAL_ACTIONS_PANEL_ENABLED, GLOBAL_ACTIONS_PANEL_ENABLED_VALIDATOR);
VALIDATORS.put(AWARE_LOCK_ENABLED, AWARE_LOCK_ENABLED_VALIDATOR);
+ VALIDATORS.put(AWARE_TAP_PAUSE_GESTURE_COUNT, NON_NEGATIVE_INTEGER_VALIDATOR);
+ VALIDATORS.put(AWARE_TAP_PAUSE_TOUCH_COUNT, NON_NEGATIVE_INTEGER_VALIDATOR);
+ VALIDATORS.put(TAP_GESTURE, TAP_GESTURE_VALIDATOR);
}
/**
diff --git a/core/java/android/service/contentsuggestions/ContentSuggestionsService.java b/core/java/android/service/contentsuggestions/ContentSuggestionsService.java
index efc8e87..306b483 100644
--- a/core/java/android/service/contentsuggestions/ContentSuggestionsService.java
+++ b/core/java/android/service/contentsuggestions/ContentSuggestionsService.java
@@ -64,14 +64,23 @@
@Override
public void provideContextImage(int taskId, GraphicBuffer contextImage,
int colorSpaceId, Bundle imageContextRequestExtras) {
+ if (imageContextRequestExtras.containsKey(ContentSuggestionsManager.EXTRA_BITMAP)
+ && contextImage != null) {
+ throw new IllegalArgumentException("Two bitmaps provided; expected one.");
+ }
Bitmap wrappedBuffer = null;
- if (contextImage != null) {
- ColorSpace colorSpace = null;
- if (colorSpaceId >= 0 && colorSpaceId < ColorSpace.Named.values().length) {
- colorSpace = ColorSpace.get(ColorSpace.Named.values()[colorSpaceId]);
+ if (imageContextRequestExtras.containsKey(ContentSuggestionsManager.EXTRA_BITMAP)) {
+ wrappedBuffer = imageContextRequestExtras.getParcelable(
+ ContentSuggestionsManager.EXTRA_BITMAP);
+ } else {
+ if (contextImage != null) {
+ ColorSpace colorSpace = null;
+ if (colorSpaceId >= 0 && colorSpaceId < ColorSpace.Named.values().length) {
+ colorSpace = ColorSpace.get(ColorSpace.Named.values()[colorSpaceId]);
+ }
+ wrappedBuffer = Bitmap.wrapHardwareBuffer(contextImage, colorSpace);
}
- wrappedBuffer = Bitmap.wrapHardwareBuffer(contextImage, colorSpace);
}
mHandler.sendMessage(
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index b44c9d5..93e3ea4 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1514,6 +1514,7 @@
private ArrayList<Notification.Action> mSmartActions;
private ArrayList<CharSequence> mSmartReplies;
private boolean mCanBubble;
+ private boolean mVisuallyInterruptive;
private static final int PARCEL_VERSION = 2;
@@ -1545,6 +1546,7 @@
out.writeTypedList(mSmartActions, flags);
out.writeCharSequenceList(mSmartReplies);
out.writeBoolean(mCanBubble);
+ out.writeBoolean(mVisuallyInterruptive);
}
/** @hide */
@@ -1577,6 +1579,7 @@
mSmartActions = in.createTypedArrayList(Notification.Action.CREATOR);
mSmartReplies = in.readCharSequenceList();
mCanBubble = in.readBoolean();
+ mVisuallyInterruptive = in.readBoolean();
}
@@ -1764,6 +1767,11 @@
}
/** @hide */
+ public boolean visuallyInterruptive() {
+ return mVisuallyInterruptive;
+ }
+
+ /** @hide */
public boolean isNoisy() {
return mNoisy;
}
@@ -1779,7 +1787,8 @@
ArrayList<SnoozeCriterion> snoozeCriteria, boolean showBadge,
int userSentiment, boolean hidden, long lastAudiblyAlertedMs,
boolean noisy, ArrayList<Notification.Action> smartActions,
- ArrayList<CharSequence> smartReplies, boolean canBubble) {
+ ArrayList<CharSequence> smartReplies, boolean canBubble,
+ boolean visuallyInterruptive) {
mKey = key;
mRank = rank;
mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW;
@@ -1800,6 +1809,7 @@
mSmartActions = smartActions;
mSmartReplies = smartReplies;
mCanBubble = canBubble;
+ mVisuallyInterruptive = visuallyInterruptive;
}
/**
@@ -1824,7 +1834,8 @@
other.mNoisy,
other.mSmartActions,
other.mSmartReplies,
- other.mCanBubble);
+ other.mCanBubble,
+ other.mVisuallyInterruptive);
}
/**
diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java
index 7c7223c..451a669 100755
--- a/core/java/android/util/DisplayMetrics.java
+++ b/core/java/android/util/DisplayMetrics.java
@@ -23,9 +23,8 @@
/**
* A structure describing general information about a display, such as its
* size, density, and font scaling.
- * <p>To access the DisplayMetrics members, initialize an object like this:</p>
- * <pre> DisplayMetrics metrics = new DisplayMetrics();
- * getWindowManager().getDefaultDisplay().getMetrics(metrics);</pre>
+ * <p>To access the DisplayMetrics members, retrieve display metrics like this:</p>
+ * <pre>context.getResources().getDisplayMetrics();</pre>
*/
public class DisplayMetrics {
/**
@@ -245,7 +244,7 @@
* this density value will be 1; on a 120 dpi screen it would be .75; etc.
*
* <p>This value does not exactly follow the real screen size (as given by
- * {@link #xdpi} and {@link #ydpi}, but rather is used to scale the size of
+ * {@link #xdpi} and {@link #ydpi}), but rather is used to scale the size of
* the overall UI in steps based on gross changes in the display dpi. For
* example, a 240x320 screen will have a density of 1 even if its width is
* 1.8", 1.3", etc. However, if the screen resolution is increased to
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 9d2040c..b7a128d 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -492,7 +492,7 @@
*
* <p>
* To initiate a layout, call {@link #requestLayout}. This method is typically
- * called by a view on itself when it believes that is can no longer fit within
+ * called by a view on itself when it believes that it can no longer fit within
* its current bounds.
* </p>
*
@@ -2850,7 +2850,7 @@
/**
* Default for the root view. The gravity determines the text alignment, ALIGN_NORMAL,
- * ALIGN_CENTER, or ALIGN_OPPOSITE, which are relative to each paragraph’s text direction.
+ * ALIGN_CENTER, or ALIGN_OPPOSITE, which are relative to each paragraph's text direction.
*
* Use with {@link #setTextAlignment(int)}
*/
@@ -2878,7 +2878,7 @@
public static final int TEXT_ALIGNMENT_CENTER = 4;
/**
- * Align to the start of the view, which is ALIGN_LEFT if the view’s resolved
+ * Align to the start of the view, which is ALIGN_LEFT if the view's resolved
* layoutDirection is LTR, and ALIGN_RIGHT otherwise.
*
* Use with {@link #setTextAlignment(int)}
@@ -2886,7 +2886,7 @@
public static final int TEXT_ALIGNMENT_VIEW_START = 5;
/**
- * Align to the end of the view, which is ALIGN_RIGHT if the view’s resolved
+ * Align to the end of the view, which is ALIGN_RIGHT if the view's resolved
* layoutDirection is LTR, and ALIGN_LEFT otherwise.
*
* Use with {@link #setTextAlignment(int)}
@@ -3689,7 +3689,7 @@
* if the user swipes from the top of the screen.
* <p>When system bars are hidden in immersive mode, they can be revealed temporarily with
* system gestures, such as swiping from the top of the screen. These transient system bars
- * will overlay app’s content, may have some degree of transparency, and will automatically
+ * will overlay app's content, may have some degree of transparency, and will automatically
* hide after a short timeout.
* </p><p>Since this flag is a modifier for {@link #SYSTEM_UI_FLAG_FULLSCREEN} and
* {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}, it only has an effect when used in combination
@@ -10298,7 +10298,7 @@
}
/**
- * Gets the unique identifier of the window in which this View reseides.
+ * Gets the unique identifier of the window in which this View resides.
*
* @return The window accessibility id.
*
@@ -26397,7 +26397,7 @@
/**
* Returns the over-scroll mode for this view. The result will be
- * one of {@link #OVER_SCROLL_ALWAYS} (default), {@link #OVER_SCROLL_IF_CONTENT_SCROLLS}
+ * one of {@link #OVER_SCROLL_ALWAYS}, {@link #OVER_SCROLL_IF_CONTENT_SCROLLS}
* (allow over-scrolling only if the view content is larger than the container),
* or {@link #OVER_SCROLL_NEVER}.
*
@@ -26414,7 +26414,7 @@
/**
* Set the over-scroll mode for this view. Valid over-scroll modes are
- * {@link #OVER_SCROLL_ALWAYS} (default), {@link #OVER_SCROLL_IF_CONTENT_SCROLLS}
+ * {@link #OVER_SCROLL_ALWAYS}, {@link #OVER_SCROLL_IF_CONTENT_SCROLLS}
* (allow over-scrolling only if the view content is larger than the container),
* or {@link #OVER_SCROLL_NEVER}.
*
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 8a1fd62..c67fca5 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -870,6 +870,94 @@
return null;
}
+ private static class StreamingPictureCallbackHandler implements AutoCloseable,
+ HardwareRenderer.PictureCapturedCallback, Runnable {
+ private final HardwareRenderer mRenderer;
+ private final Callable<OutputStream> mCallback;
+ private final Executor mExecutor;
+ private final ReentrantLock mLock = new ReentrantLock(false);
+ private final ArrayDeque<byte[]> mQueue = new ArrayDeque<>(3);
+ private final ByteArrayOutputStream mByteStream = new ByteArrayOutputStream();
+ private boolean mStopListening;
+ private Thread mRenderThread;
+
+ private StreamingPictureCallbackHandler(HardwareRenderer renderer,
+ Callable<OutputStream> callback, Executor executor) {
+ mRenderer = renderer;
+ mCallback = callback;
+ mExecutor = executor;
+ mRenderer.setPictureCaptureCallback(this);
+ }
+
+ @Override
+ public void close() {
+ mLock.lock();
+ mStopListening = true;
+ mLock.unlock();
+ mRenderer.setPictureCaptureCallback(null);
+ }
+
+ @Override
+ public void onPictureCaptured(Picture picture) {
+ mLock.lock();
+ if (mStopListening) {
+ mLock.unlock();
+ mRenderer.setPictureCaptureCallback(null);
+ return;
+ }
+ if (mRenderThread == null) {
+ mRenderThread = Thread.currentThread();
+ }
+ boolean needsInvoke = true;
+ if (mQueue.size() == 3) {
+ mQueue.removeLast();
+ needsInvoke = false;
+ }
+ picture.writeToStream(mByteStream);
+ mQueue.add(mByteStream.toByteArray());
+ mByteStream.reset();
+ mLock.unlock();
+
+ if (needsInvoke) {
+ mExecutor.execute(this);
+ }
+ }
+
+ @Override
+ public void run() {
+ mLock.lock();
+ final byte[] picture = mQueue.poll();
+ final boolean isStopped = mStopListening;
+ mLock.unlock();
+ if (Thread.currentThread() == mRenderThread) {
+ close();
+ throw new IllegalStateException(
+ "ViewDebug#startRenderingCommandsCapture must be given an executor that "
+ + "invokes asynchronously");
+ }
+ if (isStopped) {
+ return;
+ }
+ OutputStream stream = null;
+ try {
+ stream = mCallback.call();
+ } catch (Exception ex) {
+ Log.w("ViewDebug", "Aborting rendering commands capture "
+ + "because callback threw exception", ex);
+ }
+ if (stream != null) {
+ try {
+ stream.write(picture);
+ } catch (IOException ex) {
+ Log.w("ViewDebug", "Aborting rendering commands capture "
+ + "due to IOException writing to output stream", ex);
+ }
+ } else {
+ close();
+ }
+ }
+ }
+
/**
* Begins capturing the entire rendering commands for the view tree referenced by the given
* view. The view passed may be any View in the tree as long as it is attached. That is,
@@ -915,18 +1003,7 @@
}
final HardwareRenderer renderer = attachInfo.mThreadedRenderer;
if (renderer != null) {
- return new PictureCallbackHandler(renderer, (picture -> {
- try {
- OutputStream stream = callback.call();
- if (stream != null) {
- picture.writeToStream(stream);
- return true;
- }
- } catch (Exception ex) {
- // fall through
- }
- return false;
- }), executor);
+ return new StreamingPictureCallbackHandler(renderer, callback, executor);
}
return null;
}
diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java
index c877b9c..f5b0746 100644
--- a/core/java/android/view/animation/AnimationUtils.java
+++ b/core/java/android/view/animation/AnimationUtils.java
@@ -127,7 +127,7 @@
*
* @param context Application context used to access resources
* @param id The resource id of the animation to load
- * @return The animation object reference by the specified id
+ * @return The animation object referenced by the specified id
* @throws NotFoundException when the animation cannot be loaded
*/
public static Animation loadAnimation(Context context, @AnimRes int id)
@@ -208,7 +208,7 @@
*
* @param context Application context used to access resources
* @param id The resource id of the animation to load
- * @return The animation object reference by the specified id
+ * @return The animation controller object referenced by the specified id
* @throws NotFoundException when the layout animation controller cannot be loaded
*/
public static LayoutAnimationController loadLayoutAnimation(Context context, @AnimRes int id)
@@ -331,7 +331,7 @@
*
* @param context Application context used to access resources
* @param id The resource id of the animation to load
- * @return The animation object reference by the specified id
+ * @return The interpolator object referenced by the specified id
* @throws NotFoundException
*/
public static Interpolator loadInterpolator(Context context, @AnimRes @InterpolatorRes int id)
@@ -361,7 +361,7 @@
*
* @param res The resources
* @param id The resource id of the animation to load
- * @return The interpolator object reference by the specified id
+ * @return The interpolator object referenced by the specified id
* @throws NotFoundException
* @hide
*/
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 032af1c..d3618ad 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -92,7 +92,10 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
@@ -422,6 +425,13 @@
int mCursorCandEnd;
/**
+ * Initial startInput with {@link StartInputReason.WINDOW_FOCUS_GAIN} is executed
+ * in a background thread. Later, if there is an actual startInput it will wait on
+ * main thread till the background thread completes.
+ */
+ private CompletableFuture<Void> mWindowFocusGainFuture;
+
+ /**
* The instance that has previously been sent to the input method.
*/
private CursorAnchorInfo mCursorAnchorInfo = null;
@@ -645,14 +655,14 @@
} catch (RemoteException e) {
}
}
- // Check focus again in case that "onWindowFocus" is called before
- // handling this message.
- if (mServedView != null && canStartInput(mServedView)) {
- if (checkFocusNoStartInput(mRestartOnNextWindowFocus)) {
- final int reason = active ? StartInputReason.ACTIVATED_BY_IMMS
- : StartInputReason.DEACTIVATED_BY_IMMS;
- startInputInner(reason, null, 0, 0, 0);
- }
+ }
+ // Check focus again in case that "onWindowFocus" is called before
+ // handling this message.
+ if (mServedView != null && canStartInput(mServedView)) {
+ if (checkFocusNoStartInput(mRestartOnNextWindowFocus)) {
+ final int reason = active ? StartInputReason.ACTIVATED_BY_IMMS
+ : StartInputReason.DEACTIVATED_BY_IMMS;
+ startInputInner(reason, null, 0, 0, 0);
}
}
return;
@@ -1215,6 +1225,10 @@
*/
void clearBindingLocked() {
if (DEBUG) Log.v(TAG, "Clearing binding!");
+ if (mWindowFocusGainFuture != null) {
+ mWindowFocusGainFuture.cancel(false /* mayInterruptIfRunning */);
+ mWindowFocusGainFuture = null;
+ }
clearConnectionLocked();
setInputChannelLocked(null);
mBindSequence = -1;
@@ -1598,6 +1612,18 @@
boolean startInputInner(@StartInputReason int startInputReason,
@Nullable IBinder windowGainingFocus, @StartInputFlags int startInputFlags,
@SoftInputModeFlags int softInputMode, int windowFlags) {
+ if (startInputReason != StartInputReason.WINDOW_FOCUS_GAIN
+ && mWindowFocusGainFuture != null) {
+ try {
+ mWindowFocusGainFuture.get();
+ } catch (ExecutionException | InterruptedException e) {
+ // do nothing
+ } catch (CancellationException e) {
+ // window no longer has focus.
+ return true;
+ }
+ }
+
final View view;
synchronized (mH) {
view = mServedView;
@@ -1951,31 +1977,38 @@
startInputFlags |= StartInputFlags.FIRST_WINDOW_FOCUS_GAIN;
}
- if (checkFocusNoStartInput(forceNewFocus)) {
- // We need to restart input on the current focus view. This
- // should be done in conjunction with telling the system service
- // about the window gaining focus, to help make the transition
- // smooth.
- if (startInputInner(StartInputReason.WINDOW_FOCUS_GAIN, rootView.getWindowToken(),
- startInputFlags, softInputMode, windowFlags)) {
- return;
- }
+ final boolean forceNewFocus1 = forceNewFocus;
+ final int startInputFlags1 = startInputFlags;
+ if (mWindowFocusGainFuture != null) {
+ mWindowFocusGainFuture.cancel(false/* mayInterruptIfRunning */);
}
+ mWindowFocusGainFuture = CompletableFuture.runAsync(() -> {
+ if (checkFocusNoStartInput(forceNewFocus1)) {
+ // We need to restart input on the current focus view. This
+ // should be done in conjunction with telling the system service
+ // about the window gaining focus, to help make the transition
+ // smooth.
+ if (startInputInner(StartInputReason.WINDOW_FOCUS_GAIN, rootView.getWindowToken(),
+ startInputFlags1, softInputMode, windowFlags)) {
+ return;
+ }
+ }
- // For some reason we didn't do a startInput + windowFocusGain, so
- // we'll just do a window focus gain and call it a day.
- synchronized (mH) {
- try {
- if (DEBUG) Log.v(TAG, "Reporting focus gain, without startInput");
- mService.startInputOrWindowGainedFocus(
- StartInputReason.WINDOW_FOCUS_GAIN_REPORT_ONLY, mClient,
- rootView.getWindowToken(), startInputFlags, softInputMode, windowFlags,
- null, null, 0 /* missingMethodFlags */,
- rootView.getContext().getApplicationInfo().targetSdkVersion);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ // For some reason we didn't do a startInput + windowFocusGain, so
+ // we'll just do a window focus gain and call it a day.
+ synchronized (mH) {
+ try {
+ if (DEBUG) Log.v(TAG, "Reporting focus gain, without startInput");
+ mService.startInputOrWindowGainedFocus(
+ StartInputReason.WINDOW_FOCUS_GAIN_REPORT_ONLY, mClient,
+ rootView.getWindowToken(), startInputFlags1, softInputMode, windowFlags,
+ null, null, 0 /* missingMethodFlags */,
+ rootView.getContext().getApplicationInfo().targetSdkVersion);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
- }
+ });
}
/** @hide */
@@ -1990,6 +2023,10 @@
// If the mCurRootView is losing window focus, release the strong reference to it
// so as not to prevent it from being garbage-collected.
mCurRootView = null;
+ if (mWindowFocusGainFuture != null) {
+ mWindowFocusGainFuture.cancel(false /* mayInterruptIfRunning */);
+ mWindowFocusGainFuture = null;
+ }
} else {
if (DEBUG) {
Log.v(TAG, "Ignoring onPreWindowFocus()."
diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java
index 23d1237..3824c22 100644
--- a/core/java/android/webkit/CookieManager.java
+++ b/core/java/android/webkit/CookieManager.java
@@ -22,7 +22,10 @@
/**
* Manages the cookies used by an application's {@link WebView} instances.
- * Cookies are manipulated according to RFC2109.
+ * <p>
+ * CookieManager represents cookies as strings in the same format as the
+ * HTTP {@code Cookie} and {@code Set-Cookie} header fields (defined in
+ * <a href="https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03">RFC6265bis</a>).
*/
public abstract class CookieManager {
/**
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index 4db6308..f8522ed 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -71,11 +71,24 @@
}
/**
- * Notify the host application that the current page has entered full
- * screen mode. The host application must show the custom View which
- * contains the web contents — video or other HTML content —
- * in full screen mode. Also see "Full screen support" documentation on
- * {@link WebView}.
+ * Notify the host application that the current page has entered full screen mode. After this
+ * call, web content will no longer be rendered in the WebView, but will instead be rendered
+ * in {@code view}. The host application should add this View to a Window which is configured
+ * with {@link android.view.WindowManager.LayoutParams#FLAG_FULLSCREEN} flag in order to
+ * actually display this web content full screen.
+ *
+ * <p>The application may explicitly exit fullscreen mode by invoking {@code callback} (ex. when
+ * the user presses the back button). However, this is generally not necessary as the web page
+ * will often show its own UI to close out of fullscreen. Regardless of how the WebView exits
+ * fullscreen mode, WebView will invoke {@link #onHideCustomView()}, signaling for the
+ * application to remove the custom View.
+ *
+ * <p>If this method is not overridden, WebView will report to the web page it does not support
+ * fullscreen mode and will not honor the web page's request to run in fullscreen mode.
+ *
+ * <p class="note"><b>Note:</b> if overriding this method, the application must also override
+ * {@link #onHideCustomView()}.
+ *
* @param view is the View object to be shown.
* @param callback invoke this callback to request the page to exit
* full screen mode.
@@ -98,10 +111,13 @@
CustomViewCallback callback) {};
/**
- * Notify the host application that the current page has exited full
- * screen mode. The host application must hide the custom View, ie. the
- * View passed to {@link #onShowCustomView} when the content entered fullscreen.
- * Also see "Full screen support" documentation on {@link WebView}.
+ * Notify the host application that the current page has exited full screen mode. The host
+ * application must hide the custom View (the View which was previously passed to {@link
+ * #onShowCustomView(View, CustomViewCallback) onShowCustomView()}). After this call, web
+ * content will render in the original WebView again.
+ *
+ * <p class="note"><b>Note:</b> if overriding this method, the application must also override
+ * {@link #onShowCustomView(View, CustomViewCallback) onShowCustomView()}.
*/
public void onHideCustomView() {}
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 7282008..2895621 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -328,6 +328,9 @@
* <p>
* The built-in mechanisms are the only currently supported zoom
* mechanisms, so it is recommended that this setting is always enabled.
+ * However, on-screen zoom controls are deprecated in Android (see
+ * {@link android.widget.ZoomButtonsController}) so it's recommended to
+ * disable {@link #setDisplayZoomControls}.
*
* @param enabled whether the WebView should use its built-in zoom mechanisms
*/
@@ -347,7 +350,9 @@
/**
* Sets whether the WebView should display on-screen zoom controls when
* using the built-in zoom mechanisms. See {@link #setBuiltInZoomControls}.
- * The default is {@code true}.
+ * The default is {@code true}. However, on-screen zoom controls are deprecated
+ * in Android (see {@link android.widget.ZoomButtonsController}) so it's
+ * recommended to set this to {@code false}.
*
* @param enabled whether the WebView should display on-screen zoom controls
*/
diff --git a/core/java/android/widget/ArrayAdapter.java b/core/java/android/widget/ArrayAdapter.java
index 2bf1ba5..f7225d0 100644
--- a/core/java/android/widget/ArrayAdapter.java
+++ b/core/java/android/widget/ArrayAdapter.java
@@ -49,9 +49,6 @@
* To customize what type of view is used for the data object,
* override {@link #getView(int, View, ViewGroup)}
* and inflate a view resource.
- * For a code example, see
- * the <a href="https://github.com/googlesamples/android-CustomChoiceList/#readme">
- * CustomChoiceList</a> sample.
* </p>
* <p>
* For an example of using an array adapter with a ListView, see the
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 2f44d6e..b732b7e 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -120,10 +120,6 @@
* <a href="{@docRoot}training/improving-layouts/smooth-scrolling.html">
* Making ListView Scrolling Smooth</a> for more ways to ensure a smooth user experience.</p>
*
- * <p>For a more complete example of creating a custom adapter, see the
- * <a href="{@docRoot}samples/CustomChoiceList/index.html">
- * Custom Choice List</a> sample app.</p>
- *
* <p>To specify an action when a user clicks or taps on a single list item, see
* <a href="{@docRoot}guide/topics/ui/declaring-layout.html#HandlingUserSelections">
* Handling click events</a>.</p>
diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java
index b385534..4640c19 100644
--- a/core/java/android/widget/NumberPicker.java
+++ b/core/java/android/widget/NumberPicker.java
@@ -1268,12 +1268,12 @@
* current value is set to the {@link NumberPicker#getMaxValue()} value.
* </p>
* <p>
- * If the argument is less than the {@link NumberPicker#getMaxValue()} and
+ * If the argument is more than the {@link NumberPicker#getMaxValue()} and
* {@link NumberPicker#getWrapSelectorWheel()} is <code>false</code> the
* current value is set to the {@link NumberPicker#getMaxValue()} value.
* </p>
* <p>
- * If the argument is less than the {@link NumberPicker#getMaxValue()} and
+ * If the argument is more than the {@link NumberPicker#getMaxValue()} and
* {@link NumberPicker#getWrapSelectorWheel()} is <code>true</code> the
* current value is set to the {@link NumberPicker#getMinValue()} value.
* </p>
diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java
index b9da307..facb7d0 100644
--- a/core/java/android/widget/RelativeLayout.java
+++ b/core/java/android/widget/RelativeLayout.java
@@ -72,7 +72,7 @@
*
* <p>This behavior has been preserved for apps that set <code>android:targetSdkVersion="17"</code>
* or older in their manifest's <code>uses-sdk</code> tag for compatibility. Apps targeting SDK
- * version 18 or newer will receive the correct behavior</p>
+ * version 18 or newer will receive the correct behavior.</p>
*
* <p>See the <a href="{@docRoot}guide/topics/ui/layout/relative.html">Relative
* Layout</a> guide.</p>
diff --git a/core/java/com/android/internal/app/AbstractResolverComparator.java b/core/java/com/android/internal/app/AbstractResolverComparator.java
index 9ac979b..3a6a71d 100644
--- a/core/java/com/android/internal/app/AbstractResolverComparator.java
+++ b/core/java/com/android/internal/app/AbstractResolverComparator.java
@@ -30,6 +30,7 @@
import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
+import java.text.Collator;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
@@ -37,7 +38,7 @@
/**
* Used to sort resolved activities in {@link ResolverListController}.
*/
-abstract class AbstractResolverComparator implements Comparator<ResolvedComponentInfo> {
+public abstract class AbstractResolverComparator implements Comparator<ResolvedComponentInfo> {
private static final int NUM_OF_TOP_ANNOTATIONS_TO_USE = 3;
private static final boolean DEBUG = false;
@@ -62,6 +63,8 @@
// predicting ranking scores.
private static final int WATCHDOG_TIMEOUT_MILLIS = 500;
+ private final Comparator<ResolveInfo> mAzComparator;
+
protected final Handler mHandler = new Handler(Looper.getMainLooper()) {
public void handleMessage(Message msg) {
switch (msg.what) {
@@ -90,7 +93,7 @@
}
};
- AbstractResolverComparator(Context context, Intent intent) {
+ public AbstractResolverComparator(Context context, Intent intent) {
String scheme = intent.getScheme();
mHttp = "http".equals(scheme) || "https".equals(scheme);
mContentType = intent.getType();
@@ -100,6 +103,7 @@
mDefaultBrowserPackageName = mHttp
? mPm.getDefaultBrowserPackageNameAsUser(UserHandle.myUserId())
: null;
+ mAzComparator = new AzInfoComparator(context);
}
// get annotations of content from intent.
@@ -168,6 +172,20 @@
return lhsSpecific ? -1 : 1;
}
}
+
+ final boolean lPinned = lhsp.isPinned();
+ final boolean rPinned = rhsp.isPinned();
+
+ // Pinned items always receive priority.
+ if (lPinned && !rPinned) {
+ return -1;
+ } else if (!lPinned && rPinned) {
+ return 1;
+ } else if (lPinned && rPinned) {
+ // If both items are pinned, resolve the tie alphabetically.
+ return mAzComparator.compare(lhsp.getResolveInfoAt(0), rhsp.getResolveInfoAt(0));
+ }
+
return compare(lhs, rhs);
}
@@ -258,4 +276,25 @@
}
return false;
}
+
+ /**
+ * Sort intents alphabetically based on package name.
+ */
+ class AzInfoComparator implements Comparator<ResolveInfo> {
+ Collator mCollator;
+ AzInfoComparator(Context context) {
+ mCollator = Collator.getInstance(context.getResources().getConfiguration().locale);
+ }
+
+ @Override
+ public int compare(ResolveInfo lhsp, ResolveInfo rhsp) {
+ if (lhsp == null) {
+ return -1;
+ } else if (rhsp == null) {
+ return 1;
+ }
+ return mCollator.compare(lhsp.activityInfo.packageName, rhsp.activityInfo.packageName);
+ }
+ }
+
}
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 102ba5c..346361a 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -44,6 +44,7 @@
import android.content.IntentSender;
import android.content.IntentSender.SendIntentException;
import android.content.ServiceConnection;
+import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.LabeledIntent;
@@ -69,6 +70,7 @@
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
@@ -78,10 +80,12 @@
import android.os.ResultReceiver;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.storage.StorageManager;
import android.provider.DeviceConfig;
import android.provider.DocumentsContract;
import android.provider.Downloads;
import android.provider.OpenableColumns;
+import android.provider.Settings;
import android.service.chooser.ChooserTarget;
import android.service.chooser.ChooserTargetService;
import android.service.chooser.IChooserTargetResult;
@@ -104,6 +108,7 @@
import android.view.animation.DecelerateInterpolator;
import android.widget.AbsListView;
import android.widget.BaseAdapter;
+import android.widget.Button;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
@@ -120,9 +125,11 @@
import com.google.android.collect.Lists;
+import java.io.File;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.net.URISyntaxException;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Arrays;
@@ -156,6 +163,9 @@
private static final String PREF_NUM_SHEET_EXPANSIONS = "pref_num_sheet_expansions";
+ private static final String CHIP_LABEL_METADATA_KEY = "android.service.chooser.chip_label";
+ private static final String CHIP_ICON_METADATA_KEY = "android.service.chooser.chip_icon";
+
private static final boolean DEBUG = false;
/**
@@ -256,6 +266,9 @@
private static final int MAX_EXTRA_INITIAL_INTENTS = 2;
private static final int MAX_EXTRA_CHOOSER_TARGETS = 2;
+ private SharedPreferences mPinnedSharedPrefs;
+ private static final String PINNED_SHARED_PREFS_NAME = "chooser_pin_settings";
+
private boolean mListViewDataChanged = false;
@Retention(SOURCE)
@@ -519,6 +532,15 @@
mIsSuccessfullySelected = false;
Intent intent = getIntent();
Parcelable targetParcelable = intent.getParcelableExtra(Intent.EXTRA_INTENT);
+ if (targetParcelable instanceof Uri) {
+ try {
+ targetParcelable = Intent.parseUri(targetParcelable.toString(),
+ Intent.URI_INTENT_SCHEME);
+ } catch (URISyntaxException ex) {
+ // doesn't parse as an intent; let the next test fail and error out
+ }
+ }
+
if (!(targetParcelable instanceof Intent)) {
Log.w("ChooserActivity", "Target is not an intent: " + targetParcelable);
finish();
@@ -600,6 +622,8 @@
Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER);
setSafeForwardingMode(true);
+ mPinnedSharedPrefs = getPinnedSharedPrefs(this);
+
pa = intent.getParcelableArrayExtra(Intent.EXTRA_EXCLUDE_COMPONENTS);
if (pa != null) {
ComponentName[] names = new ComponentName[pa.length];
@@ -731,6 +755,23 @@
}
}
+
+ static SharedPreferences getPinnedSharedPrefs(Context context) {
+ // The code below is because in the android:ui process, no one can hear you scream.
+ // The package info in the context isn't initialized in the way it is for normal apps,
+ // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we
+ // build the path manually below using the same policy that appears in ContextImpl.
+ // This fails silently under the hood if there's a problem, so if we find ourselves in
+ // the case where we don't have access to credential encrypted storage we just won't
+ // have our pinned target info.
+ final File prefsFile = new File(new File(
+ Environment.getDataUserCePackageDirectory(StorageManager.UUID_PRIVATE_INTERNAL,
+ context.getUserId(), context.getPackageName()),
+ "shared_prefs"),
+ PINNED_SHARED_PREFS_NAME + ".xml");
+ return context.getSharedPreferences(prefsFile, MODE_PRIVATE);
+ }
+
/**
* Returns true if app prediction service is defined and the component exists on device.
*/
@@ -868,6 +909,85 @@
}
}
+ private ComponentName getNearbySharingComponent() {
+ String nearbyComponent = Settings.Secure.getString(
+ getContentResolver(),
+ Settings.Secure.NEARBY_SHARING_COMPONENT);
+ if (TextUtils.isEmpty(nearbyComponent)) {
+ nearbyComponent = getString(R.string.config_defaultNearbySharingComponent);
+ }
+ if (TextUtils.isEmpty(nearbyComponent)) {
+ return null;
+ }
+ return ComponentName.unflattenFromString(nearbyComponent);
+ }
+
+ private TargetInfo getNearbySharingTarget(Intent originalIntent) {
+ final ComponentName cn = getNearbySharingComponent();
+ if (cn == null) return null;
+
+ final Intent resolveIntent = new Intent();
+ resolveIntent.setComponent(cn);
+ final ResolveInfo ri = getPackageManager().resolveActivity(resolveIntent, 0);
+ if (ri == null) {
+ Log.e(TAG, "Device-specified nearby sharing component (" + cn
+ + ") not available");
+ return null;
+ }
+
+ // TODO(b/144290152): CHIP_LABEL_METADATA_KEY / CHIP_ICON_METADATA_KEY
+
+ CharSequence name = ri.loadLabel(getPackageManager());
+
+ final DisplayResolveInfo dri = new DisplayResolveInfo(
+ originalIntent, ri, name, "", null);
+ dri.setDisplayIcon(ri.loadIcon(getPackageManager()));
+ return dri;
+ }
+
+ private Button createActionButton(Drawable icon, CharSequence title, View.OnClickListener r) {
+ Button b = (Button) LayoutInflater.from(this).inflate(R.layout.chooser_action_button, null);
+ if (icon != null) {
+ final int size = getResources()
+ .getDimensionPixelSize(R.dimen.chooser_action_button_icon_size);
+ icon.setBounds(0, 0, size, size);
+ b.setCompoundDrawablesRelative(icon, null, null, null);
+ }
+ b.setText(title);
+ b.setOnClickListener(r);
+ return b;
+ }
+
+ private Button createCopyButton() {
+ final Button b = createActionButton(
+ getDrawable(R.drawable.ic_menu_copy_material),
+ getString(R.string.copy), this::onCopyButtonClicked);
+ b.setId(R.id.chooser_copy_button);
+ return b;
+ }
+
+ private @Nullable Button createNearbyButton(Intent originalIntent) {
+ final TargetInfo ti = getNearbySharingTarget(originalIntent);
+ if (ti == null) return null;
+
+ return createActionButton(
+ ti.getDisplayIcon(),
+ ti.getDisplayLabel(),
+ (View unused) -> safelyStartActivity(ti)
+ );
+ }
+
+ private void addActionButton(ViewGroup parent, Button b) {
+ if (b == null) return;
+ final ViewGroup.MarginLayoutParams lp = new ViewGroup.MarginLayoutParams(
+ LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT
+ );
+ final int gap = getResources().getDimensionPixelSize(R.dimen.resolver_icon_margin) / 2;
+ lp.setMarginsRelative(gap, 0, gap, 0);
+ parent.addView(b, lp);
+ }
+
private ViewGroup displayContentPreview(@ContentPreviewType int previewType,
Intent targetIntent, LayoutInflater layoutInflater, ViewGroup convertView,
ViewGroup parent) {
@@ -901,8 +1021,10 @@
ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
R.layout.chooser_grid_preview_text, parent, false);
- contentPreviewLayout.findViewById(R.id.copy_button).setOnClickListener(
- this::onCopyButtonClicked);
+ final ViewGroup actionRow =
+ (ViewGroup) contentPreviewLayout.findViewById(R.id.chooser_action_row);
+ addActionButton(actionRow, createCopyButton());
+ addActionButton(actionRow, createNearbyButton(targetIntent));
CharSequence sharingText = targetIntent.getCharSequenceExtra(Intent.EXTRA_TEXT);
if (sharingText == null) {
@@ -1060,7 +1182,8 @@
// TODO(b/120417119): Disable file copy until after moving to sysui,
// due to permissions issues
- contentPreviewLayout.findViewById(R.id.file_copy_button).setVisibility(View.GONE);
+ //((ViewGroup) contentPreviewLayout.findViewById(R.id.chooser_action_row))
+ // .addView(createCopyButton());
String action = targetIntent.getAction();
if (Intent.ACTION_SEND.equals(action)) {
@@ -1277,9 +1400,10 @@
}
ComponentName name = ri.activityInfo.getComponentName();
+ boolean pinned = mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
ResolverTargetActionsDialogFragment f =
new ResolverTargetActionsDialogFragment(ri.loadLabel(getPackageManager()),
- name);
+ name, pinned);
f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
}
@@ -1511,7 +1635,7 @@
}
return new IntentFilter(intent.getAction(), dataString);
} catch (Exception e) {
- Log.e(TAG, "failed to get target intent filter " + e);
+ Log.e(TAG, "failed to get target intent filter", e);
return null;
}
}
@@ -1956,6 +2080,12 @@
}
return false;
}
+
+ @Override
+ public boolean isComponentPinned(ComponentName name) {
+ return mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
+ }
+
}
@Override
@@ -2089,6 +2219,10 @@
public boolean isSuspended() {
return false;
}
+
+ public boolean isPinned() {
+ return false;
+ }
}
final class PlaceHolderTargetInfo extends NotSelectableTargetInfo {
@@ -2177,6 +2311,10 @@
return mIsSuspended;
}
+ public boolean isPinned() {
+ return mSourceInfo != null && mSourceInfo.isPinned();
+ }
+
/**
* Since ShortcutInfos are returned by ShortcutManager, we can cache the shortcuts and skip
* the call to LauncherApps#getShortcuts(ShortcutQuery).
@@ -2428,6 +2566,12 @@
}
}
+ @Override
+ protected boolean shouldAddFooterView() {
+ // To accommodate for window insets
+ return true;
+ }
+
public class ChooserListAdapter extends ResolveListAdapter {
public static final int TARGET_BAD = -1;
public static final int TARGET_CALLER = 0;
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 7d19eb6..4c379d2 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -386,30 +386,47 @@
finish();
}
+ /**
+ * Numerous layouts are supported, each with optional ViewGroups.
+ * Make sure the inset gets added to the correct View, using
+ * a footer for Lists so it can properly scroll under the navbar.
+ */
+ protected boolean shouldAddFooterView() {
+ if (useLayoutWithDefault()) return true;
+
+ View buttonBar = findViewById(R.id.button_bar);
+ if (buttonBar == null || buttonBar.getVisibility() == View.GONE) return true;
+
+ return false;
+ }
+
protected WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
mSystemWindowInsets = insets.getSystemWindowInsets();
mResolverDrawerLayout.setPadding(mSystemWindowInsets.left, mSystemWindowInsets.top,
mSystemWindowInsets.right, 0);
- View emptyView = findViewById(R.id.empty);
- if (emptyView != null) {
- emptyView.setPadding(0, 0, 0, mSystemWindowInsets.bottom
- + getResources().getDimensionPixelSize(
- R.dimen.chooser_edge_margin_normal) * 2);
- }
-
- if (mFooterSpacer == null) {
- mFooterSpacer = new Space(getApplicationContext());
- } else {
- ((ListView) mAdapterView).removeFooterView(mFooterSpacer);
- }
- mFooterSpacer.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT,
- mSystemWindowInsets.bottom));
- ((ListView) mAdapterView).addFooterView(mFooterSpacer);
-
resetButtonBar();
+ // Need extra padding so the list can fully scroll up
+ if (shouldAddFooterView()) {
+ if (mFooterSpacer == null) {
+ mFooterSpacer = new Space(getApplicationContext());
+ } else {
+ ((ListView) mAdapterView).removeFooterView(mFooterSpacer);
+ }
+ mFooterSpacer.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT,
+ mSystemWindowInsets.bottom));
+ ((ListView) mAdapterView).addFooterView(mFooterSpacer);
+ } else {
+ View emptyView = findViewById(R.id.empty);
+ if (emptyView != null) {
+ emptyView.setPadding(0, 0, 0, mSystemWindowInsets.bottom
+ + getResources().getDimensionPixelSize(
+ R.dimen.chooser_edge_margin_normal) * 2);
+ }
+ }
+
return insets.consumeSystemWindowInsets();
}
@@ -565,7 +582,7 @@
intent.getData().getHost(),
mAdapter.getFilteredItem().getDisplayLabel());
} else if (mAdapter.areAllTargetsBrowsers()) {
- dialogTitle = getString(ActionTitle.BROWSABLE_TITLE_RES);
+ dialogTitle = getString(ActionTitle.BROWSABLE_TITLE_RES);
} else {
dialogTitle = getString(ActionTitle.BROWSABLE_HOST_TITLE_RES,
intent.getData().getHost());
@@ -1308,6 +1325,7 @@
// In case this method is called again (due to activity recreation), avoid adding a new
// header if one is already present.
if (useHeader && listView != null && listView.getHeaderViewsCount() == 0) {
+ listView.setHeaderDividersEnabled(true);
listView.addHeaderView(LayoutInflater.from(this).inflate(
R.layout.resolver_different_item_header, listView, false));
}
@@ -1350,11 +1368,13 @@
final ViewGroup buttonLayout = findViewById(R.id.button_bar);
if (buttonLayout != null) {
buttonLayout.setVisibility(View.VISIBLE);
- int inset = mSystemWindowInsets != null ? mSystemWindowInsets.bottom : 0;
- buttonLayout.setPadding(buttonLayout.getPaddingLeft(), buttonLayout.getPaddingTop(),
- buttonLayout.getPaddingRight(), getResources().getDimensionPixelSize(
- R.dimen.resolver_button_bar_spacing) + inset);
+ if (!useLayoutWithDefault()) {
+ int inset = mSystemWindowInsets != null ? mSystemWindowInsets.bottom : 0;
+ buttonLayout.setPadding(buttonLayout.getPaddingLeft(), buttonLayout.getPaddingTop(),
+ buttonLayout.getPaddingRight(), getResources().getDimensionPixelSize(
+ R.dimen.resolver_button_bar_spacing) + inset);
+ }
mOnceButton = (Button) buttonLayout.findViewById(R.id.button_once);
mAlwaysButton = (Button) buttonLayout.findViewById(R.id.button_always);
@@ -1411,6 +1431,7 @@
private final Intent mResolvedIntent;
private final List<Intent> mSourceIntents = new ArrayList<>();
private boolean mIsSuspended;
+ private boolean mPinned = false;
public DisplayResolveInfo(Intent originalIntent, ResolveInfo pri, CharSequence pLabel,
CharSequence pInfo, Intent pOrigIntent) {
@@ -1514,6 +1535,15 @@
public boolean isSuspended() {
return mIsSuspended;
}
+
+ @Override
+ public boolean isPinned() {
+ return mPinned;
+ }
+
+ public void setPinned(boolean pinned) {
+ mPinned = pinned;
+ }
}
List<DisplayResolveInfo> getDisplayList() {
@@ -1614,6 +1644,11 @@
* @return true if this target can be selected by the user
*/
boolean isSuspended();
+
+ /**
+ * @return true if this target should be pinned to the front by the request of the user
+ */
+ boolean isPinned();
}
public class ResolveListAdapter extends BaseAdapter {
@@ -1920,6 +1955,10 @@
final Intent replaceIntent = getReplacementIntent(add.activityInfo, intent);
final DisplayResolveInfo dri = new DisplayResolveInfo(intent, add, roLabel,
extraInfo, replaceIntent);
+ dri.setPinned(rci.isPinned());
+ if (rci.isPinned()) {
+ Log.i(TAG, "Pinned item: " + rci.name);
+ }
addResolveInfo(dri);
if (replaceIntent == intent) {
// Only add alternates if we didn't get a specific replacement from
@@ -2061,7 +2100,9 @@
CharSequence subLabel = info.getExtendedInfo();
if (TextUtils.equals(label, subLabel)) subLabel = null;
- if (!TextUtils.equals(holder.text2.getText(), subLabel)) {
+ if (!TextUtils.equals(holder.text2.getText(), subLabel)
+ && !TextUtils.isEmpty(subLabel)) {
+ holder.text2.setVisibility(View.VISIBLE);
holder.text2.setText(subLabel);
}
@@ -2086,6 +2127,7 @@
public final ComponentName name;
private final List<Intent> mIntents = new ArrayList<>();
private final List<ResolveInfo> mResolveInfos = new ArrayList<>();
+ private boolean mPinned;
public ResolvedComponentInfo(ComponentName name, Intent intent, ResolveInfo info) {
this.name = name;
@@ -2126,6 +2168,15 @@
}
return -1;
}
+
+ public boolean isPinned() {
+ return mPinned;
+ }
+
+ public void setPinned(boolean pinned) {
+ mPinned = pinned;
+ }
+
}
static class ViewHolder {
diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java
index 5f92cdd..7efd5e1 100644
--- a/core/java/com/android/internal/app/ResolverListController.java
+++ b/core/java/com/android/internal/app/ResolverListController.java
@@ -156,11 +156,22 @@
newInfo.activityInfo.packageName, newInfo.activityInfo.name);
final ResolverActivity.ResolvedComponentInfo rci =
new ResolverActivity.ResolvedComponentInfo(name, intent, newInfo);
+ rci.setPinned(isComponentPinned(name));
into.add(rci);
}
}
}
+
+ /**
+ * Whether this component is pinned by the user. Always false for resolver; overridden in
+ * Chooser.
+ */
+ public boolean isComponentPinned(ComponentName name) {
+ return false;
+ }
+
+
// Filter out any activities that the launched uid does not have permission for.
// To preserve the inputList, optionally will return the original list if any modification has
// been made.
diff --git a/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java b/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java
index a49240c..df91c4a 100644
--- a/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java
+++ b/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java
@@ -23,6 +23,7 @@
import android.content.ComponentName;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
@@ -36,26 +37,33 @@
implements DialogInterface.OnClickListener {
private static final String NAME_KEY = "componentName";
private static final String TITLE_KEY = "title";
+ private static final String PINNED_KEY = "pinned";
// Sync with R.array.resolver_target_actions_* resources
- private static final int APP_INFO_INDEX = 0;
+ private static final int TOGGLE_PIN_INDEX = 0;
+ private static final int APP_INFO_INDEX = 1;
public ResolverTargetActionsDialogFragment() {
}
- public ResolverTargetActionsDialogFragment(CharSequence title, ComponentName name) {
+ public ResolverTargetActionsDialogFragment(CharSequence title, ComponentName name,
+ boolean pinned) {
Bundle args = new Bundle();
args.putCharSequence(TITLE_KEY, title);
args.putParcelable(NAME_KEY, name);
+ args.putBoolean(PINNED_KEY, pinned);
setArguments(args);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Bundle args = getArguments();
+ final int itemRes = args.getBoolean(PINNED_KEY, false)
+ ? R.array.resolver_target_actions_unpin
+ : R.array.resolver_target_actions_pin;
return new Builder(getContext())
.setCancelable(true)
- .setItems(R.array.resolver_target_actions, this)
+ .setItems(itemRes, this)
.setTitle(args.getCharSequence(TITLE_KEY))
.create();
}
@@ -65,6 +73,19 @@
final Bundle args = getArguments();
ComponentName name = args.getParcelable(NAME_KEY);
switch (which) {
+ case TOGGLE_PIN_INDEX:
+ SharedPreferences sp = ChooserActivity.getPinnedSharedPrefs(getContext());
+ final String key = name.flattenToString();
+ boolean currentVal = sp.getBoolean(name.flattenToString(), false);
+ if (currentVal) {
+ sp.edit().remove(key).apply();
+ } else {
+ sp.edit().putBoolean(key, true).apply();
+ }
+
+ // Force the chooser to requery and resort things
+ getActivity().recreate();
+ break;
case APP_INFO_INDEX:
Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
.setData(Uri.fromParts("package", name.getPackageName(), null))
diff --git a/core/java/com/android/internal/car/ICarStatsService.aidl b/core/java/com/android/internal/car/ICarStatsService.aidl
new file mode 100644
index 0000000..170b448
--- /dev/null
+++ b/core/java/com/android/internal/car/ICarStatsService.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.car;
+
+import android.os.StatsLogEventWrapper;
+
+/**
+ * Interface for pulling statsd atoms from automotive devices.
+ *
+ * @hide
+ */
+interface ICarStatsService {
+ /**
+ * Pull the specified atom. Results will be sent to statsd when complete.
+ */
+ StatsLogEventWrapper[] pullData(int atomId);
+}
diff --git a/core/java/com/android/internal/colorextraction/ColorExtractor.java b/core/java/com/android/internal/colorextraction/ColorExtractor.java
index 3003ce8..f0da0d5 100644
--- a/core/java/com/android/internal/colorextraction/ColorExtractor.java
+++ b/core/java/com/android/internal/colorextraction/ColorExtractor.java
@@ -73,8 +73,10 @@
}
mOnColorsChangedListeners = new ArrayList<>();
- wallpaperManager.addOnColorsChangedListener(this, null /* handler */);
- initExtractColors(wallpaperManager, immediately);
+ if (wallpaperManager.isWallpaperSupported()) {
+ wallpaperManager.addOnColorsChangedListener(this, null /* handler */);
+ initExtractColors(wallpaperManager, immediately);
+ }
}
private void initExtractColors(WallpaperManager wallpaperManager, boolean immediately) {
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 33b5325..334e61a 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -47,6 +47,20 @@
*/
public static final String NAS_MAX_SUGGESTIONS = "nas_max_suggestions";
+ // Flags related to screenshot intelligence
+
+ /**
+ * (bool) Whether to enable smart actions in screenshot notifications.
+ */
+ public static final String ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS =
+ "enable_screenshot_notification_smart_actions";
+
+ /**
+ * (int) Timeout value in ms to get smart actions for screenshot notification.
+ */
+ public static final String SCREENSHOT_NOTIFICATION_SMART_ACTIONS_TIMEOUT_MS =
+ "screenshot_notification_smart_actions_timeout_ms";
+
// Flags related to Smart Suggestions - these are read in SmartReplyConstants.
/** (boolean) Whether to enable smart suggestions in notifications. */
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index cac691c..d24d78c 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -6,6 +6,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
@@ -58,7 +59,7 @@
*/
public void takeScreenshot(final int screenshotType, final boolean hasStatus,
final boolean hasNav, @NonNull Handler handler,
- @Nullable Consumer<Boolean> completionConsumer) {
+ @Nullable Consumer<Uri> completionConsumer) {
takeScreenshot(screenshotType, hasStatus, hasNav, SCREENSHOT_TIMEOUT_MS, handler,
completionConsumer);
}
@@ -83,12 +84,12 @@
* the screenshot attempt will be cancelled and `completionConsumer`
* will be run.
* @param handler A handler used in case the screenshot times out
- * @param completionConsumer Consumes `false` if a screenshot was not taken, and `true` if the
- * screenshot was taken.
+ * @param completionConsumer Consumes `null` if a screenshot was not taken, and the URI of the
+ * screenshot if the screenshot was taken.
*/
public void takeScreenshot(final int screenshotType, final boolean hasStatus,
final boolean hasNav, long timeoutMs, @NonNull Handler handler,
- @Nullable Consumer<Boolean> completionConsumer) {
+ @Nullable Consumer<Uri> completionConsumer) {
synchronized (mScreenshotLock) {
if (mScreenshotConnection != null) {
return;
@@ -108,7 +109,7 @@
}
}
if (completionConsumer != null) {
- completionConsumer.accept(false);
+ completionConsumer.accept(null);
}
}
};
@@ -134,8 +135,9 @@
handler.removeCallbacks(mScreenshotTimeout);
}
}
+
if (completionConsumer != null) {
- completionConsumer.accept(true);
+ completionConsumer.accept((Uri) msg.obj);
}
}
};
@@ -148,7 +150,7 @@
} catch (RemoteException e) {
Log.e(TAG, "Couldn't take screenshot: " + e);
if (completionConsumer != null) {
- completionConsumer.accept(false);
+ completionConsumer.accept(null);
}
}
}
diff --git a/core/jni/android/graphics/ByteBufferStreamAdaptor.cpp b/core/jni/android/graphics/ByteBufferStreamAdaptor.cpp
index 173818b..d443fd8 100644
--- a/core/jni/android/graphics/ByteBufferStreamAdaptor.cpp
+++ b/core/jni/android/graphics/ByteBufferStreamAdaptor.cpp
@@ -1,6 +1,7 @@
#include "ByteBufferStreamAdaptor.h"
#include "core_jni_helpers.h"
#include "Utils.h"
+#include <jni.h>
#include <SkStream.h>
@@ -9,6 +10,24 @@
static jmethodID gByteBuffer_getMethodID;
static jmethodID gByteBuffer_setPositionMethodID;
+/**
+ * Helper method for accessing the JNI interface pointer.
+ *
+ * Image decoding (which this supports) is started on a thread that is already
+ * attached to the Java VM. But an AnimatedImageDrawable continues decoding on
+ * the AnimatedImageThread, which is not attached. This will attach if
+ * necessary.
+ */
+static JNIEnv* requireEnv(JavaVM* jvm) {
+ JNIEnv* env;
+ if (jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ if (jvm->AttachCurrentThreadAsDaemon(&env, nullptr) != JNI_OK) {
+ LOG_ALWAYS_FATAL("Failed to AttachCurrentThread!");
+ }
+ }
+ return env;
+}
+
class ByteBufferStream : public SkStreamAsset {
private:
ByteBufferStream(JavaVM* jvm, jobject jbyteBuffer, size_t initialPosition, size_t length,
@@ -46,7 +65,7 @@
}
~ByteBufferStream() override {
- auto* env = get_env_or_die(mJvm);
+ auto* env = requireEnv(mJvm);
env->DeleteGlobalRef(mByteBuffer);
env->DeleteGlobalRef(mStorage);
}
@@ -63,7 +82,7 @@
return this->setPosition(mPosition + size) ? size : 0;
}
- auto* env = get_env_or_die(mJvm);
+ auto* env = requireEnv(mJvm);
size_t bytesRead = 0;
do {
const size_t requested = (size > kStorageSize) ? kStorageSize : size;
@@ -146,7 +165,7 @@
// Range has already been checked by the caller.
bool setPosition(size_t newPosition) {
- auto* env = get_env_or_die(mJvm);
+ auto* env = requireEnv(mJvm);
env->CallObjectMethod(mByteBuffer, gByteBuffer_setPositionMethodID,
newPosition + mInitialPosition);
if (env->ExceptionCheck()) {
@@ -185,7 +204,7 @@
}
~ByteArrayStream() override {
- auto* env = get_env_or_die(mJvm);
+ auto* env = requireEnv(mJvm);
env->DeleteGlobalRef(mByteArray);
}
@@ -197,7 +216,7 @@
return 0;
}
- auto* env = get_env_or_die(mJvm);
+ auto* env = requireEnv(mJvm);
if (buffer) {
env->GetByteArrayRegion(mByteArray, mPosition + mOffset, size,
reinterpret_cast<jbyte*>(buffer));
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index ff14a2a..ca10c8d 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -488,7 +488,7 @@
proxy->setWideGamut(true);
}
proxy->setSwapBehavior(SwapBehavior::kSwap_discardBuffer);
- proxy->setSurface(surface);
+ proxy->setSurface(surface, false);
// Shadows can't be used via this interface, so just set the light source
// to all 0s.
proxy->setLightAlpha(0, 0);
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index 2b702ba..2cedd84 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2407,8 +2407,13 @@
// OS: Q
SETTINGS_AWARE_DISPLAY = 1750;
+ // OPEN: Settings > System > Input & Gesture > tap gesture
+ // CATEGORY: SETTINGS
+ // OS: Q
+ SETTINGS_GESTURE_TAP = 1751;
+
// OPEN: Settings > Developer Options > Platform Compat
// CATEGORY: SETTINGS
// OS: R
- SETTINGS_PLATFORM_COMPAT_DASHBOARD = 1805;
+ SETTINGS_PLATFORM_COMPAT_DASHBOARD = 1805;
}
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 61799ee..ef413b9 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -211,6 +211,11 @@
optional SettingProto silence_timer_touch_count = 11 [ (android.privacy).dest =
DEST_AUTOMATIC ];
optional SettingProto skip_touch_count = 12 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto aware_tap_pause_gesture_count = 13 [
+ (android.privacy).dest =
+ DEST_AUTOMATIC ];
+ optional SettingProto aware_tap_pause_touch_count = 14 [ (android.privacy).dest =
+ DEST_AUTOMATIC ];
}
optional Gesture gesture = 74;
diff --git a/core/proto/android/service/notification.proto b/core/proto/android/service/notification.proto
index 1ec05fb..ecb4193 100644
--- a/core/proto/android/service/notification.proto
+++ b/core/proto/android/service/notification.proto
@@ -264,3 +264,14 @@
optional Sender priority_calls = 16;
optional Sender priority_messages = 17;
}
+
+// Next Tag: 2
+message PackageRemoteViewInfoProto {
+ optional string package_name = 1;
+ // add per-package additional info here (like channels)
+}
+
+// Next Tag: 2
+message NotificationRemoteViewsProto {
+ repeated PackageRemoteViewInfoProto package_remote_view_info = 1;
+}
\ No newline at end of file
diff --git a/core/res/res/drawable/chooser_action_button_bg.xml b/core/res/res/drawable/chooser_action_button_bg.xml
new file mode 100644
index 0000000..a434c0b
--- /dev/null
+++ b/core/res/res/drawable/chooser_action_button_bg.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="@color/lighter_gray">
+ <item>
+ <inset
+ android:insetLeft="0dp"
+ android:insetTop="8dp"
+ android:insetRight="0dp"
+ android:insetBottom="8dp">
+ <shape android:shape="rectangle">
+ <corners android:radius="16dp"></corners>
+ <stroke android:width="1dp"
+ android:color="?attr/textColorSecondary" />
+ <solid android:color="?attr/colorBackground" />
+ </shape>
+ </inset>
+ </item>
+</ripple>
diff --git a/core/res/res/drawable/ic_content_copy_gm2.xml b/core/res/res/drawable/ic_content_copy_gm2.xml
deleted file mode 100644
index ee58738..0000000
--- a/core/res/res/drawable/ic_content_copy_gm2.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24"
- android:viewportHeight="24">
- <path
- android:fillColor="?android:attr/textColorSecondary"
- android:pathData="M18,21L4,21L4,7L2,7v14c0,1.1 0.9,2 2,2h14v-2zM21,17L21,3c0,-1.1 -0.9,-2 -2,-2L8,1c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2zM19,17L8,17L8,3h11v14z"/>
-</vector>
diff --git a/core/res/res/drawable/ic_menu_copy_material.xml b/core/res/res/drawable/ic_menu_copy_material.xml
index c03723b..ee58738 100644
--- a/core/res/res/drawable/ic_menu_copy_material.xml
+++ b/core/res/res/drawable/ic_menu_copy_material.xml
@@ -1,26 +1,25 @@
-<!--
-Copyright (C) 2014 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
+ http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
-->
+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"
- android:autoMirrored="true"
- android:tint="?attr/colorControlNormal">
- <path
- android:pathData="M16,1L4,1C2.9,1 2,1.9 2,3l0,14l2,0L4,3l12,0L16,1zM19,5L8,5C6.9,5 6,5.9 6,7l0,14c0,1.1 0.9,2 2,2l11,0c1.1,0 2,-0.9 2,-2L21,7C21,5.9 20.1,5 19,5zM19,21L8,21L8,7l11,0L19,21z"
- android:fillColor="@color/white"/>
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="?android:attr/textColorSecondary"
+ android:pathData="M18,21L4,21L4,7L2,7v14c0,1.1 0.9,2 2,2h14v-2zM21,17L21,3c0,-1.1 -0.9,-2 -2,-2L8,1c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2zM19,17L8,17L8,3h11v14z"/>
</vector>
diff --git a/core/res/res/layout/chooser_action_button.xml b/core/res/res/layout/chooser_action_button.xml
new file mode 100644
index 0000000..562f188
--- /dev/null
+++ b/core/res/res/layout/chooser_action_button.xml
@@ -0,0 +1,28 @@
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<Button xmlns:android="http://schemas.android.com/apk/res/android"
+ android:gravity="center_vertical|start"
+ android:paddingStart="12dp"
+ android:paddingEnd="12dp"
+ android:drawablePadding="8dp"
+ android:textColor="?android:textColorSecondary"
+ android:textSize="12sp"
+ android:maxWidth="192dp"
+ android:singleLine="true"
+ android:clickable="true"
+ android:background="@drawable/chooser_action_button_bg"
+ />
diff --git a/core/res/res/layout/chooser_action_row.xml b/core/res/res/layout/chooser_action_row.xml
new file mode 100644
index 0000000..ea75611
--- /dev/null
+++ b/core/res/res/layout/chooser_action_row.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="@dimen/chooser_edge_margin_normal"
+ android:paddingRight="@dimen/chooser_edge_margin_normal"
+ android:gravity="center"
+ >
+
+</LinearLayout>
diff --git a/core/res/res/layout/chooser_grid_preview_file.xml b/core/res/res/layout/chooser_grid_preview_file.xml
index f7d60c9..8605443 100644
--- a/core/res/res/layout/chooser_grid_preview_file.xml
+++ b/core/res/res/layout/chooser_grid_preview_file.xml
@@ -65,13 +65,14 @@
android:gravity="start|top"
android:paddingRight="24dp"
android:singleLine="true"/>
- <Button
- android:id="@+id/file_copy_button"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:gravity="center"
- android:layout_gravity="center_vertical"
- android:background="@drawable/ic_content_copy_gm2"/>
</LinearLayout>
+
+ <include
+ android:id="@+id/chooser_action_row"
+ layout="@layout/chooser_action_row"
+ android:layout_width="@dimen/chooser_preview_width"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/chooser_view_spacing"
+ />
</LinearLayout>
diff --git a/core/res/res/layout/chooser_grid_preview_image.xml b/core/res/res/layout/chooser_grid_preview_image.xml
index 79a0de4..29b7fb4 100644
--- a/core/res/res/layout/chooser_grid_preview_image.xml
+++ b/core/res/res/layout/chooser_grid_preview_image.xml
@@ -78,5 +78,14 @@
android:scaleType="centerCrop"/>
</RelativeLayout>
+
+ <include
+ android:id="@+id/chooser_action_row"
+ layout="@layout/chooser_action_row"
+ android:layout_width="@dimen/chooser_preview_width"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/chooser_view_spacing"
+ />
+
</LinearLayout>
diff --git a/core/res/res/layout/chooser_grid_preview_text.xml b/core/res/res/layout/chooser_grid_preview_text.xml
index 9c725b9..5998cff 100644
--- a/core/res/res/layout/chooser_grid_preview_text.xml
+++ b/core/res/res/layout/chooser_grid_preview_text.xml
@@ -37,50 +37,25 @@
<TextView
android:id="@+id/content_preview_text"
- android:layout_width="wrap_content"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
- android:layout_toStartOf="@id/copy_button"
android:layout_centerVertical="true"
android:ellipsize="end"
android:fontFamily="@android:string/config_headlineFontFamily"
android:textColor="?android:attr/textColorPrimary"
android:maxLines="2"/>
- <LinearLayout
- android:id="@+id/copy_button"
- android:orientation="vertical"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerVertical="true"
- android:layout_alignParentEnd="true"
- android:layout_marginStart="@dimen/chooser_view_spacing"
- android:gravity="center"
- android:minWidth="48dp"
- android:minHeight="48dp"
- android:clickable="true"
- style="?attr/borderlessButtonStyle">
-
- <ImageView
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:gravity="top|center_horizontal"
- android:src="@drawable/ic_content_copy_gm2" />
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="4dp"
- android:gravity="center_horizontal"
- android:text="@string/copy"
- android:textColor="?android:textColorSecondary"
- android:textSize="12sp"
- android:maxWidth="72dp"
- android:maxLines="2"
- android:ellipsize="end" />
- </LinearLayout>
</RelativeLayout>
+ <include
+ android:id="@+id/chooser_action_row"
+ layout="@layout/chooser_action_row"
+ android:layout_width="@dimen/chooser_preview_width"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/chooser_view_spacing"
+ />
+
<!-- Required sub-layout so we can get the nice rounded corners-->
<!-- around this section -->
<LinearLayout
diff --git a/core/res/res/layout/resolve_list_item.xml b/core/res/res/layout/resolve_list_item.xml
index 0bdb25a..4857095 100644
--- a/core/res/res/layout/resolve_list_item.xml
+++ b/core/res/res/layout/resolve_list_item.xml
@@ -22,8 +22,6 @@
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:minHeight="?attr/listPreferredItemHeightSmall"
- android:paddingTop="4dp"
- android:paddingBottom="4dp"
android:background="?attr/activatedBackgroundIndicator">
<!-- Activity icon when presenting dialog
@@ -32,7 +30,8 @@
android:layout_width="@dimen/resolver_icon_size"
android:layout_height="@dimen/resolver_icon_size"
android:layout_gravity="start|center_vertical"
- android:layout_marginStart="?attr/listPreferredItemPaddingStart"
+ android:layout_marginStart="@dimen/resolver_icon_margin"
+ android:layout_marginEnd="@dimen/resolver_icon_margin"
android:layout_marginTop="12dp"
android:layout_marginBottom="12dp"
android:scaleType="fitCenter" />
@@ -40,8 +39,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:gravity="start|center_vertical"
android:orientation="vertical"
- android:paddingStart="?attr/listPreferredItemPaddingStart"
- android:paddingEnd="?attr/listPreferredItemPaddingEnd"
+ android:paddingEnd="@dimen/resolver_edge_margin"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_gravity="start|center_vertical">
@@ -49,14 +47,20 @@
<TextView android:id="@android:id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="?attr/textAppearanceMedium"
- android:textColor="?attr/textColorPrimary"
+ android:layout_gravity="start|center_vertical"
+ android:textColor="?android:attr/textColorPrimary"
+ android:fontFamily="@android:string/config_bodyFontFamily"
+ android:textSize="16sp"
android:minLines="1"
android:maxLines="1"
android:ellipsize="marquee" />
<!-- Extended activity info to distinguish between duplicate activity names -->
<TextView android:id="@android:id/text2"
- android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorSecondary"
+ android:fontFamily="@android:string/config_bodyFontFamily"
+ android:layout_gravity="start|center_vertical"
+ android:textSize="14sp"
+ android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minLines="1"
diff --git a/core/res/res/layout/resolver_different_item_header.xml b/core/res/res/layout/resolver_different_item_header.xml
index 7d9ffd7..0a35edc 100644
--- a/core/res/res/layout/resolver_different_item_header.xml
+++ b/core/res/res/layout/resolver_different_item_header.xml
@@ -22,12 +22,12 @@
android:layout_height="wrap_content"
android:layout_alwaysShow="true"
android:text="@string/use_a_different_app"
- android:minHeight="56dp"
- android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="?android:attr/textColorPrimary"
+ android:fontFamily="@android:string/config_headlineFontFamilyMedium"
+ android:textSize="16sp"
android:gravity="start|center_vertical"
- android:paddingStart="16dp"
- android:paddingEnd="16dp"
- android:paddingTop="8dp"
- android:paddingBottom="8dp"
- android:elevation="8dp"
- />
+ android:paddingStart="@dimen/resolver_edge_margin"
+ android:paddingEnd="@dimen/resolver_edge_margin"
+ android:paddingTop="@dimen/resolver_small_margin"
+ android:paddingBottom="@dimen/resolver_edge_margin"
+ android:elevation="1dp" />
diff --git a/core/res/res/layout/resolver_list.xml b/core/res/res/layout/resolver_list.xml
index 1dd4207..6e45e7a 100644
--- a/core/res/res/layout/resolver_list.xml
+++ b/core/res/res/layout/resolver_list.xml
@@ -29,16 +29,18 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alwaysShow="true"
- android:elevation="8dp"
- android:background="?attr/colorBackgroundFloating">
+ android:elevation="@dimen/resolver_elevation"
+ android:paddingTop="@dimen/resolver_small_margin"
+ android:paddingStart="@dimen/resolver_edge_margin"
+ android:paddingEnd="@dimen/resolver_edge_margin"
+ android:paddingBottom="@dimen/resolver_edge_margin"
+ android:background="@drawable/bottomsheet_background">
<TextView
android:id="@+id/profile_button"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginEnd="8dp"
- android:paddingStart="8dp"
- android:paddingEnd="8dp"
android:visibility="gone"
style="?attr/borderlessButtonStyle"
android:textAppearance="?attr/textAppearanceButton"
@@ -50,36 +52,49 @@
<TextView
android:id="@+id/title"
- android:layout_width="wrap_content"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:minHeight="56dp"
- android:textAppearance="?attr/textAppearanceMedium"
- android:gravity="start|center_vertical"
- android:paddingStart="?attr/dialogPreferredPadding"
- android:paddingEnd="?attr/dialogPreferredPadding"
- android:paddingTop="8dp"
android:layout_below="@id/profile_button"
android:layout_alignParentStart="true"
- android:paddingBottom="8dp" />
+ android:textColor="?android:attr/textColorPrimary"
+ android:fontFamily="@android:string/config_headlineFontFamilyMedium"
+ android:textSize="16sp"
+ android:gravity="start|center_vertical" />
</RelativeLayout>
+ <View
+ android:layout_alwaysShow="true"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="?attr/colorBackgroundFloating"
+ android:foreground="?attr/dividerVertical" />
<ListView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/resolver_list"
android:clipToPadding="false"
- android:scrollbarStyle="outsideOverlay"
android:background="?attr/colorBackgroundFloating"
- android:elevation="8dp"
+ android:elevation="@dimen/resolver_elevation"
android:nestedScrollingEnabled="true"
+ android:scrollbarStyle="outsideOverlay"
android:scrollIndicators="top|bottom"
- android:divider="@null" />
+ android:divider="?attr/dividerVertical"
+ android:footerDividersEnabled="false"
+ android:headerDividersEnabled="false"
+ android:dividerHeight="1dp" />
+ <View
+ android:layout_alwaysShow="true"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="?attr/colorBackgroundFloating"
+ android:foreground="?attr/dividerVertical" />
+
<TextView android:id="@+id/empty"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorBackgroundFloating"
- android:elevation="8dp"
+ android:elevation="@dimen/resolver_elevation"
android:layout_alwaysShow="true"
android:text="@string/noApplications"
android:padding="32dp"
@@ -102,18 +117,19 @@
android:background="?attr/colorBackgroundFloating"
android:paddingTop="@dimen/resolver_button_bar_spacing"
android:paddingBottom="@dimen/resolver_button_bar_spacing"
- android:paddingStart="12dp"
- android:paddingEnd="12dp"
- android:elevation="8dp">
+ android:paddingStart="@dimen/resolver_edge_margin"
+ android:paddingEnd="@dimen/resolver_small_margin"
+ android:elevation="@dimen/resolver_elevation">
<Button
android:id="@+id/button_once"
android:layout_width="wrap_content"
android:layout_gravity="start"
android:maxLines="2"
- style="?attr/buttonBarNegativeButtonStyle"
- android:minHeight="@dimen/alert_dialog_button_bar_height"
+ style="?attr/buttonBarButtonStyle"
+ android:fontFamily="@android:string/config_headlineFontFamilyMedium"
android:layout_height="wrap_content"
+ android:textAllCaps="false"
android:enabled="false"
android:text="@string/activity_resolver_use_once"
android:onClick="onButtonClick" />
@@ -123,8 +139,9 @@
android:layout_width="wrap_content"
android:layout_gravity="end"
android:maxLines="2"
- android:minHeight="@dimen/alert_dialog_button_bar_height"
- style="?attr/buttonBarPositiveButtonStyle"
+ style="?attr/buttonBarButtonStyle"
+ android:fontFamily="@android:string/config_headlineFontFamilyMedium"
+ android:textAllCaps="false"
android:layout_height="wrap_content"
android:enabled="false"
android:text="@string/activity_resolver_use_always"
diff --git a/core/res/res/layout/resolver_list_with_default.xml b/core/res/res/layout/resolver_list_with_default.xml
index 740a7eb..dbba0b7 100644
--- a/core/res/res/layout/resolver_list_with_default.xml
+++ b/core/res/res/layout/resolver_list_with_default.xml
@@ -29,22 +29,22 @@
android:layout_height="wrap_content"
android:layout_alwaysShow="true"
android:orientation="vertical"
- android:background="?attr/colorBackgroundFloating"
- android:elevation="8dp">
+ android:background="@drawable/bottomsheet_background"
+ android:paddingTop="@dimen/resolver_small_margin"
+ android:elevation="@dimen/resolver_elevation">
<LinearLayout
android:layout_width="match_parent"
- android:layout_height="64dp"
- android:orientation="horizontal">
-
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingBottom="@dimen/resolver_edge_margin"
+ android:paddingEnd="@dimen/resolver_edge_margin">
<ImageView
android:id="@+id/icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
+ android:layout_width="@dimen/resolver_icon_size"
+ android:layout_height="@dimen/resolver_icon_size"
android:layout_gravity="start|top"
- android:layout_marginStart="16dp"
- android:layout_marginEnd="16dp"
- android:layout_marginTop="20dp"
+ android:layout_marginStart="@dimen/resolver_icon_margin"
android:src="@drawable/resolver_icon_placeholder"
android:scaleType="fitCenter" />
@@ -52,9 +52,11 @@
android:id="@+id/title"
android:layout_width="0dp"
android:layout_weight="1"
- android:layout_height="?attr/listPreferredItemHeight"
- android:layout_marginStart="16dp"
- android:textAppearance="?attr/textAppearanceMedium"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/resolver_icon_margin"
+ android:textColor="?android:attr/textColorPrimary"
+ android:fontFamily="@android:string/config_headlineFontFamilyMedium"
+ android:textSize="16sp"
android:gravity="start|center_vertical"
android:paddingEnd="16dp" />
@@ -107,21 +109,22 @@
android:orientation="horizontal"
android:layoutDirection="locale"
android:measureWithLargestChild="true"
- android:paddingTop="8dp"
- android:paddingBottom="8dp"
- android:paddingStart="12dp"
- android:paddingEnd="12dp"
- android:elevation="8dp">
+ android:paddingTop="@dimen/resolver_button_bar_spacing"
+ android:paddingBottom="@dimen/resolver_button_bar_spacing"
+ android:paddingStart="@dimen/resolver_edge_margin"
+ android:paddingEnd="@dimen/resolver_small_margin"
+ android:elevation="@dimen/resolver_elevation">
<Button
android:id="@+id/button_once"
android:layout_width="wrap_content"
android:layout_gravity="start"
android:maxLines="2"
- style="?attr/buttonBarNegativeButtonStyle"
- android:minHeight="@dimen/alert_dialog_button_bar_height"
+ style="?attr/buttonBarButtonStyle"
+ android:fontFamily="@android:string/config_headlineFontFamilyMedium"
android:layout_height="wrap_content"
android:enabled="false"
+ android:textAllCaps="false"
android:text="@string/activity_resolver_use_once"
android:onClick="onButtonClick" />
@@ -130,29 +133,40 @@
android:layout_width="wrap_content"
android:layout_gravity="end"
android:maxLines="2"
- android:minHeight="@dimen/alert_dialog_button_bar_height"
- style="?attr/buttonBarPositiveButtonStyle"
+ style="?attr/buttonBarButtonStyle"
+ android:fontFamily="@android:string/config_headlineFontFamilyMedium"
android:layout_height="wrap_content"
android:enabled="false"
+ android:textAllCaps="false"
android:text="@string/activity_resolver_use_always"
android:onClick="onButtonClick" />
</LinearLayout>
-
- <View
- android:layout_width="match_parent"
- android:layout_height="1dp"
- android:background="?attr/dividerVertical" />
</LinearLayout>
+ <View
+ android:layout_alwaysShow="true"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="?attr/colorBackgroundFloating"
+ android:foreground="?attr/dividerVertical" />
<ListView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/resolver_list"
android:clipToPadding="false"
- android:scrollbarStyle="outsideOverlay"
android:background="?attr/colorBackgroundFloating"
- android:elevation="8dp"
+ android:elevation="@dimen/resolver_elevation"
android:nestedScrollingEnabled="true"
- android:divider="@null" />
-
+ android:scrollbarStyle="outsideOverlay"
+ android:scrollIndicators="top|bottom"
+ android:divider="?attr/dividerVertical"
+ android:footerDividersEnabled="false"
+ android:headerDividersEnabled="false"
+ android:dividerHeight="1dp" />
+ <View
+ android:layout_alwaysShow="true"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="?attr/colorBackgroundFloating"
+ android:foreground="?attr/dividerVertical" />
</com.android.internal.widget.ResolverDrawerLayout>
diff --git a/core/res/res/values-mcc310-mnc170/config.xml b/core/res/res/values-mcc310-mnc170/config.xml
index 26b9192..12e448c 100644
--- a/core/res/res/values-mcc310-mnc170/config.xml
+++ b/core/res/res/values-mcc310-mnc170/config.xml
@@ -22,5 +22,9 @@
<resources>
<!-- Enable 5 bar signal strength icon -->
<bool name="config_inflateSignalStrength">true</bool>
+
+ <!-- Boolean indicating whether frameworks needs to reset cell broadcast geo-fencing
+ check after reboot or airplane mode toggling -->
+ <bool translatable="false" name="reset_geo_fencing_check_after_boot_or_apm">true</bool>
</resources>
diff --git a/core/res/res/values-mcc310-mnc380/config.xml b/core/res/res/values-mcc310-mnc380/config.xml
index 26b9192..12e448c 100644
--- a/core/res/res/values-mcc310-mnc380/config.xml
+++ b/core/res/res/values-mcc310-mnc380/config.xml
@@ -22,5 +22,9 @@
<resources>
<!-- Enable 5 bar signal strength icon -->
<bool name="config_inflateSignalStrength">true</bool>
+
+ <!-- Boolean indicating whether frameworks needs to reset cell broadcast geo-fencing
+ check after reboot or airplane mode toggling -->
+ <bool translatable="false" name="reset_geo_fencing_check_after_boot_or_apm">true</bool>
</resources>
diff --git a/core/res/res/values-mcc310-mnc410/config.xml b/core/res/res/values-mcc310-mnc410/config.xml
index 3fb3f0f..22b8fef 100644
--- a/core/res/res/values-mcc310-mnc410/config.xml
+++ b/core/res/res/values-mcc310-mnc410/config.xml
@@ -52,4 +52,7 @@
<!-- Enable 5 bar signal strength icon -->
<bool name="config_inflateSignalStrength">true</bool>
+ <!-- Boolean indicating whether frameworks needs to reset cell broadcast geo-fencing
+ check after reboot or airplane mode toggling -->
+ <bool translatable="false" name="reset_geo_fencing_check_after_boot_or_apm">true</bool>
</resources>
diff --git a/core/res/res/values-mcc310-mnc560/config.xml b/core/res/res/values-mcc310-mnc560/config.xml
index 26b9192..12e448c 100644
--- a/core/res/res/values-mcc310-mnc560/config.xml
+++ b/core/res/res/values-mcc310-mnc560/config.xml
@@ -22,5 +22,9 @@
<resources>
<!-- Enable 5 bar signal strength icon -->
<bool name="config_inflateSignalStrength">true</bool>
+
+ <!-- Boolean indicating whether frameworks needs to reset cell broadcast geo-fencing
+ check after reboot or airplane mode toggling -->
+ <bool translatable="false" name="reset_geo_fencing_check_after_boot_or_apm">true</bool>
</resources>
diff --git a/core/res/res/values-mcc311-mnc180/config.xml b/core/res/res/values-mcc311-mnc180/config.xml
index 26b9192..12e448c 100644
--- a/core/res/res/values-mcc311-mnc180/config.xml
+++ b/core/res/res/values-mcc311-mnc180/config.xml
@@ -22,5 +22,9 @@
<resources>
<!-- Enable 5 bar signal strength icon -->
<bool name="config_inflateSignalStrength">true</bool>
+
+ <!-- Boolean indicating whether frameworks needs to reset cell broadcast geo-fencing
+ check after reboot or airplane mode toggling -->
+ <bool translatable="false" name="reset_geo_fencing_check_after_boot_or_apm">true</bool>
</resources>
diff --git a/core/res/res/values-mcc313-mnc100/config.xml b/core/res/res/values-mcc313-mnc100/config.xml
index ccd03f1..a8e481a 100644
--- a/core/res/res/values-mcc313-mnc100/config.xml
+++ b/core/res/res/values-mcc313-mnc100/config.xml
@@ -42,4 +42,8 @@
<item>"#8"</item>
<item>"#9"</item>
</string-array>
+
+ <!-- Boolean indicating whether frameworks needs to reset cell broadcast geo-fencing
+ check after reboot or airplane mode toggling -->
+ <bool translatable="false" name="reset_geo_fencing_check_after_boot_or_apm">true</bool>
</resources>
diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml
index f058985..dca9c72 100644
--- a/core/res/res/values/arrays.xml
+++ b/core/res/res/values/arrays.xml
@@ -175,7 +175,15 @@
</array>
<!-- Used in ResolverTargetActionsDialogFragment -->
- <string-array name="resolver_target_actions">
+
+ <!-- Used in ResolverTargetActionsDialogFragment -->
+ <string-array name="resolver_target_actions_pin">
+ <item>@string/pin_target</item>
+ <item>@string/app_info</item>
+ </string-array>
+
+ <string-array name="resolver_target_actions_unpin">
+ <item>@string/unpin_target</item>
<item>@string/app_info</item>
</string-array>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 17045d8..01fb7d4 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -352,10 +352,10 @@
will be given a single shared user ID, so they can for example run
in the same process. Note that for them to actually get the same
user ID, they must also be signed with the same signature.
- @deprecated Shared user id's cause non-deterministic behaviour within the
- package manager. As such, it's use is discouraged, deprecated, and will
- be removed altogether in a future version of Android. Instead, proper
- communication mechanisms such as services and providers should be used
+ @deprecated Shared user IDs cause non-deterministic behavior within the
+ package manager. As such, its use is strongly discouraged and may be
+ removed in a future version of Android. Instead, apps should use proper
+ communication mechanisms, such as services and content providers,
to facilitate interoperability between shared components. -->
<attr name="sharedUserId" format="string" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 4c25a7a..ee4c509 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4314,4 +4314,12 @@
create additional screen real estate outside beyond the keyboard. Note that the user needs
to have a confirmed way to dismiss the keyboard when desired. -->
<bool name="config_automotiveHideNavBarForKeyboard">false</bool>
+
+ <!-- Component name that accepts ACTION_SEND intents for nearby (proximity-based) sharing.
+ Used by ChooserActivity. -->
+ <string translatable="false" name="config_defaultNearbySharingComponent"></string>
+
+ <!-- Boolean indicating whether frameworks needs to reset cell broadcast geo-fencing
+ check after reboot or airplane mode toggling -->
+ <bool translatable="false" name="reset_geo_fencing_check_after_boot_or_apm">false</bool>
</resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 609659b..4fdb498 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -750,7 +750,7 @@
<dimen name="seekbar_thumb_exclusion_max_size">48dp</dimen>
- <!-- chooser (sharesheet) spacing -->
+ <!-- chooser/resolver (sharesheet) spacing -->
<dimen name="chooser_corner_radius">8dp</dimen>
<dimen name="chooser_row_text_option_translate">25dp</dimen>
<dimen name="chooser_view_spacing">18dp</dimen>
@@ -759,11 +759,16 @@
<dimen name="chooser_preview_image_font_size">20sp</dimen>
<dimen name="chooser_preview_image_border">1dp</dimen>
<dimen name="chooser_preview_width">-1px</dimen>
- <dimen name="resolver_icon_size">42dp</dimen>
- <dimen name="resolver_button_bar_spacing">8dp</dimen>
- <dimen name="resolver_badge_size">18dp</dimen>
<dimen name="chooser_target_width">90dp</dimen>
<dimen name="chooser_header_scroll_elevation">4dp</dimen>
<dimen name="chooser_max_collapsed_height">288dp</dimen>
<dimen name="chooser_direct_share_label_placeholder_max_width">72dp</dimen>
+ <dimen name="resolver_icon_size">32dp</dimen>
+ <dimen name="resolver_button_bar_spacing">8dp</dimen>
+ <dimen name="resolver_badge_size">18dp</dimen>
+ <dimen name="resolver_icon_margin">16dp</dimen>
+ <dimen name="resolver_small_margin">18dp</dimen>
+ <dimen name="resolver_edge_margin">24dp</dimen>
+ <dimen name="resolver_elevation">1dp</dimen>
+ <dimen name="chooser_action_button_icon_size">18dp</dimen>
</resources>
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index 2b0c86b..ba64bf0 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -193,4 +193,7 @@
<!-- A tag used to save the index where the custom view is stored -->
<item type="id" name="notification_custom_view_index_tag" />
+
+ <!-- Marks the "copy to clipboard" button in the ChooserActivity -->
+ <item type="id" name="chooser_copy_button" />
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index fdfedea..d706e95 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3371,6 +3371,26 @@
<!-- If there is ever a ringtone set for some setting, but that ringtone can no longer be resolved, t his is shown instead. For example, if the ringtone was on a SD card and it had been removed, this woudl be shown for ringtones on that SD card. -->
<string name="ringtone_unknown">Unknown</string>
+ <!-- Start of string constants used to inform the user that the current network may be failing to connect due to not supporting randomized MAC-->
+ <!-- Notification title [CHAR_LIMIT=NONE]-->
+ <string name="wifi_cannot_connect_with_randomized_mac_title">Can\u2019t connect to <xliff:g id="ssid" example="SSID_1">%1$s</xliff:g></string>
+ <!-- Notification text [CHAR_LIMIT=NONE]-->
+ <string name="wifi_cannot_connect_with_randomized_mac_message">Tap to change privacy settings and retry</string>
+ <!-- Title of the dialog which pops up when the notification is tapped. [CHAR_LIMIT=NONE]-->
+ <string name="wifi_disable_mac_randomization_dialog_title">Change privacy setting?</string>
+ <!-- Dialog text [CHAR_LIMIT=NONE]-->
+ <string name="wifi_disable_mac_randomization_dialog_message"><xliff:g id="ssid" example="SSID_1">%1$s</xliff:g> may want to connect using your device MAC address, a unique identifier. This may allow your device\u2019s location to be tracked by nearby devices.
+ \n\nIf you continue, <xliff:g id="ssid" example="SSID_1">%1$s</xliff:g> will change your privacy setting and try to connect again.</string>
+ <!-- Text of the button that will disable MAC randomization for the network when tapped. [CHAR_LIMIT=NONE]-->
+ <string name="wifi_disable_mac_randomization_dialog_confirm_text">Change setting</string>
+ <!-- Toast message which shows up after MAC randomization for the network is disabled. [CHAR_LIMIT=NONE]-->
+ <string name="wifi_disable_mac_randomization_dialog_success">Setting updated. Try Connecting again.</string>
+ <!-- Toast message which shows up if the operation failed. [CHAR_LIMIT=NONE]-->
+ <string name="wifi_disable_mac_randomization_dialog_failure">Can\u2019t change privacy setting</string>
+ <!-- Toast message which shows up if the network no longer exists on the device. [CHAR_LIMIT=NONE]-->
+ <string name="wifi_disable_mac_randomization_dialog_network_not_found">Network not found</string>
+ <!-- End of string constants used to inform the user that the current network may be failing to connect due to not supporting randomized MAC-->
+
<!-- A notification is shown when there are open wireless networks nearby. This is the notification's title. -->
<plurals name="wifi_available">
<item quantity="one">Wi-Fi network available</item>
@@ -4846,10 +4866,10 @@
<string name="confirm_battery_saver">OK</string>
<!-- [CHAR_LIMIT=NONE] Battery saver: Feature description, with a "learn more" link. -->
- <string name="battery_saver_description_with_learn_more">Battery Saver turns off or restricts background activity, some visual effects \u0026 other high-power features to extend battery life. <annotation id="url">Learn More</annotation></string>
+ <string name="battery_saver_description_with_learn_more">To extend battery life, Battery Saver:\n·Turns on Dark theme\n·Turns off or restricts background activity, some visual effects, and other features like \u201cHey Google\u201d\n\n<annotation id="url">Learn more</annotation></string>
<!-- [CHAR_LIMIT=NONE] Battery saver: Feature description, without a "learn more" link. -->
- <string name="battery_saver_description">Battery Saver turns off or restricts background activity, some visual effects \u0026 other high-power features to extend battery life.</string>
+ <string name="battery_saver_description">To extend battery life, Battery Saver:\n·Turns on Dark theme\n·Turns off or restricts background activity, some visual effects, and other features like \u201cHey Google\u201d</string>
<!-- [CHAR_LIMIT=NONE] Data saver: Feature description -->
<string name="data_saver_description">To help reduce data usage, Data Saver prevents some apps from sending or receiving data in the background. An app you’re currently using can access data, but may do so less frequently. This may mean, for example, that images don’t display until you tap them.</string>
@@ -5079,6 +5099,10 @@
<string name="usb_mtp_launch_notification_description">Tap to view files</string>
<!-- Resolver target actions strings -->
+ <!-- Pin this app to the top of the Sharesheet app list. [CHAR LIMIT=60]-->
+ <string name="pin_target">Pin</string>
+ <!-- Un-pin this app in the Sharesheet, so that it is sorted normally. [CHAR LIMIT=60]-->
+ <string name="unpin_target">Unpin</string>
<!-- View application info for a target. -->
<string name="app_info">App info</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a1f66d3..34e9209 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -66,8 +66,7 @@
<java-symbol type="id" name="content_preview_text_layout" />
<java-symbol type="id" name="content_preview_title" />
<java-symbol type="id" name="content_preview_title_layout" />
- <java-symbol type="id" name="copy_button" />
- <java-symbol type="id" name="file_copy_button" />
+ <java-symbol type="id" name="chooser_action_row" />
<java-symbol type="id" name="current_scene" />
<java-symbol type="id" name="scene_layoutid_cache" />
<java-symbol type="id" name="customPanel" />
@@ -2068,6 +2067,14 @@
<java-symbol type="layout" name="safe_mode" />
<java-symbol type="layout" name="simple_list_item_2_single_choice" />
<java-symbol type="layout" name="app_error_dialog" />
+ <java-symbol type="string" name="wifi_cannot_connect_with_randomized_mac_title" />
+ <java-symbol type="string" name="wifi_cannot_connect_with_randomized_mac_message" />
+ <java-symbol type="string" name="wifi_disable_mac_randomization_dialog_title" />
+ <java-symbol type="string" name="wifi_disable_mac_randomization_dialog_message" />
+ <java-symbol type="string" name="wifi_disable_mac_randomization_dialog_confirm_text" />
+ <java-symbol type="string" name="wifi_disable_mac_randomization_dialog_success" />
+ <java-symbol type="string" name="wifi_disable_mac_randomization_dialog_failure" />
+ <java-symbol type="string" name="wifi_disable_mac_randomization_dialog_network_not_found" />
<java-symbol type="plurals" name="wifi_available" />
<java-symbol type="plurals" name="wifi_available_detailed" />
<java-symbol type="string" name="wifi_available_title" />
@@ -3052,7 +3059,8 @@
<java-symbol type="color" name="notification_material_background_color" />
<!-- Resolver target actions -->
- <java-symbol type="array" name="resolver_target_actions" />
+ <java-symbol type="array" name="resolver_target_actions_pin" />
+ <java-symbol type="array" name="resolver_target_actions_unpin" />
<java-symbol type="array" name="non_removable_euicc_slots" />
@@ -3809,6 +3817,10 @@
<java-symbol type="dimen" name="resolver_icon_size"/>
<java-symbol type="dimen" name="resolver_badge_size"/>
<java-symbol type="dimen" name="resolver_button_bar_spacing"/>
+ <java-symbol type="dimen" name="resolver_icon_margin"/>
+ <java-symbol type="dimen" name="resolver_small_margin"/>
+ <java-symbol type="dimen" name="resolver_edge_margin"/>
+ <java-symbol type="dimen" name="resolver_elevation"/>
<!-- For DropBox -->
<java-symbol type="integer" name="config_dropboxLowPriorityBroadcastRateLimitPeriod" />
@@ -3841,5 +3853,11 @@
<java-symbol type="drawable" name="android_logotype" />
<java-symbol type="layout" name="platlogo_layout" />
+ <java-symbol type="id" name="chooser_copy_button" />
+ <java-symbol type="layout" name="chooser_action_button" />
+ <java-symbol type="dimen" name="chooser_action_button_icon_size" />
+ <java-symbol type="string" name="config_defaultNearbySharingComponent" />
+
<java-symbol type="bool" name="config_automotiveHideNavBarForKeyboard" />
+ <java-symbol type="bool" name="reset_geo_fencing_check_after_boot_or_apm" />
</resources>
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index 6289262..2b37a0c 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -1733,6 +1733,7 @@
<item name="colorBackground">@color/background_device_default_light</item>
<item name="colorBackgroundFloating">@color/background_device_default_light</item>
<item name="layout_gravity">center</item>
+ <item name="windowAnimationStyle">@style/Animation.DeviceDefault.Dialog</item>
</style>
<style name="Theme.DeviceDefault.Notification" parent="@style/Theme.Material.Notification">
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index e60e555..b7b02a3 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -595,7 +595,10 @@
Settings.Secure.ANR_SHOW_BACKGROUND,
Settings.Secure.ASSISTANT,
Settings.Secure.ASSIST_DISCLOSURE_ENABLED,
+ Settings.Secure.ASSIST_GESTURE_ENABLED,
Settings.Secure.ASSIST_GESTURE_SENSITIVITY,
+ Settings.Secure.ASSIST_GESTURE_WAKE_ENABLED,
+ Settings.Secure.ASSIST_GESTURE_SILENCE_ALERTS_ENABLED,
Settings.Secure.ASSIST_GESTURE_SETUP_COMPLETE,
Settings.Secure.ASSIST_SCREENSHOT_ENABLED,
Settings.Secure.ASSIST_STRUCTURE_ENABLED,
@@ -723,6 +726,12 @@
Settings.Secure.BIOMETRIC_DEBUG_ENABLED,
Settings.Secure.FACE_UNLOCK_ATTENTION_REQUIRED,
Settings.Secure.FACE_UNLOCK_DIVERSITY_REQUIRED,
+ Settings.Secure.AWARE_ENABLED,
+ Settings.Secure.SKIP_GESTURE,
+ Settings.Secure.SILENCE_GESTURE,
+ Settings.Secure.DOZE_WAKE_LOCK_SCREEN_GESTURE,
+ Settings.Secure.DOZE_WAKE_DISPLAY_GESTURE,
+ Settings.Secure.TAP_GESTURE,
Settings.Secure.FACE_UNLOCK_RE_ENROLL);
@Test
diff --git a/core/tests/coretests/src/com/android/internal/app/AbstractResolverComparatorTest.java b/core/tests/coretests/src/com/android/internal/app/AbstractResolverComparatorTest.java
new file mode 100644
index 0000000..36dd3e4
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/app/AbstractResolverComparatorTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import static junit.framework.Assert.assertEquals;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ResolveInfo;
+import android.os.Message;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Test;
+
+import java.util.List;
+
+public class AbstractResolverComparatorTest {
+
+ @Test
+ public void testPinned() {
+ ResolverActivity.ResolvedComponentInfo r1 = new ResolverActivity.ResolvedComponentInfo(
+ new ComponentName("package", "class"), new Intent(), new ResolveInfo()
+ );
+ r1.setPinned(true);
+
+ ResolverActivity.ResolvedComponentInfo r2 = new ResolverActivity.ResolvedComponentInfo(
+ new ComponentName("zackage", "zlass"), new Intent(), new ResolveInfo()
+ );
+
+ Context context = InstrumentationRegistry.getTargetContext();
+ AbstractResolverComparator comparator = getTestComparator(context);
+
+ assertEquals("Pinned ranks over unpinned", -1, comparator.compare(r1, r2));
+ assertEquals("Unpinned ranks under pinned", 1, comparator.compare(r2, r1));
+ }
+
+
+ @Test
+ public void testBothPinned() {
+ ResolveInfo pmInfo1 = new ResolveInfo();
+ pmInfo1.activityInfo = new ActivityInfo();
+ pmInfo1.activityInfo.packageName = "aaa";
+
+ ResolverActivity.ResolvedComponentInfo r1 = new ResolverActivity.ResolvedComponentInfo(
+ new ComponentName("package", "class"), new Intent(), pmInfo1);
+ r1.setPinned(true);
+
+ ResolveInfo pmInfo2 = new ResolveInfo();
+ pmInfo2.activityInfo = new ActivityInfo();
+ pmInfo2.activityInfo.packageName = "zzz";
+ ResolverActivity.ResolvedComponentInfo r2 = new ResolverActivity.ResolvedComponentInfo(
+ new ComponentName("zackage", "zlass"), new Intent(), pmInfo2);
+ r2.setPinned(true);
+
+ Context context = InstrumentationRegistry.getTargetContext();
+ AbstractResolverComparator comparator = getTestComparator(context);
+
+ assertEquals("Both pinned should rank alphabetically", -1, comparator.compare(r1, r2));
+ }
+
+ private AbstractResolverComparator getTestComparator(Context context) {
+ Intent intent = new Intent();
+
+ AbstractResolverComparator testComparator =
+ new AbstractResolverComparator(context, intent) {
+
+ @Override
+ int compare(ResolveInfo lhs, ResolveInfo rhs) {
+ // Used for testing pinning, so we should never get here --- the overrides should
+ // determine the result instead.
+ return 1;
+ }
+
+ @Override
+ void doCompute(List<ResolverActivity.ResolvedComponentInfo> targets) {}
+
+ @Override
+ float getScore(ComponentName name) {
+ return 0;
+ }
+
+ @Override
+ void handleResultMessage(Message message) {}
+ };
+ return testComparator;
+ }
+
+}
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index c44b7d8..4dfa9bf 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -469,8 +469,8 @@
.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withId(R.id.copy_button)).check(matches(isDisplayed()));
- onView(withId(R.id.copy_button)).perform(click());
+ onView(withId(R.id.chooser_copy_button)).check(matches(isDisplayed()));
+ onView(withId(R.id.chooser_copy_button)).perform(click());
ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(
Context.CLIPBOARD_SERVICE);
ClipData clipData = clipboard.getPrimaryClip();
diff --git a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
index 8483645..e16d1ca 100644
--- a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
+++ b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
@@ -19,7 +19,7 @@
import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
import static android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION;
-import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
@@ -80,8 +80,8 @@
CountDownLatch lock = new CountDownLatch(1);
mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN, false, false, timeoutMs,
mHandler,
- worked -> {
- assertFalse(worked);
+ uri -> {
+ assertNull(uri);
lock.countDown();
});
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index 5648b85..a815f20 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -2095,9 +2095,11 @@
/**
* Draw the text, with origin at (x,y), using the specified paint, along the specified path. The
- * paint's Align setting determins where along the path to start the text.
+ * paint's Align setting determines where along the path to start the text.
*
* @param text The text to be drawn
+ * @param index The starting index within the text to be drawn
+ * @param count Starting from index, the number of characters to draw
* @param path The path the text should follow for its baseline
* @param hOffset The distance along the path to add to the text's starting position
* @param vOffset The distance above(-) or below(+) the path to position the text
@@ -2110,7 +2112,7 @@
/**
* Draw the text, with origin at (x,y), using the specified paint, along the specified path. The
- * paint's Align setting determins where along the path to start the text.
+ * paint's Align setting determines where along the path to start the text.
*
* @param text The text to be drawn
* @param path The path the text should follow for its baseline
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index b7316ab..109d863 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -1114,7 +1114,7 @@
* Return the width for stroking.
* <p />
* A value of 0 strokes in hairline mode.
- * Hairlines always draws a single pixel independent of the canva's matrix.
+ * Hairlines always draws a single pixel independent of the canvas's matrix.
*
* @return the paint's stroke width, used whenever the paint's style is
* Stroke or StrokeAndFill.
@@ -1126,7 +1126,7 @@
/**
* Set the width for stroking.
* Pass 0 to stroke in hairline mode.
- * Hairlines always draws a single pixel independent of the canva's matrix.
+ * Hairlines always draws a single pixel independent of the canvas's matrix.
*
* @param width set the paint's stroke width, used whenever the paint's
* style is Stroke or StrokeAndFill.
@@ -1958,8 +1958,8 @@
* <code>
* Paint paint = new Paint();
* paint.setStartHyphenEdit(Paint.START_HYPHEN_EDIT_INSERT_HYPHEN);
- * paint.measureText("abc", 0, 3); // Returns the width of "‐abc"
- * Canvas.drawText("abc", 0, 3, 0f, 0f, paint); // Draws "‐abc"
+ * paint.measureText("abc", 0, 3); // Returns the width of "-abc"
+ * Canvas.drawText("abc", 0, 3, 0f, 0f, paint); // Draws "-abc"
* </code>
* </pre>
*
@@ -1985,8 +1985,8 @@
* <code>
* Paint paint = new Paint();
* paint.setEndHyphenEdit(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN);
- * paint.measureText("abc", 0, 3); // Returns the width of "abc‐"
- * Canvas.drawText("abc", 0, 3, 0f, 0f, paint); // Draws "abc‐"
+ * paint.measureText("abc", 0, 3); // Returns the width of "abc-"
+ * Canvas.drawText("abc", 0, 3, 0f, 0f, paint); // Draws "abc-"
* </code>
* </pre>
*
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 9898a1c..827cced 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -143,13 +143,15 @@
mAnimationContext->destroy();
}
-void CanvasContext::setSurface(sp<Surface>&& surface) {
+void CanvasContext::setSurface(sp<Surface>&& surface, bool enableTimeout) {
ATRACE_CALL();
if (surface) {
mNativeSurface = new ReliableSurface{std::move(surface)};
- // TODO: Fix error handling & re-shorten timeout
- mNativeSurface->setDequeueTimeout(4000_ms);
+ if (enableTimeout) {
+ // TODO: Fix error handling & re-shorten timeout
+ mNativeSurface->setDequeueTimeout(4000_ms);
+ }
} else {
mNativeSurface = nullptr;
}
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 982c087..a0233ca 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -110,7 +110,7 @@
// Won't take effect until next EGLSurface creation
void setSwapBehavior(SwapBehavior swapBehavior);
- void setSurface(sp<Surface>&& surface);
+ void setSurface(sp<Surface>&& surface, bool enableTimeout = true);
bool pauseSurface();
void setStopped(bool stopped);
bool hasSurface() const { return mNativeSurface.get(); }
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 1a1b9da..edb82f4 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -82,9 +82,10 @@
mRenderThread.queue().runSync([this, name]() { mContext->setName(std::string(name)); });
}
-void RenderProxy::setSurface(const sp<Surface>& surface) {
- mRenderThread.queue().post(
- [this, surf = surface]() mutable { mContext->setSurface(std::move(surf)); });
+void RenderProxy::setSurface(const sp<Surface>& surface, bool enableTimeout) {
+ mRenderThread.queue().post([this, surf = surface, enableTimeout]() mutable {
+ mContext->setSurface(std::move(surf), enableTimeout);
+ });
}
void RenderProxy::allocateBuffers() {
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index a0f08cb..76cd0ee 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -69,7 +69,7 @@
ANDROID_API bool loadSystemProperties();
ANDROID_API void setName(const char* name);
- ANDROID_API void setSurface(const sp<Surface>& surface);
+ ANDROID_API void setSurface(const sp<Surface>& surface, bool enableTimeout = true);
ANDROID_API void allocateBuffers();
ANDROID_API bool pause();
ANDROID_API void setStopped(bool stopped);
diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java
index 9c36d76..db06399 100644
--- a/location/java/android/location/Location.java
+++ b/location/java/android/location/Location.java
@@ -158,7 +158,10 @@
* <p>By default time, latitude and longitude are 0, and the location
* has no bearing, altitude, speed, accuracy or extras.
*
- * @param provider the name of the provider that generated this location
+ * @param provider the source that provides the location. It can be of type
+ * {@link LocationManager#GPS_PROVIDER}, {@link LocationManager#NETWORK_PROVIDER},
+ * or {@link LocationManager#PASSIVE_PROVIDER}. You can also define your own
+ * provider string, in which case an empty string is a valid provider.
*/
public Location(String provider) {
mProvider = provider;
diff --git a/location/java/android/location/OnNmeaMessageListener.java b/location/java/android/location/OnNmeaMessageListener.java
index ccf6ce8..05647bc 100644
--- a/location/java/android/location/OnNmeaMessageListener.java
+++ b/location/java/android/location/OnNmeaMessageListener.java
@@ -28,7 +28,9 @@
/**
* Called when an NMEA message is received.
* @param message NMEA message
- * @param timestamp milliseconds since January 1, 1970.
+ * @param timestamp Date and time of the location fix, as reported by the GNSS
+ * chipset. The value is specified in milliseconds since 0:00
+ * UTC 1 January 1970.
*/
void onNmeaMessage(String message, long timestamp);
}
diff --git a/media/Android.bp b/media/Android.bp
index a768b81..2f75e44 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -33,6 +33,8 @@
"framework_media_annotation",
"android_system_stubs_current",
],
+
+ plugins: ["java_api_finder"],
}
filegroup {
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 5645ba5..6c723b3 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -1345,7 +1345,9 @@
private ByteOrder mExifByteOrder = ByteOrder.BIG_ENDIAN;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private boolean mHasThumbnail;
- // The following values used for indicating a thumbnail position.
+ private boolean mHasThumbnailStrips;
+ private boolean mAreThumbnailStripsConsecutive;
+ // Used to indicate the position of the thumbnail (includes offset to EXIF data segment).
private int mThumbnailOffset;
private int mThumbnailLength;
private byte[] mThumbnailBytes;
@@ -2043,10 +2045,12 @@
/**
* Returns the offset and length of thumbnail inside the image file, or
- * {@code null} if there is no thumbnail.
+ * {@code null} if either there is no thumbnail or the thumbnail bytes are stored
+ * non-consecutively.
*
* @return two-element array, the offset in the first value, and length in
- * the second, or {@code null} if no thumbnail was found.
+ * the second, or {@code null} if no thumbnail was found or the thumbnail strips are
+ * not placed consecutively.
* @throws IllegalStateException if {@link #saveAttributes()} has been
* called since the underlying file was initially parsed, since
* that means offsets may have changed.
@@ -2058,10 +2062,12 @@
}
if (mHasThumbnail) {
+ if (mHasThumbnailStrips && !mAreThumbnailStripsConsecutive) {
+ return null;
+ }
return new long[] { mThumbnailOffset, mThumbnailLength };
- } else {
- return null;
}
+ return null;
}
/**
@@ -2536,10 +2542,9 @@
final byte[] value = Arrays.copyOfRange(bytes,
IDENTIFIER_EXIF_APP1.length, bytes.length);
- readExifSegment(value, imageType);
-
// Save offset values for createJpegThumbnailBitmap() function
mExifOffset = (int) offset;
+ readExifSegment(value, imageType);
} else if (ArrayUtils.startsWith(bytes, IDENTIFIER_XMP_APP1)) {
// See XMP Specification Part 3: Storage in Files, 1.1.3 JPEG, Table 6
final long offset = start + IDENTIFIER_XMP_APP1.length;
@@ -2843,6 +2848,8 @@
if (in.read(bytes) != length) {
throw new IOException("Can't read exif");
}
+ // Save offset values for handling thumbnail and attribute offsets.
+ mExifOffset = offset;
readExifSegment(bytes, IFD_TYPE_PRIMARY);
}
@@ -2988,7 +2995,7 @@
// Write EXIF APP1 segment
dataOutputStream.writeByte(MARKER);
dataOutputStream.writeByte(MARKER_APP1);
- writeExifSegment(dataOutputStream, 6);
+ writeExifSegment(dataOutputStream);
byte[] bytes = new byte[4096];
@@ -3319,7 +3326,7 @@
continue;
}
- final int bytesOffset = dataInputStream.peek();
+ final int bytesOffset = dataInputStream.peek() + mExifOffset;
final byte[] bytes = new byte[(int) byteCount];
dataInputStream.readFully(bytes);
ExifAttribute attribute = new ExifAttribute(dataFormat, numberOfComponents,
@@ -3451,31 +3458,28 @@
// The following code limits the size of thumbnail size not to overflow EXIF data area.
thumbnailLength = Math.min(thumbnailLength, in.getLength() - thumbnailOffset);
- if (mMimeType == IMAGE_TYPE_JPEG || mMimeType == IMAGE_TYPE_RAF
- || mMimeType == IMAGE_TYPE_RW2) {
- thumbnailOffset += mExifOffset;
- } else if (mMimeType == IMAGE_TYPE_ORF) {
+ if (mMimeType == IMAGE_TYPE_ORF) {
// Update offset value since RAF files have IFD data preceding MakerNote data.
thumbnailOffset += mOrfMakerNoteOffset;
}
- if (DEBUG) {
- Log.d(TAG, "Setting thumbnail attributes with offset: " + thumbnailOffset
- + ", length: " + thumbnailLength);
- }
if (thumbnailOffset > 0 && thumbnailLength > 0) {
mHasThumbnail = true;
- mThumbnailOffset = thumbnailOffset;
+ mThumbnailOffset = thumbnailOffset + mExifOffset;
mThumbnailLength = thumbnailLength;
mThumbnailCompression = DATA_JPEG;
if (mFilename == null && mAssetInputStream == null
&& mSeekableFileDescriptor == null) {
// Save the thumbnail in memory if the input doesn't support reading again.
- byte[] thumbnailBytes = new byte[thumbnailLength];
- in.seek(thumbnailOffset);
+ byte[] thumbnailBytes = new byte[mThumbnailLength];
+ in.seek(mThumbnailOffset);
in.readFully(thumbnailBytes);
mThumbnailBytes = thumbnailBytes;
}
+ if (DEBUG) {
+ Log.d(TAG, "Setting thumbnail attributes with offset: " + thumbnailOffset
+ + ", length: " + thumbnailLength);
+ }
}
}
}
@@ -3494,12 +3498,16 @@
long[] stripByteCounts =
convertToLongArray(stripByteCountsAttribute.getValue(mExifByteOrder));
- if (stripOffsets == null) {
- Log.w(TAG, "stripOffsets should not be null.");
+ if (stripOffsets == null || stripOffsets.length == 0) {
+ Log.w(TAG, "stripOffsets should not be null or have zero length.");
return;
}
- if (stripByteCounts == null) {
- Log.w(TAG, "stripByteCounts should not be null.");
+ if (stripByteCounts == null || stripByteCounts.length == 0) {
+ Log.w(TAG, "stripByteCounts should not be null or have zero length.");
+ return;
+ }
+ if (stripOffsets.length != stripByteCounts.length) {
+ Log.w(TAG, "stripOffsets and stripByteCounts should have same length.");
return;
}
@@ -3509,10 +3517,18 @@
int bytesRead = 0;
int bytesAdded = 0;
+ mHasThumbnail = mHasThumbnailStrips = mAreThumbnailStripsConsecutive = true;
for (int i = 0; i < stripOffsets.length; i++) {
int stripOffset = (int) stripOffsets[i];
int stripByteCount = (int) stripByteCounts[i];
+ // Check if strips are consecutive
+ // TODO: Add test for non-consecutive thumbnail image
+ if (i < stripOffsets.length - 1
+ && stripOffset + stripByteCount != stripOffsets[i + 1]) {
+ mAreThumbnailStripsConsecutive = false;
+ }
+
// Skip to offset
int skipBytes = stripOffset - bytesRead;
if (skipBytes < 0) {
@@ -3531,10 +3547,13 @@
stripBytes.length);
bytesAdded += stripBytes.length;
}
-
- mHasThumbnail = true;
mThumbnailBytes = totalStripBytes;
- mThumbnailLength = totalStripBytes.length;
+
+ if (mAreThumbnailStripsConsecutive) {
+ // Need to add mExifOffset, which is the offset to the EXIF data segment
+ mThumbnailOffset = (int) stripOffsets[0] + mExifOffset;
+ mThumbnailLength = totalStripBytes.length;
+ }
}
}
@@ -3691,8 +3710,7 @@
}
// Writes an Exif segment into the given output stream.
- private int writeExifSegment(ByteOrderedDataOutputStream dataOutputStream,
- int exifOffsetFromBeginning) throws IOException {
+ private int writeExifSegment(ByteOrderedDataOutputStream dataOutputStream) throws IOException {
// The following variables are for calculating each IFD tag group size in bytes.
int[] ifdOffsets = new int[EXIF_TAGS.length];
int[] ifdDataSizes = new int[EXIF_TAGS.length];
@@ -3751,6 +3769,8 @@
}
// Calculate IFD offsets.
+ // 8 bytes are for TIFF headers: 2 bytes (byte order) + 2 bytes (identifier) + 4 bytes
+ // (offset of IFDs)
int position = 8;
for (int ifdType = 0; ifdType < EXIF_TAGS.length; ++ifdType) {
if (!mAttributes[ifdType].isEmpty()) {
@@ -3762,7 +3782,8 @@
int thumbnailOffset = position;
mAttributes[IFD_TYPE_THUMBNAIL].put(JPEG_INTERCHANGE_FORMAT_TAG.name,
ExifAttribute.createULong(thumbnailOffset, mExifByteOrder));
- mThumbnailOffset = exifOffsetFromBeginning + thumbnailOffset;
+ // Need to add mExifOffset, which is the offset to the EXIF data segment
+ mThumbnailOffset = thumbnailOffset + mExifOffset;
position += mThumbnailLength;
}
diff --git a/media/java/android/media/Image.java b/media/java/android/media/Image.java
index 70a343f..7ba122b 100644
--- a/media/java/android/media/Image.java
+++ b/media/java/android/media/Image.java
@@ -395,7 +395,7 @@
* <p>The row stride for this color plane, in bytes.</p>
*
* <p>This is the distance between the start of two consecutive rows of
- * pixels in the image. Note that row stried is undefined for some formats
+ * pixels in the image. Note that row stride is undefined for some formats
* such as
* {@link android.graphics.ImageFormat#RAW_PRIVATE RAW_PRIVATE},
* and calling getRowStride on images of these formats will
diff --git a/media/java/android/media/ImageWriter.java b/media/java/android/media/ImageWriter.java
index f813d1b..7bc2b31 100644
--- a/media/java/android/media/ImageWriter.java
+++ b/media/java/android/media/ImageWriter.java
@@ -192,13 +192,15 @@
mMaxImages = maxImages;
- if (format == ImageFormat.UNKNOWN) {
- format = SurfaceUtils.getSurfaceFormat(surface);
- }
// Note that the underlying BufferQueue is working in synchronous mode
// to avoid dropping any buffers.
mNativeContext = nativeInit(new WeakReference<>(this), surface, maxImages, format);
+ // nativeInit internally overrides UNKNOWN format. So does surface format query after
+ // nativeInit and before getEstimatedNativeAllocBytes().
+ if (format == ImageFormat.UNKNOWN) {
+ format = SurfaceUtils.getSurfaceFormat(surface);
+ }
// Estimate the native buffer allocation size and register it so it gets accounted for
// during GC. Note that this doesn't include the buffers required by the buffer queue
// itself and the buffers requested by the producer.
diff --git a/packages/CarSystemUI/res/layout/car_top_navigation_bar_unprovisioned.xml b/packages/CarSystemUI/res/layout/car_top_navigation_bar_unprovisioned.xml
new file mode 100644
index 0000000..8247211
--- /dev/null
+++ b/packages/CarSystemUI/res/layout/car_top_navigation_bar_unprovisioned.xml
@@ -0,0 +1,147 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<com.android.systemui.statusbar.car.CarNavigationBarView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/car_top_bar"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/system_bar_background"
+ android:orientation="vertical">
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1">
+
+ <FrameLayout
+ android:id="@+id/left_hvac_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_alignParentStart="true"
+ >
+
+ <com.android.systemui.statusbar.car.CarNavigationButton
+ android:id="@+id/hvacleft"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@null"
+ systemui:broadcast="true"
+ systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
+ />
+
+ <com.android.systemui.statusbar.hvac.AnimatedTemperatureView
+ android:id="@+id/lefttext"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:paddingStart="@*android:dimen/car_padding_4"
+ android:paddingEnd="16dp"
+ android:gravity="center_vertical|start"
+ android:minEms="4"
+ android:textAppearance="@style/TextAppearance.CarStatus"
+ systemui:hvacAreaId="49"
+ systemui:hvacMaxText="@string/hvac_max_text"
+ systemui:hvacMaxValue="@dimen/hvac_max_value"
+ systemui:hvacMinText="@string/hvac_min_text"
+ systemui:hvacMinValue="@dimen/hvac_min_value"
+ systemui:hvacPivotOffset="60dp"
+ systemui:hvacPropertyId="358614275"
+ systemui:hvacTempFormat="%.0f\u00B0"
+ />
+ </FrameLayout>
+
+ <FrameLayout
+ android:id="@+id/clock_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_centerInParent="true">
+ <com.android.systemui.statusbar.car.CarNavigationButton
+ android:id="@+id/qs"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@null"/>
+ <com.android.systemui.statusbar.policy.Clock
+ android:id="@+id/clock"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:elevation="5dp"
+ android:singleLine="true"
+ android:textAppearance="@style/TextAppearance.StatusBar.Clock"/>
+ </FrameLayout>
+
+ <LinearLayout
+ android:id="@+id/system_icon_area"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_centerHorizontal="true"
+ android:layout_centerVertical="true"
+ android:layout_toEndOf="@+id/clock_container"
+ android:paddingStart="@*android:dimen/car_padding_1"
+ android:gravity="center_vertical"
+ android:orientation="horizontal"
+ >
+
+ <include
+ layout="@layout/system_icons"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingStart="4dp"
+ android:gravity="center_vertical"
+ />
+ </LinearLayout>
+
+ <FrameLayout
+ android:id="@+id/right_hvac_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_alignParentEnd="true"
+ >
+
+ <com.android.systemui.statusbar.car.CarNavigationButton
+ android:id="@+id/hvacright"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@null"
+ systemui:broadcast="true"
+ systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
+ />
+
+ <com.android.systemui.statusbar.hvac.AnimatedTemperatureView
+ android:id="@+id/righttext"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:paddingStart="16dp"
+ android:paddingEnd="@*android:dimen/car_padding_4"
+ android:gravity="center_vertical|end"
+ android:minEms="4"
+ android:textAppearance="@style/TextAppearance.CarStatus"
+ systemui:hvacAreaId="68"
+ systemui:hvacMaxText="@string/hvac_max_text"
+ systemui:hvacMaxValue="@dimen/hvac_max_value"
+ systemui:hvacMinText="@string/hvac_min_text"
+ systemui:hvacMinValue="@dimen/hvac_min_value"
+ systemui:hvacPivotOffset="60dp"
+ systemui:hvacPropertyId="358614275"
+ systemui:hvacTempFormat="%.0f\u00B0"
+ />
+ </FrameLayout>
+ </RelativeLayout>
+
+</com.android.systemui.statusbar.car.CarNavigationBarView>
diff --git a/packages/CarSystemUI/res/layout/super_status_bar.xml b/packages/CarSystemUI/res/layout/super_status_bar.xml
index e1bcc2e..7fee805 100644
--- a/packages/CarSystemUI/res/layout/super_status_bar.xml
+++ b/packages/CarSystemUI/res/layout/super_status_bar.xml
@@ -42,14 +42,6 @@
</com.android.systemui.statusbar.BackDropView>
<com.android.systemui.statusbar.ScrimView
- android:id="@+id/scrim_for_bubble"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:importantForAccessibility="no"
- sysui:ignoreRightInset="true"
- />
-
- <com.android.systemui.statusbar.ScrimView
android:id="@+id/scrim_behind"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -69,10 +61,10 @@
android:visibility="gone"
/>
- <include layout="@layout/car_top_navigation_bar"
+ <FrameLayout
+ android:id="@+id/car_top_navigation_bar_container"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- />
+ android:layout_height="wrap_content"/>
</LinearLayout>
<include layout="@layout/brightness_mirror"/>
diff --git a/packages/CarSystemUI/res/values/config.xml b/packages/CarSystemUI/res/values/config.xml
index 467c4a4..cbf2287 100644
--- a/packages/CarSystemUI/res/values/config.xml
+++ b/packages/CarSystemUI/res/values/config.xml
@@ -40,4 +40,19 @@
slots that may be reused for things like IME control. -->
<integer name="config_maxNotificationIcons">0</integer>
+ <!--
+ Initial alpha percent value for the background when the notification
+ shade is open. Should be a number between, and inclusive, 0 and 100.
+ If the number is 0, then the background alpha starts off fully
+ transparent. If the number if 100, then the background alpha starts off
+ fully opaque. -->
+ <integer name="config_initialNotificationBackgroundAlpha">0</integer>
+ <!--
+ Final alpha percent value for the background when the notification
+ shade is fully open. Should be a number between, and inclusive, 0 and
+ 100. If this value is smaller than
+ config_initialNotificationBackgroundAlpha, the background will default
+ to a constant alpha percent value using the initial alpha. -->
+ <integer name="config_finalNotificationBackgroundAlpha">100</integer>
+
</resources>
diff --git a/packages/CarSystemUI/res/values/integers_car.xml b/packages/CarSystemUI/res/values/integers_car.xml
index fb67b30..ba3d329 100644
--- a/packages/CarSystemUI/res/values/integers_car.xml
+++ b/packages/CarSystemUI/res/values/integers_car.xml
@@ -34,4 +34,9 @@
<!-- The delay before the unlock dialog pops up -->
<integer name="unlock_dialog_delay_ms">0</integer>
+ <!-- Timeout values in milliseconds for displaying volume dialog-->
+ <integer name="car_volume_dialog_display_normal_timeout">3000</integer>
+ <integer name="car_volume_dialog_display_hovering_timeout">16000</integer>
+ <integer name="car_volume_dialog_display_expanded_normal_timeout">6000</integer>
+ <integer name="car_volume_dialog_display_expanded_hovering_timeout">32000</integer>
</resources>
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java
index c7654e8..a423554 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java
@@ -20,6 +20,7 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.ViewMediatorCallback;
+import com.android.systemui.car.CarServiceProvider;
import com.android.systemui.statusbar.car.CarFacetButtonController;
import com.android.systemui.statusbar.car.CarStatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -36,6 +37,7 @@
public class CarSystemUIFactory extends SystemUIFactory {
private CarDependencyComponent mCarDependencyComponent;
+ private CarServiceProvider mCarServiceProvider;
@Override
protected SystemUIRootComponent buildSystemUIRootComponent(Context context) {
@@ -48,6 +50,14 @@
.build();
}
+ /** Gets a {@link CarServiceProvider}. */
+ public CarServiceProvider getCarServiceProvider(Context context) {
+ if (mCarServiceProvider == null) {
+ mCarServiceProvider = new CarServiceProvider(context);
+ }
+ return mCarServiceProvider;
+ }
+
public CarDependencyComponent getCarDependencyComponent() {
return mCarDependencyComponent;
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java
index afd722b..447e579 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java
@@ -22,6 +22,7 @@
import com.android.systemui.statusbar.notification.NotificationFilter;
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.policy.BatteryController;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -34,8 +35,9 @@
@Inject
public CarNotificationInterruptionStateProvider(Context context,
NotificationFilter filter,
- StatusBarStateController stateController) {
- super(context, filter, stateController);
+ StatusBarStateController stateController,
+ BatteryController batteryController) {
+ super(context, filter, stateController, batteryController);
}
@Override
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarServiceProvider.java b/packages/CarSystemUI/src/com/android/systemui/car/CarServiceProvider.java
new file mode 100644
index 0000000..9ee368e
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/car/CarServiceProvider.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.car;
+
+import android.car.Car;
+import android.car.Car.CarServiceLifecycleListener;
+import android.content.Context;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Connects to the car service a single time for shared use across all of system ui.
+ */
+public class CarServiceProvider {
+
+ private final Context mContext;
+ private final List<CarServiceLifecycleListener> mListeners = new ArrayList<>();
+ private Car mCar;
+
+ public CarServiceProvider(Context context) {
+ mContext = context;
+ mCar = Car.createCar(mContext, /* handler= */ null, Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT,
+ (car, ready) -> {
+ mCar = car;
+
+ synchronized (mListeners) {
+ for (CarServiceLifecycleListener listener : mListeners) {
+ listener.onLifecycleChanged(mCar, ready);
+ }
+ }
+ });
+ }
+
+ /**
+ * Let's other components hook into the connection to the car service. If we're already
+ * connected
+ * to the car service, the callback is immediately triggered.
+ */
+ public void addListener(CarServiceLifecycleListener listener) {
+ if (mCar.isConnected()) {
+ listener.onLifecycleChanged(mCar, /* ready= */ true);
+ }
+ mListeners.add(listener);
+ }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarBatteryController.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarBatteryController.java
index 58f80a4..d79849c 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarBatteryController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarBatteryController.java
@@ -257,6 +257,11 @@
return false;
}
+ @Override
+ public boolean isAodPowerSave() {
+ return false;
+ }
+
private void notifyBatteryLevelChanged() {
for (int i = 0, size = mChangeCallbacks.size(); i < size; i++) {
mChangeCallbacks.get(i)
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarFacetButton.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarFacetButton.java
index a371a1d8..fd9ce43 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarFacetButton.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarFacetButton.java
@@ -110,24 +110,34 @@
mComponentNames = componentNameString.split(FACET_FILTER_DELIMITER);
}
- setOnClickListener(v -> {
- intent.putExtra(EXTRA_FACET_LAUNCH_PICKER, mSelected);
- mContext.startActivityAsUser(intent, UserHandle.CURRENT);
- });
+ setOnClickListener(getButtonClickListener(intent));
if (longPressIntentString != null) {
final Intent longPressIntent = Intent.parseUri(longPressIntentString,
Intent.URI_INTENT_SCHEME);
- setOnLongClickListener(v -> {
- mContext.startActivityAsUser(longPressIntent, UserHandle.CURRENT);
- return true;
- });
+ setOnLongClickListener(getButtonLongClickListener(longPressIntent));
}
} catch (Exception e) {
throw new RuntimeException("Failed to attach intent", e);
}
}
+ /** Defines the behavior of a button click. */
+ protected OnClickListener getButtonClickListener(Intent toSend) {
+ return v -> {
+ toSend.putExtra(EXTRA_FACET_LAUNCH_PICKER, mSelected);
+ mContext.startActivityAsUser(toSend, UserHandle.CURRENT);
+ };
+ }
+
+ /** Defines the behavior of a long click. */
+ protected OnLongClickListener getButtonLongClickListener(Intent toSend) {
+ return v -> {
+ mContext.startActivityAsUser(toSend, UserHandle.CURRENT);
+ return true;
+ };
+ }
+
private void setupIcons(TypedArray styledAttributes) {
mSelectedAlpha = styledAttributes.getFloat(
R.styleable.CarFacetButton_selectedAlpha, mSelectedAlpha);
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationButton.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationButton.java
index 8879742..bdf23c5 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationButton.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationButton.java
@@ -90,17 +90,7 @@
try {
if (mIntent != null) {
final Intent intent = Intent.parseUri(mIntent, Intent.URI_INTENT_SCHEME);
- setOnClickListener(v -> {
- try {
- if (mBroadcastIntent) {
- mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
- return;
- }
- mContext.startActivityAsUser(intent, UserHandle.CURRENT);
- } catch (Exception e) {
- Log.e(TAG, "Failed to launch intent", e);
- }
- });
+ setOnClickListener(getButtonClickListener(intent));
}
} catch (URISyntaxException e) {
throw new RuntimeException("Failed to attach intent", e);
@@ -109,21 +99,41 @@
try {
if (mLongIntent != null) {
final Intent intent = Intent.parseUri(mLongIntent, Intent.URI_INTENT_SCHEME);
- setOnLongClickListener(v -> {
- try {
- mContext.startActivityAsUser(intent, UserHandle.CURRENT);
- } catch (Exception e) {
- Log.e(TAG, "Failed to launch intent", e);
- }
- // consume event either way
- return true;
- });
+ setOnLongClickListener(getButtonLongClickListener(intent));
}
} catch (URISyntaxException e) {
throw new RuntimeException("Failed to attach long press intent", e);
}
}
+ /** Defines the behavior of a button click. */
+ protected OnClickListener getButtonClickListener(Intent toSend) {
+ return v -> {
+ try {
+ if (mBroadcastIntent) {
+ mContext.sendBroadcastAsUser(toSend, UserHandle.CURRENT);
+ return;
+ }
+ mContext.startActivityAsUser(toSend, UserHandle.CURRENT);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to launch intent", e);
+ }
+ };
+ }
+
+ /** Defines the behavior of a long click. */
+ protected OnLongClickListener getButtonLongClickListener(Intent toSend) {
+ return v -> {
+ try {
+ mContext.startActivityAsUser(toSend, UserHandle.CURRENT);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to launch intent", e);
+ }
+ // consume event either way
+ return true;
+ };
+ }
+
/**
* @param selected true if should indicate if this is a selected state, false otherwise
*/
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 25191f6..db98c36 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -23,16 +23,20 @@
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.car.Car;
+import android.car.CarNotConnectedException;
import android.car.drivingstate.CarDrivingStateEvent;
import android.car.drivingstate.CarUxRestrictionsManager;
import android.car.hardware.power.CarPowerManager.CarPowerStateListener;
-import android.car.trust.CarTrustAgentEnrollmentManager;
+import android.car.media.CarAudioManager;
import android.content.Context;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.inputmethodservice.InputMethodService;
+import android.media.AudioManager;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.util.Log;
import android.view.Display;
import android.view.GestureDetector;
@@ -61,26 +65,46 @@
import com.android.systemui.BatteryMeterView;
import com.android.systemui.CarSystemUIFactory;
import com.android.systemui.Dependency;
+import com.android.systemui.ForegroundServiceController;
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.classifier.FalsingLog;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.qs.car.CarQSFragment;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.statusbar.FlingAnimationUtils;
+import com.android.systemui.statusbar.NavigationBarController;
+import com.android.systemui.statusbar.NotificationListener;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationViewHierarchyManager;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.car.hvac.HvacController;
import com.android.systemui.statusbar.car.hvac.TemperatureView;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
+import com.android.systemui.statusbar.phone.LightBarController;
+import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.volume.VolumeUI;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -101,6 +125,9 @@
private float mOpeningVelocity = DEFAULT_FLING_VELOCITY;
private float mClosingVelocity = DEFAULT_FLING_VELOCITY;
+ private float mBackgroundAlphaDiff;
+ private float mInitialBackgroundAlpha;
+
private TaskStackListenerImpl mTaskStackListener;
private FullscreenUserSwitcher mFullscreenUserSwitcher;
@@ -109,9 +136,11 @@
private BatteryMeterView mBatteryMeterView;
private Drawable mNotificationPanelBackground;
+ private ViewGroup mTopNavigationBarContainer;
private ViewGroup mNavigationBarWindow;
private ViewGroup mLeftNavigationBarWindow;
private ViewGroup mRightNavigationBarWindow;
+ private CarNavigationBarView mTopNavigationBarView;
private CarNavigationBarView mNavigationBarView;
private CarNavigationBarView mLeftNavigationBarView;
private CarNavigationBarView mRightNavigationBarView;
@@ -123,7 +152,7 @@
private CarFacetButtonController mCarFacetButtonController;
private ActivityManagerWrapper mActivityManagerWrapper;
private DeviceProvisionedController mDeviceProvisionedController;
- private boolean mDeviceIsProvisioned = true;
+ private boolean mDeviceIsSetUpForUser = true;
private HvacController mHvacController;
private DrivingStateHelper mDrivingStateHelper;
private PowerManagerHelper mPowerManagerHelper;
@@ -132,6 +161,8 @@
private NotificationDataManager mNotificationDataManager;
private NotificationClickHandlerFactory mNotificationClickHandlerFactory;
private ScreenLifecycle mScreenLifecycle;
+ private CarAudioManager mCarAudioManager;
+ private VolumeUI mVolumeUI;
// The container for the notifications.
private CarNotificationView mNotificationView;
@@ -144,6 +175,9 @@
private boolean mNotificationListAtBottom;
// Was the notification list at the bottom when the user first touched the screen
private boolean mNotificationListAtBottomAtTimeOfTouch;
+ // To be attached to the top navigation bar (i.e. status bar) to pull down the notification
+ // panel.
+ private View.OnTouchListener mTopNavBarNotificationTouchListener;
// To be attached to the navigation bars such that they can close the notification panel if
// it's open.
private View.OnTouchListener mNavBarNotificationTouchListener;
@@ -174,6 +208,8 @@
private boolean mHideNavBarForKeyboard;
private boolean mBottomNavBarVisible;
+ private CarUxRestrictionManagerWrapper mCarUxRestrictionManagerWrapper;
+
private final CarPowerStateListener mCarPowerStateListener =
(int state) -> {
// When the car powers on, clear all notifications and mute/unread states.
@@ -188,12 +224,38 @@
}
};
+ private final CarAudioManager.CarVolumeCallback mVolumeChangeCallback =
+ new CarAudioManager.CarVolumeCallback() {
+ @Override
+ public void onGroupVolumeChanged(int zoneId, int groupId, int flags) {
+ if (mVolumeUI == null && (flags & AudioManager.FLAG_SHOW_UI) != 0) {
+ new Handler(Looper.getMainLooper()).post(() -> {
+ // Initialize Volume UI
+ mVolumeUI = new VolumeUI();
+ mVolumeUI.mComponents = mComponents;
+ mVolumeUI.mContext = mContext;
+ mVolumeUI.start();
+ });
+ mCarAudioManager.unregisterCarVolumeCallback(mVolumeChangeCallback);
+ }
+ }
+
+ @Override
+ public void onMasterMuteChanged(int zoneId, int flags) {
+ // ignored
+ }
+ };
+
@Override
public void start() {
+ // Non blocking call to connect to car service. Call this early so that we'll be connected
+ // asap.
+ ((CarSystemUIFactory) SystemUIFactory.getInstance()).getCarServiceProvider(mContext);
+
// get the provisioned state before calling the parent class since it's that flow that
// builds the nav bar
mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
- mDeviceIsProvisioned = mDeviceProvisionedController.isDeviceProvisioned();
+ mDeviceIsSetUpForUser = mDeviceProvisionedController.isCurrentUserSetup();
// Keyboard related setup, before nav bars are created.
mHideNavBarForKeyboard = mContext.getResources().getBoolean(
@@ -205,6 +267,29 @@
mScreenLifecycle = Dependency.get(ScreenLifecycle.class);
mScreenLifecycle.addObserver(mScreenObserver);
+ // Need to initialize HVAC controller before calling super.start - before system bars are
+ // created.
+ mHvacController = new HvacController(mContext);
+
+ // Notification bar related setup.
+ mInitialBackgroundAlpha = (float) mContext.getResources().getInteger(
+ R.integer.config_initialNotificationBackgroundAlpha) / 100;
+ if (mInitialBackgroundAlpha < 0 || mInitialBackgroundAlpha > 100) {
+ throw new RuntimeException(
+ "Unable to setup notification bar due to incorrect initial background alpha"
+ + " percentage");
+ }
+ float finalBackgroundAlpha = Math.max(
+ mInitialBackgroundAlpha,
+ (float) mContext.getResources().getInteger(
+ R.integer.config_finalNotificationBackgroundAlpha) / 100);
+ if (finalBackgroundAlpha < 0 || finalBackgroundAlpha > 100) {
+ throw new RuntimeException(
+ "Unable to setup notification bar due to incorrect final background alpha"
+ + " percentage");
+ }
+ mBackgroundAlphaDiff = finalBackgroundAlpha - mInitialBackgroundAlpha;
+
super.start();
mTaskStackListener = new TaskStackListenerImpl();
mActivityManagerWrapper = ActivityManagerWrapper.getInstance();
@@ -223,25 +308,22 @@
mHvacController.connectToCarService();
- CarSystemUIFactory factory = SystemUIFactory.getInstance();
- if (!mDeviceIsProvisioned) {
- mDeviceProvisionedController.addCallback(
- new DeviceProvisionedController.DeviceProvisionedListener() {
- @Override
- public void onDeviceProvisionedChanged() {
- mHandler.post(() -> {
- // on initial boot we are getting a call even though the value
- // is the same so we are confirming the reset is needed
- boolean deviceProvisioned =
- mDeviceProvisionedController.isDeviceProvisioned();
- if (mDeviceIsProvisioned != deviceProvisioned) {
- mDeviceIsProvisioned = deviceProvisioned;
- restartNavBars();
- }
- });
- }
- });
- }
+ mDeviceProvisionedController.addCallback(
+ new DeviceProvisionedController.DeviceProvisionedListener() {
+ @Override
+ public void onUserSetupChanged() {
+ mHandler.post(() -> restartNavBarsIfNecessary());
+ }
+
+ @Override
+ public void onUserSwitched() {
+ mHandler.post(() -> restartNavBarsIfNecessary());
+ }
+ });
+
+ // Used by onDrivingStateChanged and it can be called inside
+ // DrivingStateHelper.connectToCarService()
+ mSwitchToGuestTimer = new SwitchToGuestTimer(mContext);
// Register a listener for driving state changes.
mDrivingStateHelper = new DrivingStateHelper(mContext, this::onDrivingStateChanged);
@@ -250,7 +332,68 @@
mPowerManagerHelper = new PowerManagerHelper(mContext, mCarPowerStateListener);
mPowerManagerHelper.connectToCarService();
- mSwitchToGuestTimer = new SwitchToGuestTimer(mContext);
+ ((CarSystemUIFactory) SystemUIFactory.getInstance()).getCarServiceProvider(mContext)
+ .addListener((car, ready) -> {
+ if (!ready || mCarAudioManager != null) {
+ return;
+ }
+ try {
+ mCarAudioManager = (CarAudioManager) car.getCarManager(Car.AUDIO_SERVICE);
+ Log.d(TAG, "Registering mVolumeChangeCallback.");
+ // This volume call back is never unregistered because CarStatusBar is
+ // never destroyed.
+ mCarAudioManager.registerCarVolumeCallback(mVolumeChangeCallback);
+ } catch (CarNotConnectedException e) {
+ Log.wtf(TAG, " mVolumeChangeCallback failed to connect to car ", e);
+ }
+ });
+ }
+
+ private void restartNavBarsIfNecessary() {
+ boolean currentUserSetup = mDeviceProvisionedController.isCurrentUserSetup();
+ if (mDeviceIsSetUpForUser != currentUserSetup) {
+ mDeviceIsSetUpForUser = currentUserSetup;
+ restartNavBars();
+ }
+ }
+
+ @Override
+ protected void getDependencies() {
+ // Keyguard
+ mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
+ mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
+ mScreenLifecycle = Dependency.get(ScreenLifecycle.class);
+
+ // Policy
+ mZenController = Dependency.get(ZenModeController.class);
+
+ // Icon
+ mIconController = Dependency.get(StatusBarIconController.class);
+ mLightBarController = Dependency.get(LightBarController.class);
+
+ // Notifications
+ mEntryManager = Dependency.get(NotificationEntryManager.class);
+ mForegroundServiceController = Dependency.get(ForegroundServiceController.class);
+ mGroupAlertTransferHelper = Dependency.get(NotificationGroupAlertTransferHelper.class);
+ mGroupManager = Dependency.get(NotificationGroupManager.class);
+ mGutsManager = Dependency.get(NotificationGutsManager.class);
+ mLockscreenUserManager = Dependency.get(NotificationLockscreenUserManager.class);
+ mMediaManager = Dependency.get(NotificationMediaManager.class);
+ mNotificationListener = Dependency.get(NotificationListener.class);
+ mNotificationLogger = Dependency.get(NotificationLogger.class);
+ mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class);
+ mViewHierarchyManager = Dependency.get(NotificationViewHierarchyManager.class);
+ mVisualStabilityManager = Dependency.get(VisualStabilityManager.class);
+
+ // Others
+ mColorExtractor = Dependency.get(SysuiColorExtractor.class);
+ mNavigationBarController = Dependency.get(NavigationBarController.class);
+ mUserSwitcherController = Dependency.get(UserSwitcherController.class);
+ }
+
+ @Override
+ protected void setUpQuickSettingsTilePanel() {
+ // ignore.
}
/**
@@ -261,8 +404,8 @@
// remove and reattach all hvac components such that we don't keep a reference to unused
// ui elements
mHvacController.removeAllComponents();
- addTemperatureViewToController(mStatusBarWindow);
mCarFacetButtonController.removeAll();
+
if (mNavigationBarWindow != null) {
mNavigationBarWindow.removeAllViews();
mNavigationBarView = null;
@@ -356,7 +499,6 @@
@Override
protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {
super.makeStatusBarView(result);
- mHvacController = new HvacController(mContext);
CarSystemUIFactory factory = SystemUIFactory.getInstance();
mCarFacetButtonController = factory.getCarDependencyComponent()
@@ -381,7 +523,8 @@
* touch listeners needed for opening and closing the notification panel
*/
private void connectNotificationsUI() {
- // Attached to the status bar to detect pull down of the notification shade.
+ // Attached to the top navigation bar (i.e. status bar) to detect pull down of the
+ // notification shade.
GestureDetector openGestureDetector = new GestureDetector(mContext,
new OpenNotificationGestureListener() {
@Override
@@ -414,6 +557,18 @@
GestureDetector handleBarCloseNotificationGestureDetector = new GestureDetector(mContext,
new HandleBarCloseNotificationGestureListener());
+ mTopNavBarNotificationTouchListener = (v, event) -> {
+ if (!mDeviceIsSetUpForUser) {
+ return true;
+ }
+ boolean consumed = openGestureDetector.onTouchEvent(event);
+ if (consumed) {
+ return true;
+ }
+ maybeCompleteAnimation(event);
+ return true;
+ };
+
mNavBarNotificationTouchListener =
(v, event) -> {
boolean consumed = navBarCloseNotificationGestureDetector.onTouchEvent(event);
@@ -424,21 +579,6 @@
return true;
};
- // The following are the ui elements that the user would call the status bar.
- // This will set the status bar so it they can make call backs.
- CarNavigationBarView topBar = mStatusBarWindow.findViewById(R.id.car_top_bar);
- topBar.setStatusBar(this);
- topBar.setStatusBarWindowTouchListener((v1, event1) -> {
-
- boolean consumed = openGestureDetector.onTouchEvent(event1);
- if (consumed) {
- return true;
- }
- maybeCompleteAnimation(event1);
- return true;
- }
- );
-
mNotificationClickHandlerFactory = new NotificationClickHandlerFactory(
mBarService,
launchResult -> {
@@ -447,33 +587,12 @@
animateCollapsePanels();
}
});
- Car car = Car.createCar(mContext);
- CarUxRestrictionsManager carUxRestrictionsManager = (CarUxRestrictionsManager)
- car.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE);
+
CarNotificationListener carNotificationListener = new CarNotificationListener();
- CarUxRestrictionManagerWrapper carUxRestrictionManagerWrapper =
- new CarUxRestrictionManagerWrapper();
- carUxRestrictionManagerWrapper.setCarUxRestrictionsManager(carUxRestrictionsManager);
+ mCarUxRestrictionManagerWrapper = new CarUxRestrictionManagerWrapper();
mNotificationDataManager = new NotificationDataManager();
- mNotificationDataManager.setOnUnseenCountUpdateListener(
- () -> {
- if (mNavigationBarView != null && mNotificationDataManager != null) {
- Boolean hasUnseen =
- mNotificationDataManager.getUnseenNotificationCount() > 0;
- if (mNavigationBarView != null) {
- mNavigationBarView.toggleNotificationUnseenIndicator(hasUnseen);
- }
-
- if (mLeftNavigationBarView != null) {
- mLeftNavigationBarView.toggleNotificationUnseenIndicator(hasUnseen);
- }
-
- if (mRightNavigationBarView != null) {
- mRightNavigationBarView.toggleNotificationUnseenIndicator(hasUnseen);
- }
- }
- });
+ mNotificationDataManager.setOnUnseenCountUpdateListener(this::onUnseenCountUpdate);
mEnableHeadsUpNotificationWhenNotificationShadeOpen = mContext.getResources().getBoolean(
R.bool.config_enableHeadsUpNotificationWhenNotificationShadeOpen);
@@ -482,7 +601,7 @@
mNotificationClickHandlerFactory, mNotificationDataManager);
mNotificationClickHandlerFactory.setNotificationDataManager(mNotificationDataManager);
- carNotificationListener.registerAsSystemService(mContext, carUxRestrictionManagerWrapper,
+ carNotificationListener.registerAsSystemService(mContext, mCarUxRestrictionManagerWrapper,
carHeadsUpNotificationManager, mNotificationDataManager);
mNotificationView = mStatusBarWindow.findViewById(R.id.notification_view);
@@ -577,14 +696,48 @@
return handled || isTracking;
}
});
+ ((CarSystemUIFactory) SystemUIFactory.getInstance()).getCarServiceProvider(mContext)
+ .addListener((car, ready) -> {
+ if (!ready) {
+ return;
+ }
+ CarUxRestrictionsManager carUxRestrictionsManager =
+ (CarUxRestrictionsManager)
+ car.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE);
+ mCarUxRestrictionManagerWrapper.setCarUxRestrictionsManager(
+ carUxRestrictionsManager);
- mNotificationViewController = new NotificationViewController(
- mNotificationView,
- PreprocessingManager.getInstance(mContext),
- carNotificationListener,
- carUxRestrictionManagerWrapper,
- mNotificationDataManager);
- mNotificationViewController.enable();
+ mNotificationViewController = new NotificationViewController(
+ mNotificationView,
+ PreprocessingManager.getInstance(mContext),
+ carNotificationListener,
+ mCarUxRestrictionManagerWrapper,
+ mNotificationDataManager);
+ mNotificationViewController.enable();
+ });
+ }
+
+ /**
+ * This method is called whenever there is an update to the number of unseen notifications.
+ * This method can be extended by OEMs to customize the desired logic.
+ */
+ protected void onUnseenCountUpdate() {
+ if (mNavigationBarView != null && mNotificationDataManager != null) {
+ Boolean hasUnseen =
+ mNotificationDataManager.getUnseenNotificationCount() > 0;
+
+ if (mNavigationBarView != null) {
+ mNavigationBarView.toggleNotificationUnseenIndicator(hasUnseen);
+ }
+
+ if (mLeftNavigationBarView != null) {
+ mLeftNavigationBarView.toggleNotificationUnseenIndicator(hasUnseen);
+ }
+
+ if (mRightNavigationBarView != null) {
+ mRightNavigationBarView.toggleNotificationUnseenIndicator(hasUnseen);
+ }
+ }
}
/**
@@ -745,23 +898,30 @@
}
private void buildNavBarContent() {
+ // Always build top bar.
+ buildTopBar((mDeviceIsSetUpForUser) ? R.layout.car_top_navigation_bar :
+ R.layout.car_top_navigation_bar_unprovisioned);
+
if (mShowBottom) {
- buildBottomBar((mDeviceIsProvisioned) ? R.layout.car_navigation_bar :
+ buildBottomBar((mDeviceIsSetUpForUser) ? R.layout.car_navigation_bar :
R.layout.car_navigation_bar_unprovisioned);
}
if (mShowLeft) {
- buildLeft((mDeviceIsProvisioned) ? R.layout.car_left_navigation_bar :
+ buildLeft((mDeviceIsSetUpForUser) ? R.layout.car_left_navigation_bar :
R.layout.car_left_navigation_bar_unprovisioned);
}
if (mShowRight) {
- buildRight((mDeviceIsProvisioned) ? R.layout.car_right_navigation_bar :
+ buildRight((mDeviceIsSetUpForUser) ? R.layout.car_right_navigation_bar :
R.layout.car_right_navigation_bar_unprovisioned);
}
}
private void buildNavBarWindows() {
+ mTopNavigationBarContainer = mStatusBarWindow
+ .findViewById(R.id.car_top_navigation_bar_container);
+
if (mShowBottom) {
mNavigationBarWindow = (ViewGroup) View.inflate(mContext,
R.layout.navigation_bar_window, null);
@@ -794,15 +954,25 @@
}
boolean isKeyboardVisible = (vis & InputMethodService.IME_VISIBLE) != 0;
- if (!isKeyboardVisible) {
- attachBottomNavBarWindow();
- } else {
- detachBottomNavBarWindow();
- }
+ showBottomNavBarWindow(isKeyboardVisible);
}
private void attachNavBarWindows() {
- attachBottomNavBarWindow();
+ if (mShowBottom && !mBottomNavBarVisible) {
+ mBottomNavBarVisible = true;
+
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+ | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
+ PixelFormat.TRANSLUCENT);
+ lp.setTitle("CarNavigationBar");
+ lp.windowAnimations = 0;
+ mWindowManager.addView(mNavigationBarWindow, lp);
+ }
if (mShowLeft) {
int width = mContext.getResources().getDimensionPixelSize(
@@ -840,47 +1010,32 @@
}
}
- /**
- * Attaches the bottom nav bar window. Can be extended to modify the specific behavior of
- * attaching the bottom nav bar.
- */
- protected void attachBottomNavBarWindow() {
+ private void showBottomNavBarWindow(boolean isKeyboardVisible) {
if (!mShowBottom) {
return;
}
- if (mBottomNavBarVisible) {
+ // If keyboard is visible and bottom nav bar not visible, this is the correct state, so do
+ // nothing. Same with if keyboard is not visible and bottom nav bar is visible.
+ if (isKeyboardVisible ^ mBottomNavBarVisible) {
return;
}
- mBottomNavBarVisible = true;
- WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
- LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
- WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
- | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
- | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
- PixelFormat.TRANSLUCENT);
- lp.setTitle("CarNavigationBar");
- lp.windowAnimations = 0;
- mWindowManager.addView(mNavigationBarWindow, lp);
+ mNavigationBarWindow.setVisibility(isKeyboardVisible ? View.GONE : View.VISIBLE);
+ mBottomNavBarVisible = !isKeyboardVisible;
}
- /**
- * Detaches the bottom nav bar window. Can be extended to modify the specific behavior of
- * detaching the bottom nav bar.
- */
- protected void detachBottomNavBarWindow() {
- if (!mShowBottom) {
- return;
+ private void buildTopBar(int layout) {
+ mTopNavigationBarContainer.removeAllViews();
+ View.inflate(mContext, layout, mTopNavigationBarContainer);
+ mTopNavigationBarView = (CarNavigationBarView) mTopNavigationBarContainer.getChildAt(0);
+ if (mTopNavigationBarView == null) {
+ Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_top_navigation_bar");
+ throw new RuntimeException("Unable to build top nav bar due to missing layout");
}
-
- if (!mBottomNavBarVisible) {
- return;
- }
- mBottomNavBarVisible = false;
- mWindowManager.removeView(mNavigationBarWindow);
+ mTopNavigationBarView.setStatusBar(this);
+ addTemperatureViewToController(mTopNavigationBarView);
+ mTopNavigationBarView.setStatusBarWindowTouchListener(mTopNavBarNotificationTouchListener);
}
private void buildBottomBar(int layout) {
@@ -891,7 +1046,7 @@
mNavigationBarView = (CarNavigationBarView) mNavigationBarWindow.getChildAt(0);
if (mNavigationBarView == null) {
Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_navigation_bar");
- throw new RuntimeException("Unable to build botom nav bar due to missing layout");
+ throw new RuntimeException("Unable to build bottom nav bar due to missing layout");
}
mNavigationBarView.setStatusBar(this);
addTemperatureViewToController(mNavigationBarView);
@@ -902,7 +1057,7 @@
View.inflate(mContext, layout, mLeftNavigationBarWindow);
mLeftNavigationBarView = (CarNavigationBarView) mLeftNavigationBarWindow.getChildAt(0);
if (mLeftNavigationBarView == null) {
- Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_navigation_bar");
+ Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_left_navigation_bar");
throw new RuntimeException("Unable to build left nav bar due to missing layout");
}
mLeftNavigationBarView.setStatusBar(this);
@@ -915,7 +1070,7 @@
View.inflate(mContext, layout, mRightNavigationBarWindow);
mRightNavigationBarView = (CarNavigationBarView) mRightNavigationBarWindow.getChildAt(0);
if (mRightNavigationBarView == null) {
- Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_navigation_bar");
+ Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_right_navigation_bar");
throw new RuntimeException("Unable to build right nav bar due to missing layout");
}
mRightNavigationBarView.setStatusBar(this);
@@ -1030,12 +1185,8 @@
UserSwitcherController userSwitcherController =
Dependency.get(UserSwitcherController.class);
if (userSwitcherController.useFullscreenUserSwitcher()) {
- Car car = Car.createCar(mContext);
- CarTrustAgentEnrollmentManager enrollmentManager = (CarTrustAgentEnrollmentManager) car
- .getCarManager(Car.CAR_TRUST_AGENT_ENROLLMENT_SERVICE);
mFullscreenUserSwitcher = new FullscreenUserSwitcher(this,
- mStatusBarWindow.findViewById(R.id.fullscreen_user_switcher_stub),
- enrollmentManager, mContext);
+ mStatusBarWindow.findViewById(R.id.fullscreen_user_switcher_stub), mContext);
} else {
super.createUserSwitcher();
}
@@ -1146,17 +1297,22 @@
mHandleBar.setTranslationY(height - mHandleBar.getHeight() - lp.bottomMargin);
}
if (mNotificationView.getHeight() > 0) {
- // Calculates the alpha value for the background based on how much of the notification
- // shade is visible to the user. When the notification shade is completely open then
- // alpha value will be 1.
- float alpha = (float) height / mNotificationView.getHeight();
Drawable background = mNotificationView.getBackground().mutate();
-
- background.setAlpha((int) (alpha * 255));
+ background.setAlpha((int) (getBackgroundAlpha(height) * 255));
mNotificationView.setBackground(background);
}
}
+ /**
+ * Calculates the alpha value for the background based on how much of the notification
+ * shade is visible to the user. When the notification shade is completely open then
+ * alpha value will be 1.
+ */
+ private float getBackgroundAlpha(int height) {
+ return mInitialBackgroundAlpha +
+ ((float) height / mNotificationView.getHeight() * mBackgroundAlphaDiff);
+ }
+
private void calculatePercentageFromBottom(float height) {
if (mNotificationView.getHeight() > 0) {
mPercentageFromBottom = (int) Math.abs(
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/DrivingStateHelper.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/DrivingStateHelper.java
index a442426..76ad04f 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/DrivingStateHelper.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/DrivingStateHelper.java
@@ -17,18 +17,18 @@
package com.android.systemui.statusbar.car;
import android.car.Car;
-import android.car.CarNotConnectedException;
+import android.car.Car.CarServiceLifecycleListener;
import android.car.drivingstate.CarDrivingStateEvent;
import android.car.drivingstate.CarDrivingStateManager;
import android.car.drivingstate.CarDrivingStateManager.CarDrivingStateEventListener;
-import android.content.ComponentName;
import android.content.Context;
-import android.content.ServiceConnection;
-import android.os.IBinder;
import android.util.Log;
import androidx.annotation.NonNull;
+import com.android.systemui.CarSystemUIFactory;
+import com.android.systemui.SystemUIFactory;
+
/**
* Helper class for connecting to the {@link CarDrivingStateManager} and listening for driving state
* changes.
@@ -38,7 +38,6 @@
private final Context mContext;
private CarDrivingStateManager mDrivingStateManager;
- private Car mCar;
private CarDrivingStateEventListener mDrivingStateHandler;
public DrivingStateHelper(Context context,
@@ -55,16 +54,11 @@
if (mDrivingStateManager == null) {
return false;
}
- try {
- CarDrivingStateEvent currentState = mDrivingStateManager.getCurrentCarDrivingState();
- if (currentState != null) {
- return currentState.eventValue == CarDrivingStateEvent.DRIVING_STATE_IDLING
- || currentState.eventValue == CarDrivingStateEvent.DRIVING_STATE_MOVING;
- }
- } catch (CarNotConnectedException e) {
- Log.e(TAG, "Cannot determine current driving state. Car not connected", e);
+ CarDrivingStateEvent currentState = mDrivingStateManager.getCurrentCarDrivingState();
+ if (currentState != null) {
+ return currentState.eventValue == CarDrivingStateEvent.DRIVING_STATE_IDLING
+ || currentState.eventValue == CarDrivingStateEvent.DRIVING_STATE_MOVING;
}
-
return false; // Default to false.
}
@@ -72,55 +66,25 @@
* Establishes connection with the Car service.
*/
public void connectToCarService() {
- mCar = Car.createCar(mContext, mCarConnectionListener);
- if (mCar != null) {
- mCar.connect();
- }
+ ((CarSystemUIFactory) SystemUIFactory.getInstance()).getCarServiceProvider(mContext)
+ .addListener(mCarServiceLifecycleListener);
}
- /**
- * Disconnects from Car service and cleans up listeners.
- */
- public void disconnectFromCarService() {
- if (mCar != null) {
- mCar.disconnect();
+ private final CarServiceLifecycleListener mCarServiceLifecycleListener = (car, ready) -> {
+ if (!ready) {
+ return;
}
- }
-
- private final ServiceConnection mCarConnectionListener =
- new ServiceConnection() {
- public void onServiceConnected(ComponentName name, IBinder service) {
- logD("Car Service connected");
- try {
- mDrivingStateManager = (CarDrivingStateManager) mCar.getCarManager(
- Car.CAR_DRIVING_STATE_SERVICE);
- if (mDrivingStateManager != null) {
- mDrivingStateManager.registerListener(mDrivingStateHandler);
- mDrivingStateHandler.onDrivingStateChanged(
- mDrivingStateManager.getCurrentCarDrivingState());
- } else {
- Log.e(TAG, "CarDrivingStateService service not available");
- }
- } catch (CarNotConnectedException e) {
- Log.e(TAG, "Car not connected", e);
- }
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- destroyDrivingStateManager();
- }
- };
-
- private void destroyDrivingStateManager() {
- try {
- if (mDrivingStateManager != null) {
- mDrivingStateManager.unregisterListener();
- }
- } catch (CarNotConnectedException e) {
- Log.e(TAG, "Error unregistering listeners", e);
+ logD("Car Service connected");
+ mDrivingStateManager = (CarDrivingStateManager) car.getCarManager(
+ Car.CAR_DRIVING_STATE_SERVICE);
+ if (mDrivingStateManager != null) {
+ mDrivingStateManager.registerListener(mDrivingStateHandler);
+ mDrivingStateHandler.onDrivingStateChanged(
+ mDrivingStateManager.getCurrentCarDrivingState());
+ } else {
+ Log.e(TAG, "CarDrivingStateService service not available");
}
- }
+ };
private void logD(String message) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
index 0f7c1ee..f74dbe9 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
@@ -18,6 +18,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.car.Car;
import android.car.trust.CarTrustAgentEnrollmentManager;
import android.car.userlib.CarUserManagerHelper;
import android.content.BroadcastReceiver;
@@ -33,7 +34,9 @@
import androidx.recyclerview.widget.GridLayoutManager;
+import com.android.systemui.CarSystemUIFactory;
import com.android.systemui.R;
+import com.android.systemui.SystemUIFactory;
import com.android.systemui.statusbar.car.CarTrustAgentUnlockDialogHelper.OnHideListener;
import com.android.systemui.statusbar.car.UserGridRecyclerView.UserRecord;
@@ -42,15 +45,13 @@
*/
public class FullscreenUserSwitcher {
private static final String TAG = FullscreenUserSwitcher.class.getSimpleName();
- // Because user 0 is headless, user count for single user is 2
- private static final int NUMBER_OF_BACKGROUND_USERS = 1;
private final UserGridRecyclerView mUserGridView;
private final View mParent;
private final int mShortAnimDuration;
private final CarStatusBar mStatusBar;
private final Context mContext;
private final UserManager mUserManager;
- private final CarTrustAgentEnrollmentManager mEnrollmentManager;
+ private CarTrustAgentEnrollmentManager mEnrollmentManager;
private CarTrustAgentUnlockDialogHelper mUnlockDialogHelper;
private UserGridRecyclerView.UserRecord mSelectedUser;
private CarUserManagerHelper mCarUserManagerHelper;
@@ -65,12 +66,9 @@
}
};
-
- public FullscreenUserSwitcher(CarStatusBar statusBar, ViewStub containerStub,
- CarTrustAgentEnrollmentManager enrollmentManager, Context context) {
+ public FullscreenUserSwitcher(CarStatusBar statusBar, ViewStub containerStub, Context context) {
mStatusBar = statusBar;
mParent = containerStub.inflate();
- mEnrollmentManager = enrollmentManager;
mContext = context;
View container = mParent.findViewById(R.id.container);
@@ -86,6 +84,15 @@
mUnlockDialogHelper = new CarTrustAgentUnlockDialogHelper(mContext);
mUserManager = mContext.getSystemService(UserManager.class);
+ ((CarSystemUIFactory) SystemUIFactory.getInstance()).getCarServiceProvider(mContext)
+ .addListener((car, ready) -> {
+ if (!ready) {
+ return;
+ }
+ mEnrollmentManager = (CarTrustAgentEnrollmentManager) car
+ .getCarManager(Car.CAR_TRUST_AGENT_ENROLLMENT_SERVICE);
+ });
+
mShortAnimDuration = container.getResources()
.getInteger(android.R.integer.config_shortAnimTime);
IntentFilter filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
@@ -109,27 +116,18 @@
/* isStartGuestSession= */ false,
/* isAddUser= */ false,
/* isForeground= */ true);
- // For single user without trusted device, hide the user switcher.
- if (!hasMultipleUsers() && !hasTrustedDevice(initialUser)) {
- dismissUserSwitcher();
- return;
- }
- // Show unlock dialog for initial user
+
+ // If the initial user has trusted device, display the unlock dialog on the keyguard.
if (hasTrustedDevice(initialUser)) {
mUnlockDialogHelper.showUnlockDialogAfterDelay(initialUser,
mOnHideListener);
+ } else {
+ // If no trusted device, dismiss the keyguard.
+ dismissUserSwitcher();
}
}
/**
- * Check if there is only one possible user to login in.
- * In a Multi-User system there is always one background user (user 0)
- */
- private boolean hasMultipleUsers() {
- return mUserManager.getUserCount() > NUMBER_OF_BACKGROUND_USERS + 1;
- }
-
- /**
* Makes user grid visible.
*/
public void show() {
@@ -201,6 +199,9 @@
}
private boolean hasTrustedDevice(int uid) {
+ if (mEnrollmentManager == null) { // car service not ready, so it cannot be available.
+ return false;
+ }
return !mEnrollmentManager.getEnrolledDeviceInfoForUser(uid).isEmpty();
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/PowerManagerHelper.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/PowerManagerHelper.java
index 8de1439..d87b54c 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/PowerManagerHelper.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/PowerManagerHelper.java
@@ -18,15 +18,15 @@
import android.annotation.NonNull;
import android.car.Car;
-import android.car.CarNotConnectedException;
+import android.car.Car.CarServiceLifecycleListener;
import android.car.hardware.power.CarPowerManager;
import android.car.hardware.power.CarPowerManager.CarPowerStateListener;
-import android.content.ComponentName;
import android.content.Context;
-import android.content.ServiceConnection;
-import android.os.IBinder;
import android.util.Log;
+import com.android.systemui.CarSystemUIFactory;
+import com.android.systemui.SystemUIFactory;
+
/**
* Helper class for connecting to the {@link CarPowerManager} and listening for power state changes.
*/
@@ -36,58 +36,32 @@
private final Context mContext;
private final CarPowerStateListener mCarPowerStateListener;
- private Car mCar;
private CarPowerManager mCarPowerManager;
- private final ServiceConnection mCarConnectionListener =
- new ServiceConnection() {
- public void onServiceConnected(ComponentName name, IBinder service) {
- Log.d(TAG, "Car Service connected");
- try {
- mCarPowerManager = (CarPowerManager) mCar.getCarManager(Car.POWER_SERVICE);
- if (mCarPowerManager != null) {
- mCarPowerManager.setListener(mCarPowerStateListener);
- } else {
- Log.e(TAG, "CarPowerManager service not available");
- }
- } catch (CarNotConnectedException e) {
- Log.e(TAG, "Car not connected", e);
- }
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- destroyCarPowerManager();
- }
- };
+ private final CarServiceLifecycleListener mCarServiceLifecycleListener;
PowerManagerHelper(Context context, @NonNull CarPowerStateListener listener) {
mContext = context;
mCarPowerStateListener = listener;
+ mCarServiceLifecycleListener = (car, ready) -> {
+ if (!ready) {
+ return;
+ }
+ Log.d(TAG, "Car Service connected");
+ mCarPowerManager = (CarPowerManager) car.getCarManager(Car.POWER_SERVICE);
+ if (mCarPowerManager != null) {
+ mCarPowerManager.setListener(mCarPowerStateListener);
+ } else {
+ Log.e(TAG, "CarPowerManager service not available");
+ }
+ };
}
/**
* Connect to Car service.
*/
void connectToCarService() {
- mCar = Car.createCar(mContext, mCarConnectionListener);
- if (mCar != null) {
- mCar.connect();
- }
- }
-
- /**
- * Disconnects from Car service.
- */
- void disconnectFromCarService() {
- if (mCar != null) {
- mCar.disconnect();
- }
- }
-
- private void destroyCarPowerManager() {
- if (mCarPowerManager != null) {
- mCarPowerManager.clearListener();
- }
+ ((CarSystemUIFactory) SystemUIFactory.getInstance()).getCarServiceProvider(mContext)
+ .addListener(mCarServiceLifecycleListener);
}
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/hvac/HvacController.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/hvac/HvacController.java
index 30429ed..1d96750 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/hvac/HvacController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/hvac/HvacController.java
@@ -20,17 +20,17 @@
import static android.car.VehiclePropertyIds.HVAC_TEMPERATURE_DISPLAY_UNITS;
import android.car.Car;
+import android.car.Car.CarServiceLifecycleListener;
import android.car.VehicleUnit;
import android.car.hardware.CarPropertyValue;
import android.car.hardware.hvac.CarHvacManager;
import android.car.hardware.hvac.CarHvacManager.CarHvacEventCallback;
-import android.content.ComponentName;
import android.content.Context;
-import android.content.ServiceConnection;
-import android.os.Handler;
-import android.os.IBinder;
import android.util.Log;
+import com.android.systemui.CarSystemUIFactory;
+import com.android.systemui.SystemUIFactory;
+
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
@@ -43,15 +43,12 @@
* {@link TemperatureView}s
*/
public class HvacController {
-
public static final String TAG = "HvacController";
- public static final int BIND_TO_HVAC_RETRY_DELAY = 5000;
private Context mContext;
- private Handler mHandler;
- private Car mCar;
private CarHvacManager mHvacManager;
private HashMap<HvacKey, List<TemperatureView>> mTempComponents = new HashMap<>();
+
/**
* Callback for getting changes from {@link CarHvacManager} and setting the UI elements to
* match.
@@ -83,39 +80,17 @@
+ " zone: " + zone);
}
};
- /**
- * If the connection to car service goes away then restart it.
- */
- private final IBinder.DeathRecipient mRestart = new IBinder.DeathRecipient() {
- @Override
- public void binderDied() {
- Log.d(TAG, "Death of HVAC triggering a restart");
- if (mCar != null) {
- mCar.disconnect();
- }
- destroyHvacManager();
- mHandler.postDelayed(() -> mCar.connect(), BIND_TO_HVAC_RETRY_DELAY);
- }
- };
- /**
- * Registers callbacks and initializes components upon connection.
- */
- private ServiceConnection mServiceConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- try {
- service.linkToDeath(mRestart, 0);
- mHvacManager = (CarHvacManager) mCar.getCarManager(Car.HVAC_SERVICE);
- mHvacManager.registerCallback(mHardwareCallback);
- initComponents();
- } catch (Exception e) {
- Log.e(TAG, "Failed to correctly connect to HVAC", e);
- }
- }
- @Override
- public void onServiceDisconnected(ComponentName name) {
- destroyHvacManager();
+ private final CarServiceLifecycleListener mCarServiceLifecycleListener = (car, ready) -> {
+ if (!ready) {
+ return;
+ }
+ try {
+ mHvacManager = (CarHvacManager) car.getCarManager(Car.HVAC_SERVICE);
+ mHvacManager.registerCallback(mHardwareCallback);
+ initComponents();
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to correctly connect to HVAC", e);
}
};
@@ -128,19 +103,8 @@
* ({@link CarHvacManager}) will happen on the same thread this method was called from.
*/
public void connectToCarService() {
- mHandler = new Handler();
- mCar = Car.createCar(mContext, mServiceConnection, mHandler);
- if (mCar != null) {
- // note: this connect call handles the retries
- mCar.connect();
- }
- }
-
- private void destroyHvacManager() {
- if (mHvacManager != null) {
- mHvacManager.unregisterCallback(mHardwareCallback);
- mHvacManager = null;
- }
+ ((CarSystemUIFactory) SystemUIFactory.getInstance()).getCarServiceProvider(mContext)
+ .addListener(mCarServiceLifecycleListener);
}
/**
diff --git a/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogComponent.java b/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogComponent.java
index 71cc19b..aa78b960 100644
--- a/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogComponent.java
+++ b/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogComponent.java
@@ -31,6 +31,10 @@
}
protected VolumeDialog createDefault() {
- return new CarVolumeDialogImpl(mContext);
+ CarVolumeDialogImpl carVolumeDialog = new CarVolumeDialogImpl(mContext);
+ // Since VolumeUI is initialized when the first Volume Up/Down event is received we need to
+ // show the dialog on initialization too.
+ carVolumeDialog.show(Events.SHOW_REASON_VOLUME_CHANGED);
+ return carVolumeDialog;
}
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java b/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
index d0a63f0..f91c90e 100644
--- a/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
+++ b/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
@@ -24,12 +24,10 @@
import android.app.Dialog;
import android.app.KeyguardManager;
import android.car.Car;
-import android.car.CarNotConnectedException;
+import android.car.Car.CarServiceLifecycleListener;
import android.car.media.CarAudioManager;
-import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
-import android.content.ServiceConnection;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.Color;
@@ -39,7 +37,6 @@
import android.media.AudioManager;
import android.os.Debug;
import android.os.Handler;
-import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.util.AttributeSet;
@@ -58,14 +55,15 @@
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+import com.android.systemui.CarSystemUIFactory;
import com.android.systemui.R;
+import com.android.systemui.SystemUIFactory;
import com.android.systemui.plugins.VolumeDialog;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Iterator;
import java.util.List;
/**
@@ -79,8 +77,6 @@
private static final String XML_TAG_VOLUME_ITEMS = "carVolumeItems";
private static final String XML_TAG_VOLUME_ITEM = "item";
- private static final int HOVERING_TIMEOUT = 16000;
- private static final int NORMAL_TIMEOUT = 3000;
private static final int LISTVIEW_ANIMATION_DURATION_IN_MILLIS = 250;
private static final int DISMISS_DELAY_IN_MILLIS = 50;
private static final int ARROW_FADE_IN_START_DELAY_IN_MILLIS = 100;
@@ -94,12 +90,24 @@
// Volume items in the RecyclerView.
private final List<CarVolumeItem> mCarVolumeLineItems = new ArrayList<>();
private final KeyguardManager mKeyguard;
+ private final int mNormalTimeout;
+ private final int mHoveringTimeout;
+ private final int mExpNormalTimeout;
+ private final int mExpHoveringTimeout;
+
private Window mWindow;
private CustomDialog mDialog;
private RecyclerView mListView;
private CarVolumeItemAdapter mVolumeItemsAdapter;
- private Car mCar;
private CarAudioManager mCarAudioManager;
+ private boolean mHovering;
+ private int mCurrentlyDisplayingGroupId;
+ private int mPreviouslyDisplayingGroupId;
+ private boolean mShowing;
+ private boolean mDismissing;
+ private boolean mExpanded;
+ private View mExpandIcon;
+
private final CarAudioManager.CarVolumeCallback mVolumeChangeCallback =
new CarAudioManager.CarVolumeCallback() {
@Override
@@ -129,6 +137,7 @@
volumeItem.progress = value;
}
if ((flags & AudioManager.FLAG_SHOW_UI) != 0) {
+ mPreviouslyDisplayingGroupId = mCurrentlyDisplayingGroupId;
mCurrentlyDisplayingGroupId = groupId;
mHandler.obtainMessage(H.SHOW,
Events.SHOW_REASON_VOLUME_CHANGED).sendToTarget();
@@ -140,80 +149,51 @@
// ignored
}
};
- private boolean mHovering;
- private int mCurrentlyDisplayingGroupId;
- private boolean mShowing;
- private boolean mExpanded;
- private View mExpandIcon;
- private final ServiceConnection mServiceConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- try {
- mExpanded = false;
- mCarAudioManager = (CarAudioManager) mCar.getCarManager(Car.AUDIO_SERVICE);
- int volumeGroupCount = mCarAudioManager.getVolumeGroupCount();
- // Populates volume slider items from volume groups to UI.
- for (int groupId = 0; groupId < volumeGroupCount; groupId++) {
- VolumeItem volumeItem = getVolumeItemForUsages(
- mCarAudioManager.getUsagesForVolumeGroupId(groupId));
- mAvailableVolumeItems.add(volumeItem);
- // The first one is the default item.
- if (groupId == 0) {
- setuptListItem(0);
- }
- }
- // If list is already initiated, update its content.
- if (mVolumeItemsAdapter != null) {
- mVolumeItemsAdapter.notifyDataSetChanged();
- }
- mCarAudioManager.registerCarVolumeCallback(mVolumeChangeCallback);
- } catch (CarNotConnectedException e) {
- Log.e(TAG, "Car is not connected!", e);
+ private final CarServiceLifecycleListener mCarServiceLifecycleListener = (car, ready) -> {
+ if (!ready) {
+ return;
+ }
+ mExpanded = false;
+ mCarAudioManager = (CarAudioManager) car.getCarManager(Car.AUDIO_SERVICE);
+ int volumeGroupCount = mCarAudioManager.getVolumeGroupCount();
+ // Populates volume slider items from volume groups to UI.
+ for (int groupId = 0; groupId < volumeGroupCount; groupId++) {
+ VolumeItem volumeItem = getVolumeItemForUsages(
+ mCarAudioManager.getUsagesForVolumeGroupId(groupId));
+ mAvailableVolumeItems.add(volumeItem);
+ // The first one is the default item.
+ if (groupId == 0) {
+ clearAllAndSetupDefaultCarVolumeLineItem(0);
}
}
- /**
- * This does not get called when service is properly disconnected.
- * So we need to also handle cleanups in destroy().
- */
- @Override
- public void onServiceDisconnected(ComponentName name) {
- cleanupAudioManager();
+ // If list is already initiated, update its content.
+ if (mVolumeItemsAdapter != null) {
+ mVolumeItemsAdapter.notifyDataSetChanged();
}
+ mCarAudioManager.registerCarVolumeCallback(mVolumeChangeCallback);
};
- private void setuptListItem(int groupId) {
- mCarVolumeLineItems.clear();
- VolumeItem volumeItem = mAvailableVolumeItems.get(groupId);
- volumeItem.defaultItem = true;
- addCarVolumeListItem(volumeItem, /* volumeGroupId = */ groupId,
- R.drawable.car_ic_keyboard_arrow_down, new ExpandIconListener()
- );
- }
-
public CarVolumeDialogImpl(Context context) {
mContext = context;
mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
- mCar = Car.createCar(mContext, mServiceConnection);
+ mNormalTimeout = mContext.getResources().getInteger(
+ R.integer.car_volume_dialog_display_normal_timeout);
+ mHoveringTimeout = mContext.getResources().getInteger(
+ R.integer.car_volume_dialog_display_hovering_timeout);
+ mExpNormalTimeout = mContext.getResources().getInteger(
+ R.integer.car_volume_dialog_display_expanded_normal_timeout);
+ mExpHoveringTimeout = mContext.getResources().getInteger(
+ R.integer.car_volume_dialog_display_expanded_hovering_timeout);
}
private static int getSeekbarValue(CarAudioManager carAudioManager, int volumeGroupId) {
- try {
- return carAudioManager.getGroupVolume(volumeGroupId);
- } catch (CarNotConnectedException e) {
- Log.e(TAG, "Car is not connected!", e);
- }
- return 0;
+ return carAudioManager.getGroupVolume(volumeGroupId);
}
private static int getMaxSeekbarValue(CarAudioManager carAudioManager, int volumeGroupId) {
- try {
- return carAudioManager.getGroupMaxVolume(volumeGroupId);
- } catch (CarNotConnectedException e) {
- Log.e(TAG, "Car is not connected!", e);
- }
- return 0;
+ return carAudioManager.getGroupMaxVolume(volumeGroupId);
}
/**
@@ -224,17 +204,15 @@
public void init(int windowType, Callback callback) {
initDialog();
- mCar.connect();
+ ((CarSystemUIFactory) SystemUIFactory.getInstance()).getCarServiceProvider(mContext)
+ .addListener(mCarServiceLifecycleListener);
}
@Override
public void destroy() {
- mHandler.removeCallbacksAndMessages(null);
+ mHandler.removeCallbacksAndMessages(/* token= */ null);
cleanupAudioManager();
- // unregisterVolumeCallback is not being called when disconnect car, so we manually cleanup
- // audio manager beforehand.
- mCar.disconnect();
}
private void initDialog() {
@@ -244,6 +222,7 @@
mHovering = false;
mShowing = false;
+ mDismissing = false;
mExpanded = false;
mWindow = mDialog.getWindow();
mWindow.requestFeature(Window.FEATURE_NO_TITLE);
@@ -293,6 +272,19 @@
mListView.setLayoutManager(new LinearLayoutManager(mContext));
}
+ /**
+ * Reveals volume dialog.
+ */
+ public void show(int reason) {
+ mHandler.obtainMessage(H.SHOW, reason).sendToTarget();
+ }
+
+ /**
+ * Hides volume dialog.
+ */
+ public void dismiss(int reason) {
+ mHandler.obtainMessage(H.DISMISS, reason).sendToTarget();
+ }
private void showH(int reason) {
if (D.BUG) {
@@ -301,19 +293,36 @@
mHandler.removeMessages(H.SHOW);
mHandler.removeMessages(H.DISMISS);
+
rescheduleTimeoutH();
+
// Refresh the data set before showing.
mVolumeItemsAdapter.notifyDataSetChanged();
+
if (mShowing) {
+ if (mPreviouslyDisplayingGroupId == mCurrentlyDisplayingGroupId || mExpanded) {
+ return;
+ }
+
+ clearAllAndSetupDefaultCarVolumeLineItem(mCurrentlyDisplayingGroupId);
return;
}
+
mShowing = true;
- setuptListItem(mCurrentlyDisplayingGroupId);
+ clearAllAndSetupDefaultCarVolumeLineItem(mCurrentlyDisplayingGroupId);
mDialog.show();
Events.writeEvent(mContext, Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked());
}
- private void rescheduleTimeoutH() {
+ private void clearAllAndSetupDefaultCarVolumeLineItem(int groupId) {
+ mCarVolumeLineItems.clear();
+ VolumeItem volumeItem = mAvailableVolumeItems.get(groupId);
+ volumeItem.defaultItem = true;
+ addCarVolumeListItem(volumeItem, /* volumeGroupId = */ groupId,
+ R.drawable.car_ic_keyboard_arrow_down, new ExpandIconListener());
+ }
+
+ protected void rescheduleTimeoutH() {
mHandler.removeMessages(H.DISMISS);
final int timeout = computeTimeoutH();
mHandler.sendMessageDelayed(mHandler
@@ -325,7 +334,11 @@
}
private int computeTimeoutH() {
- return mHovering ? HOVERING_TIMEOUT : NORMAL_TIMEOUT;
+ if (mExpanded) {
+ return mHovering ? mExpHoveringTimeout : mExpNormalTimeout;
+ } else {
+ return mHovering ? mHoveringTimeout : mNormalTimeout;
+ }
}
private void dismissH(int reason) {
@@ -335,14 +348,11 @@
mHandler.removeMessages(H.DISMISS);
mHandler.removeMessages(H.SHOW);
- if (!mShowing) {
+ if (!mShowing || mDismissing) {
return;
}
- mListView.animate().cancel();
-
- mListView.setTranslationY(0);
- mListView.setAlpha(1);
+ mDismissing = true;
mListView.animate()
.alpha(0)
.translationY(-mListView.getHeight())
@@ -354,7 +364,7 @@
}
mDialog.dismiss();
mShowing = false;
- mShowing = false;
+ mDismissing = false;
// if mExpandIcon is null that means user never clicked on the expanded arrow
// which implies that the dialog is still not expanded. In that case we do
// not want to reset the state
@@ -390,12 +400,13 @@
if (XML_TAG_VOLUME_ITEM.equals(parser.getName())) {
TypedArray item = mContext.getResources().obtainAttributes(
attrs, R.styleable.carVolumeItems_item);
- int usage = item.getInt(R.styleable.carVolumeItems_item_usage, -1);
+ int usage = item.getInt(R.styleable.carVolumeItems_item_usage,
+ /* defValue= */ -1);
if (usage >= 0) {
VolumeItem volumeItem = new VolumeItem();
volumeItem.rank = rank;
- volumeItem.icon = item.getResourceId(R.styleable.carVolumeItems_item_icon,
- 0);
+ volumeItem.icon = item.getResourceId(
+ R.styleable.carVolumeItems_item_icon, /* defValue= */ 0);
mVolumeItems.put(usage, volumeItem);
rank++;
}
@@ -420,22 +431,22 @@
return result;
}
- private CarVolumeItem addCarVolumeListItem(VolumeItem volumeItem, int volumeGroupId,
- int supplementalIconId,
+ private CarVolumeItem createCarVolumeListItem(VolumeItem volumeItem, int volumeGroupId,
+ Drawable supplementalIcon, int seekbarProgressValue,
@Nullable View.OnClickListener supplementalIconOnClickListener) {
CarVolumeItem carVolumeItem = new CarVolumeItem();
carVolumeItem.setMax(getMaxSeekbarValue(mCarAudioManager, volumeGroupId));
- int color = mContext.getResources().getColor(R.color.car_volume_dialog_tint);
- int progress = getSeekbarValue(mCarAudioManager, volumeGroupId);
- carVolumeItem.setProgress(progress);
+ carVolumeItem.setProgress(seekbarProgressValue);
carVolumeItem.setOnSeekBarChangeListener(
new CarVolumeDialogImpl.VolumeSeekBarChangeListener(volumeGroupId,
mCarAudioManager));
- Drawable primaryIcon = mContext.getResources().getDrawable(volumeItem.icon);
+ carVolumeItem.setGroupId(volumeGroupId);
+
+ int color = mContext.getColor(R.color.car_volume_dialog_tint);
+ Drawable primaryIcon = mContext.getDrawable(volumeItem.icon);
primaryIcon.mutate().setTint(color);
carVolumeItem.setPrimaryIcon(primaryIcon);
- if (supplementalIconId != 0) {
- Drawable supplementalIcon = mContext.getResources().getDrawable(supplementalIconId);
+ if (supplementalIcon != null) {
supplementalIcon.mutate().setTint(color);
carVolumeItem.setSupplementalIcon(supplementalIcon,
/* showSupplementalIconDivider= */ true);
@@ -444,21 +455,23 @@
carVolumeItem.setSupplementalIcon(/* drawable= */ null,
/* showSupplementalIconDivider= */ false);
}
- carVolumeItem.setGroupId(volumeGroupId);
- mCarVolumeLineItems.add(carVolumeItem);
+
volumeItem.carVolumeItem = carVolumeItem;
- volumeItem.progress = progress;
+ volumeItem.progress = seekbarProgressValue;
+
return carVolumeItem;
}
- private VolumeItem findVolumeItem(CarVolumeItem targetItem) {
- for (int i = 0; i < mVolumeItems.size(); ++i) {
- VolumeItem volumeItem = mVolumeItems.valueAt(i);
- if (volumeItem.carVolumeItem == targetItem) {
- return volumeItem;
- }
- }
- return null;
+ private CarVolumeItem addCarVolumeListItem(VolumeItem volumeItem, int volumeGroupId,
+ int supplementalIconId,
+ @Nullable View.OnClickListener supplementalIconOnClickListener) {
+ int seekbarProgressValue = getSeekbarValue(mCarAudioManager, volumeGroupId);
+ Drawable supplementalIcon = supplementalIconId == 0 ? null : mContext.getDrawable(
+ supplementalIconId);
+ CarVolumeItem carVolumeItem = createCarVolumeListItem(volumeItem, volumeGroupId,
+ supplementalIcon, seekbarProgressValue, supplementalIconOnClickListener);
+ mCarVolumeLineItems.add(carVolumeItem);
+ return carVolumeItem;
}
private void cleanupAudioManager() {
@@ -544,6 +557,7 @@
public void onClick(final View v) {
mExpandIcon = v;
toggleDialogExpansion(true);
+ rescheduleTimeoutH();
}
}
@@ -554,21 +568,15 @@
for (int groupId = 0; groupId < mAvailableVolumeItems.size(); ++groupId) {
if (groupId != mCurrentlyDisplayingGroupId) {
VolumeItem volumeItem = mAvailableVolumeItems.get(groupId);
- addCarVolumeListItem(volumeItem, groupId, 0, null);
+ addCarVolumeListItem(volumeItem, groupId, /* supplementalIconId= */ 0,
+ /* supplementalIconOnClickListener= */ null);
}
}
inAnimator = AnimatorInflater.loadAnimator(
mContext, R.anim.car_arrow_fade_in_rotate_up);
} else {
- // Only keeping the default stream if it is not expended.
- Iterator itr = mCarVolumeLineItems.iterator();
- while (itr.hasNext()) {
- CarVolumeItem carVolumeItem = (CarVolumeItem) itr.next();
- if (carVolumeItem.getGroupId() != mCurrentlyDisplayingGroupId) {
- itr.remove();
- }
- }
+ clearAllAndSetupDefaultCarVolumeLineItem(mCurrentlyDisplayingGroupId);
inAnimator = AnimatorInflater.loadAnimator(
mContext, R.anim.car_arrow_fade_in_rotate_down);
}
@@ -606,18 +614,14 @@
// sent back down again.
return;
}
- try {
- if (mCarAudioManager == null) {
- Log.w(TAG, "Ignoring volume change event because the car isn't connected");
- return;
- }
- mAvailableVolumeItems.get(mVolumeGroupId).progress = progress;
- mAvailableVolumeItems.get(
- mVolumeGroupId).carVolumeItem.setProgress(progress);
- mCarAudioManager.setGroupVolume(mVolumeGroupId, progress, 0);
- } catch (CarNotConnectedException e) {
- Log.e(TAG, "Car is not connected!", e);
+ if (mCarAudioManager == null) {
+ Log.w(TAG, "Ignoring volume change event because the car isn't connected");
+ return;
}
+ mAvailableVolumeItems.get(mVolumeGroupId).progress = progress;
+ mAvailableVolumeItems.get(
+ mVolumeGroupId).carVolumeItem.setProgress(progress);
+ mCarAudioManager.setGroupVolume(mVolumeGroupId, progress, 0);
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/ThreadUtils.java b/packages/SettingsLib/src/com/android/settingslib/utils/ThreadUtils.java
index 5c9a06f..69f1c17 100644
--- a/packages/SettingsLib/src/com/android/settingslib/utils/ThreadUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/utils/ThreadUtils.java
@@ -18,6 +18,7 @@
import android.os.Handler;
import android.os.Looper;
+import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
@@ -26,7 +27,7 @@
private static volatile Thread sMainThread;
private static volatile Handler sMainThreadHandler;
- private static volatile ExecutorService sSingleThreadExecutor;
+ private static volatile ExecutorService sThreadExecutor;
/**
* Returns true if the current thread is the UI thread.
@@ -64,10 +65,16 @@
* @Return A future of the task that can be monitored for updates or cancelled.
*/
public static Future postOnBackgroundThread(Runnable runnable) {
- if (sSingleThreadExecutor == null) {
- sSingleThreadExecutor = Executors.newSingleThreadExecutor();
- }
- return sSingleThreadExecutor.submit(runnable);
+ return getThreadExecutor().submit(runnable);
+ }
+
+ /**
+ * Posts callable in background using shared background thread pool.
+ *
+ * @Return A future of the task that can be monitored for updates or cancelled.
+ */
+ public static Future postOnBackgroundThread(Callable callable) {
+ return getThreadExecutor().submit(callable);
}
/**
@@ -77,4 +84,11 @@
getUiThreadHandler().post(runnable);
}
+ private static synchronized ExecutorService getThreadExecutor() {
+ if (sThreadExecutor == null) {
+ sThreadExecutor = Executors.newFixedThreadPool(
+ Runtime.getRuntime().availableProcessors());
+ }
+ return sThreadExecutor;
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/ThreadUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/ThreadUtilsTest.java
index 26db124..5114b00 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/ThreadUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/ThreadUtilsTest.java
@@ -50,7 +50,7 @@
}
@Test
- public void testPostOnMainThread_shouldRunOnMainTread() {
+ public void testPostOnMainThread_shouldRunOnMainThread() {
TestRunnable cr = new TestRunnable();
ShadowLooper.pauseMainLooper();
ThreadUtils.postOnMainThread(cr);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index f8c9bcc..894ba9c 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1969,6 +1969,12 @@
dumpSetting(s, p,
Settings.Secure.SKIP_TOUCH_COUNT,
SecureSettingsProto.Gesture.SKIP_TOUCH_COUNT);
+ dumpSetting(s, p,
+ Settings.Secure.AWARE_TAP_PAUSE_GESTURE_COUNT,
+ SecureSettingsProto.Gesture.AWARE_TAP_PAUSE_GESTURE_COUNT);
+ dumpSetting(s, p,
+ Settings.Secure.AWARE_TAP_PAUSE_TOUCH_COUNT,
+ SecureSettingsProto.Gesture.AWARE_TAP_PAUSE_TOUCH_COUNT);
p.end(gestureToken);
dumpSetting(s, p,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index c05c4cd..33085a1 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -64,6 +64,8 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -716,6 +718,23 @@
}
} catch (Throwable t) {
Slog.wtf(LOG_TAG, "Failed to write settings, restoring backup", t);
+ if (t instanceof IOException) {
+ // we failed to create a directory, so log the permissions and existence
+ // state for the settings file and directory
+ logSettingsDirectoryInformation(destination.getBaseFile());
+ if (t.getMessage().contains("Couldn't create directory")) {
+ // attempt to create the directory with Files.createDirectories, which
+ // throws more informative errors than File.mkdirs.
+ Path parentPath = destination.getBaseFile().getParentFile().toPath();
+ try {
+ Files.createDirectories(parentPath);
+ Slog.i(LOG_TAG, "Successfully created " + parentPath);
+ } catch (Throwable t2) {
+ Slog.e(LOG_TAG, "Failed to write " + parentPath
+ + " with Files.writeDirectories", t2);
+ }
+ }
+ }
destination.failWrite(out);
} finally {
IoUtils.closeQuietly(out);
@@ -729,6 +748,33 @@
}
}
+ private static void logSettingsDirectoryInformation(File settingsFile) {
+ File parent = settingsFile.getParentFile();
+ Slog.i(LOG_TAG, "directory info for directory/file " + settingsFile
+ + " with stacktrace ", new Exception());
+ File ancestorDir = parent;
+ while (ancestorDir != null) {
+ if (!ancestorDir.exists()) {
+ Slog.i(LOG_TAG, "ancestor directory " + ancestorDir
+ + " does not exist");
+ ancestorDir = ancestorDir.getParentFile();
+ } else {
+ Slog.i(LOG_TAG, "ancestor directory " + ancestorDir
+ + " exists");
+ Slog.i(LOG_TAG, "ancestor directory " + ancestorDir
+ + " permissions: r: " + ancestorDir.canRead() + " w: "
+ + ancestorDir.canWrite() + " x: " + ancestorDir.canExecute());
+ File ancestorParent = ancestorDir.getParentFile();
+ if (ancestorParent != null) {
+ Slog.i(LOG_TAG, "ancestor's parent directory " + ancestorParent
+ + " permissions: r: " + ancestorParent.canRead() + " w: "
+ + ancestorParent.canWrite() + " x: " + ancestorParent.canExecute());
+ }
+ break;
+ }
+ }
+ }
+
static void writeSingleSetting(int version, XmlSerializer serializer, String id,
String name, String value, String defaultValue, String packageName,
String tag, boolean defaultSysSet) throws IOException {
@@ -803,6 +849,7 @@
in = new AtomicFile(mStatePersistFile).openRead();
} catch (FileNotFoundException fnfe) {
Slog.i(LOG_TAG, "No settings state " + mStatePersistFile);
+ logSettingsDirectoryInformation(mStatePersistFile);
addHistoricalOperationLocked(HISTORICAL_OPERATION_INITIALIZE, null);
return;
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 403e894..862abfd 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -60,6 +60,9 @@
<uses-permission android:name="android.permission.GET_APP_OPS_STATS" />
<uses-permission android:name="android.permission.USE_RESERVED_DISK" />
+ <!-- to invoke ContentSuggestionsService -->
+ <uses-permission android:name="android.permission.MANAGE_CONTENT_SUGGESTIONS"/>
+
<!-- Networking and telephony -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
@@ -366,6 +369,10 @@
<receiver android:name=".screenshot.GlobalScreenshot$DeleteScreenshotReceiver"
android:exported="false" />
+ <!-- Callback for invoking a smart action from the screenshot notification. -->
+ <receiver android:name=".screenshot.GlobalScreenshot$SmartActionsReceiver"
+ android:exported="false"/>
+
<!-- started from UsbDeviceSettingsManager -->
<activity android:name=".usb.UsbConfirmActivity"
android:exported="true"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d58f83f..2365838 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -894,6 +894,10 @@
<string name="quick_settings_ui_mode_night_label">Dark theme</string>
<!-- QuickSettings: Label for the dark theme tile when enabled by battery saver. [CHAR LIMIT=40] -->
<string name="quick_settings_ui_mode_night_label_battery_saver">Dark theme\nBattery saver</string>
+ <!-- QuickSettings: Secondary text for when the Dark Mode will be enabled at sunset. [CHAR LIMIT=20] -->
+ <string name="quick_settings_dark_mode_secondary_label_on_at_sunset">On at sunset</string>
+ <!-- QuickSettings: Secondary text for when the Dark Mode will be on until sunrise. [CHAR LIMIT=20] -->
+ <string name="quick_settings_dark_mode_secondary_label_until_sunrise">Until sunrise</string>
<!-- QuickSettings: NFC tile [CHAR LIMIT=NONE] -->
<string name="quick_settings_nfc_label">NFC</string>
@@ -964,6 +968,12 @@
<!-- Message shown when face authentication fails and the pin pad is visible. [CHAR LIMIT=60] -->
<string name="keyguard_retry">Swipe up to try again</string>
+ <!-- Indication when device is slow charging due to misalignment on the dock. [CHAR LIMIT=60] -->
+ <string name="dock_alignment_slow_charging" product="default">Realign phone for faster charging</string>
+
+ <!-- Indication when device is not charging due to bad placement on the dock. [CHAR LIMIT=60] -->
+ <string name="dock_alignment_not_charging" product="default">Realign phone to charge wirelessly</string>
+
<!-- Text on keyguard screen and in Quick Settings footer indicating that the device is enterprise-managed by a Device Owner [CHAR LIMIT=60] -->
<string name="do_disclosure_generic">This device is managed by your organization</string>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index ca7cd0d..ace24a3 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -44,6 +44,7 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.SystemUIFactory;
@@ -615,6 +616,15 @@
StatsLog.write(StatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED,
StatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__SUCCESS);
mLockPatternUtils.reportSuccessfulPasswordAttempt(userId);
+ // Force a garbage collection in an attempt to erase any lockscreen password left in
+ // memory. Do it asynchronously with a 5-sec delay to avoid making the keyguard
+ // dismiss animation janky.
+ ThreadUtils.postOnBackgroundThread(() -> {
+ try {
+ Thread.sleep(5000);
+ } catch (InterruptedException ignored) { }
+ Runtime.getRuntime().gc();
+ });
} else {
StatsLog.write(StatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED,
StatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__FAILURE);
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender.java
deleted file mode 100644
index 05acdd0..0000000
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui;
-
-import android.annotation.NonNull;
-import android.app.Notification;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.ArraySet;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.statusbar.NotificationLifetimeExtender;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-
-/**
- * Extends the lifetime of foreground notification services such that they show for at least
- * five seconds
- */
-public class ForegroundServiceLifetimeExtender implements NotificationLifetimeExtender {
-
- private static final String TAG = "FGSLifetimeExtender";
- @VisibleForTesting
- static final int MIN_FGS_TIME_MS = 5000;
-
- private NotificationSafeToRemoveCallback mNotificationSafeToRemoveCallback;
- private ArraySet<NotificationEntry> mManagedEntries = new ArraySet<>();
- private Handler mHandler = new Handler(Looper.getMainLooper());
-
- public ForegroundServiceLifetimeExtender() {
- }
-
- @Override
- public void setCallback(@NonNull NotificationSafeToRemoveCallback callback) {
- mNotificationSafeToRemoveCallback = callback;
- }
-
- @Override
- public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) {
- if ((entry.notification.getNotification().flags
- & Notification.FLAG_FOREGROUND_SERVICE) == 0) {
- return false;
- }
-
- long currentTime = System.currentTimeMillis();
- return currentTime - entry.notification.getPostTime() < MIN_FGS_TIME_MS;
- }
-
- @Override
- public boolean shouldExtendLifetimeForPendingNotification(
- @NonNull NotificationEntry entry) {
- return shouldExtendLifetime(entry);
- }
-
- @Override
- public void setShouldManageLifetime(
- @NonNull NotificationEntry entry, boolean shouldManage) {
- if (!shouldManage) {
- mManagedEntries.remove(entry);
- return;
- }
-
- mManagedEntries.add(entry);
-
- Runnable r = () -> {
- if (mManagedEntries.contains(entry)) {
- mManagedEntries.remove(entry);
- if (mNotificationSafeToRemoveCallback != null) {
- mNotificationSafeToRemoveCallback.onSafeToRemove(entry.key);
- }
- }
- };
- long delayAmt = MIN_FGS_TIME_MS
- - (System.currentTimeMillis() - entry.notification.getPostTime());
- mHandler.postDelayed(r, delayAmt);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
index 0162deb..96b62ac 100644
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
@@ -66,9 +66,6 @@
removeNotification(entry.notification);
}
});
-
- notificationEntryManager.addNotificationLifetimeExtender(
- new ForegroundServiceLifetimeExtender());
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index bd91333..c0c14fb 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.graphics.Rect;
import android.os.HandlerThread;
+import android.os.Trace;
import android.service.wallpaper.WallpaperService;
import android.util.Log;
import android.util.Size;
@@ -48,6 +49,7 @@
private static final int DELAY_FINISH_RENDERING = 1000;
private static final int INTERVAL_WAIT_FOR_RENDERING = 100;
private static final int PATIENCE_WAIT_FOR_RENDERING = 10;
+ private static final boolean DEBUG = true;
private HandlerThread mWorker;
@Override
@@ -125,6 +127,10 @@
@Override
public void onAmbientModeChanged(boolean inAmbientMode, long animationDuration) {
if (!mNeedTransition) return;
+ if (DEBUG) {
+ Log.d(TAG, "onAmbientModeChanged: inAmbient=" + inAmbientMode
+ + ", duration=" + animationDuration);
+ }
mWorker.getThreadHandler().post(
() -> mRenderer.updateAmbientMode(inAmbientMode, animationDuration));
if (inAmbientMode && animationDuration == 0) {
@@ -177,6 +183,10 @@
@Override
public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
mWorker.getThreadHandler().post(() -> {
+ if (DEBUG) {
+ Log.d(TAG, "onSurfaceChanged: w=" + width + ", h=" + height);
+ }
+
mRenderer.onSurfaceChanged(width, height);
mNeedRedraw = true;
});
@@ -185,16 +195,31 @@
@Override
public void onSurfaceRedrawNeeded(SurfaceHolder holder) {
mWorker.getThreadHandler().post(() -> {
+ if (DEBUG) {
+ Log.d(TAG, "onSurfaceRedrawNeeded: mNeedRedraw=" + mNeedRedraw);
+ }
+
if (mNeedRedraw) {
- preRender();
- requestRender();
- postRender();
+ drawFrame();
mNeedRedraw = false;
}
});
}
@Override
+ public void onVisibilityChanged(boolean visible) {
+ if (DEBUG) {
+ Log.d(TAG, "wallpaper visibility changes to: " + visible);
+ }
+ }
+
+ private void drawFrame() {
+ preRender();
+ requestRender();
+ postRender();
+ }
+
+ @Override
public void onStatePostChange() {
// When back to home, we try to release EGL, which is preserved in lock screen or aod.
if (mController.getState() == StatusBarState.SHADE) {
@@ -204,8 +229,18 @@
@Override
public void preRender() {
+ if (DEBUG) {
+ Log.d(TAG, "preRender start");
+ }
+
// This method should only be invoked from worker thread.
+ Trace.beginSection("ImageWallpaper#preRender");
preRenderInternal();
+ Trace.endSection();
+
+ if (DEBUG) {
+ Log.d(TAG, "preRender end");
+ }
}
private void preRenderInternal() {
@@ -240,7 +275,9 @@
@Override
public void requestRender() {
// This method should only be invoked from worker thread.
+ Trace.beginSection("ImageWallpaper#requestRender");
requestRenderInternal();
+ Trace.endSection();
}
private void requestRenderInternal() {
@@ -262,9 +299,19 @@
@Override
public void postRender() {
+ if (DEBUG) {
+ Log.d(TAG, "postRender start");
+ }
+
// This method should only be invoked from worker thread.
+ Trace.beginSection("ImageWallpaper#postRender");
notifyWaitingThread();
scheduleFinishRendering();
+ Trace.endSection();
+
+ if (DEBUG) {
+ Log.d(TAG, "postRender end");
+ }
}
private void notifyWaitingThread() {
@@ -289,12 +336,18 @@
}
private void finishRendering() {
+ if (DEBUG) {
+ Log.d(TAG, "finishRendering, preserve=" + needPreserveEglContext());
+ }
+
+ Trace.beginSection("ImageWallpaper#finishRendering");
if (mEglHelper != null) {
mEglHelper.destroyEglSurface();
if (!needPreserveEglContext()) {
mEglHelper.destroyEglContext();
}
}
+ Trace.endSection();
}
private boolean needPreserveEglContext() {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 48127a7..9252e57 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -142,7 +142,7 @@
public void startServicesIfNeeded() {
String[] names = getResources().getStringArray(R.array.config_systemUIServiceComponents);
- startServicesIfNeeded(names);
+ startServicesIfNeeded(/* metricsPrefix= */ "StartServices", names);
}
/**
@@ -154,10 +154,10 @@
void startSecondaryUserServicesIfNeeded() {
String[] names =
getResources().getStringArray(R.array.config_systemUIServiceComponentsPerUser);
- startServicesIfNeeded(names);
+ startServicesIfNeeded(/* metricsPrefix= */ "StartSecondaryServices", names);
}
- private void startServicesIfNeeded(String[] services) {
+ private void startServicesIfNeeded(String metricsPrefix, String[] services) {
if (mServicesStarted) {
return;
}
@@ -176,14 +176,16 @@
Log.v(TAG, "Starting SystemUI services for user " +
Process.myUserHandle().getIdentifier() + ".");
+
TimingsTraceLog log = new TimingsTraceLog("SystemUIBootTiming",
Trace.TRACE_TAG_APP);
- log.traceBegin("StartServices");
+ log.traceBegin(metricsPrefix);
+
final int N = services.length;
for (int i = 0; i < N; i++) {
String clsName = services[i];
if (DEBUG) Log.d(TAG, "loading: " + clsName);
- log.traceBegin("StartServices" + clsName);
+ log.traceBegin(metricsPrefix + clsName);
long ti = System.currentTimeMillis();
Class cls;
try {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 0899d95..2531b60 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -31,6 +31,7 @@
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.ScrimView;
@@ -49,6 +50,7 @@
import com.android.systemui.statusbar.policy.KeyguardMonitor;
import com.android.systemui.volume.VolumeDialogComponent;
+import java.util.concurrent.Executor;
import java.util.function.Consumer;
import dagger.Module;
@@ -110,6 +112,17 @@
return new StatusBarKeyguardViewManager(context, viewMediatorCallback, lockPatternUtils);
}
+ /**
+ * Creates an instance of ScreenshotNotificationSmartActionsProvider.
+ * This method is overridden in vendor specific implementation of Sys UI.
+ */
+ public ScreenshotNotificationSmartActionsProvider
+ createScreenshotNotificationSmartActionsProvider(Context context,
+ Executor executor,
+ Handler uiHandler) {
+ return new ScreenshotNotificationSmartActionsProvider();
+ }
+
public KeyguardBouncer createKeyguardBouncer(Context context, ViewMediatorCallback callback,
LockPatternUtils lockPatternUtils, ViewGroup container,
DismissCallbackRegistry dismissCallbackRegistry,
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
index 1c5e800..150a40a 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
@@ -16,6 +16,7 @@
package com.android.systemui;
+import android.annotation.NonNull;
import android.app.Service;
import android.content.Intent;
import android.os.Build;
@@ -66,7 +67,13 @@
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (args != null && args.length > 0 && args[0].equals("--config")) {
+ dumpConfig(pw);
+ return;
+ }
+
dumpServices(((SystemUIApplication) getApplication()).getServices(), fd, pw, args);
+ dumpConfig(pw);
}
static void dumpServices(
@@ -95,5 +102,29 @@
}
}
}
+
+ private void dumpConfig(@NonNull PrintWriter pw) {
+ pw.println("SystemUiServiceComponents configuration:");
+
+ pw.print("vendor component: ");
+ pw.println(getResources().getString(R.string.config_systemUIVendorServiceComponent));
+
+ dumpConfig(pw, "global", R.array.config_systemUIServiceComponents);
+ dumpConfig(pw, "per-user", R.array.config_systemUIServiceComponentsPerUser);
+ }
+
+ private void dumpConfig(@NonNull PrintWriter pw, @NonNull String type, int resId) {
+ final String[] services = getResources().getStringArray(resId);
+ pw.print(type); pw.print(": ");
+ if (services == null) {
+ pw.println("N/A");
+ return;
+ }
+ pw.print(services.length);
+ pw.println(" services");
+ for (int i = 0; i < services.length; i++) {
+ pw.print(" "); pw.print(i); pw.print(": "); pw.println(services[i]);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java
index 738ec80..8a8c6d1 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java
@@ -72,7 +72,7 @@
private final Runnable mHideHandles = this::hideHandles;
private final Runnable mShowAndGo = this::showAndGoInternal;
private final Provider<ScreenDecorations> mScreenDecorations;
- private final PhenotypeHelper mPhenotypeHelper;
+ private final DeviceConfigHelper mDeviceConfigHelper;
private final Map<AssistHandleBehavior, BehaviorController> mBehaviorMap;
private boolean mHandlesShowing = false;
@@ -91,7 +91,7 @@
AssistUtils assistUtils,
@Named(ASSIST_HANDLE_THREAD_NAME) Handler handler,
Provider<ScreenDecorations> screenDecorations,
- PhenotypeHelper phenotypeHelper,
+ DeviceConfigHelper deviceConfigHelper,
Map<AssistHandleBehavior, BehaviorController> behaviorMap,
NavigationModeController navigationModeController,
DumpController dumpController) {
@@ -99,14 +99,14 @@
mAssistUtils = assistUtils;
mHandler = handler;
mScreenDecorations = screenDecorations;
- mPhenotypeHelper = phenotypeHelper;
+ mDeviceConfigHelper = deviceConfigHelper;
mBehaviorMap = behaviorMap;
mInGesturalMode = QuickStepContract.isGesturalMode(
navigationModeController.addListener(this::handleNavigationModeChange));
setBehavior(getBehaviorMode());
- mPhenotypeHelper.addOnPropertiesChangedListener(
+ mDeviceConfigHelper.addOnPropertiesChangedListener(
mHandler::post,
(properties) -> {
if (properties.getKeyset().contains(
@@ -206,19 +206,19 @@
}
private long getShownFrequencyThreshold() {
- return mPhenotypeHelper.getLong(
+ return mDeviceConfigHelper.getLong(
SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOWN_FREQUENCY_THRESHOLD_MS,
DEFAULT_SHOWN_FREQUENCY_THRESHOLD_MS);
}
private long getShowAndGoDuration() {
- return mPhenotypeHelper.getLong(
+ return mDeviceConfigHelper.getLong(
SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOW_AND_GO_DURATION_MS,
DEFAULT_SHOW_AND_GO_DURATION_MS);
}
private String getBehaviorMode() {
- return mPhenotypeHelper.getString(
+ return mDeviceConfigHelper.getString(
SystemUiDeviceConfigFlags.ASSIST_HANDLES_BEHAVIOR_MODE,
DEFAULT_BEHAVIOR.toString());
}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java
index 0394048..b1fcd9d 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java
@@ -156,7 +156,7 @@
private final Clock mClock;
private final Handler mHandler;
- private final PhenotypeHelper mPhenotypeHelper;
+ private final DeviceConfigHelper mDeviceConfigHelper;
private final Lazy<StatusBarStateController> mStatusBarStateController;
private final Lazy<ActivityManagerWrapper> mActivityManagerWrapper;
private final Lazy<OverviewProxyService> mOverviewProxyService;
@@ -188,7 +188,7 @@
AssistHandleReminderExpBehavior(
@Named(UPTIME_NAME) Clock clock,
@Named(ASSIST_HANDLE_THREAD_NAME) Handler handler,
- PhenotypeHelper phenotypeHelper,
+ DeviceConfigHelper deviceConfigHelper,
Lazy<StatusBarStateController> statusBarStateController,
Lazy<ActivityManagerWrapper> activityManagerWrapper,
Lazy<OverviewProxyService> overviewProxyService,
@@ -196,7 +196,7 @@
Lazy<PackageManagerWrapper> packageManagerWrapper) {
mClock = clock;
mHandler = handler;
- mPhenotypeHelper = phenotypeHelper;
+ mDeviceConfigHelper = deviceConfigHelper;
mStatusBarStateController = statusBarStateController;
mActivityManagerWrapper = activityManagerWrapper;
mOverviewProxyService = overviewProxyService;
@@ -457,55 +457,55 @@
}
private long getLearningTimeMs() {
- return mPhenotypeHelper.getLong(
+ return mDeviceConfigHelper.getLong(
SystemUiDeviceConfigFlags.ASSIST_HANDLES_LEARN_TIME_MS,
DEFAULT_LEARNING_TIME_MS);
}
private int getLearningCount() {
- return mPhenotypeHelper.getInt(
+ return mDeviceConfigHelper.getInt(
SystemUiDeviceConfigFlags.ASSIST_HANDLES_LEARN_COUNT,
DEFAULT_LEARNING_COUNT);
}
private long getShowAndGoDelayedShortDelayMs() {
- return mPhenotypeHelper.getLong(
+ return mDeviceConfigHelper.getLong(
SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOW_AND_GO_DELAYED_SHORT_DELAY_MS,
DEFAULT_SHOW_AND_GO_DELAYED_SHORT_DELAY_MS);
}
private long getShowAndGoDelayedLongDelayMs() {
- return mPhenotypeHelper.getLong(
+ return mDeviceConfigHelper.getLong(
SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOW_AND_GO_DELAYED_LONG_DELAY_MS,
DEFAULT_SHOW_AND_GO_DELAYED_LONG_DELAY_MS);
}
private long getShowAndGoDelayResetTimeoutMs() {
- return mPhenotypeHelper.getLong(
+ return mDeviceConfigHelper.getLong(
SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOW_AND_GO_DELAY_RESET_TIMEOUT_MS,
DEFAULT_SHOW_AND_GO_DELAY_RESET_TIMEOUT_MS);
}
private boolean getSuppressOnLockscreen() {
- return mPhenotypeHelper.getBoolean(
+ return mDeviceConfigHelper.getBoolean(
SystemUiDeviceConfigFlags.ASSIST_HANDLES_SUPPRESS_ON_LOCKSCREEN,
DEFAULT_SUPPRESS_ON_LOCKSCREEN);
}
private boolean getSuppressOnLauncher() {
- return mPhenotypeHelper.getBoolean(
+ return mDeviceConfigHelper.getBoolean(
SystemUiDeviceConfigFlags.ASSIST_HANDLES_SUPPRESS_ON_LAUNCHER,
DEFAULT_SUPPRESS_ON_LAUNCHER);
}
private boolean getSuppressOnApps() {
- return mPhenotypeHelper.getBoolean(
+ return mDeviceConfigHelper.getBoolean(
SystemUiDeviceConfigFlags.ASSIST_HANDLES_SUPPRESS_ON_APPS,
DEFAULT_SUPPRESS_ON_APPS);
}
private boolean getShowWhenTaught() {
- return mPhenotypeHelper.getBoolean(
+ return mDeviceConfigHelper.getBoolean(
SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOW_WHEN_TAUGHT,
DEFAULT_SHOW_WHEN_TAUGHT);
}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/PhenotypeHelper.java b/packages/SystemUI/src/com/android/systemui/assist/DeviceConfigHelper.java
similarity index 92%
rename from packages/SystemUI/src/com/android/systemui/assist/PhenotypeHelper.java
rename to packages/SystemUI/src/com/android/systemui/assist/DeviceConfigHelper.java
index ff76adf..3005c97 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/PhenotypeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/DeviceConfigHelper.java
@@ -26,15 +26,15 @@
import javax.inject.Singleton;
/**
- * Wrapper class for retrieving phenotype flag values.
+ * Wrapper class for retrieving System UI device configuration values.
*
* Can be mocked in tests for ease of testing the effects of particular values.
*/
@Singleton
-public class PhenotypeHelper {
+public class DeviceConfigHelper {
@Inject
- public PhenotypeHelper() {}
+ public DeviceConfigHelper() {}
public long getLong(String name, long defaultValue) {
return DeviceConfig.getLong(DeviceConfig.NAMESPACE_SYSTEMUI, name, defaultValue);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java
index ce67577..cbbd3a0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java
@@ -499,6 +499,8 @@
if (newState == STATE_PENDING_CONFIRMATION || newState == STATE_AUTHENTICATED) {
mNegativeButton.setText(R.string.cancel);
mNegativeButton.setContentDescription(getResources().getString(R.string.cancel));
+ } else {
+ mNegativeButton.setText(mBundle.getCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT));
}
updateIcon(mState, newState);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index eb826e5..d43e030 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -184,6 +184,8 @@
Log.d(TAG, "notificationEntryUpdated: " + entry);
}
Bubble bubble = getBubbleWithKey(entry.key);
+ suppressFlyout = !entry.isVisuallyInterruptive || suppressFlyout;
+
if (bubble == null) {
// Create a new bubble
bubble = new Bubble(mContext, entry);
@@ -193,8 +195,10 @@
} else {
// Updates an existing bubble
bubble.updateEntry(entry);
+ bubble.setSuppressFlyout(suppressFlyout);
doUpdate(bubble);
}
+
if (bubble.shouldAutoExpand()) {
setSelectedBubbleInternal(bubble);
if (!mExpanded) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index b68b762..31cf853 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -169,7 +169,7 @@
* Callback to run after the flyout hides. Also called if a new flyout is shown before the
* previous one animates out.
*/
- private Runnable mAfterFlyoutHides;
+ private Runnable mFlyoutOnHide;
/** Layout change listener that moves the stack to the nearest valid position on rotation. */
private OnLayoutChangeListener mOrientationChangedListener;
@@ -1401,111 +1401,106 @@
@VisibleForTesting
void animateInFlyoutForBubble(Bubble bubble) {
final CharSequence updateMessage = bubble.getUpdateMessage(getContext());
-
if (!bubble.showFlyoutForBubble()) {
// In case flyout was suppressed for this update, reset now.
bubble.setSuppressFlyout(false);
return;
}
-
if (updateMessage == null
|| isExpanded()
|| mIsExpansionAnimating
|| mIsGestureInProgress
- || mBubbleToExpandAfterFlyoutCollapse != null) {
+ || mBubbleToExpandAfterFlyoutCollapse != null
+ || bubble.getIconView() == null) {
// Skip the message if none exists, we're expanded or animating expansion, or we're
- // about to expand a bubble from the previous tapped flyout.
+ // about to expand a bubble from the previous tapped flyout, or if bubble view is null.
return;
}
-
- if (bubble.getIconView() != null) {
- // Temporarily suppress the dot while the flyout is visible.
- bubble.getIconView().setSuppressDot(
- true /* suppressDot */, false /* animate */);
-
- mFlyout.removeCallbacks(mAnimateInFlyout);
- mFlyoutDragDeltaX = 0f;
-
- if (mAfterFlyoutHides != null) {
- mAfterFlyoutHides.run();
+ mFlyoutDragDeltaX = 0f;
+ clearFlyoutOnHide();
+ mFlyoutOnHide = () -> {
+ resetDot(bubble);
+ if (mBubbleToExpandAfterFlyoutCollapse == null) {
+ return;
}
+ mBubbleData.setSelectedBubble(mBubbleToExpandAfterFlyoutCollapse);
+ mBubbleData.setExpanded(true);
+ mBubbleToExpandAfterFlyoutCollapse = null;
+ };
+ mFlyout.setVisibility(INVISIBLE);
- mAfterFlyoutHides = () -> {
- final boolean suppressDot = !bubble.showBubbleDot();
- // If we're going to suppress the dot, make it visible first so it'll
- // visibly animate away.
- if (suppressDot) {
- bubble.getIconView().setSuppressDot(
- false /* suppressDot */, false /* animate */);
- }
- // Reset dot suppression. If we're not suppressing due to DND, then
- // stop suppressing it with no animation (since the flyout has
- // transformed into the dot). If we are suppressing due to DND, animate
- // it away.
- bubble.getIconView().setSuppressDot(
- suppressDot /* suppressDot */,
- suppressDot /* animate */);
+ // Temporarily suppress the dot while the flyout is visible.
+ bubble.getIconView().setSuppressDot(
+ true /* suppressDot */, false /* animate */);
- if (mBubbleToExpandAfterFlyoutCollapse != null) {
- mBubbleData.setSelectedBubble(mBubbleToExpandAfterFlyoutCollapse);
- mBubbleData.setExpanded(true);
- mBubbleToExpandAfterFlyoutCollapse = null;
- }
- };
-
- mFlyout.setVisibility(INVISIBLE);
-
- // Post in case layout isn't complete and getWidth returns 0.
- post(() -> {
- // An auto-expanding bubble could have been posted during the time it takes to
- // layout.
- if (isExpanded()) {
- return;
- }
-
- final Runnable afterShow = () -> {
- mAnimateInFlyout = () -> {
- mFlyout.setVisibility(VISIBLE);
- bubble.getIconView().setSuppressDot(
- true /* suppressDot */, false /* animate */);
- mFlyoutDragDeltaX =
- mStackAnimationController.isStackOnLeftSide()
- ? -mFlyout.getWidth()
- : mFlyout.getWidth();
- animateFlyoutCollapsed(false /* collapsed */, 0 /* velX */);
- mFlyout.postDelayed(mHideFlyout, FLYOUT_HIDE_AFTER);
- };
-
- mFlyout.postDelayed(mAnimateInFlyout, 200);
+ // Start flyout expansion. Post in case layout isn't complete and getWidth returns 0.
+ post(() -> {
+ // An auto-expanding bubble could have been posted during the time it takes to
+ // layout.
+ if (isExpanded()) {
+ return;
+ }
+ final Runnable expandFlyoutAfterDelay = () -> {
+ mAnimateInFlyout = () -> {
+ mFlyout.setVisibility(VISIBLE);
+ mFlyoutDragDeltaX =
+ mStackAnimationController.isStackOnLeftSide()
+ ? -mFlyout.getWidth()
+ : mFlyout.getWidth();
+ animateFlyoutCollapsed(false /* collapsed */, 0 /* velX */);
+ mFlyout.postDelayed(mHideFlyout, FLYOUT_HIDE_AFTER);
};
-
- mFlyout.setupFlyoutStartingAsDot(
- updateMessage, mStackAnimationController.getStackPosition(), getWidth(),
- mStackAnimationController.isStackOnLeftSide(),
- bubble.getIconView().getBadgeColor(),
- afterShow,
- mAfterFlyoutHides,
- bubble.getIconView().getDotCenter());
- mFlyout.bringToFront();
- });
- }
-
+ mFlyout.postDelayed(mAnimateInFlyout, 200);
+ };
+ mFlyout.setupFlyoutStartingAsDot(
+ updateMessage, mStackAnimationController.getStackPosition(), getWidth(),
+ mStackAnimationController.isStackOnLeftSide(),
+ bubble.getIconView().getBadgeColor() /* dotColor */,
+ expandFlyoutAfterDelay /* onLayoutComplete */,
+ mFlyoutOnHide,
+ bubble.getIconView().getDotCenter());
+ mFlyout.bringToFront();
+ });
mFlyout.removeCallbacks(mHideFlyout);
mFlyout.postDelayed(mHideFlyout, FLYOUT_HIDE_AFTER);
logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__FLYOUT);
}
+ private void resetDot(Bubble bubble) {
+ final boolean suppressDot = !bubble.showBubbleDot();
+ // If we're going to suppress the dot, make it visible first so it'll
+ // visibly animate away.
+
+ if (suppressDot) {
+ bubble.getIconView().setSuppressDot(
+ false /* suppressDot */, false /* animate */);
+ }
+ // Reset dot suppression. If we're not suppressing due to DND, then
+ // stop suppressing it with no animation (since the flyout has
+ // transformed into the dot). If we are suppressing due to DND, animate
+ // it away.
+ bubble.getIconView().setSuppressDot(
+ suppressDot /* suppressDot */,
+ suppressDot /* animate */);
+ }
+
/** Hide the flyout immediately and cancel any pending hide runnables. */
private void hideFlyoutImmediate() {
- if (mAfterFlyoutHides != null) {
- mAfterFlyoutHides.run();
- }
-
+ clearFlyoutOnHide();
mFlyout.removeCallbacks(mAnimateInFlyout);
mFlyout.removeCallbacks(mHideFlyout);
mFlyout.hideFlyout();
}
+ private void clearFlyoutOnHide() {
+ mFlyout.removeCallbacks(mAnimateInFlyout);
+ if (mFlyoutOnHide == null) {
+ return;
+ }
+ mFlyoutOnHide.run();
+ mFlyoutOnHide = null;
+ }
+
@Override
public void getBoundsOnScreen(Rect outRect) {
if (!mIsExpanded) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
index 603c416..4512aa8 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
@@ -61,7 +61,7 @@
// mBubbleIconFactory cannot be static because it depends on Context.
private BubbleIconFactory mBubbleIconFactory;
- private boolean mSuppressDot = false;
+ private boolean mSuppressDot;
private Bubble mBubble;
@@ -140,6 +140,7 @@
public void setAppIcon(Drawable appIcon) {
mUserBadgedAppIcon = appIcon;
}
+
/**
* @return the {@link ExpandableNotificationRow} view to display notification content when the
* bubble is expanded.
@@ -154,7 +155,6 @@
updateDotVisibility(animate, null /* after */);
}
-
/**
* Sets whether or not to hide the dot even if we'd otherwise show it. This is used while the
* flyout is visible or animating, to hide the dot until the flyout visually transforms into it.
@@ -166,7 +166,7 @@
/** Sets the position of the 'new' dot, animating it out and back in if requested. */
void setDotPosition(boolean onLeft, boolean animate) {
- if (animate && onLeft != mBadgedImageView.getDotOnLeft() && !mSuppressDot) {
+ if (animate && onLeft != mBadgedImageView.getDotOnLeft() && shouldShowDot()) {
animateDot(false /* showDot */, () -> {
mBadgedImageView.setDotOnLeft(onLeft);
animateDot(true /* showDot */, null);
@@ -190,12 +190,12 @@
* after animation if requested.
*/
private void updateDotVisibility(boolean animate, Runnable after) {
- boolean showDot = mBubble.showBubbleDot() && !mSuppressDot;
-
+ final boolean showDot = shouldShowDot();
if (animate) {
animateDot(showDot, after);
} else {
mBadgedImageView.setShowDot(showDot);
+ mBadgedImageView.setDotScale(showDot ? 1f : 0f);
}
}
@@ -203,27 +203,25 @@
* Animates the badge to show or hide.
*/
private void animateDot(boolean showDot, Runnable after) {
- if (mBadgedImageView.isShowingDot() != showDot) {
- if (showDot) {
- mBadgedImageView.setShowDot(true);
- }
- mBadgedImageView.clearAnimation();
- mBadgedImageView.animate().setDuration(200)
- .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
- .setUpdateListener((valueAnimator) -> {
- float fraction = valueAnimator.getAnimatedFraction();
- fraction = showDot ? fraction : 1f - fraction;
- mBadgedImageView.setDotScale(fraction);
- }).withEndAction(() -> {
- if (!showDot) {
- mBadgedImageView.setShowDot(false);
- }
-
- if (after != null) {
- after.run();
- }
- }).start();
+ if (mBadgedImageView.isShowingDot() == showDot) {
+ return;
}
+ // Do NOT wait until after animation ends to setShowDot
+ // to avoid overriding more recent showDot states.
+ mBadgedImageView.setShowDot(showDot);
+ mBadgedImageView.clearAnimation();
+ mBadgedImageView.animate().setDuration(200)
+ .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+ .setUpdateListener((valueAnimator) -> {
+ float fraction = valueAnimator.getAnimatedFraction();
+ fraction = showDot ? fraction : 1f - fraction;
+ mBadgedImageView.setDotScale(fraction);
+ }).withEndAction(() -> {
+ mBadgedImageView.setDotScale(showDot ? 1f : 0f);
+ if (after != null) {
+ after.run();
+ }
+ }).start();
}
void updateViews() {
@@ -273,7 +271,11 @@
iconPath.transform(matrix);
mBadgedImageView.drawDot(iconPath);
- animateDot(mBubble.showBubbleDot() /* showDot */, null /* after */);
+ animateDot(shouldShowDot(), null /* after */);
+ }
+
+ boolean shouldShowDot() {
+ return mBubble.showBubbleDot() && !mSuppressDot;
}
int getBadgeColor() {
diff --git a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
index d3e8b3d..3ca1f59 100644
--- a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
+++ b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
@@ -68,9 +68,11 @@
mBackdropColors.setMainColor(Color.BLACK);
// Listen to all users instead of only the current one.
- wallpaperManager.removeOnColorsChangedListener(this);
- wallpaperManager.addOnColorsChangedListener(this, null /* handler */,
- UserHandle.USER_ALL);
+ if (wallpaperManager.isWallpaperSupported()) {
+ wallpaperManager.removeOnColorsChangedListener(this);
+ wallpaperManager.addOnColorsChangedListener(this, null /* handler */,
+ UserHandle.USER_ALL);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/dock/DockManager.java b/packages/SystemUI/src/com/android/systemui/dock/DockManager.java
index d332f59..4ee2ed3 100644
--- a/packages/SystemUI/src/com/android/systemui/dock/DockManager.java
+++ b/packages/SystemUI/src/com/android/systemui/dock/DockManager.java
@@ -17,12 +17,12 @@
package com.android.systemui.dock;
/**
- * Allows an app to handle dock events
+ * Allows an app to handle dock events.
*/
public interface DockManager {
/**
- * Uninitialized / undocking dock states
+ * Uninitialized / undocking dock states.
*/
int STATE_NONE = 0;
/**
@@ -30,34 +30,81 @@
*/
int STATE_DOCKED = 1;
/**
- * The state for docking without showing UI
+ * The state for docking without showing UI.
*/
int STATE_DOCKED_HIDE = 2;
/**
- * Add a dock event listener into manager
+ * Indicates there's no alignment info. This could happen when the device is unable to decide
+ * its alignment condition.
+ */
+ int ALIGN_STATE_UNKNOWN = -1;
+
+ /**
+ * Indicates there's no alignment issue.
+ */
+ int ALIGN_STATE_GOOD = 0;
+
+ /**
+ * Indicates it's slightly not aligned with dock. Normally combines with slow charging issue.
+ */
+ int ALIGN_STATE_POOR = 1;
+
+ /**
+ * Indicates it's not aligned with dock. Normally combines with not charging issue.
+ */
+ int ALIGN_STATE_TERRIBLE = 2;
+
+ /**
+ * Adds a dock event listener into manager.
*
* @param callback A {@link DockEventListener} which want to add
*/
void addListener(DockEventListener callback);
/**
- * Remove the added listener from dock manager
+ * Removes the added listener from dock manager
*
* @param callback A {@link DockEventListener} which want to remove
*/
void removeListener(DockEventListener callback);
/**
+ * Adds a alignment listener into manager.
+ *
+ * @param listener A {@link AlignmentStateListener} which want to add
+ */
+ void addAlignmentStateListener(AlignmentStateListener listener);
+
+ /**
+ * Removes the added alignment listener from dock manager.
+ *
+ * @param listener A {@link AlignmentStateListener} which want to remove
+ */
+ void removeAlignmentStateListener(AlignmentStateListener listener);
+
+ /**
* Returns true if the device is in docking state.
*/
boolean isDocked();
- /** Callback for receiving dock events */
+ /**
+ * Listens to dock events.
+ */
interface DockEventListener {
/**
- * Override to handle dock events
+ * Override to handle dock events.
*/
void onEvent(int event);
}
+
+ /**
+ * Listens to dock alignment state changed.
+ */
+ interface AlignmentStateListener {
+ /**
+ * Override to handle alignment state changes.
+ */
+ void onAlignmentStateChanged(int alignState);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dock/DockManagerImpl.java b/packages/SystemUI/src/com/android/systemui/dock/DockManagerImpl.java
index fa7f503..f6d24e8 100644
--- a/packages/SystemUI/src/com/android/systemui/dock/DockManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/dock/DockManagerImpl.java
@@ -35,6 +35,14 @@
}
@Override
+ public void addAlignmentStateListener(AlignmentStateListener listener) {
+ }
+
+ @Override
+ public void removeAlignmentStateListener(AlignmentStateListener listener) {
+ }
+
+ @Override
public boolean isDocked() {
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index 90cb05a..0e124e4 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -33,6 +33,7 @@
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.statusbar.phone.BiometricUnlockController;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.AsyncSensorManager;
import com.android.systemui.util.wakelock.DelayedWakeLock;
import com.android.systemui.util.wakelock.WakeLock;
@@ -64,7 +65,7 @@
params);
DozeMachine machine = new DozeMachine(wrappedService, config, wakeLock,
- wakefulnessLifecycle);
+ wakefulnessLifecycle, Dependency.get(BatteryController.class));
machine.setParts(new DozeMachine.Part[]{
new DozePauser(handler, machine, alarmManager, params.getPolicy()),
new DozeFalsingManagerAdapter(falsingManager),
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index 93a51cc..7f2d527 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -27,6 +27,7 @@
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle.Wakefulness;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.Assert;
import com.android.systemui.util.wakelock.WakeLock;
@@ -121,6 +122,7 @@
private final WakeLock mWakeLock;
private final AmbientDisplayConfiguration mConfig;
private final WakefulnessLifecycle mWakefulnessLifecycle;
+ private final BatteryController mBatteryController;
private Part[] mParts;
private final ArrayList<State> mQueuedRequests = new ArrayList<>();
@@ -129,11 +131,13 @@
private boolean mWakeLockHeldForCurrentState = false;
public DozeMachine(Service service, AmbientDisplayConfiguration config,
- WakeLock wakeLock, WakefulnessLifecycle wakefulnessLifecycle) {
+ WakeLock wakeLock, WakefulnessLifecycle wakefulnessLifecycle,
+ BatteryController batteryController) {
mDozeService = service;
mConfig = config;
mWakefulnessLifecycle = wakefulnessLifecycle;
mWakeLock = wakeLock;
+ mBatteryController = batteryController;
}
/** Initializes the set of {@link Part}s. Must be called exactly once after construction. */
@@ -316,6 +320,9 @@
Log.i(TAG, "Dropping pulse done because current state is already done: " + mState);
return mState;
}
+ if (requestedState == State.DOZE_AOD && mBatteryController.isAodPowerSave()) {
+ return State.DOZE;
+ }
if (requestedState == State.DOZE_REQUEST_PULSE && !mState.canPulse()) {
Log.i(TAG, "Dropping pulse request because current state can't pulse: " + mState);
return mState;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index bab64db..6199a0d 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -345,7 +345,6 @@
private void checkTriggersAtInit() {
if (mUiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR
- || mDozeHost.isPowerSaveActive()
|| mDozeHost.isBlockingDoze()
|| !mDozeHost.isProvisioned()) {
mMachine.requestState(DozeMachine.State.FINISH);
@@ -574,8 +573,8 @@
@Override
public void onPowerSaveChanged(boolean active) {
- if (active) {
- mMachine.requestState(DozeMachine.State.FINISH);
+ if (mDozeHost.isPowerSaveActive()) {
+ mMachine.requestState(DozeMachine.State.DOZE);
}
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index 1f33af8..3869e77 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -83,7 +83,8 @@
*/
private void updateAnimateScreenOff() {
if (mCanAnimateTransition) {
- final boolean controlScreenOff = mDozeParameters.getAlwaysOn() && mKeyguardShowing;
+ final boolean controlScreenOff = mDozeParameters.getAlwaysOn() && mKeyguardShowing
+ && !mHost.isPowerSaveActive();
mDozeParameters.setControlScreenOffAnimation(controlScreenOff);
mHost.setAnimateScreenOff(controlScreenOff);
}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index ff02e71..a48b9e6 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -86,6 +86,7 @@
import com.android.internal.util.ScreenshotHelper;
import com.android.internal.view.RotationPolicy;
import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.MultiListLayout;
@@ -213,11 +214,13 @@
Dependency.get(ConfigurationController.class).addCallback(this);
mActivityStarter = Dependency.get(ActivityStarter.class);
+ KeyguardUpdateMonitor keyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(context);
UnlockMethodCache unlockMethodCache = UnlockMethodCache.getInstance(context);
unlockMethodCache.addListener(
() -> {
if (mDialog != null && mDialog.mPanelController != null) {
- boolean locked = !unlockMethodCache.canSkipBouncer();
+ boolean locked = !unlockMethodCache.canSkipBouncer()
+ && keyguardUpdateMonitor.isKeyguardVisible();
mDialog.mPanelController.onDeviceLockStateChanged(locked);
}
});
@@ -414,9 +417,7 @@
new GlobalActionsPanelPlugin.Callbacks() {
@Override
public void dismissGlobalActionsMenu() {
- if (mDialog != null) {
- mDialog.dismiss();
- }
+ dismissDialog();
}
@Override
@@ -912,6 +913,9 @@
/** {@inheritDoc} */
public void onDismiss(DialogInterface dialog) {
+ if (mDialog == dialog) {
+ mDialog = null;
+ }
mWindowManagerFuncs.onGlobalActionsHidden();
if (mShowSilentToggle) {
try {
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageRevealHelper.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageRevealHelper.java
index b154e66..6474b39 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageRevealHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageRevealHelper.java
@@ -19,6 +19,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
+import android.util.Log;
import com.android.systemui.Interpolators;
@@ -30,6 +31,7 @@
private static final String TAG = ImageRevealHelper.class.getSimpleName();
private static final float MAX_REVEAL = 0f;
private static final float MIN_REVEAL = 1f;
+ private static final boolean DEBUG = true;
private final ValueAnimator mAnimator;
private final RevealStateListener mRevealListener;
@@ -56,8 +58,13 @@
@Override
public void onAnimationEnd(Animator animation) {
- if (!mIsCanceled && mRevealListener != null) {
- mRevealListener.onRevealEnd();
+ if (mRevealListener != null) {
+ if (DEBUG) {
+ Log.d(TAG, "transition end, cancel=" + mIsCanceled + ", reveal=" + mReveal);
+ }
+ if (!mIsCanceled) {
+ mRevealListener.onRevealEnd();
+ }
}
mIsCanceled = false;
}
@@ -65,25 +72,25 @@
@Override
public void onAnimationStart(Animator animation) {
if (mRevealListener != null) {
+ if (DEBUG) {
+ Log.d(TAG, "transition start");
+ }
mRevealListener.onRevealStart(true /* animate */);
}
}
});
}
- private void animate() {
- mAnimator.cancel();
- mAnimator.setFloatValues(mReveal, mAwake ? MAX_REVEAL : MIN_REVEAL);
- mAnimator.start();
- }
-
public float getReveal() {
return mReveal;
}
void updateAwake(boolean awake, long duration) {
+ if (DEBUG) {
+ Log.d(TAG, "updateAwake: awake=" + awake + ", duration=" + duration);
+ }
+ mAnimator.cancel();
mAwake = awake;
- mAnimator.setDuration(duration);
if (duration == 0) {
// We are transiting from home to aod or aod to home directly,
// we don't need to do transition in these cases.
@@ -92,7 +99,9 @@
mRevealListener.onRevealStateChanged();
mRevealListener.onRevealEnd();
} else {
- animate();
+ mAnimator.setDuration(duration);
+ mAnimator.setFloatValues(mReveal, mAwake ? MAX_REVEAL : MIN_REVEAL);
+ mAnimator.start();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
index 2960634..be6f7bf 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
@@ -46,6 +46,7 @@
private static final String TAG = ImageWallpaperRenderer.class.getSimpleName();
private static final float SCALE_VIEWPORT_MIN = 1f;
private static final float SCALE_VIEWPORT_MAX = 1.1f;
+ private static final boolean DEBUG = true;
private final WallpaperManager mWallpaperManager;
private final ImageGLProgram mProgram;
@@ -107,6 +108,9 @@
}
private boolean loadBitmap() {
+ if (DEBUG) {
+ Log.d(TAG, "loadBitmap: mBitmap=" + mBitmap);
+ }
if (mWallpaperManager != null && mBitmap == null) {
mBitmap = mWallpaperManager.getBitmap();
mWallpaperManager.forgetLoadedWallpaper();
@@ -119,6 +123,9 @@
mSurfaceSize.set(0, 0, surfaceWidth, surfaceHeight);
}
}
+ if (DEBUG) {
+ Log.d(TAG, "loadBitmap done, surface size=" + mSurfaceSize);
+ }
return mBitmap != null;
}
@@ -193,16 +200,28 @@
@Override
public void onRevealStart(boolean animate) {
+ if (DEBUG) {
+ Log.v(TAG, "onRevealStart: start, anim=" + animate);
+ }
+
if (animate) {
mScissorMode = true;
// Use current display area of texture.
mWallpaper.adjustTextureCoordinates(mSurfaceSize, mScissor, mXOffset, mYOffset);
}
mProxy.preRender();
+
+ if (DEBUG) {
+ Log.v(TAG, "onRevealStart: done");
+ }
}
@Override
public void onRevealEnd() {
+ if (DEBUG) {
+ Log.v(TAG, "onRevealEnd: start, mScissorMode=" + mScissorMode);
+ }
+
if (mScissorMode) {
mScissorMode = false;
// reset texture coordinates to use full texture.
@@ -211,6 +230,10 @@
mProxy.requestRender();
}
mProxy.postRender();
+
+ if (DEBUG) {
+ Log.v(TAG, "onRevealEnd: done");
+ }
}
@Override
@@ -223,6 +246,7 @@
out.print(prefix); out.print("mXOffset="); out.print(mXOffset);
out.print(prefix); out.print("mYOffset="); out.print(mYOffset);
out.print(prefix); out.print("threshold="); out.print(mImageProcessHelper.getThreshold());
+ out.print(prefix); out.print("mReveal="); out.print(mImageRevealHelper.getReveal());
mWallpaper.dump(prefix, fd, out, args);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 48f32cf..e408745 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -103,8 +103,8 @@
protected final Uri mMediaUri;
private final Date mCurrentTime = new Date();
private final Handler mHandler;
+ private final Handler mMediaHandler;
private final AlarmManager.OnAlarmListener mUpdateNextAlarm = this::updateNextAlarm;
- private final Object mMediaToken = new Object();
private DozeParameters mDozeParameters;
@VisibleForTesting
protected SettableWakeLock mMediaWakeLock;
@@ -169,17 +169,13 @@
}
};
- public KeyguardSliceProvider() {
- this(new Handler());
- }
-
public static KeyguardSliceProvider getAttachedInstance() {
return KeyguardSliceProvider.sInstance;
}
- @VisibleForTesting
- KeyguardSliceProvider(Handler handler) {
- mHandler = handler;
+ public KeyguardSliceProvider() {
+ mHandler = new Handler();
+ mMediaHandler = new Handler();
mSliceUri = Uri.parse(KEYGUARD_SLICE_URI);
mHeaderUri = Uri.parse(KEYGUARD_HEADER_URI);
mDateUri = Uri.parse(KEYGUARD_DATE_URI);
@@ -462,16 +458,18 @@
public void onMetadataOrStateChanged(MediaMetadata metadata, @PlaybackState.State int state) {
synchronized (this) {
boolean nextVisible = NotificationMediaManager.isPlayingState(state);
- mHandler.removeCallbacksAndMessages(mMediaToken);
+ mMediaHandler.removeCallbacksAndMessages(null);
if (mMediaIsVisible && !nextVisible && mStatusBarState != StatusBarState.SHADE) {
// We need to delay this event for a few millis when stopping to avoid jank in the
// animation. The media app might not send its update when buffering, and the slice
// would end up without a header for 0.5 second.
mMediaWakeLock.setAcquired(true);
- mHandler.postDelayed(() -> {
- updateMediaStateLocked(metadata, state);
- mMediaWakeLock.setAcquired(false);
- }, mMediaToken, 2000);
+ mMediaHandler.postDelayed(() -> {
+ synchronized (this) {
+ updateMediaStateLocked(metadata, state);
+ mMediaWakeLock.setAcquired(false);
+ }
+ }, 2000);
} else {
mMediaWakeLock.setAcquired(false);
updateMediaStateLocked(metadata, state);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
index dd0ea5e..dc9a2ce 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
@@ -21,7 +21,7 @@
import android.content.res.Configuration;
import android.provider.Settings;
import android.service.quicksettings.Tile;
-import android.widget.Switch;
+import android.text.TextUtils;
import com.android.internal.logging.nano.MetricsProto;
import com.android.systemui.R;
@@ -79,24 +79,33 @@
return;
}
boolean newState = !mState.value;
- mUiModeManager.setNightMode(newState ? UiModeManager.MODE_NIGHT_YES
- : UiModeManager.MODE_NIGHT_NO);
+ mUiModeManager.setNightModeActivated(newState);
refreshState(newState);
}
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
+ int uiMode = mUiModeManager.getNightMode();
boolean powerSave = mBatteryController.isPowerSave();
+ boolean isAuto = uiMode == UiModeManager.MODE_NIGHT_AUTO;
boolean nightMode = (mContext.getResources().getConfiguration().uiMode
- & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
+ & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
+ if (isAuto) {
+ state.secondaryLabel = mContext.getResources().getString(nightMode
+ ? R.string.quick_settings_dark_mode_secondary_label_until_sunrise
+ : R.string.quick_settings_dark_mode_secondary_label_on_at_sunset);
+ } else {
+ state.secondaryLabel = null;
+ }
state.value = nightMode;
state.label = mContext.getString(powerSave
? R.string.quick_settings_ui_mode_night_label_battery_saver
: R.string.quick_settings_ui_mode_night_label);
- state.contentDescription = state.label;
state.icon = mIcon;
- state.expandedAccessibilityClassName = Switch.class.getName();
+ state.contentDescription = TextUtils.isEmpty(state.secondaryLabel)
+ ? state.label
+ : TextUtils.concat(state.label, ", ", state.secondaryLabel);
if (powerSave) {
state.state = Tile.STATE_UNAVAILABLE;
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 11ca94f..9b30a77 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -17,6 +17,7 @@
package com.android.systemui.screenshot;
import static android.content.Context.NOTIFICATION_SERVICE;
+import static android.os.AsyncTask.THREAD_POOL_EXECUTOR;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ACTION_INTENT;
@@ -29,7 +30,9 @@
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.app.ActivityManager;
import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
import android.app.Notification;
import android.app.Notification.BigPictureStyle;
import android.app.NotificationManager;
@@ -42,6 +45,7 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -58,10 +62,16 @@
import android.media.MediaActionSound;
import android.net.Uri;
import android.os.AsyncTask;
+import android.os.Bundle;
import android.os.Environment;
+import android.os.Handler;
import android.os.PowerManager;
import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.DeviceConfig;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.util.DisplayMetrics;
@@ -77,10 +87,13 @@
import android.widget.ImageView;
import android.widget.Toast;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.systemui.R;
import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.SystemUI;
+import com.android.systemui.SystemUIFactory;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.util.NotificationChannels;
@@ -91,11 +104,17 @@
import java.io.OutputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.Date;
+import java.util.List;
+import java.util.Random;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
-
+import java.util.function.Consumer;
/**
@@ -105,7 +124,7 @@
Context context;
Bitmap image;
Uri imageUri;
- Runnable finisher;
+ Consumer<Uri> finisher;
int iconSize;
int previewWidth;
int previewheight;
@@ -128,6 +147,7 @@
private static final String TAG = "SaveImageInBackgroundTask";
private static final String SCREENSHOT_FILE_NAME_TEMPLATE = "Screenshot_%s.png";
+ private static final String SCREENSHOT_ID_TEMPLATE = "Screenshot_%s";
private static final String SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)";
private final SaveImageInBackgroundData mParams;
@@ -138,6 +158,10 @@
private final BigPictureStyle mNotificationStyle;
private final int mImageWidth;
private final int mImageHeight;
+ private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider;
+ private final String mScreenshotId;
+ private final boolean mSmartActionsEnabled;
+ private final Random mRandom = new Random();
SaveImageInBackgroundTask(Context context, SaveImageInBackgroundData data,
NotificationManager nManager) {
@@ -148,6 +172,20 @@
mImageTime = System.currentTimeMillis();
String imageDate = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date(mImageTime));
mImageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, imageDate);
+ mScreenshotId = String.format(SCREENSHOT_ID_TEMPLATE, UUID.randomUUID());
+
+ // Initialize screenshot notification smart actions provider.
+ mSmartActionsEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS, false);
+ if (mSmartActionsEnabled) {
+ mSmartActionsProvider =
+ SystemUIFactory.getInstance()
+ .createScreenshotNotificationSmartActionsProvider(
+ context, THREAD_POOL_EXECUTOR, new Handler());
+ } else {
+ // If smart actions is not enabled use empty implementation.
+ mSmartActionsProvider = new ScreenshotNotificationSmartActionsProvider();
+ }
// Create the large notification icon
mImageWidth = data.image.getWidth();
@@ -222,6 +260,55 @@
mNotificationStyle.bigLargeIcon((Bitmap) null);
}
+ private List<Notification.Action> buildSmartActions(
+ List<Notification.Action> actions, Context context) {
+ List<Notification.Action> broadcastActions = new ArrayList<>();
+ for (Notification.Action action : actions) {
+ // Proxy smart actions through {@link GlobalScreenshot.SmartActionsReceiver}
+ // for logging smart actions.
+ Bundle extras = action.getExtras();
+ String actionType = extras.getString(
+ ScreenshotNotificationSmartActionsProvider.ACTION_TYPE,
+ ScreenshotNotificationSmartActionsProvider.DEFAULT_ACTION_TYPE);
+ Intent intent = new Intent(context,
+ GlobalScreenshot.SmartActionsReceiver.class).putExtra(
+ GlobalScreenshot.EXTRA_ACTION_INTENT, action.actionIntent);
+ addIntentExtras(mScreenshotId, intent, actionType, mSmartActionsEnabled);
+ PendingIntent broadcastIntent = PendingIntent.getBroadcast(context,
+ mRandom.nextInt(),
+ intent,
+ PendingIntent.FLAG_CANCEL_CURRENT);
+ broadcastActions.add(new Notification.Action.Builder(action.getIcon(), action.title,
+ broadcastIntent).setContextual(true).addExtras(extras).build());
+ }
+ return broadcastActions;
+ }
+
+ private static void addIntentExtras(String screenshotId, Intent intent, String actionType,
+ boolean smartActionsEnabled) {
+ intent
+ .putExtra(GlobalScreenshot.EXTRA_ACTION_TYPE, actionType)
+ .putExtra(GlobalScreenshot.EXTRA_ID, screenshotId)
+ .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED, smartActionsEnabled);
+ }
+
+ private int getUserHandleOfForegroundApplication(Context context) {
+ // This logic matches
+ // com.android.systemui.statusbar.phone.PhoneStatusBarPolicy#updateManagedProfile
+ try {
+ return ActivityTaskManager.getService().getLastResumedActivityUserId();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "getUserHandleOfForegroundApplication: ", e);
+ return context.getUserId();
+ }
+ }
+
+ private boolean isManagedProfile(Context context) {
+ UserManager manager = UserManager.get(context);
+ UserInfo info = manager.getUserInfo(getUserHandleOfForegroundApplication(context));
+ return info.isManagedProfile();
+ }
+
/**
* Generates a new hardware bitmap with specified values, copying the content from the passed
* in bitmap.
@@ -251,6 +338,10 @@
Resources r = context.getResources();
try {
+ CompletableFuture<List<Notification.Action>> smartActionsFuture =
+ GlobalScreenshot.getSmartActionsFuture(mScreenshotId, image,
+ mSmartActionsProvider, mSmartActionsEnabled, isManagedProfile(context));
+
// Save the screenshot to the MediaStore
final MediaStore.PendingParams params = new MediaStore.PendingParams(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mImageFileName, "image/png");
@@ -273,77 +364,7 @@
IoUtils.closeQuietly(session);
}
- // Note: Both the share and edit actions are proxied through ActionProxyReceiver in
- // order to do some common work like dismissing the keyguard and sending
- // closeSystemWindows
-
- // Create a share intent, this will always go through the chooser activity first which
- // should not trigger auto-enter PiP
- String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime));
- String subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
- Intent sharingIntent = new Intent(Intent.ACTION_SEND);
- sharingIntent.setType("image/png");
- sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
- // Include URI in ClipData also, so that grantPermission picks it up.
- // We don't use setData here because some apps interpret this as "to:".
- ClipData clipdata = new ClipData(new ClipDescription("content",
- new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}),
- new ClipData.Item(uri));
- sharingIntent.setClipData(clipdata);
- sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
- sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-
- PendingIntent chooserAction = PendingIntent.getBroadcast(context, 0,
- new Intent(context, GlobalScreenshot.TargetChosenReceiver.class),
- PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
- Intent sharingChooserIntent = Intent.createChooser(sharingIntent, null,
- chooserAction.getIntentSender())
- .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK)
- .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-
- // Create a share action for the notification
- PendingIntent shareAction = PendingIntent.getBroadcastAsUser(context, 0,
- new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
- .putExtra(EXTRA_ACTION_INTENT, sharingChooserIntent)
- .putExtra(EXTRA_DISALLOW_ENTER_PIP, true),
- PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
- Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder(
- R.drawable.ic_screenshot_share,
- r.getString(com.android.internal.R.string.share), shareAction);
- mNotificationBuilder.addAction(shareActionBuilder.build());
-
- // Create an edit intent, if a specific package is provided as the editor, then launch
- // that directly
- String editorPackage = context.getString(R.string.config_screenshotEditor);
- Intent editIntent = new Intent(Intent.ACTION_EDIT);
- if (!TextUtils.isEmpty(editorPackage)) {
- editIntent.setComponent(ComponentName.unflattenFromString(editorPackage));
- }
- editIntent.setType("image/png");
- editIntent.setData(uri);
- editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- editIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-
- // Create a edit action
- PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, 1,
- new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
- .putExtra(EXTRA_ACTION_INTENT, editIntent)
- .putExtra(EXTRA_CANCEL_NOTIFICATION, editIntent.getComponent() != null),
- PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
- Notification.Action.Builder editActionBuilder = new Notification.Action.Builder(
- R.drawable.ic_screenshot_edit,
- r.getString(com.android.internal.R.string.screenshot_edit), editAction);
- mNotificationBuilder.addAction(editActionBuilder.build());
-
- // Create a delete action for the notification
- PendingIntent deleteAction = PendingIntent.getBroadcast(context, 0,
- new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class)
- .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString()),
- PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
- Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder(
- R.drawable.ic_screenshot_delete,
- r.getString(com.android.internal.R.string.delete), deleteAction);
- mNotificationBuilder.addAction(deleteActionBuilder.build());
+ populateNotificationActions(context, r, uri, smartActionsFuture, mNotificationBuilder);
mParams.imageUri = uri;
mParams.image = null;
@@ -364,6 +385,105 @@
return null;
}
+ @VisibleForTesting
+ void populateNotificationActions(Context context, Resources r, Uri uri,
+ CompletableFuture<List<Notification.Action>> smartActionsFuture,
+ Notification.Builder notificationBuilder) {
+ // Note: Both the share and edit actions are proxied through ActionProxyReceiver in
+ // order to do some common work like dismissing the keyguard and sending
+ // closeSystemWindows
+
+ // Create a share intent, this will always go through the chooser activity first which
+ // should not trigger auto-enter PiP
+ String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime));
+ String subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
+ Intent sharingIntent = new Intent(Intent.ACTION_SEND);
+ sharingIntent.setType("image/png");
+ sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
+ // Include URI in ClipData also, so that grantPermission picks it up.
+ // We don't use setData here because some apps interpret this as "to:".
+ ClipData clipdata = new ClipData(new ClipDescription("content",
+ new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}),
+ new ClipData.Item(uri));
+ sharingIntent.setClipData(clipdata);
+ sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
+ sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+ PendingIntent chooserAction = PendingIntent.getBroadcast(context, 0,
+ new Intent(context, GlobalScreenshot.TargetChosenReceiver.class),
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
+ Intent sharingChooserIntent = Intent.createChooser(sharingIntent, null,
+ chooserAction.getIntentSender())
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK)
+ .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+ // Create a share action for the notification
+ PendingIntent shareAction = PendingIntent.getBroadcastAsUser(context, 0,
+ new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
+ .putExtra(EXTRA_ACTION_INTENT, sharingChooserIntent)
+ .putExtra(EXTRA_DISALLOW_ENTER_PIP, true)
+ .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
+ .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
+ mSmartActionsEnabled),
+ PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
+ Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder(
+ R.drawable.ic_screenshot_share,
+ r.getString(com.android.internal.R.string.share), shareAction);
+ notificationBuilder.addAction(shareActionBuilder.build());
+
+ // Create an edit intent, if a specific package is provided as the editor, then launch
+ // that directly
+ String editorPackage = context.getString(R.string.config_screenshotEditor);
+ Intent editIntent = new Intent(Intent.ACTION_EDIT);
+ if (!TextUtils.isEmpty(editorPackage)) {
+ editIntent.setComponent(ComponentName.unflattenFromString(editorPackage));
+ }
+ editIntent.setType("image/png");
+ editIntent.setData(uri);
+ editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ editIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
+ // Create a edit action
+ PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, 1,
+ new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
+ .putExtra(EXTRA_ACTION_INTENT, editIntent)
+ .putExtra(EXTRA_CANCEL_NOTIFICATION, editIntent.getComponent() != null)
+ .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
+ .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
+ mSmartActionsEnabled),
+ PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
+ Notification.Action.Builder editActionBuilder = new Notification.Action.Builder(
+ R.drawable.ic_screenshot_edit,
+ r.getString(com.android.internal.R.string.screenshot_edit), editAction);
+ notificationBuilder.addAction(editActionBuilder.build());
+
+ // Create a delete action for the notification
+ PendingIntent deleteAction = PendingIntent.getBroadcast(context, 0,
+ new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class)
+ .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString())
+ .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
+ .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
+ mSmartActionsEnabled),
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
+ Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder(
+ R.drawable.ic_screenshot_delete,
+ r.getString(com.android.internal.R.string.delete), deleteAction);
+ notificationBuilder.addAction(deleteActionBuilder.build());
+
+ if (mSmartActionsEnabled) {
+ int timeoutMs = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags
+ .SCREENSHOT_NOTIFICATION_SMART_ACTIONS_TIMEOUT_MS,
+ 1000);
+ List<Notification.Action> smartActions = GlobalScreenshot.getSmartActions(mScreenshotId,
+ smartActionsFuture, timeoutMs, mSmartActionsProvider);
+ smartActions = buildSmartActions(smartActions, context);
+ for (Notification.Action action : smartActions) {
+ notificationBuilder.addAction(action);
+ }
+ }
+ }
+
@Override
protected void onPostExecute(Void params) {
if (mParams.errorMsgResId != 0) {
@@ -406,7 +526,7 @@
mNotificationManager.notify(SystemMessage.NOTE_GLOBAL_SCREENSHOT,
mNotificationBuilder.build());
}
- mParams.finisher.run();
+ mParams.finisher.accept(mParams.imageUri);
mParams.clearContext();
}
@@ -415,7 +535,7 @@
// If we are cancelled while the task is running in the background, we may get null params.
// The finisher is expected to always be called back, so just use the baked-in params from
// the ctor in any case.
- mParams.finisher.run();
+ mParams.finisher.accept(null);
mParams.clearImage();
mParams.clearContext();
@@ -446,6 +566,15 @@
}
class GlobalScreenshot {
+ // These strings are used for communicating the action invoked to
+ // ScreenshotNotificationSmartActionsProvider.
+ static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type";
+ static final String EXTRA_ID = "android:screenshot_id";
+ static final String ACTION_TYPE_DELETE = "Delete";
+ static final String ACTION_TYPE_SHARE = "Share";
+ static final String ACTION_TYPE_EDIT = "Edit";
+ static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled";
+
static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id";
static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent";
static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification";
@@ -566,7 +695,7 @@
/**
* Creates a new worker thread and saves the screenshot to the media store.
*/
- private void saveScreenshotInWorkerThread(Runnable finisher) {
+ private void saveScreenshotInWorkerThread(Consumer<Uri> finisher) {
SaveImageInBackgroundData data = new SaveImageInBackgroundData();
data.context = mContext;
data.image = mScreenBitmap;
@@ -584,8 +713,8 @@
/**
* Takes a screenshot of the current display and shows an animation.
*/
- private void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible,
- Rect crop) {
+ private void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible,
+ boolean navBarVisible, Rect crop) {
int rot = mDisplay.getRotation();
int width = crop.width();
int height = crop.height();
@@ -595,7 +724,7 @@
if (mScreenBitmap == null) {
notifyScreenshotError(mContext, mNotificationManager,
R.string.screenshot_failed_to_capture_text);
- finisher.run();
+ finisher.accept(null);
return;
}
@@ -608,7 +737,7 @@
statusBarVisible, navBarVisible);
}
- void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible) {
+ void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible, boolean navBarVisible) {
mDisplay.getRealMetrics(mDisplayMetrics);
takeScreenshot(finisher, statusBarVisible, navBarVisible,
new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
@@ -617,7 +746,7 @@
/**
* Displays a screenshot selector
*/
- void takeScreenshotPartial(final Runnable finisher, final boolean statusBarVisible,
+ void takeScreenshotPartial(final Consumer<Uri> finisher, final boolean statusBarVisible,
final boolean navBarVisible) {
mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
mScreenshotSelectorView.setOnTouchListener(new View.OnTouchListener() {
@@ -677,8 +806,8 @@
/**
* Starts the animation after taking the screenshot
*/
- private void startAnimation(final Runnable finisher, int w, int h, boolean statusBarVisible,
- boolean navBarVisible) {
+ private void startAnimation(final Consumer<Uri> finisher, int w, int h,
+ boolean statusBarVisible, boolean navBarVisible) {
// If power save is on, show a toast so there is some visual indication that a screenshot
// has been taken.
PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
@@ -904,6 +1033,100 @@
nManager.notify(SystemMessage.NOTE_GLOBAL_SCREENSHOT, n);
}
+ @VisibleForTesting
+ static CompletableFuture<List<Notification.Action>> getSmartActionsFuture(String screenshotId,
+ Bitmap image, ScreenshotNotificationSmartActionsProvider smartActionsProvider,
+ boolean smartActionsEnabled, boolean isManagedProfile) {
+ if (!smartActionsEnabled) {
+ Slog.i(TAG, "Screenshot Intelligence not enabled, returning empty list.");
+ return CompletableFuture.completedFuture(Collections.emptyList());
+ }
+ if (image.getConfig() != Bitmap.Config.HARDWARE) {
+ Slog.w(TAG, String.format(
+ "Bitmap expected: Hardware, Bitmap found: %s. Returning empty list.",
+ image.getConfig()));
+ return CompletableFuture.completedFuture(Collections.emptyList());
+ }
+
+ Slog.d(TAG, "Screenshot from a managed profile: " + isManagedProfile);
+ CompletableFuture<List<Notification.Action>> smartActionsFuture;
+ long startTimeMs = SystemClock.uptimeMillis();
+ try {
+ ActivityManager.RunningTaskInfo runningTask =
+ ActivityManagerWrapper.getInstance().getRunningTask();
+ ComponentName componentName =
+ (runningTask != null && runningTask.topActivity != null)
+ ? runningTask.topActivity
+ : new ComponentName("", "");
+ smartActionsFuture = smartActionsProvider.getActions(screenshotId, image,
+ componentName,
+ isManagedProfile);
+ } catch (Throwable e) {
+ long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs;
+ smartActionsFuture = CompletableFuture.completedFuture(Collections.emptyList());
+ Slog.e(TAG, "Failed to get future for screenshot notification smart actions.", e);
+ notifyScreenshotOp(screenshotId, smartActionsProvider,
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOp.REQUEST_SMART_ACTIONS,
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.ERROR,
+ waitTimeMs);
+ }
+ return smartActionsFuture;
+ }
+
+ @VisibleForTesting
+ static List<Notification.Action> getSmartActions(String screenshotId,
+ CompletableFuture<List<Notification.Action>> smartActionsFuture, int timeoutMs,
+ ScreenshotNotificationSmartActionsProvider smartActionsProvider) {
+ long startTimeMs = SystemClock.uptimeMillis();
+ try {
+ List<Notification.Action> actions = smartActionsFuture.get(timeoutMs,
+ TimeUnit.MILLISECONDS);
+ long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs;
+ Slog.d(TAG, String.format("Got %d smart actions. Wait time: %d ms",
+ actions.size(), waitTimeMs));
+ notifyScreenshotOp(screenshotId, smartActionsProvider,
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOp.WAIT_FOR_SMART_ACTIONS,
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.SUCCESS,
+ waitTimeMs);
+ return actions;
+ } catch (Throwable e) {
+ long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs;
+ Slog.e(TAG, String.format("Error getting smart actions. Wait time: %d ms", waitTimeMs),
+ e);
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus status =
+ (e instanceof TimeoutException)
+ ? ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.TIMEOUT
+ : ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.ERROR;
+ notifyScreenshotOp(screenshotId, smartActionsProvider,
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOp.WAIT_FOR_SMART_ACTIONS,
+ status, waitTimeMs);
+ return Collections.emptyList();
+ }
+ }
+
+ static void notifyScreenshotOp(String screenshotId,
+ ScreenshotNotificationSmartActionsProvider smartActionsProvider,
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOp op,
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus status, long durationMs) {
+ try {
+ smartActionsProvider.notifyOp(screenshotId, op, status, durationMs);
+ } catch (Throwable e) {
+ Slog.e(TAG, "Error in notifyScreenshotOp: ", e);
+ }
+ }
+
+ static void notifyScreenshotAction(Context context, String screenshotId, String action,
+ boolean isSmartAction) {
+ try {
+ ScreenshotNotificationSmartActionsProvider provider =
+ SystemUIFactory.getInstance().createScreenshotNotificationSmartActionsProvider(
+ context, THREAD_POOL_EXECUTOR, new Handler());
+ provider.notifyAction(screenshotId, action, isSmartAction);
+ } catch (Throwable e) {
+ Slog.e(TAG, "Error in notifyScreenshotAction: ", e);
+ }
+ }
+
/**
* Receiver to proxy the share or edit intent, used to clean up the notification and send
* appropriate signals to the system (ie. to dismiss the keyguard if necessary).
@@ -913,6 +1136,7 @@
@Override
public void onReceive(Context context, final Intent intent) {
+ Intent actionIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT);
Runnable startActivityRunnable = () -> {
try {
ActivityManagerWrapper.getInstance().closeSystemWindows(
@@ -923,7 +1147,6 @@
return;
}
- Intent actionIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT);
if (intent.getBooleanExtra(EXTRA_CANCEL_NOTIFICATION, false)) {
cancelScreenshotNotification(context);
}
@@ -935,6 +1158,14 @@
StatusBar statusBar = SysUiServiceProvider.getComponent(context, StatusBar.class);
statusBar.executeRunnableDismissingKeyguard(startActivityRunnable, null,
true /* dismissShade */, true /* afterKeyguardGone */, true /* deferred */);
+
+ if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) {
+ String actionType = Intent.ACTION_EDIT.equals(actionIntent.getAction())
+ ? ACTION_TYPE_EDIT
+ : ACTION_TYPE_SHARE;
+ notifyScreenshotAction(context, intent.getStringExtra(EXTRA_ID),
+ actionType, false);
+ }
}
}
@@ -965,6 +1196,31 @@
// And delete the image from the media store
final Uri uri = Uri.parse(intent.getStringExtra(SCREENSHOT_URI_ID));
new DeleteImageInBackgroundTask(context).execute(uri);
+ if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) {
+ notifyScreenshotAction(context, intent.getStringExtra(EXTRA_ID),
+ ACTION_TYPE_DELETE,
+ false);
+ }
+ }
+ }
+
+ /**
+ * Executes the smart action tapped by the user in the notification.
+ */
+ public static class SmartActionsReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ PendingIntent pendingIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT);
+ Intent actionIntent = pendingIntent.getIntent();
+ String actionType = intent.getStringExtra(EXTRA_ACTION_TYPE);
+ Slog.d(TAG, "Executing smart action [" + actionType + "]:" + actionIntent);
+ ActivityOptions opts = ActivityOptions.makeBasic();
+ context.startActivityAsUser(actionIntent, opts.toBundle(),
+ UserHandle.CURRENT);
+
+ notifyScreenshotAction(context, intent.getStringExtra(EXTRA_ID),
+ actionType,
+ true);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java
new file mode 100644
index 0000000..b6f5447
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+
+import android.app.Notification;
+import android.content.ComponentName;
+import android.graphics.Bitmap;
+import android.util.Log;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * This class can be overridden by a vendor-specific sys UI implementation,
+ * in order to provide smart actions in the screenshot notification.
+ */
+public class ScreenshotNotificationSmartActionsProvider {
+ /* Key provided in the notification action to get the type of smart action. */
+ public static final String ACTION_TYPE = "action_type";
+ public static final String DEFAULT_ACTION_TYPE = "Smart Action";
+
+ /* Define phases of screenshot execution. */
+ protected enum ScreenshotOp {
+ OP_UNKNOWN,
+ RETRIEVE_SMART_ACTIONS,
+ REQUEST_SMART_ACTIONS,
+ WAIT_FOR_SMART_ACTIONS
+ }
+
+ /* Enum to report success or failure for screenshot execution phases. */
+ protected enum ScreenshotOpStatus {
+ OP_STATUS_UNKNOWN,
+ SUCCESS,
+ ERROR,
+ TIMEOUT
+ }
+
+ private static final String TAG = "ScreenshotActions";
+
+ /**
+ * Default implementation that returns an empty list.
+ * This method is overridden in vendor-specific Sys UI implementation.
+ *
+ * @param screenshotId A generated random unique id for the screenshot.
+ * @param bitmap The bitmap of the screenshot. The bitmap config must be {@link
+ * HARDWARE}.
+ * @param componentName Contains package and activity class names where the screenshot was
+ * taken. This is used as an additional signal to generate and rank more
+ * relevant actions.
+ * @param isManagedProfile The screenshot was taken for a work profile app.
+ */
+ public CompletableFuture<List<Notification.Action>> getActions(
+ String screenshotId,
+ Bitmap bitmap,
+ ComponentName componentName,
+ boolean isManagedProfile) {
+ Log.d(TAG, "Returning empty smart action list.");
+ return CompletableFuture.completedFuture(Collections.emptyList());
+ }
+
+ /**
+ * Notify exceptions and latency encountered during generating smart actions.
+ * This method is overridden in vendor-specific Sys UI implementation.
+ *
+ * @param screenshotId Unique id of the screenshot.
+ * @param op screenshot execution phase defined in {@link ScreenshotOp}
+ * @param status {@link ScreenshotOpStatus} to report success or failure.
+ * @param durationMs latency experienced in different phases of screenshots.
+ */
+ public void notifyOp(String screenshotId, ScreenshotOp op, ScreenshotOpStatus status,
+ long durationMs) {
+ Log.d(TAG, "Return without notify.");
+ }
+
+ /**
+ * Notify screenshot notification action invoked.
+ * This method is overridden in vendor-specific Sys UI implementation.
+ *
+ * @param screenshotId Unique id of the screenshot.
+ * @param action type of notification action invoked.
+ * @param isSmartAction whether action invoked was a smart action.
+ */
+ public void notifyAction(String screenshotId, String action, boolean isSmartAction) {
+ Log.d(TAG, "Return without notify.");
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 34b8bfe..330a6b5 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -18,6 +18,7 @@
import android.app.Service;
import android.content.Intent;
+import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
@@ -27,6 +28,8 @@
import android.util.Log;
import android.view.WindowManager;
+import java.util.function.Consumer;
+
public class TakeScreenshotService extends Service {
private static final String TAG = "TakeScreenshotService";
@@ -36,10 +39,10 @@
@Override
public void handleMessage(Message msg) {
final Messenger callback = msg.replyTo;
- Runnable finisher = new Runnable() {
+ Consumer<Uri> finisher = new Consumer<Uri>() {
@Override
- public void run() {
- Message reply = Message.obtain(null, 1);
+ public void accept(Uri uri) {
+ Message reply = Message.obtain(null, 1, uri);
try {
callback.send(reply);
} catch (RemoteException e) {
@@ -52,7 +55,7 @@
// animation and error notification.
if (!getSystemService(UserManager.class).isUserUnlocked()) {
Log.w(TAG, "Skipping screenshot because storage is locked!");
- post(finisher);
+ post(() -> finisher.accept(null));
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 6f87b29..8ee7305 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -50,6 +50,7 @@
import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
+import com.android.systemui.dock.DockManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
@@ -96,6 +97,7 @@
private final IBatteryStats mBatteryInfo;
private final SettableWakeLock mWakeLock;
private final LockPatternUtils mLockPatternUtils;
+ private final DockManager mDockManager;
private final int mSlowThreshold;
private final int mFastThreshold;
@@ -104,10 +106,12 @@
private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
private String mRestingIndication;
+ private String mAlignmentIndication = "";
private CharSequence mTransientIndication;
private ColorStateList mTransientTextColorState;
private ColorStateList mInitialTextColorState;
private boolean mVisible;
+ private boolean mHideTransientMessageOnScreenOff;
private boolean mPowerPluggedIn;
private boolean mPowerPluggedInWired;
@@ -140,7 +144,8 @@
Dependency.get(AccessibilityController.class),
UnlockMethodCache.getInstance(context),
Dependency.get(StatusBarStateController.class),
- KeyguardUpdateMonitor.getInstance(context));
+ KeyguardUpdateMonitor.getInstance(context),
+ Dependency.get(DockManager.class));
}
/**
@@ -151,7 +156,8 @@
LockPatternUtils lockPatternUtils, WakeLock wakeLock, ShadeController shadeController,
AccessibilityController accessibilityController, UnlockMethodCache unlockMethodCache,
StatusBarStateController statusBarStateController,
- KeyguardUpdateMonitor keyguardUpdateMonitor) {
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ DockManager dockManager) {
mContext = context;
mLockIcon = lockIcon;
mShadeController = shadeController;
@@ -159,6 +165,8 @@
mUnlockMethodCache = unlockMethodCache;
mStatusBarStateController = statusBarStateController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mDockManager = dockManager;
+ mDockManager.addAlignmentStateListener(this::handleAlignStateChanged);
// lock icon is not used on all form factors.
if (mLockIcon != null) {
mLockIcon.setOnLongClickListener(this::handleLockLongClick);
@@ -212,6 +220,21 @@
mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
}
+ private void handleAlignStateChanged(int alignState) {
+ String alignmentIndication = "";
+ if (alignState == DockManager.ALIGN_STATE_POOR) {
+ alignmentIndication =
+ mContext.getResources().getString(R.string.dock_alignment_slow_charging);
+ } else if (alignState == DockManager.ALIGN_STATE_TERRIBLE) {
+ alignmentIndication =
+ mContext.getResources().getString(R.string.dock_alignment_not_charging);
+ }
+ if (!alignmentIndication.equals(mAlignmentIndication)) {
+ mAlignmentIndication = alignmentIndication;
+ updateIndication(false);
+ }
+ }
+
/**
* Gets the {@link KeyguardUpdateMonitorCallback} instance associated with this
* {@link KeyguardIndicationController}.
@@ -255,7 +278,7 @@
if (visible) {
// If this is called after an error message was already shown, we should not clear it.
// Otherwise the error message won't be shown
- if (!mHandler.hasMessages(MSG_HIDE_TRANSIENT)) {
+ if (!mHandler.hasMessages(MSG_HIDE_TRANSIENT)) {
hideTransientIndication();
}
updateIndication(false);
@@ -317,15 +340,17 @@
* Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}.
*/
public void showTransientIndication(CharSequence transientIndication) {
- showTransientIndication(transientIndication, mInitialTextColorState);
+ showTransientIndication(transientIndication, mInitialTextColorState,
+ false /* hideOnScreenOff */);
}
/**
* Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}.
*/
- public void showTransientIndication(CharSequence transientIndication,
- ColorStateList textColorState) {
+ private void showTransientIndication(CharSequence transientIndication,
+ ColorStateList textColorState, boolean hideOnScreenOff) {
mTransientIndication = transientIndication;
+ mHideTransientMessageOnScreenOff = hideOnScreenOff && transientIndication != null;
mTransientTextColorState = textColorState;
mHandler.removeMessages(MSG_HIDE_TRANSIENT);
mHandler.removeMessages(MSG_SWIPE_UP_TO_UNLOCK);
@@ -344,6 +369,7 @@
public void hideTransientIndication() {
if (mTransientIndication != null) {
mTransientIndication = null;
+ mHideTransientMessageOnScreenOff = false;
mHandler.removeMessages(MSG_HIDE_TRANSIENT);
updateIndication(false);
}
@@ -363,6 +389,9 @@
mTextView.setTextColor(Color.WHITE);
if (!TextUtils.isEmpty(mTransientIndication)) {
mTextView.switchIndication(mTransientIndication);
+ } else if (!TextUtils.isEmpty(mAlignmentIndication)) {
+ mTextView.switchIndication(mAlignmentIndication);
+ mTextView.setTextColor(Utils.getColorError(mContext));
} else if (mPowerPluggedIn) {
String indication = computePowerIndication();
if (animate) {
@@ -391,6 +420,9 @@
&& mKeyguardUpdateMonitor.getUserHasTrust(userId)) {
mTextView.switchIndication(trustGrantedIndication);
mTextView.setTextColor(mInitialTextColorState);
+ } else if (!TextUtils.isEmpty(mAlignmentIndication)) {
+ mTextView.switchIndication(mAlignmentIndication);
+ mTextView.setTextColor(Utils.getColorError(mContext));
} else if (mPowerPluggedIn) {
String indication = computePowerIndication();
if (DEBUG_CHARGING_SPEED) {
@@ -566,7 +598,8 @@
String message = mContext.getString(R.string.keyguard_retry);
mStatusBarKeyguardViewManager.showBouncerMessage(message, mInitialTextColorState);
} else if (mKeyguardUpdateMonitor.isScreenOn()) {
- showTransientIndication(mContext.getString(R.string.keyguard_unlock));
+ showTransientIndication(mContext.getString(R.string.keyguard_unlock),
+ mInitialTextColorState, true /* hideOnScreenOff */);
hideTransientIndicationDelayed(BaseKeyguardCallback.HIDE_DELAY_MS);
}
}
@@ -576,7 +609,11 @@
return;
}
mDozing = dozing;
- updateIndication(false);
+ if (mHideTransientMessageOnScreenOff && mDozing) {
+ hideTransientIndication();
+ } else {
+ updateIndication(false);
+ }
updateDisclosure();
}
@@ -646,8 +683,7 @@
@Override
public void onBiometricHelp(int msgId, String helpString,
BiometricSourceType biometricSourceType) {
- KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
- if (!updateMonitor.isUnlockingWithBiometricAllowed()) {
+ if (!mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed()) {
return;
}
boolean showSwipeToUnlock =
@@ -655,8 +691,8 @@
if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
mStatusBarKeyguardViewManager.showBouncerMessage(helpString,
mInitialTextColorState);
- } else if (updateMonitor.isScreenOn()) {
- showTransientIndication(helpString);
+ } else if (mKeyguardUpdateMonitor.isScreenOn()) {
+ showTransientIndication(helpString, mInitialTextColorState, showSwipeToUnlock);
if (!showSwipeToUnlock) {
hideTransientIndicationDelayed(TRANSIENT_BIOMETRIC_ERROR_TIMEOUT);
}
@@ -670,8 +706,7 @@
@Override
public void onBiometricError(int msgId, String errString,
BiometricSourceType biometricSourceType) {
- KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
- if (shouldSuppressBiometricError(msgId, biometricSourceType, updateMonitor)) {
+ if (shouldSuppressBiometricError(msgId, biometricSourceType, mKeyguardUpdateMonitor)) {
return;
}
animatePadlockError();
@@ -681,7 +716,7 @@
showSwipeUpToUnlock();
} else if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
mStatusBarKeyguardViewManager.showBouncerMessage(errString, mInitialTextColorState);
- } else if (updateMonitor.isScreenOn()) {
+ } else if (mKeyguardUpdateMonitor.isScreenOn()) {
showTransientIndication(errString);
// We want to keep this message around in case the screen was off
hideTransientIndicationDelayed(HIDE_DELAY_MS);
@@ -721,13 +756,15 @@
@Override
public void onTrustAgentErrorMessage(CharSequence message) {
- showTransientIndication(message, Utils.getColorError(mContext));
+ showTransientIndication(message, Utils.getColorError(mContext),
+ false /* hideOnScreenOff */);
}
@Override
public void onScreenTurnedOn() {
if (mMessageToShowOnScreenOn != null) {
- showTransientIndication(mMessageToShowOnScreenOn, Utils.getColorError(mContext));
+ showTransientIndication(mMessageToShowOnScreenOn, Utils.getColorError(mContext),
+ false /* hideOnScreenOff */);
// We want to keep this message around in case the screen was off
hideTransientIndicationDelayed(HIDE_DELAY_MS);
mMessageToShowOnScreenOn = null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java
index 48e2923..0f295ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java
@@ -27,18 +27,6 @@
boolean shouldExtendLifetime(@NonNull NotificationEntry entry);
/**
- * It's possible that a notification was canceled before it ever became visible. This callback
- * gives lifetime extenders a chance to make sure it shows up. For example if a foreground
- * service is canceled too quickly but we still want to make sure a FGS notification shows.
- * @param pendingEntry the canceled (but pending) entry
- * @return true if the notification lifetime should be extended
- */
- default boolean shouldExtendLifetimeForPendingNotification(
- @NonNull NotificationEntry pendingEntry) {
- return false;
- }
-
- /**
* Sets whether or not the lifetime should be managed by the extender. In practice, if
* shouldManage is true, this is where the extender starts managing the entry internally and is
* now responsible for calling {@link NotificationSafeToRemoveCallback#onSafeToRemove(String)}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index a37367e..f8fef7d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -281,25 +281,11 @@
}
final NotificationEntry entry = mNotificationData.get(key);
+
+ abortExistingInflation(key);
+
boolean lifetimeExtended = false;
- // Notification was canceled before it got inflated
- if (entry == null) {
- NotificationEntry pendingEntry = mPendingNotifications.get(key);
- if (pendingEntry != null) {
- for (NotificationLifetimeExtender extender : mNotificationLifetimeExtenders) {
- if (extender.shouldExtendLifetimeForPendingNotification(pendingEntry)) {
- extendLifetime(pendingEntry, extender);
- lifetimeExtended = true;
- }
- }
- }
- }
-
- if (!lifetimeExtended) {
- abortExistingInflation(key);
- }
-
if (entry != null) {
// If a manager needs to keep the notification around for whatever reason, we
// keep the notification
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
index 150667b..ef09434 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
@@ -39,6 +39,7 @@
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import javax.inject.Inject;
@@ -63,6 +64,7 @@
private final Context mContext;
private final PowerManager mPowerManager;
private final IDreamManager mDreamManager;
+ private final BatteryController mBatteryController;
private NotificationPresenter mPresenter;
private HeadsUpManager mHeadsUpManager;
@@ -75,13 +77,14 @@
@Inject
public NotificationInterruptionStateProvider(Context context, NotificationFilter filter,
- StatusBarStateController stateController) {
+ StatusBarStateController stateController, BatteryController batteryController) {
this(context,
(PowerManager) context.getSystemService(Context.POWER_SERVICE),
IDreamManager.Stub.asInterface(
ServiceManager.checkService(DreamService.DREAM_SERVICE)),
new AmbientDisplayConfiguration(context),
filter,
+ batteryController,
stateController);
}
@@ -92,10 +95,12 @@
IDreamManager dreamManager,
AmbientDisplayConfiguration ambientDisplayConfiguration,
NotificationFilter notificationFilter,
+ BatteryController batteryController,
StatusBarStateController statusBarStateController) {
mContext = context;
mPowerManager = powerManager;
mDreamManager = dreamManager;
+ mBatteryController = batteryController;
mAmbientDisplayConfiguration = ambientDisplayConfiguration;
mNotificationFilter = notificationFilter;
mStatusBarStateController = statusBarStateController;
@@ -293,6 +298,13 @@
return false;
}
+ if (mBatteryController.isAodPowerSave()) {
+ if (DEBUG_HEADS_UP) {
+ Log.d(TAG, "No pulsing: disabled by battery saver: " + sbn.getKey());
+ }
+ return false;
+ }
+
if (!canAlertCommon(entry)) {
if (DEBUG_HEADS_UP) {
Log.d(TAG, "No pulsing: notification shouldn't alert: " + sbn.getKey());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java
index 0009292..299511c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java
@@ -57,7 +57,6 @@
private final ArrayMap<String, NotificationEntry> mEntries = new ArrayMap<>();
private final ArrayList<NotificationEntry> mSortedAndFiltered = new ArrayList<>();
- private final ArrayList<NotificationEntry> mFilteredForUser = new ArrayList<>();
private final NotificationGroupManager mGroupManager =
Dependency.get(NotificationGroupManager.class);
@@ -166,20 +165,20 @@
}
public ArrayList<NotificationEntry> getNotificationsForCurrentUser() {
- mFilteredForUser.clear();
-
synchronized (mEntries) {
final int len = mEntries.size();
+ ArrayList<NotificationEntry> filteredForUser = new ArrayList<>(len);
+
for (int i = 0; i < len; i++) {
NotificationEntry entry = mEntries.valueAt(i);
final StatusBarNotification sbn = entry.notification;
if (!getEnvironment().isNotificationForCurrentProfiles(sbn)) {
continue;
}
- mFilteredForUser.add(entry);
+ filteredForUser.add(entry);
}
+ return filteredForUser;
}
- return mFilteredForUser;
}
public NotificationEntry get(String key) {
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 027e8e4..121508b 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
@@ -155,6 +155,12 @@
public boolean canBubble;
/**
+ * Whether this notification has changed in visual appearance since the previous post.
+ * New notifications are interruptive by default.
+ */
+ public boolean isVisuallyInterruptive;
+
+ /**
* Whether this notification is shown to the user as a high priority notification: visible on
* the lock screen/status bar and in the top section in the shade.
*/
@@ -196,6 +202,7 @@
suppressedVisualEffects = ranking.getSuppressedVisualEffects();
suspended = ranking.isSuspended();
canBubble = ranking.canBubble();
+ isVisuallyInterruptive = ranking.visuallyInterruptive();
}
public void setInterruption() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
index 4700baa..18d436f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
@@ -421,7 +421,7 @@
}
/** Listener for animations executed in {@link #animateClose(int, int, boolean)}. */
- private static class AnimateCloseListener extends AnimatorListenerAdapter {
+ private class AnimateCloseListener extends AnimatorListenerAdapter {
final View mView;
private final GutsContent mGutsContent;
@@ -433,8 +433,10 @@
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
- mView.setVisibility(View.GONE);
- mGutsContent.onFinishedClosing();
+ if (!isExposed()) {
+ mView.setVisibility(View.GONE);
+ mGutsContent.onFinishedClosing();
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 8f7671a..719ec32 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -100,6 +100,7 @@
protected String mKeyToRemoveOnGutsClosed;
private StatusBar mStatusBar;
+ private Runnable mOpenRunnable;
@Inject
public NotificationGutsManager(
@@ -343,6 +344,7 @@
public void closeAndSaveGuts(boolean removeLeavebehinds, boolean force, boolean removeControls,
int x, int y, boolean resetMenu) {
if (mNotificationGutsExposed != null) {
+ mNotificationGutsExposed.removeCallbacks(mOpenRunnable);
mNotificationGutsExposed.closeControls(removeLeavebehinds, removeControls, x, y, force);
}
if (resetMenu) {
@@ -445,7 +447,7 @@
// ensure that it's laid but not visible until actually laid out
guts.setVisibility(View.INVISIBLE);
// Post to ensure the the guts are properly laid out.
- guts.post(new Runnable() {
+ mOpenRunnable = new Runnable() {
@Override
public void run() {
if (row.getWindowToken() == null) {
@@ -470,7 +472,8 @@
mListContainer.onHeightChanged(row, true /* needsAnimation */);
mGutsMenuItem = menuItem;
}
- });
+ };
+ guts.post(mOpenRunnable);
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 179375e..4e91e4c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -240,7 +240,7 @@
* @return Alpha from 0 to 1.
*/
private float getClockAlpha(int y) {
- float alphaKeyguard = Math.max(0, y / Math.max(1f, getExpandedPreferredClockY()));
+ float alphaKeyguard = Math.max(0, y / Math.max(1f, getClockY(1f)));
alphaKeyguard = Interpolators.ACCELERATE.getInterpolation(alphaKeyguard);
return MathUtils.lerp(alphaKeyguard, 1f, mDarkAmount);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
index f4635d1..f7b8a2e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
@@ -67,6 +67,9 @@
}
private fun updateListeningState() {
+ if (pickupSensor == null) {
+ return
+ }
val onKeyguard = keyguardUpdateMonitor.isKeyguardVisible &&
!statusBarStateController.isDozing
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index d2023ec..dcb349b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -79,10 +79,13 @@
IWallpaperManager service = IWallpaperManager.Stub.asInterface(
ServiceManager.getService(Context.WALLPAPER_SERVICE));
- try {
- service.setLockWallpaperCallback(this);
- } catch (RemoteException e) {
- Log.e(TAG, "System dead?" + e);
+ if (service != null) {
+ // Service is disabled on some devices like Automotive
+ try {
+ service.setLockWallpaperCallback(this);
+ } catch (RemoteException e) {
+ Log.e(TAG, "System dead?" + e);
+ }
}
}
@@ -108,6 +111,11 @@
public LoaderResult loadBitmap(int currentUserId, UserHandle selectedUser) {
// May be called on any thread - only use thread safe operations.
+ if (!mWallpaperManager.isWallpaperSupported()) {
+ // When wallpaper is not supported, show the system wallpaper
+ return LoaderResult.success(null);
+ }
+
// Prefer the selected user (when specified) over the current user for the FLAG_SET_LOCK
// wallpaper.
final int lockWallpaperUserId =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 559df18..91cf89c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -229,7 +229,6 @@
import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.InjectionInflationController;
import com.android.systemui.volume.VolumeComponent;
@@ -344,7 +343,8 @@
private BrightnessMirrorController mBrightnessMirrorController;
private boolean mBrightnessMirrorVisible;
protected BiometricUnlockController mBiometricUnlockController;
- private LightBarController mLightBarController;
+ protected LightBarController mLightBarController;
+ @Nullable
protected LockscreenWallpaper mLockscreenWallpaper;
@VisibleForTesting
protected AutoHideController mAutoHideController;
@@ -410,17 +410,17 @@
private final int[] mAbsPos = new int[2];
private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
- private NotificationGutsManager mGutsManager;
+ protected NotificationGutsManager mGutsManager;
protected NotificationLogger mNotificationLogger;
protected NotificationEntryManager mEntryManager;
- private NotificationListController mNotificationListController;
- private NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
+ protected NotificationListController mNotificationListController;
+ protected NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
protected NotificationViewHierarchyManager mViewHierarchyManager;
protected ForegroundServiceController mForegroundServiceController;
protected AppOpsController mAppOpsController;
protected KeyguardViewMediator mKeyguardViewMediator;
- private ZenModeController mZenController;
- private final NotificationAlertingManager mNotificationAlertingManager =
+ protected ZenModeController mZenController;
+ protected final NotificationAlertingManager mNotificationAlertingManager =
Dependency.get(NotificationAlertingManager.class);
// for disabling the status bar
@@ -483,18 +483,20 @@
protected boolean mDozing;
private boolean mDozingRequested;
- private NotificationMediaManager mMediaManager;
+ protected NotificationMediaManager mMediaManager;
protected NotificationLockscreenUserManager mLockscreenUserManager;
protected NotificationRemoteInputManager mRemoteInputManager;
+ private boolean mWallpaperSupported;
private final BroadcastReceiver mWallpaperChangedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- WallpaperManager wallpaperManager = context.getSystemService(WallpaperManager.class);
- if (wallpaperManager == null) {
- Log.w(TAG, "WallpaperManager not available");
+ if (!mWallpaperSupported) {
+ // Receiver should not have been registered at all...
+ Log.wtf(TAG, "WallpaperManager not supported");
return;
}
+ WallpaperManager wallpaperManager = context.getSystemService(WallpaperManager.class);
WallpaperInfo info = wallpaperManager.getWallpaperInfo(UserHandle.USER_CURRENT);
final boolean deviceSupportsAodWallpaper = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_dozeSupportsAodWallpaper);
@@ -555,18 +557,19 @@
private KeyguardUserSwitcher mKeyguardUserSwitcher;
protected UserSwitcherController mUserSwitcherController;
- private NetworkController mNetworkController;
- private KeyguardMonitor mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
- private BatteryController mBatteryController;
+ protected NetworkController mNetworkController;
+ protected KeyguardMonitor mKeyguardMonitor;
+ protected BatteryController mBatteryController;
protected boolean mPanelExpanded;
private UiModeManager mUiModeManager;
protected boolean mIsKeyguard;
private LogMaker mStatusBarStateLog;
protected NotificationIconAreaController mNotificationIconAreaController;
@Nullable private View mAmbientIndicationContainer;
- private SysuiColorExtractor mColorExtractor;
- private ScreenLifecycle mScreenLifecycle;
- @VisibleForTesting WakefulnessLifecycle mWakefulnessLifecycle;
+ protected SysuiColorExtractor mColorExtractor;
+ protected ScreenLifecycle mScreenLifecycle;
+ @VisibleForTesting
+ protected WakefulnessLifecycle mWakefulnessLifecycle;
private final View.OnClickListener mGoToLockedShadeListener = v -> {
if (mState == StatusBarState.KEYGUARD) {
@@ -598,7 +601,7 @@
private HeadsUpAppearanceController mHeadsUpAppearanceController;
private boolean mVibrateOnOpening;
- private VibratorHelper mVibratorHelper;
+ protected VibratorHelper mVibratorHelper;
private ActivityLaunchAnimator mActivityLaunchAnimator;
protected StatusBarNotificationPresenter mPresenter;
private NotificationActivityStarter mNotificationActivityStarter;
@@ -628,40 +631,24 @@
@Override
public void start() {
- mGroupManager = Dependency.get(NotificationGroupManager.class);
- mGroupAlertTransferHelper = Dependency.get(NotificationGroupAlertTransferHelper.class);
- mVisualStabilityManager = Dependency.get(VisualStabilityManager.class);
- mNotificationLogger = Dependency.get(NotificationLogger.class);
- mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class);
- mNotificationListener = Dependency.get(NotificationListener.class);
+ getDependencies();
+ if (mScreenLifecycle != null && mScreenObserver != null) {
+ mScreenLifecycle.addObserver(mScreenObserver);
+ }
+
+ if (mWakefulnessLifecycle != null && mWakefulnessObserver != null) {
+ mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
+ }
+
mNotificationListener.registerAsSystemService();
- mNetworkController = Dependency.get(NetworkController.class);
- mUserSwitcherController = Dependency.get(UserSwitcherController.class);
- mScreenLifecycle = Dependency.get(ScreenLifecycle.class);
- mScreenLifecycle.addObserver(mScreenObserver);
- mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
- mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
- mBatteryController = Dependency.get(BatteryController.class);
- mAssistManager = Dependency.get(AssistManager.class);
+ if (mBubbleController != null) {
+ mBubbleController.setExpandListener(mBubbleExpandListener);
+ }
+
mUiModeManager = mContext.getSystemService(UiModeManager.class);
- mLockscreenUserManager = Dependency.get(NotificationLockscreenUserManager.class);
- mGutsManager = Dependency.get(NotificationGutsManager.class);
- mMediaManager = Dependency.get(NotificationMediaManager.class);
- mEntryManager = Dependency.get(NotificationEntryManager.class);
- mBypassHeadsUpNotifier.setUp(mEntryManager);
- mNotificationInterruptionStateProvider =
- Dependency.get(NotificationInterruptionStateProvider.class);
- mViewHierarchyManager = Dependency.get(NotificationViewHierarchyManager.class);
- mForegroundServiceController = Dependency.get(ForegroundServiceController.class);
- mAppOpsController = Dependency.get(AppOpsController.class);
- mZenController = Dependency.get(ZenModeController.class);
mKeyguardViewMediator = getComponent(KeyguardViewMediator.class);
- mColorExtractor = Dependency.get(SysuiColorExtractor.class);
- mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
- mNavigationBarController = Dependency.get(NavigationBarController.class);
- mBubbleController = Dependency.get(BubbleController.class);
- mBubbleController.setExpandListener(mBubbleExpandListener);
mActivityIntentHelper = new ActivityIntentHelper(mContext);
+
KeyguardSliceProvider sliceProvider = KeyguardSliceProvider.getAttachedInstance();
if (sliceProvider != null) {
sliceProvider.initDependencies(mMediaManager, mStatusBarStateController,
@@ -721,11 +708,18 @@
createAndAddWindows(result);
- // Make sure we always have the most current wallpaper info.
- IntentFilter wallpaperChangedFilter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED);
- mContext.registerReceiverAsUser(mWallpaperChangedReceiver, UserHandle.ALL,
- wallpaperChangedFilter, null /* broadcastPermission */, null /* scheduler */);
- mWallpaperChangedReceiver.onReceive(mContext, null);
+ mWallpaperSupported =
+ mContext.getSystemService(WallpaperManager.class).isWallpaperSupported();
+
+ if (mWallpaperSupported) {
+ // Make sure we always have the most current wallpaper info.
+ IntentFilter wallpaperChangedFilter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED);
+ mContext.registerReceiverAsUser(mWallpaperChangedReceiver, UserHandle.ALL,
+ wallpaperChangedFilter, null /* broadcastPermission */, null /* scheduler */);
+ mWallpaperChangedReceiver.onReceive(mContext, null);
+ } else if (DEBUG) {
+ Log.v(TAG, "start(): no wallpaper service ");
+ }
// Set up the initial notification state. This needs to happen before CommandQueue.disable()
setUpPresenter();
@@ -760,12 +754,14 @@
mContext.registerReceiver(mBannerActionBroadcastReceiver, internalFilter, PERMISSION_SELF,
null);
- IWallpaperManager wallpaperManager = IWallpaperManager.Stub.asInterface(
- ServiceManager.getService(Context.WALLPAPER_SERVICE));
- try {
- wallpaperManager.setInAmbientMode(false /* ambientMode */, 0L /* duration */);
- } catch (RemoteException e) {
- // Just pass, nothing critical.
+ if (mWallpaperSupported) {
+ IWallpaperManager wallpaperManager = IWallpaperManager.Stub.asInterface(
+ ServiceManager.getService(Context.WALLPAPER_SERVICE));
+ try {
+ wallpaperManager.setInAmbientMode(false /* ambientMode */, 0L /* duration */);
+ } catch (RemoteException e) {
+ // Just pass, nothing critical.
+ }
}
// end old BaseStatusBar.start().
@@ -896,7 +892,7 @@
createNavigationBar(result);
- if (ENABLE_LOCKSCREEN_WALLPAPER) {
+ if (ENABLE_LOCKSCREEN_WALLPAPER && mWallpaperSupported) {
mLockscreenWallpaper = new LockscreenWallpaper(mContext, this, mHandler);
}
@@ -910,20 +906,22 @@
R.id.ambient_indication_container);
// TODO: Find better place for this callback.
- mBatteryController.addCallback(new BatteryStateChangeCallback() {
- @Override
- public void onPowerSaveChanged(boolean isPowerSave) {
- mHandler.post(mCheckBarModes);
- if (mDozeServiceHost != null) {
- mDozeServiceHost.firePowerSaveChanged(isPowerSave);
+ if (mBatteryController != null) {
+ mBatteryController.addCallback(new BatteryStateChangeCallback() {
+ @Override
+ public void onPowerSaveChanged(boolean isPowerSave) {
+ mHandler.post(mCheckBarModes);
+ if (mDozeServiceHost != null) {
+ mDozeServiceHost.firePowerSaveChanged(isPowerSave);
+ }
}
- }
- @Override
- public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
- // noop
- }
- });
+ @Override
+ public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
+ // noop
+ }
+ });
+ }
mAutoHideController = Dependency.get(AutoHideController.class);
mAutoHideController.setStatusBar(this);
@@ -965,28 +963,7 @@
mStatusBarWindow::onShowingLaunchAffordanceChanged);
// Set up the quick settings tile panel
- View container = mStatusBarWindow.findViewById(R.id.qs_frame);
- if (container != null) {
- FragmentHostManager fragmentHostManager = FragmentHostManager.get(container);
- ExtensionFragmentListener.attachExtensonToFragment(container, QS.TAG, R.id.qs_frame,
- Dependency.get(ExtensionController.class)
- .newExtension(QS.class)
- .withPlugin(QS.class)
- .withDefault(this::createDefaultQSFragment)
- .build());
- mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow,
- (visible) -> {
- mBrightnessMirrorVisible = visible;
- updateScrimController();
- });
- fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> {
- QS qs = (QS) f;
- if (qs instanceof QSFragment) {
- mQSPanel = ((QSFragment) qs).getQsPanel();
- mQSPanel.setBrightnessMirror(mBrightnessMirrorController);
- }
- });
- }
+ setUpQuickSettingsTilePanel();
mReportRejectedTouch = mStatusBarWindow.findViewById(R.id.report_rejected_touch);
if (mReportRejectedTouch != null) {
@@ -1086,7 +1063,9 @@
mForegroundServiceController,
mDeviceProvisionedController);
- mAppOpsController.addCallback(APP_OPS, this);
+ if (mAppOpsController != null) {
+ mAppOpsController.addCallback(APP_OPS, this);
+ }
mNotificationShelf.setOnActivatedListener(mPresenter);
mRemoteInputManager.getController().addCallback(mStatusBarWindowController);
@@ -1116,6 +1095,73 @@
mNotificationListController.bind();
}
+ protected void getDependencies() {
+ // Icons
+ mIconController = Dependency.get(StatusBarIconController.class);
+ mLightBarController = Dependency.get(LightBarController.class);
+
+ // Keyguard
+ mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
+ mScreenLifecycle = Dependency.get(ScreenLifecycle.class);
+ mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
+
+ // Notifications
+ mEntryManager = Dependency.get(NotificationEntryManager.class);
+ mForegroundServiceController = Dependency.get(ForegroundServiceController.class);
+ mGroupAlertTransferHelper = Dependency.get(NotificationGroupAlertTransferHelper.class);
+ mGroupManager = Dependency.get(NotificationGroupManager.class);
+ mGutsManager = Dependency.get(NotificationGutsManager.class);
+ mLockscreenUserManager = Dependency.get(NotificationLockscreenUserManager.class);
+ mMediaManager = Dependency.get(NotificationMediaManager.class);
+ mNotificationInterruptionStateProvider =
+ Dependency.get(NotificationInterruptionStateProvider.class);
+ mNotificationListener = Dependency.get(NotificationListener.class);
+ mNotificationLogger = Dependency.get(NotificationLogger.class);
+ mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class);
+ mViewHierarchyManager = Dependency.get(NotificationViewHierarchyManager.class);
+ mVisualStabilityManager = Dependency.get(VisualStabilityManager.class);
+
+ // Policy
+ mBatteryController = Dependency.get(BatteryController.class);
+ mNetworkController = Dependency.get(NetworkController.class);
+ mZenController = Dependency.get(ZenModeController.class);
+
+ // Others
+ mAppOpsController = Dependency.get(AppOpsController.class);
+ mAssistManager = Dependency.get(AssistManager.class);
+ mBubbleController = Dependency.get(BubbleController.class);
+ mColorExtractor = Dependency.get(SysuiColorExtractor.class);
+ mNavigationBarController = Dependency.get(NavigationBarController.class);
+ mUserSwitcherController = Dependency.get(UserSwitcherController.class);
+ mVibratorHelper = Dependency.get(VibratorHelper.class);
+ }
+
+ protected void setUpQuickSettingsTilePanel() {
+ View container = mStatusBarWindow.findViewById(R.id.qs_frame);
+ if (container != null) {
+ FragmentHostManager fragmentHostManager = FragmentHostManager.get(container);
+ ExtensionFragmentListener.attachExtensonToFragment(container, QS.TAG, R.id.qs_frame,
+ Dependency.get(ExtensionController.class)
+ .newExtension(QS.class)
+ .withPlugin(QS.class)
+ .withDefault(this::createDefaultQSFragment)
+ .build());
+ mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow,
+ (visible) -> {
+ mBrightnessMirrorVisible = visible;
+ updateScrimController();
+ });
+ fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> {
+ QS qs = (QS) f;
+ if (qs instanceof QSFragment) {
+ mQSPanel = ((QSFragment) qs).getQsPanel();
+ mQSPanel.setBrightnessMirror(mBrightnessMirrorController);
+ }
+ });
+ }
+ }
+
+
/**
* Post-init task of {@link #start()}
* @param state1 disable1 flags
@@ -1936,7 +1982,9 @@
mStatusBarWindow.cancelExpandHelper();
mStatusBarView.collapsePanel(true /* animate */, delayed, speedUpFactor);
} else {
- mBubbleController.collapseStack();
+ if (mBubbleController != null) {
+ mBubbleController.collapseStack();
+ }
}
}
@@ -2318,6 +2366,7 @@
pw.println(Settings.Global.zenModeToString(Settings.Global.getInt(
mContext.getContentResolver(), Settings.Global.ZEN_MODE,
Settings.Global.ZEN_MODE_OFF)));
+ pw.print(" mWallpaperSupported= "); pw.println(mWallpaperSupported);
if (mStatusBarView != null) {
dumpBarTransitions(pw, "mStatusBarView", mStatusBarView.getBarTransitions());
@@ -2590,7 +2639,7 @@
if (mRemoteInputManager.getController() != null) {
mRemoteInputManager.getController().closeRemoteInputs();
}
- if (mBubbleController.isStackExpanded()) {
+ if (mBubbleController != null && mBubbleController.isStackExpanded()) {
mBubbleController.collapseStack();
}
if (mLockscreenUserManager.isCurrentProfile(getSendingUserId())) {
@@ -2606,7 +2655,7 @@
if (mStatusBarWindowController != null) {
mStatusBarWindowController.setNotTouchable(false);
}
- if (mBubbleController.isStackExpanded()) {
+ if (mBubbleController != null && mBubbleController.isStackExpanded()) {
mBubbleController.collapseStack();
}
finishBarAnimations();
@@ -2700,9 +2749,13 @@
@Override
public void setLockscreenUser(int newUserId) {
- mLockscreenWallpaper.setCurrentUser(newUserId);
+ if (mLockscreenWallpaper != null) {
+ mLockscreenWallpaper.setCurrentUser(newUserId);
+ }
mScrimController.setCurrentUser(newUserId);
- mWallpaperChangedReceiver.onReceive(mContext, null);
+ if (mWallpaperSupported) {
+ mWallpaperChangedReceiver.onReceive(mContext, null);
+ }
}
/**
@@ -2984,7 +3037,9 @@
mStatusBarStateController.setLeaveOpenOnKeyguardHide(false);
mPendingRemoteInputView = null;
updateIsKeyguard();
- mAssistManager.onLockscreenShown();
+ if (mAssistManager != null) {
+ mAssistManager.onLockscreenShown();
+ }
}
public boolean hideKeyguard() {
@@ -3030,7 +3085,7 @@
public void showKeyguardImpl() {
mIsKeyguard = true;
- if (mKeyguardMonitor.isLaunchTransitionFadingAway()) {
+ if (mKeyguardMonitor != null && mKeyguardMonitor.isLaunchTransitionFadingAway()) {
mNotificationPanel.animate().cancel();
onLaunchTransitionFadingEnded();
}
@@ -3334,7 +3389,9 @@
if (mNotificationPanel.canPanelBeCollapsed()) {
animateCollapsePanels();
} else {
- mBubbleController.performBackPressIfNeeded();
+ if (mBubbleController != null) {
+ mBubbleController.performBackPressIfNeeded();
+ }
}
return true;
}
@@ -3495,9 +3552,11 @@
}
private void updateKeyguardState() {
- mKeyguardMonitor.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(),
- mUnlockMethodCache.isMethodSecure(),
- mStatusBarKeyguardViewManager.isOccluded());
+ if (mKeyguardMonitor != null) {
+ mKeyguardMonitor.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(),
+ mUnlockMethodCache.isMethodSecure(),
+ mStatusBarKeyguardViewManager.isOccluded());
+ }
}
public void onActivationReset() {
@@ -3886,7 +3945,7 @@
// We don't want to end up in KEYGUARD state when we're unlocking with
// fingerprint from doze. We should cross fade directly from black.
boolean unlocking = mBiometricUnlockController.isWakeAndUnlock()
- || mKeyguardMonitor.isKeyguardFadingAway();
+ || (mKeyguardMonitor != null && mKeyguardMonitor.isKeyguardFadingAway());
// Do not animate the scrim expansion when triggered by the fingerprint sensor.
mScrimController.setExpansionAffectsAlpha(
@@ -3915,7 +3974,7 @@
mScrimController.transitionTo(ScrimState.AOD);
} else if (mIsKeyguard && !unlocking) {
mScrimController.transitionTo(ScrimState.KEYGUARD);
- } else if (mBubbleController.isStackExpanded()) {
+ } else if (mBubbleController != null && mBubbleController.isStackExpanded()) {
mScrimController.transitionTo(ScrimState.BUBBLE_EXPANDED);
} else {
mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
@@ -4217,8 +4276,8 @@
protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
protected KeyguardManager mKeyguardManager;
- private DeviceProvisionedController mDeviceProvisionedController
- = Dependency.get(DeviceProvisionedController.class);
+ protected DeviceProvisionedController mDeviceProvisionedController =
+ Dependency.get(DeviceProvisionedController.class);
protected NavigationBarController mNavigationBarController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 0c47d14..68ee8bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -62,6 +62,8 @@
import java.io.PrintWriter;
import java.util.ArrayList;
+import androidx.annotation.VisibleForTesting;
+
/**
* Manages creating, showing, hiding and resetting the keyguard within the status bar. Calls back
* via {@link ViewMediatorCallback} to poke the wake lock and report that the keyguard is done,
@@ -161,6 +163,7 @@
private boolean mLastLockVisible;
private OnDismissAction mAfterKeyguardGoneAction;
+ private Runnable mKeyguardGoneCancelAction;
private final ArrayList<Runnable> mAfterKeyguardGoneRunnables = new ArrayList<>();
// Dismiss action to be launched when we stop dozing or the keyguard is gone.
@@ -328,10 +331,20 @@
return false;
}
- private void hideBouncer(boolean destroyView) {
+ @VisibleForTesting
+ void hideBouncer(boolean destroyView) {
if (mBouncer == null) {
return;
}
+ if (mShowing) {
+ // If we were showing the bouncer and then aborting, we need to also clear out any
+ // potential actions unless we actually unlocked.
+ mAfterKeyguardGoneAction = null;
+ if (mKeyguardGoneCancelAction != null) {
+ mKeyguardGoneCancelAction.run();
+ mKeyguardGoneCancelAction = null;
+ }
+ }
mBouncer.hide(destroyView);
cancelPendingWakeupAction();
}
@@ -364,6 +377,7 @@
mBouncer.showWithDismissAction(r, cancelAction);
} else {
mAfterKeyguardGoneAction = r;
+ mKeyguardGoneCancelAction = cancelAction;
mBouncer.show(false /* resetSecuritySelection */);
}
}
@@ -671,6 +685,7 @@
mAfterKeyguardGoneAction.onDismiss();
mAfterKeyguardGoneAction = null;
}
+ mKeyguardGoneCancelAction = null;
for (int i = 0; i < mAfterKeyguardGoneRunnables.size(); i++) {
mAfterKeyguardGoneRunnables.get(i).run();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 06921980..a870590 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -224,7 +224,7 @@
mVisualStabilityManager.setUpWithPresenter(this);
mGutsManager.setUpWithPresenter(this,
notifListContainer, mCheckSaveListener, mOnSettingsClickListener);
- // ForegroundServiceNotificationListener adds its listener in its constructor
+ // ForegroundServiceControllerListener adds its listener in its constructor
// but we need to request it here in order for it to be instantiated.
// TODO: figure out how to do this correctly once Dependency.get() is gone.
Dependency.get(ForegroundServiceNotificationListener.class);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index 111cdd2..738d076 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -45,9 +45,7 @@
/**
* Returns {@code true} if AOD was disabled by power saving policies.
*/
- default boolean isAodPowerSave() {
- return isPowerSave();
- }
+ boolean isAodPowerSave();
/**
* A listener that will be notified whenever a change in battery level or power save mode has
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
index ed0b9d9..919ca12 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
@@ -22,6 +22,7 @@
import android.text.method.TransformationMethod;
import android.util.AttributeSet;
import android.util.Log;
+import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -238,13 +239,15 @@
public List<Button> inflateSmartActions(Context packageContext,
@NonNull SmartActions smartActions, SmartReplyController smartReplyController,
NotificationEntry entry, HeadsUpManager headsUpManager, boolean delayOnClickListener) {
+ Context themedPackageContext = new ContextThemeWrapper(packageContext, mContext.getTheme());
List<Button> buttons = new ArrayList<>();
int numSmartActions = smartActions.actions.size();
for (int n = 0; n < numSmartActions; n++) {
Notification.Action action = smartActions.actions.get(n);
if (action.actionIntent != null) {
buttons.add(inflateActionButton(
- this, getContext(), packageContext, n, smartActions, smartReplyController,
+ this, getContext(), themedPackageContext, n, smartActions,
+ smartReplyController,
entry, headsUpManager, delayOnClickListener));
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceLifetimeExtenderTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceLifetimeExtenderTest.java
deleted file mode 100644
index b1dabdd..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceLifetimeExtenderTest.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui;
-
-import static com.android.systemui.ForegroundServiceLifetimeExtender.MIN_FGS_TIME_MS;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.app.Notification;
-import android.service.notification.StatusBarNotification;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class ForegroundServiceLifetimeExtenderTest extends SysuiTestCase {
- private ForegroundServiceLifetimeExtender mExtender = new ForegroundServiceLifetimeExtender();
- private StatusBarNotification mSbn;
- private NotificationEntry mEntry;
- private Notification mNotif;
-
- @Before
- public void setup() {
- mNotif = new Notification.Builder(mContext, "")
- .setSmallIcon(R.drawable.ic_person)
- .setContentTitle("Title")
- .setContentText("Text")
- .build();
-
- mSbn = mock(StatusBarNotification.class);
- when(mSbn.getNotification()).thenReturn(mNotif);
-
- mEntry = new NotificationEntry(mSbn);
- }
-
- @Test
- public void testShouldExtendLifetime_should_foreground() {
- // Extend the lifetime of a FGS notification iff it has not been visible
- // for the minimum time
- mNotif.flags |= Notification.FLAG_FOREGROUND_SERVICE;
- when(mSbn.getPostTime()).thenReturn(System.currentTimeMillis());
- assertTrue(mExtender.shouldExtendLifetime(mEntry));
- }
-
- @Test
- public void testShouldExtendLifetime_shouldNot_foreground() {
- mNotif.flags |= Notification.FLAG_FOREGROUND_SERVICE;
- when(mSbn.getPostTime()).thenReturn(System.currentTimeMillis() - MIN_FGS_TIME_MS - 1);
- assertFalse(mExtender.shouldExtendLifetime(mEntry));
- }
-
- @Test
- public void testShouldExtendLifetime_shouldNot_notForeground() {
- mNotif.flags = 0;
- when(mSbn.getPostTime()).thenReturn(System.currentTimeMillis() - MIN_FGS_TIME_MS - 1);
- assertFalse(mExtender.shouldExtendLifetime(mEntry));
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java
index 20983fc..caca663 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java
@@ -67,7 +67,7 @@
@Mock private ScreenDecorations mMockScreenDecorations;
@Mock private AssistUtils mMockAssistUtils;
@Mock private Handler mMockHandler;
- @Mock private PhenotypeHelper mMockPhenotypeHelper;
+ @Mock private DeviceConfigHelper mMockDeviceConfigHelper;
@Mock private AssistHandleOffBehavior mMockOffBehavior;
@Mock private AssistHandleLikeHomeBehavior mMockLikeHomeBehavior;
@Mock private AssistHandleReminderExpBehavior mMockReminderExpBehavior;
@@ -97,7 +97,7 @@
mMockAssistUtils,
mMockHandler,
() -> mMockScreenDecorations,
- mMockPhenotypeHelper,
+ mMockDeviceConfigHelper,
behaviorMap,
mMockNavigationModeController,
mMockDumpController);
@@ -216,7 +216,7 @@
public void showAndGo_doesNothingIfRecentlyHidden() {
// Arrange
when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(COMPONENT_NAME);
- when(mMockPhenotypeHelper.getLong(
+ when(mMockDeviceConfigHelper.getLong(
eq(SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOWN_FREQUENCY_THRESHOLD_MS),
anyLong())).thenReturn(10000L);
mAssistHandleBehaviorController.showAndGo();
@@ -297,7 +297,7 @@
public void showAndGoDelayed_doesNothingIfRecentlyHidden() {
// Arrange
when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(COMPONENT_NAME);
- when(mMockPhenotypeHelper.getLong(
+ when(mMockDeviceConfigHelper.getLong(
eq(SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOWN_FREQUENCY_THRESHOLD_MS),
anyLong())).thenReturn(10000L);
mAssistHandleBehaviorController.showAndGo();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index ba434d4..5a4e6c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -74,6 +74,7 @@
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.StatusBarWindowController;
+import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -173,7 +174,8 @@
TestableNotificationInterruptionStateProvider interruptionStateProvider =
new TestableNotificationInterruptionStateProvider(mContext,
mock(NotificationFilter.class),
- mock(StatusBarStateController.class));
+ mock(StatusBarStateController.class),
+ mock(BatteryController.class));
interruptionStateProvider.setUpWithPresenter(
mock(NotificationPresenter.class),
mock(HeadsUpManager.class),
@@ -659,8 +661,9 @@
NotificationInterruptionStateProvider {
TestableNotificationInterruptionStateProvider(Context context,
- NotificationFilter filter, StatusBarStateController controller) {
- super(context, filter, controller);
+ NotificationFilter filter, StatusBarStateController controller,
+ BatteryController batteryController) {
+ super(context, filter, controller, batteryController);
mUseHeadsUp = true;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dock/DockManagerFake.java b/packages/SystemUI/tests/src/com/android/systemui/dock/DockManagerFake.java
index 839b5e4..fd48d34 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dock/DockManagerFake.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dock/DockManagerFake.java
@@ -21,6 +21,7 @@
*/
public class DockManagerFake implements DockManager {
DockEventListener mCallback;
+ AlignmentStateListener mAlignmentListener;
@Override
public void addListener(DockEventListener callback) {
@@ -33,6 +34,16 @@
}
@Override
+ public void addAlignmentStateListener(AlignmentStateListener listener) {
+ mAlignmentListener = listener;
+ }
+
+ @Override
+ public void removeAlignmentStateListener(AlignmentStateListener listener) {
+ mAlignmentListener = listener;
+ }
+
+ @Override
public boolean isDocked() {
return false;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
index 1e18e51..1eb75aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
@@ -46,6 +46,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.wakelock.WakeLockFake;
import org.junit.Before;
@@ -76,8 +77,8 @@
mConfigMock = mock(AmbientDisplayConfiguration.class);
mPartMock = mock(DozeMachine.Part.class);
- mMachine = new DozeMachine(mServiceFake, mConfigMock, mWakeLockFake, mWakefulnessLifecycle);
-
+ mMachine = new DozeMachine(mServiceFake, mConfigMock, mWakeLockFake,
+ mWakefulnessLifecycle, mock(BatteryController.class));
mMachine.setParts(new DozeMachine.Part[]{mPartMock});
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
new file mode 100644
index 0000000..d46d7a2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+
+import static android.content.Context.NOTIFICATION_SERVICE;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SystemUIFactory;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.NotificationChannels;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for exception handling and bitmap configuration in adding smart actions to Screenshot
+ * Notification.
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase {
+ private ScreenshotNotificationSmartActionsProvider mSmartActionsProvider;
+ private Handler mHandler;
+
+ @Before
+ public void setup() {
+ mSmartActionsProvider = mock(
+ ScreenshotNotificationSmartActionsProvider.class);
+ mHandler = mock(Handler.class);
+ }
+
+ // Tests any exception thrown in getting smart actions future does not affect regular
+ // screenshot flow.
+ @Test
+ public void testExceptionHandlingInGetSmartActionsFuture()
+ throws Exception {
+ Bitmap bitmap = mock(Bitmap.class);
+ when(bitmap.getConfig()).thenReturn(Bitmap.Config.HARDWARE);
+ ScreenshotNotificationSmartActionsProvider smartActionsProvider = mock(
+ ScreenshotNotificationSmartActionsProvider.class);
+ when(smartActionsProvider.getActions(any(), any(), any(),
+ eq(false))).thenThrow(RuntimeException.class);
+ CompletableFuture<List<Notification.Action>> smartActionsFuture =
+ GlobalScreenshot.getSmartActionsFuture("", bitmap,
+ smartActionsProvider, true, false);
+ Assert.assertNotNull(smartActionsFuture);
+ List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
+ Assert.assertEquals(Collections.emptyList(), smartActions);
+ }
+
+ // Tests any exception thrown in waiting for smart actions future to complete does
+ // not affect regular screenshot flow.
+ @Test
+ public void testExceptionHandlingInGetSmartActions()
+ throws Exception {
+ CompletableFuture<List<Notification.Action>> smartActionsFuture = mock(
+ CompletableFuture.class);
+ int timeoutMs = 1000;
+ when(smartActionsFuture.get(timeoutMs, TimeUnit.MILLISECONDS)).thenThrow(
+ RuntimeException.class);
+ List<Notification.Action> actions = GlobalScreenshot.getSmartActions(
+ "", smartActionsFuture, timeoutMs, mSmartActionsProvider);
+ Assert.assertEquals(Collections.emptyList(), actions);
+ }
+
+ // Tests any exception thrown in notifying feedback does not affect regular screenshot flow.
+ @Test
+ public void testExceptionHandlingInNotifyingFeedback() {
+ doThrow(RuntimeException.class).when(mSmartActionsProvider).notifyOp(any(), any(), any(),
+ anyLong());
+ GlobalScreenshot.notifyScreenshotOp(null, mSmartActionsProvider, null, null, -1);
+ }
+
+ // Tests for a non-hardware bitmap, ScreenshotNotificationSmartActionsProvider is never invoked
+ // and a completed future is returned.
+ @Test
+ public void testUnsupportedBitmapConfiguration()
+ throws Exception {
+ Bitmap bitmap = mock(Bitmap.class);
+ when(bitmap.getConfig()).thenReturn(Bitmap.Config.RGB_565);
+ CompletableFuture<List<Notification.Action>> smartActionsFuture =
+ GlobalScreenshot.getSmartActionsFuture("", bitmap,
+ mSmartActionsProvider, true, true);
+ verify(mSmartActionsProvider, never()).getActions(any(), any(), any(),
+ eq(false));
+ Assert.assertNotNull(smartActionsFuture);
+ List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
+ Assert.assertEquals(Collections.emptyList(), smartActions);
+ }
+
+ // Tests for a hardware bitmap, ScreenshotNotificationSmartActionsProvider is invoked once.
+ @Test
+ public void testScreenshotNotificationSmartActionsProviderInvokedOnce() {
+ Bitmap bitmap = mock(Bitmap.class);
+ when(bitmap.getConfig()).thenReturn(Bitmap.Config.HARDWARE);
+ GlobalScreenshot.getSmartActionsFuture("", bitmap, mSmartActionsProvider,
+ true, true);
+ verify(mSmartActionsProvider, times(1))
+ .getActions(any(), any(), any(), eq(true));
+ }
+
+ // Tests for a hardware bitmap, a completed future is returned.
+ @Test
+ public void testSupportedBitmapConfiguration()
+ throws Exception {
+ Bitmap bitmap = mock(Bitmap.class);
+ when(bitmap.getConfig()).thenReturn(Bitmap.Config.HARDWARE);
+ ScreenshotNotificationSmartActionsProvider actionsProvider =
+ SystemUIFactory.getInstance().createScreenshotNotificationSmartActionsProvider(
+ mContext, null, mHandler);
+ CompletableFuture<List<Notification.Action>> smartActionsFuture =
+ GlobalScreenshot.getSmartActionsFuture("", bitmap,
+ actionsProvider,
+ true, true);
+ Assert.assertNotNull(smartActionsFuture);
+ List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
+ Assert.assertEquals(smartActions.size(), 0);
+ }
+
+ // Tests for notification action extras.
+ @Test
+ public void testNotificationActionExtras() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ NotificationManager notificationManager =
+ (NotificationManager) mContext.getSystemService(NOTIFICATION_SERVICE);
+ SaveImageInBackgroundData data = new SaveImageInBackgroundData();
+ data.context = mContext;
+ data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
+ data.iconSize = 10;
+ data.finisher = null;
+ data.previewWidth = 10;
+ data.previewheight = 10;
+ SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, data,
+ notificationManager);
+ Uri uri = Uri.parse("Screenshot_123.png");
+ Notification.Builder notificationBuilder = new Notification.Builder(mContext,
+ NotificationChannels.SCREENSHOTS_HEADSUP);
+ task.populateNotificationActions(mContext, mContext.getResources(),
+ uri,
+ CompletableFuture.completedFuture(Collections.emptyList()), notificationBuilder);
+
+ Notification notification = notificationBuilder.build();
+ Assert.assertEquals(notification.actions.length, 3);
+ boolean isShareFound = false;
+ boolean isEditFound = false;
+ boolean isDeleteFound = false;
+ for (Notification.Action action : notification.actions) {
+ Intent intent = action.actionIntent.getIntent();
+ Intent actionIntent = intent.getParcelableExtra(GlobalScreenshot.EXTRA_ACTION_INTENT);
+ Assert.assertNotNull(intent);
+ Bundle bundle = intent.getExtras();
+ Assert.assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_ID));
+ Assert.assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED));
+ if (uri.toString().equals(bundle.getString(GlobalScreenshot.SCREENSHOT_URI_ID))) {
+ isDeleteFound = true;
+ } else if (Intent.ACTION_EDIT.equals(actionIntent.getAction())) {
+ isEditFound = true;
+ } else if (Intent.ACTION_CHOOSER.equals(actionIntent.getAction())) {
+ isShareFound = true;
+ }
+ }
+
+ Assert.assertTrue(isEditFound);
+ Assert.assertTrue(isShareFound);
+ Assert.assertTrue(isDeleteFound);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index daee55b..2fe51d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -35,8 +35,11 @@
import android.app.trust.TrustManager;
import android.content.Context;
import android.graphics.Color;
+import android.hardware.biometrics.BiometricSourceType;
+import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Looper;
+import android.os.UserManager;
import android.view.View;
import android.view.ViewGroup;
@@ -47,12 +50,15 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dock.DockManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
import com.android.systemui.statusbar.phone.LockIcon;
import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.UnlockMethodCache;
import com.android.systemui.statusbar.policy.AccessibilityController;
import com.android.systemui.util.wakelock.WakeLockFake;
@@ -61,6 +67,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -92,6 +99,14 @@
private StatusBarStateController mStatusBarStateController;
@Mock
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Mock
+ private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ @Mock
+ private UserManager mUserManager;
+ @Mock
+ private DockManager mDockManager;
+ @Captor
+ private ArgumentCaptor<DockManager.AlignmentStateListener> mAlignmentListener;
private KeyguardIndicationTextView mTextView;
private KeyguardIndicationController mController;
@@ -105,14 +120,18 @@
mTextView = new KeyguardIndicationTextView(mContext);
mContext.addMockSystemService(Context.DEVICE_POLICY_SERVICE, mDevicePolicyManager);
+ mContext.addMockSystemService(UserManager.class, mUserManager);
mContext.addMockSystemService(Context.TRUST_SERVICE, mock(TrustManager.class));
mContext.addMockSystemService(Context.FINGERPRINT_SERVICE, mock(FingerprintManager.class));
mDisclosureWithOrganization = mContext.getString(R.string.do_disclosure_with_name,
ORGANIZATION_NAME);
+ when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(true);
+ when(mKeyguardUpdateMonitor.isScreenOn()).thenReturn(true);
when(mIndicationArea.findViewById(R.id.keyguard_indication_enterprise_disclosure))
.thenReturn(mDisclosure);
when(mIndicationArea.findViewById(R.id.keyguard_indication_text)).thenReturn(mTextView);
+ when(mUserManager.isUserUnlocked(anyInt())).thenReturn(true);
mWakeLock = new WakeLockFake();
}
@@ -123,7 +142,9 @@
}
mController = new KeyguardIndicationController(mContext, mIndicationArea, mLockIcon,
mLockPatternUtils, mWakeLock, mShadeController, mAccessibilityController,
- mUnlockMethodCache, mStatusBarStateController, mKeyguardUpdateMonitor);
+ mUnlockMethodCache, mStatusBarStateController, mKeyguardUpdateMonitor,
+ mDockManager);
+ mController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
}
@Test
@@ -193,6 +214,74 @@
}
@Test
+ public void createController_addsAlignmentListener() {
+ createController();
+
+ verify(mDockManager).addAlignmentStateListener(
+ any(DockManager.AlignmentStateListener.class));
+ }
+
+ @Test
+ public void onAlignmentStateChanged_showsSlowChargingIndication() {
+ createController();
+ verify(mDockManager).addAlignmentStateListener(mAlignmentListener.capture());
+ mController.setVisible(true);
+
+ mAlignmentListener.getValue().onAlignmentStateChanged(
+ DockManager.ALIGN_STATE_POOR);
+
+ assertThat(mTextView.getText()).isEqualTo(
+ mContext.getResources().getString(R.string.dock_alignment_slow_charging));
+ assertThat(mTextView.getCurrentTextColor()).isEqualTo(
+ Utils.getColorError(mContext).getDefaultColor());
+ }
+
+ @Test
+ public void onAlignmentStateChanged_showsNotChargingIndication() {
+ createController();
+ verify(mDockManager).addAlignmentStateListener(mAlignmentListener.capture());
+ mController.setVisible(true);
+
+ mAlignmentListener.getValue().onAlignmentStateChanged(DockManager.ALIGN_STATE_TERRIBLE);
+
+ assertThat(mTextView.getText()).isEqualTo(
+ mContext.getResources().getString(R.string.dock_alignment_not_charging));
+ assertThat(mTextView.getCurrentTextColor()).isEqualTo(
+ Utils.getColorError(mContext).getDefaultColor());
+ }
+
+ @Test
+ public void onAlignmentStateChanged_whileDozing_showsSlowChargingIndication() {
+ createController();
+ verify(mDockManager).addAlignmentStateListener(mAlignmentListener.capture());
+ mController.setVisible(true);
+ mController.setDozing(true);
+
+ mAlignmentListener.getValue().onAlignmentStateChanged(
+ DockManager.ALIGN_STATE_POOR);
+
+ assertThat(mTextView.getText()).isEqualTo(
+ mContext.getResources().getString(R.string.dock_alignment_slow_charging));
+ assertThat(mTextView.getCurrentTextColor()).isEqualTo(
+ Utils.getColorError(mContext).getDefaultColor());
+ }
+
+ @Test
+ public void onAlignmentStateChanged_whileDozing_showsNotChargingIndication() {
+ createController();
+ verify(mDockManager).addAlignmentStateListener(mAlignmentListener.capture());
+ mController.setVisible(true);
+ mController.setDozing(true);
+
+ mAlignmentListener.getValue().onAlignmentStateChanged(DockManager.ALIGN_STATE_TERRIBLE);
+
+ assertThat(mTextView.getText()).isEqualTo(
+ mContext.getResources().getString(R.string.dock_alignment_not_charging));
+ assertThat(mTextView.getCurrentTextColor()).isEqualTo(
+ Utils.getColorError(mContext).getDefaultColor());
+ }
+
+ @Test
public void transientIndication_holdsWakeLock_whenDozing() {
createController();
@@ -245,6 +334,49 @@
}
@Test
+ public void transientIndication_visibleWhenDozing_unlessSwipeUp_fromHelp() {
+ createController();
+ String message = "A message";
+
+ mController.setVisible(true);
+ mController.getKeyguardCallback().onBiometricHelp(
+ KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED, message,
+ BiometricSourceType.FACE);
+ assertThat(mTextView.getText()).isEqualTo(message);
+ mController.setDozing(true);
+
+ assertThat(mTextView.getText()).isNotEqualTo(message);
+ }
+
+ @Test
+ public void transientIndication_visibleWhenDozing_unlessSwipeUp_fromError() {
+ createController();
+ String message = mContext.getString(R.string.keyguard_unlock);
+
+ mController.setVisible(true);
+ mController.getKeyguardCallback().onBiometricError(FaceManager.FACE_ERROR_TIMEOUT,
+ "A message", BiometricSourceType.FACE);
+
+ assertThat(mTextView.getText()).isEqualTo(message);
+ mController.setDozing(true);
+
+ assertThat(mTextView.getText()).isNotEqualTo(message);
+ }
+
+ @Test
+ public void transientIndication_swipeUpToRetry() {
+ createController();
+ String message = mContext.getString(R.string.keyguard_retry);
+ when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
+
+ mController.setVisible(true);
+ mController.getKeyguardCallback().onBiometricError(FaceManager.FACE_ERROR_TIMEOUT,
+ "A message", BiometricSourceType.FACE);
+
+ verify(mStatusBarKeyguardViewManager).showBouncerMessage(eq(message), any());
+ }
+
+ @Test
public void lockIcon_click() {
createController();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java
index a66cf84..0db1f68 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java
@@ -53,6 +53,7 @@
import com.android.systemui.statusbar.notification.NotificationFilter;
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import org.junit.Before;
@@ -85,6 +86,8 @@
HeadsUpManager mHeadsUpManager;
@Mock
NotificationInterruptionStateProvider.HeadsUpSuppressor mHeadsUpSuppressor;
+ @Mock
+ BatteryController mBatteryController;
private NotificationInterruptionStateProvider mNotifInterruptionStateProvider;
@@ -98,7 +101,8 @@
mDreamManager,
mAmbientDisplayConfiguration,
mNotificationFilter,
- mStatusBarStateController);
+ mStatusBarStateController,
+ mBatteryController);
mNotifInterruptionStateProvider.setUpWithPresenter(
mPresenter,
@@ -573,17 +577,17 @@
/**
* Testable class overriding constructor.
*/
- public class TestableNotificationInterruptionStateProvider extends
+ public static class TestableNotificationInterruptionStateProvider extends
NotificationInterruptionStateProvider {
TestableNotificationInterruptionStateProvider(Context context,
PowerManager powerManager, IDreamManager dreamManager,
AmbientDisplayConfiguration ambientDisplayConfiguration,
NotificationFilter notificationFilter,
- StatusBarStateController statusBarStateController) {
+ StatusBarStateController statusBarStateController,
+ BatteryController batteryController) {
super(context, powerManager, dreamManager, ambientDisplayConfiguration,
- notificationFilter,
- statusBarStateController);
+ notificationFilter, batteryController, statusBarStateController);
}
}
}
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 2ca1b06..b07ac5f 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
@@ -182,7 +182,7 @@
0,
NotificationManager.IMPORTANCE_DEFAULT,
null, null,
- null, null, null, true, sentiment, false, -1, false, null, null, false);
+ null, null, null, true, sentiment, false, -1, false, null, null, false, false);
return true;
}).when(mRankingMap).getRanking(eq(key), any(NotificationListenerService.Ranking.class));
}
@@ -201,7 +201,7 @@
null, null,
null, null, null, true,
NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL, false, -1,
- false, smartActions, null, false);
+ false, smartActions, null, false, false);
return true;
}).when(mRankingMap).getRanking(eq(key), any(NotificationListenerService.Ranking.class));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java
index e2d8e56..cf0c718 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java
@@ -623,6 +623,7 @@
public static final String OVERRIDE_SMART_ACTIONS = "sa";
public static final String OVERRIDE_SMART_REPLIES = "sr";
public static final String OVERRIDE_BUBBLE = "cb";
+ public static final String OVERRIDE_VISUALLY_INTERRUPTIVE = "vi";
public Map<String, Bundle> rankingOverrides = new HashMap<>();
@@ -683,7 +684,9 @@
overrides.containsKey(OVERRIDE_SMART_REPLIES)
? overrides.getCharSequenceArrayList(OVERRIDE_SMART_REPLIES)
: currentReplies,
- overrides.getBoolean(OVERRIDE_BUBBLE, outRanking.canBubble()));
+ overrides.getBoolean(OVERRIDE_BUBBLE, outRanking.canBubble()),
+ overrides.getBoolean(OVERRIDE_VISUALLY_INTERRUPTIVE,
+ outRanking.visuallyInterruptive()));
}
return true;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 63f653b..0da0e76 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -221,6 +221,31 @@
verify(mStatusBar, never()).animateKeyguardUnoccluding();
}
+ @Test
+ public void testHiding_cancelsGoneRunnable() {
+ OnDismissAction action = mock(OnDismissAction.class);
+ Runnable cancelAction = mock(Runnable.class);
+ mStatusBarKeyguardViewManager.dismissWithAction(action, cancelAction,
+ true /* afterKeyguardGone */);
+
+ mStatusBarKeyguardViewManager.hideBouncer(true);
+ mStatusBarKeyguardViewManager.hide(0, 30);
+ verify(action, never()).onDismiss();
+ verify(cancelAction).run();
+ }
+
+ @Test
+ public void testHiding_doesntCancelWhenShowing() {
+ OnDismissAction action = mock(OnDismissAction.class);
+ Runnable cancelAction = mock(Runnable.class);
+ mStatusBarKeyguardViewManager.dismissWithAction(action, cancelAction,
+ true /* afterKeyguardGone */);
+
+ mStatusBarKeyguardViewManager.hide(0, 30);
+ verify(action).onDismiss();
+ verify(cancelAction, never()).run();
+ }
+
private class TestableStatusBarKeyguardViewManager extends StatusBarKeyguardViewManager {
public TestableStatusBarKeyguardViewManager(Context context,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 178ff22..02215a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -110,6 +110,7 @@
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
import com.android.systemui.statusbar.policy.UserSwitcherController;
@@ -156,6 +157,7 @@
@Mock private NotificationRemoteInputManager mRemoteInputManager;
@Mock private RemoteInputController mRemoteInputController;
@Mock private StatusBarStateControllerImpl mStatusBarStateController;
+ @Mock private BatteryController mBatteryController;
@Mock private DeviceProvisionedController mDeviceProvisionedController;
@Mock private StatusBarNotificationPresenter mNotificationPresenter;
@Mock
@@ -209,7 +211,7 @@
mNotificationInterruptionStateProvider =
new TestableNotificationInterruptionStateProvider(mContext, mPowerManager,
mDreamManager, mAmbientDisplayConfiguration, mNotificationFilter,
- mStatusBarStateController);
+ mStatusBarStateController, mBatteryController);
mDependency.injectTestDependency(NotificationInterruptionStateProvider.class,
mNotificationInterruptionStateProvider);
mDependency.injectMockDependency(NavigationBarController.class);
@@ -873,9 +875,10 @@
IDreamManager dreamManager,
AmbientDisplayConfiguration ambientDisplayConfiguration,
NotificationFilter filter,
- StatusBarStateController controller) {
+ StatusBarStateController controller,
+ BatteryController batteryController) {
super(context, powerManager, dreamManager, ambientDisplayConfiguration, filter,
- controller);
+ batteryController, controller);
mUseHeadsUp = true;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java
index a843cca..df76f01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java
@@ -48,4 +48,9 @@
public boolean isPowerSave() {
return false;
}
+
+ @Override
+ public boolean isAodPowerSave() {
+ return false;
+ }
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index d4fb9ac..a6c48c4 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -215,6 +215,8 @@
private final AppOpsManager mAppOpsManager;
+ private final ActivityTaskManagerInternal mActivityTaskManagerService;
+
private final MainHandler mMainHandler;
private final GlobalActionPerformer mGlobalActionPerformer;
@@ -306,6 +308,7 @@
mMainHandler = new MainHandler(mContext.getMainLooper());
mGlobalActionPerformer = new GlobalActionPerformer(mContext, mWindowManagerService);
mA11yDisplayListener = new AccessibilityDisplayListener(mContext, mMainHandler);
+ mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class);
registerBroadcastReceivers();
new AccessibilityContentObserver(mMainHandler).register(
@@ -1633,7 +1636,8 @@
if (service == null) {
service = new AccessibilityServiceConnection(userState, mContext, componentName,
installedService, sIdCounter++, mMainHandler, mLock, mSecurityPolicy,
- this, mWindowManagerService, mGlobalActionPerformer);
+ this, mWindowManagerService, mGlobalActionPerformer,
+ mActivityTaskManagerService);
} else if (userState.mBoundServices.contains(service)) {
continue;
}
@@ -3026,7 +3030,7 @@
userState, mContext,
COMPONENT_NAME, info, sIdCounter++, mMainHandler, mLock, mSecurityPolicy,
AccessibilityManagerService.this, mWindowManagerService,
- mGlobalActionPerformer) {
+ mGlobalActionPerformer, mActivityTaskManagerService) {
@Override
public boolean supportsFlagForNotImportantViews(AccessibilityServiceInfo info) {
return true;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index b66caa5..91031e6 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -34,6 +34,7 @@
import com.android.server.accessibility.AccessibilityManagerService.SecurityPolicy;
import com.android.server.accessibility.AccessibilityManagerService.UserState;
+import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
import java.lang.ref.WeakReference;
@@ -58,6 +59,7 @@
*/
final WeakReference<UserState> mUserStateWeakReference;
final Intent mIntent;
+ final ActivityTaskManagerInternal mActivityTaskManagerService;
private final Handler mMainHandler;
@@ -69,7 +71,8 @@
AccessibilityServiceInfo accessibilityServiceInfo, int id, Handler mainHandler,
Object lock, SecurityPolicy securityPolicy, SystemSupport systemSupport,
WindowManagerInternal windowManagerInternal,
- GlobalActionPerformer globalActionPerfomer) {
+ GlobalActionPerformer globalActionPerfomer,
+ ActivityTaskManagerInternal activityTaskManagerService) {
super(context, componentName, accessibilityServiceInfo, id, mainHandler, lock,
securityPolicy, systemSupport, windowManagerInternal, globalActionPerfomer);
mUserStateWeakReference = new WeakReference<UserState>(userState);
@@ -77,6 +80,7 @@
mMainHandler = mainHandler;
mIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
com.android.internal.R.string.accessibility_binding_label);
+ mActivityTaskManagerService = activityTaskManagerService;
final long identity = Binder.clearCallingIdentity();
try {
mIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, mSystemSupport.getPendingIntentActivity(
@@ -103,6 +107,9 @@
} finally {
Binder.restoreCallingIdentity(identity);
}
+ mActivityTaskManagerService.setAllowAppSwitches(mComponentName.flattenToString(),
+ mAccessibilityServiceInfo.getResolveInfo().serviceInfo.applicationInfo.uid,
+ userState.mUserId);
}
public void unbindLocked() {
@@ -111,6 +118,9 @@
if (userState == null) return;
userState.removeServiceLocked(this);
mSystemSupport.getMagnificationController().resetAllIfNeeded(mId);
+ // Set uid to -1 to clear allowing app switches.
+ mActivityTaskManagerService.setAllowAppSwitches(mComponentName.flattenToString(),
+ /* uid= */ -1, userState.mUserId);
resetLocked();
}
@@ -208,6 +218,12 @@
@Override
public void onServiceDisconnected(ComponentName componentName) {
binderDied();
+ UserState userState = mUserStateWeakReference.get();
+ if (userState != null) {
+ // Set uid to -1 to clear allowing app switches.
+ mActivityTaskManagerService.setAllowAppSwitches(mComponentName.flattenToString(),
+ /* uid= */ -1, userState.mUserId);
+ }
}
@Override
diff --git a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java
index ecea251c..9cdb58d 100644
--- a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java
+++ b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java
@@ -16,7 +16,6 @@
package com.android.server.contentsuggestions;
-import static android.Manifest.permission.BIND_CONTENT_SUGGESTIONS_SERVICE;
import static android.Manifest.permission.MANAGE_CONTENT_SUGGESTIONS;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -96,7 +95,7 @@
private void enforceCaller(int userId, String func) {
Context ctx = getContext();
- if (ctx.checkCallingPermission(BIND_CONTENT_SUGGESTIONS_SERVICE) == PERMISSION_GRANTED
+ if (ctx.checkCallingPermission(MANAGE_CONTENT_SUGGESTIONS) == PERMISSION_GRANTED
|| mServiceNameResolver.isTemporary(userId)
|| mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid())) {
return;
diff --git a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java
index 06d9395..7828050 100644
--- a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java
+++ b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java
@@ -22,6 +22,7 @@
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.contentsuggestions.ClassificationsRequest;
+import android.app.contentsuggestions.ContentSuggestionsManager;
import android.app.contentsuggestions.IClassificationsCallback;
import android.app.contentsuggestions.ISelectionsCallback;
import android.app.contentsuggestions.SelectionsRequest;
@@ -97,15 +98,19 @@
void provideContextImageLocked(int taskId, @NonNull Bundle imageContextRequestExtras) {
RemoteContentSuggestionsService service = ensureRemoteServiceLocked();
if (service != null) {
- ActivityManager.TaskSnapshot snapshot =
- mActivityTaskManagerInternal.getTaskSnapshotNoRestore(taskId, false);
GraphicBuffer snapshotBuffer = null;
int colorSpaceId = 0;
- if (snapshot != null) {
- snapshotBuffer = snapshot.getSnapshot();
- ColorSpace colorSpace = snapshot.getColorSpace();
- if (colorSpace != null) {
- colorSpaceId = colorSpace.getId();
+
+ // Skip taking TaskSnapshot when bitmap is provided.
+ if (!imageContextRequestExtras.containsKey(ContentSuggestionsManager.EXTRA_BITMAP)) {
+ ActivityManager.TaskSnapshot snapshot =
+ mActivityTaskManagerInternal.getTaskSnapshotNoRestore(taskId, false);
+ if (snapshot != null) {
+ snapshotBuffer = snapshot.getSnapshot();
+ ColorSpace colorSpace = snapshot.getColorSpace();
+ if (colorSpace != null) {
+ colorSpaceId = colorSpace.getId();
+ }
}
}
diff --git a/services/core/java/com/android/server/AlarmManagerInternal.java b/services/core/java/com/android/server/AlarmManagerInternal.java
index 5b0de5e..0a73502 100644
--- a/services/core/java/com/android/server/AlarmManagerInternal.java
+++ b/services/core/java/com/android/server/AlarmManagerInternal.java
@@ -16,6 +16,8 @@
package com.android.server;
+import android.app.PendingIntent;
+
public interface AlarmManagerInternal {
// Some other components in the system server need to know about
// broadcast alarms currently in flight
@@ -30,4 +32,10 @@
boolean isIdling();
public void removeAlarmsForUid(int uid);
public void registerInFlightListener(InFlightListener callback);
+
+ /**
+ * Removes any alarm with the given pending intent with equality determined using
+ * {@link android.app.PendingIntent#equals(java.lang.Object) PendingIntent.equals}
+ */
+ void remove(PendingIntent rec);
}
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index d162441..386b49e 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -210,7 +210,6 @@
IAlarmListener mTimeTickTrigger;
PendingIntent mDateChangeSender;
Random mRandom;
- PendingIntent.CancelListener mOperationCancelListener;
boolean mInteractive = true;
long mNonInteractiveStartTime;
long mNonInteractiveTime;
@@ -1498,7 +1497,6 @@
synchronized (mLock) {
mHandler = new AlarmHandler();
- mOperationCancelListener = (intent) -> removeImpl(intent, null);
mConstants = new Constants(mHandler);
mAppWakeupHistory = new AppWakeupHistory(Constants.DEFAULT_APP_STANDBY_WINDOW);
@@ -1750,9 +1748,6 @@
} else {
maxElapsed = triggerElapsed + windowLength;
}
- if (operation != null) {
- operation.registerCancelListener(mOperationCancelListener);
- }
synchronized (mLock) {
if (DEBUG_BATCH) {
Slog.v(TAG, "set(" + operation + ") : type=" + type
@@ -1765,8 +1760,6 @@
"Maximum limit of concurrent alarms " + mConstants.MAX_ALARMS_PER_UID
+ " reached for uid: " + UserHandle.formatUid(callingUid)
+ ", callingPackage: " + callingPackage;
- mHandler.obtainMessage(AlarmHandler.UNREGISTER_CANCEL_LISTENER,
- operation).sendToTarget();
Slog.w(TAG, errorMsg);
throw new IllegalStateException(errorMsg);
}
@@ -1787,8 +1780,6 @@
if (ActivityManager.getService().isAppStartModeDisabled(callingUid, callingPackage)) {
Slog.w(TAG, "Not setting alarm from " + callingUid + ":" + a
+ " -- package not allowed to start");
- mHandler.obtainMessage(AlarmHandler.UNREGISTER_CANCEL_LISTENER,
- operation).sendToTarget();
return;
}
} catch (RemoteException e) {
@@ -2042,6 +2033,11 @@
}
@Override
+ public void remove(PendingIntent pi) {
+ mHandler.obtainMessage(AlarmHandler.REMOVE_FOR_CANCELED, pi).sendToTarget();
+ }
+
+ @Override
public void registerInFlightListener(InFlightListener callback) {
synchronized (mLock) {
mInFlightListeners.add(callback);
@@ -2146,8 +2142,6 @@
synchronized (mLock) {
removeLocked(operation, listener);
}
- mHandler.obtainMessage(AlarmHandler.UNREGISTER_CANCEL_LISTENER,
- operation).sendToTarget();
}
@Override
@@ -4151,7 +4145,7 @@
public static final int APP_STANDBY_BUCKET_CHANGED = 5;
public static final int APP_STANDBY_PAROLE_CHANGED = 6;
public static final int REMOVE_FOR_STOPPED = 7;
- public static final int UNREGISTER_CANCEL_LISTENER = 8;
+ public static final int REMOVE_FOR_CANCELED = 8;
AlarmHandler() {
super(Looper.myLooper());
@@ -4234,10 +4228,10 @@
}
break;
- case UNREGISTER_CANCEL_LISTENER:
- final PendingIntent pi = (PendingIntent) msg.obj;
- if (pi != null) {
- pi.unregisterCancelListener(mOperationCancelListener);
+ case REMOVE_FOR_CANCELED:
+ final PendingIntent operation = (PendingIntent) msg.obj;
+ synchronized (mLock) {
+ removeLocked(operation, null);
}
break;
@@ -4696,11 +4690,6 @@
Intent.EXTRA_ALARM_COUNT, alarm.count),
mDeliveryTracker, mHandler, null,
allowWhileIdle ? mIdleOptions : null);
- if (alarm.repeatInterval == 0) {
- // Keep the listener for repeating alarms until they get cancelled
- mHandler.obtainMessage(AlarmHandler.UNREGISTER_CANCEL_LISTENER,
- alarm.operation).sendToTarget();
- }
} catch (PendingIntent.CanceledException e) {
if (alarm.repeatInterval > 0) {
// This IntentSender is no longer valid, but this
diff --git a/services/core/java/com/android/server/BinderCallsStatsService.java b/services/core/java/com/android/server/BinderCallsStatsService.java
index e510259..f2ce444 100644
--- a/services/core/java/com/android/server/BinderCallsStatsService.java
+++ b/services/core/java/com/android/server/BinderCallsStatsService.java
@@ -19,6 +19,7 @@
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+import android.app.ActivityThread;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -40,6 +41,7 @@
import com.android.internal.os.BinderCallsStats;
import com.android.internal.os.BinderInternal;
import com.android.internal.os.CachedDeviceState;
+import com.android.internal.util.DumpUtils;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -49,6 +51,7 @@
public class BinderCallsStatsService extends Binder {
private static final String TAG = "BinderCallsStatsService";
+ private static final String SERVICE_NAME = "binder_calls_stats";
private static final String PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING
= "persist.sys.binder_calls_detailed_tracking";
@@ -246,7 +249,7 @@
mService = new BinderCallsStatsService(
mBinderCallsStats, mWorkSourceProvider);
publishLocalService(Internal.class, new Internal(mBinderCallsStats));
- publishBinderService("binder_calls_stats", mService);
+ publishBinderService(SERVICE_NAME, mService);
boolean detailedTrackingEnabled = SystemProperties.getBoolean(
PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING, false);
@@ -293,6 +296,11 @@
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (!DumpUtils.checkDumpAndUsageStatsPermission(ActivityThread.currentApplication(),
+ SERVICE_NAME, pw)) {
+ return;
+ }
+
boolean verbose = false;
if (args != null) {
for (final String arg : args) {
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 2ab46e6..4aaf0b7 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -291,6 +291,7 @@
private boolean mLightEnabled;
private boolean mDeepEnabled;
private boolean mQuickDozeActivated;
+ private boolean mQuickDozeActivatedWhileIdling;
private boolean mForceIdle;
private boolean mNetworkConnected;
private boolean mScreenOn;
@@ -302,6 +303,10 @@
private boolean mHasNetworkLocation;
private Location mLastGenericLocation;
private Location mLastGpsLocation;
+
+ /** Time in the elapsed realtime timebase when this listener last received a motion event. */
+ private long mLastMotionEventElapsed;
+
// Current locked state of the screen
private boolean mScreenLocked;
private int mNumBlockingConstraints = 0;
@@ -547,6 +552,9 @@
*/
private ArrayMap<String, Integer> mRemovedFromSystemWhitelistApps = new ArrayMap<>();
+ private final ArraySet<StationaryListener> mStationaryListeners =
+ new ArraySet<>();
+
private static final int EVENT_NULL = 0;
private static final int EVENT_NORMAL = 1;
private static final int EVENT_LIGHT_IDLE = 2;
@@ -605,6 +613,21 @@
}
};
+ private final AlarmManager.OnAlarmListener mMotionTimeoutAlarmListener = () -> {
+ synchronized (DeviceIdleController.this) {
+ if (!isStationaryLocked()) {
+ // If the device keeps registering motion, then the alarm should be
+ // rescheduled, so this shouldn't go off until the device is stationary.
+ // This case may happen in a race condition (alarm goes off right before
+ // motion is detected, but handleMotionDetectedLocked is called before
+ // we enter this block).
+ Slog.w(TAG, "motion timeout went off and device isn't stationary");
+ return;
+ }
+ }
+ postStationaryStatusUpdated();
+ };
+
private final AlarmManager.OnAlarmListener mSensingTimeoutAlarmListener
= new AlarmManager.OnAlarmListener() {
@Override
@@ -654,12 +677,70 @@
}
};
+ /** Post stationary status only to this listener. */
+ private void postStationaryStatus(StationaryListener listener) {
+ mHandler.obtainMessage(MSG_REPORT_STATIONARY_STATUS, listener).sendToTarget();
+ }
+
+ /** Post stationary status to all registered listeners. */
+ private void postStationaryStatusUpdated() {
+ mHandler.sendEmptyMessage(MSG_REPORT_STATIONARY_STATUS);
+ }
+
+ private boolean isStationaryLocked() {
+ final long now = mInjector.getElapsedRealtime();
+ return mMotionListener.active
+ // Listening for motion for long enough and last motion was long enough ago.
+ && now - Math.max(mMotionListener.activatedTimeElapsed, mLastMotionEventElapsed)
+ >= mConstants.MOTION_INACTIVE_TIMEOUT;
+ }
+
+ @VisibleForTesting
+ void registerStationaryListener(StationaryListener listener) {
+ synchronized (this) {
+ if (!mStationaryListeners.add(listener)) {
+ // Listener already registered.
+ return;
+ }
+ postStationaryStatus(listener);
+ if (mMotionListener.active) {
+ if (!isStationaryLocked() && mStationaryListeners.size() == 1) {
+ // First listener to be registered and the device isn't stationary, so we
+ // need to register the alarm to report the device is stationary.
+ scheduleMotionTimeoutAlarmLocked();
+ }
+ } else {
+ startMonitoringMotionLocked();
+ scheduleMotionTimeoutAlarmLocked();
+ }
+ }
+ }
+
+ private void unregisterStationaryListener(StationaryListener listener) {
+ synchronized (this) {
+ if (mStationaryListeners.remove(listener) && mStationaryListeners.size() == 0
+ // Motion detection is started when transitioning from INACTIVE to IDLE_PENDING
+ // and so doesn't need to be on for ACTIVE or INACTIVE states.
+ // Motion detection isn't needed when idling due to Quick Doze.
+ && (mState == STATE_ACTIVE || mState == STATE_INACTIVE
+ || mQuickDozeActivated)) {
+ maybeStopMonitoringMotionLocked();
+ }
+ }
+ }
+
@VisibleForTesting
final class MotionListener extends TriggerEventListener
implements SensorEventListener {
boolean active = false;
+ /**
+ * Time in the elapsed realtime timebase when this listener was activated. Only valid if
+ * {@link #active} is true.
+ */
+ long activatedTimeElapsed;
+
public boolean isActive() {
return active;
}
@@ -667,7 +748,6 @@
@Override
public void onTrigger(TriggerEvent event) {
synchronized (DeviceIdleController.this) {
- active = false;
motionLocked();
}
}
@@ -675,8 +755,6 @@
@Override
public void onSensorChanged(SensorEvent event) {
synchronized (DeviceIdleController.this) {
- mSensorManager.unregisterListener(this, mMotionSensor);
- active = false;
motionLocked();
}
}
@@ -694,6 +772,7 @@
}
if (success) {
active = true;
+ activatedTimeElapsed = mInjector.getElapsedRealtime();
} else {
Slog.e(TAG, "Unable to register for " + mMotionSensor);
}
@@ -1307,6 +1386,8 @@
private static final int MSG_SEND_CONSTRAINT_MONITORING = 10;
private static final int MSG_UPDATE_PRE_IDLE_TIMEOUT_FACTOR = 11;
private static final int MSG_RESET_PRE_IDLE_TIMEOUT_FACTOR = 12;
+ @VisibleForTesting
+ static final int MSG_REPORT_STATIONARY_STATUS = 13;
final class MyHandler extends Handler {
MyHandler(Looper looper) {
@@ -1443,6 +1524,30 @@
updatePreIdleFactor();
maybeDoImmediateMaintenance();
} break;
+ case MSG_REPORT_STATIONARY_STATUS: {
+ final StationaryListener newListener = (StationaryListener) msg.obj;
+ final StationaryListener[] listeners;
+ final boolean isStationary;
+ synchronized (DeviceIdleController.this) {
+ isStationary = isStationaryLocked();
+ if (newListener == null) {
+ // Only notify all listeners if we aren't directing to one listener.
+ listeners = mStationaryListeners.toArray(
+ new StationaryListener[mStationaryListeners.size()]);
+ } else {
+ listeners = null;
+ }
+ }
+ if (listeners != null) {
+ for (StationaryListener listener : listeners) {
+ listener.onDeviceStationaryChanged(isStationary);
+ }
+ }
+ if (newListener != null) {
+ newListener.onDeviceStationaryChanged(isStationary);
+ }
+ }
+ break;
}
}
}
@@ -1628,6 +1733,19 @@
}
}
+ /**
+ * Listener to be notified when DeviceIdleController determines that the device has
+ * moved or is stationary.
+ */
+ public interface StationaryListener {
+ /**
+ * Called when DeviceIdleController has determined that the device is stationary or moving.
+ *
+ * @param isStationary true if the device is stationary, false otherwise
+ */
+ void onDeviceStationaryChanged(boolean isStationary);
+ }
+
public class LocalService {
public void onConstraintStateChanged(IDeviceIdleConstraint constraint, boolean active) {
synchronized (DeviceIdleController.this) {
@@ -1693,6 +1811,24 @@
public int[] getPowerSaveTempWhitelistAppIds() {
return DeviceIdleController.this.getAppIdTempWhitelistInternal();
}
+
+ /**
+ * Registers a listener that will be notified when the system has detected that the device
+ * is
+ * stationary or in motion.
+ */
+ public void registerStationaryListener(StationaryListener listener) {
+ DeviceIdleController.this.registerStationaryListener(listener);
+ }
+
+ /**
+ * Unregisters a registered stationary listener from being notified when the system has
+ * detected
+ * that the device is stationary or in motion.
+ */
+ public void unregisterStationaryListener(StationaryListener listener) {
+ DeviceIdleController.this.unregisterStationaryListener(listener);
+ }
}
static class Injector {
@@ -1734,6 +1870,11 @@
return mConstants;
}
+ /** Returns the current elapsed realtime in milliseconds. */
+ long getElapsedRealtime() {
+ return SystemClock.elapsedRealtime();
+ }
+
LocationManager getLocationManager() {
if (mLocationManager == null) {
mLocationManager = mContext.getSystemService(LocationManager.class);
@@ -2601,6 +2742,8 @@
void updateQuickDozeFlagLocked(boolean enabled) {
if (DEBUG) Slog.i(TAG, "updateQuickDozeFlagLocked: enabled=" + enabled);
mQuickDozeActivated = enabled;
+ mQuickDozeActivatedWhileIdling =
+ mQuickDozeActivated && (mState == STATE_IDLE || mState == STATE_IDLE_MAINTENANCE);
if (enabled) {
// If Quick Doze is enabled, see if we should go straight into it.
becomeInactiveIfAppropriateLocked();
@@ -2767,10 +2910,11 @@
mNextIdleDelay = 0;
mNextLightIdleDelay = 0;
mIdleStartTime = 0;
+ mQuickDozeActivatedWhileIdling = false;
cancelAlarmLocked();
cancelSensingTimeoutAlarmLocked();
cancelLocatingLocked();
- stopMonitoringMotionLocked();
+ maybeStopMonitoringMotionLocked();
mAnyMotionDetector.stop();
updateActiveConstraintsLocked();
}
@@ -3270,11 +3414,23 @@
void motionLocked() {
if (DEBUG) Slog.d(TAG, "motionLocked()");
- // The motion sensor will have been disabled at this point
+ mLastMotionEventElapsed = mInjector.getElapsedRealtime();
handleMotionDetectedLocked(mConstants.MOTION_INACTIVE_TIMEOUT, "motion");
}
void handleMotionDetectedLocked(long timeout, String type) {
+ if (mStationaryListeners.size() > 0) {
+ postStationaryStatusUpdated();
+ scheduleMotionTimeoutAlarmLocked();
+ }
+ if (mQuickDozeActivated && !mQuickDozeActivatedWhileIdling) {
+ // Don't exit idle due to motion if quick doze is enabled.
+ // However, if the device started idling due to the normal progression (going through
+ // all the states) and then had quick doze activated, come out briefly on motion so the
+ // user can get slightly fresher content.
+ return;
+ }
+ maybeStopMonitoringMotionLocked();
// The device is not yet active, so we want to go back to the pending idle
// state to wait again for no motion. Note that we only monitor for motion
// after moving out of the inactive state, so no need to worry about that.
@@ -3326,10 +3482,15 @@
}
}
- void stopMonitoringMotionLocked() {
- if (DEBUG) Slog.d(TAG, "stopMonitoringMotionLocked()");
- if (mMotionSensor != null && mMotionListener.active) {
+ /**
+ * Stops motion monitoring. Will not stop monitoring if there are registered stationary
+ * listeners.
+ */
+ private void maybeStopMonitoringMotionLocked() {
+ if (DEBUG) Slog.d(TAG, "maybeStopMonitoringMotionLocked()");
+ if (mMotionSensor != null && mMotionListener.active && mStationaryListeners.size() == 0) {
mMotionListener.unregisterLocked();
+ cancelMotionTimeoutAlarmLocked();
}
}
@@ -3356,6 +3517,10 @@
}
}
+ private void cancelMotionTimeoutAlarmLocked() {
+ mAlarmManager.cancel(mMotionTimeoutAlarmListener);
+ }
+
void cancelSensingTimeoutAlarmLocked() {
if (mNextSensingTimeoutAlarmTime != 0) {
mNextSensingTimeoutAlarmTime = 0;
@@ -3402,6 +3567,14 @@
mNextLightAlarmTime, "DeviceIdleController.light", mLightAlarmListener, mHandler);
}
+ private void scheduleMotionTimeoutAlarmLocked() {
+ if (DEBUG) Slog.d(TAG, "scheduleMotionAlarmLocked");
+ long nextMotionTimeoutAlarmTime =
+ mInjector.getElapsedRealtime() + mConstants.MOTION_INACTIVE_TIMEOUT;
+ mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextMotionTimeoutAlarmTime,
+ "DeviceIdleController.motion", mMotionTimeoutAlarmListener, mHandler);
+ }
+
void scheduleSensingTimeoutAlarmLocked(long delay) {
if (DEBUG) Slog.d(TAG, "scheduleSensingAlarmLocked(" + delay + ")");
mNextSensingTimeoutAlarmTime = SystemClock.elapsedRealtime() + delay;
@@ -4322,9 +4495,14 @@
}
pw.println(" }");
}
- if (mUseMotionSensor) {
+ if (mUseMotionSensor || mStationaryListeners.size() > 0) {
pw.print(" mMotionActive="); pw.println(mMotionListener.active);
pw.print(" mNotMoving="); pw.println(mNotMoving);
+ pw.print(" mMotionListener.activatedTimeElapsed=");
+ pw.println(mMotionListener.activatedTimeElapsed);
+ pw.print(" mLastMotionEventElapsed="); pw.println(mLastMotionEventElapsed);
+ pw.print(" "); pw.print(mStationaryListeners.size());
+ pw.println(" stationary listeners registered");
}
pw.print(" mLocating="); pw.print(mLocating); pw.print(" mHasGps=");
pw.print(mHasGps); pw.print(" mHasNetwork=");
diff --git a/services/core/java/com/android/server/MasterClearReceiver.java b/services/core/java/com/android/server/MasterClearReceiver.java
index 06c46b9..6a9246d 100644
--- a/services/core/java/com/android/server/MasterClearReceiver.java
+++ b/services/core/java/com/android/server/MasterClearReceiver.java
@@ -82,7 +82,7 @@
}
};
- if (mWipeExternalStorage || mWipeEsims) {
+ if (mWipeExternalStorage) {
// thr will be started at the end of this task.
new WipeDataTask(context, thr).execute();
} else {
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 9d71896..e6d303b 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -56,8 +56,8 @@
import android.service.vr.IVrStateCallbacks;
import android.util.ArraySet;
import android.util.Slog;
-
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.DisableCarModeActivity;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
@@ -65,6 +65,7 @@
import com.android.server.twilight.TwilightListener;
import com.android.server.twilight.TwilightManager;
import com.android.server.twilight.TwilightState;
+import com.android.server.wm.WindowManagerInternal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -72,6 +73,8 @@
import java.util.Map;
import java.util.Set;
+import static android.content.Intent.ACTION_SCREEN_OFF;
+
final class UiModeManagerService extends SystemService {
private static final String TAG = UiModeManager.class.getSimpleName();
private static final boolean LOG = false;
@@ -85,6 +88,10 @@
private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
private int mNightMode = UiModeManager.MODE_NIGHT_NO;
+ // we use the override auto mode
+ // for example: force night mode off in the night time while in auto mode
+ private int mNightModeOverride = mNightMode;
+ protected static final String OVERRIDE_NIGHT_MODE = Secure.UI_NIGHT_MODE + "_override";
private Map<Integer, String> mCarModePackagePriority = new HashMap<>();
private boolean mCarModeEnabled = false;
@@ -119,6 +126,7 @@
private TwilightManager mTwilightManager;
private NotificationManager mNotificationManager;
private StatusBarManager mStatusBarManager;
+ private WindowManagerInternal mWindowManager;
private PowerManager.WakeLock mWakeLock;
@@ -128,6 +136,17 @@
super(context);
}
+ @VisibleForTesting
+ protected UiModeManagerService(Context context, WindowManagerInternal wm,
+ PowerManager.WakeLock wl, TwilightManager tm,
+ boolean setupWizardComplete) {
+ super(context);
+ mWindowManager = wm;
+ mWakeLock = wl;
+ mTwilightManager = tm;
+ mSetupWizardComplete = setupWizardComplete;
+ }
+
private static Intent buildHomeIntent(String category) {
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(category);
@@ -189,8 +208,23 @@
public void onTwilightStateChanged(@Nullable TwilightState state) {
synchronized (mLock) {
if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
- updateComputedNightModeLocked();
- updateLocked(0, 0);
+ final IntentFilter intentFilter =
+ new IntentFilter(ACTION_SCREEN_OFF);
+ getContext().registerReceiver(mOnScreenOffHandler, intentFilter);
+ }
+ }
+ }
+ };
+
+ private final BroadcastReceiver mOnScreenOffHandler = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ synchronized (mLock) {
+ updateLocked(0, 0);
+ try {
+ getContext().unregisterReceiver(mOnScreenOffHandler);
+ } catch (IllegalArgumentException e) {
+ // we ignore this exception if the receiver is unregistered already.
}
}
}
@@ -227,8 +261,10 @@
private final ContentObserver mDarkThemeObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange, Uri uri) {
- final int mode = Secure.getIntForUser(getContext().getContentResolver(),
- Secure.UI_NIGHT_MODE, mNightMode, 0);
+ int mode = Secure.getIntForUser(getContext().getContentResolver(), Secure.UI_NIGHT_MODE,
+ mNightMode, 0);
+ mode = mode == UiModeManager.MODE_NIGHT_AUTO
+ ? UiModeManager.MODE_NIGHT_YES : UiModeManager.MODE_NIGHT_NO;
SystemProperties.set(SYSTEM_PROPERTY_DEVICE_THEME, Integer.toString(mode));
}
};
@@ -247,6 +283,7 @@
final PowerManager powerManager =
(PowerManager) context.getSystemService(Context.POWER_SERVICE);
mWakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
+ mWindowManager = LocalServices.getService(WindowManagerInternal.class);
// If setup isn't complete for this user listen for completion so we can unblock
// being able to send a night mode configuration change event
@@ -313,6 +350,16 @@
false, mDarkThemeObserver, 0);
}
+ @VisibleForTesting
+ protected IUiModeManager getService() {
+ return mService;
+ }
+
+ @VisibleForTesting
+ protected Configuration getConfiguration() {
+ return mConfiguration;
+ }
+
// Records whether setup wizard has happened or not and adds an observer for this user if not.
private void verifySetupWizardCompleted() {
final Context context = getContext();
@@ -347,8 +394,11 @@
if (mSetupWizardComplete) {
mNightMode = Secure.getIntForUser(context.getContentResolver(),
Secure.UI_NIGHT_MODE, defaultNightMode, userId);
+ mNightModeOverride = Secure.getIntForUser(context.getContentResolver(),
+ OVERRIDE_NIGHT_MODE, defaultNightMode, userId);
} else {
mNightMode = defaultNightMode;
+ mNightModeOverride = defaultNightMode;
}
return oldNightMode != mNightMode;
@@ -474,14 +524,30 @@
try {
synchronized (mLock) {
if (mNightMode != mode) {
+ if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
+ try {
+ getContext().unregisterReceiver(mOnScreenOffHandler);
+ } catch (IllegalArgumentException e) {
+ // we ignore this exception if the receiver is unregistered already.
+ }
+ }
// Only persist setting if not in car mode
if (!mCarModeEnabled) {
Secure.putIntForUser(getContext().getContentResolver(),
Secure.UI_NIGHT_MODE, mode, user);
+ Secure.putIntForUser(getContext().getContentResolver(),
+ OVERRIDE_NIGHT_MODE, mNightModeOverride, user);
}
mNightMode = mode;
- updateLocked(0, 0);
+ mNightModeOverride = mode;
+ //on screen off will update configuration instead
+ if (mNightMode != UiModeManager.MODE_NIGHT_AUTO) {
+ updateLocked(0, 0);
+ } else {
+ getContext().registerReceiver(
+ mOnScreenOffHandler, new IntentFilter(ACTION_SCREEN_OFF));
+ }
}
}
} finally {
@@ -521,6 +587,34 @@
if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
dumpImpl(pw);
}
+
+ @Override
+ public boolean setNightModeActivated(boolean active) {
+ synchronized (mLock) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
+ try {
+ getContext().unregisterReceiver(mOnScreenOffHandler);
+ } catch (IllegalArgumentException e) {
+ }
+ mNightModeOverride = active
+ ? UiModeManager.MODE_NIGHT_YES : UiModeManager.MODE_NIGHT_NO;
+ } else if (mNightMode == UiModeManager.MODE_NIGHT_NO
+ && active) {
+ mNightMode = UiModeManager.MODE_NIGHT_YES;
+ } else if (mNightMode == UiModeManager.MODE_NIGHT_YES
+ && !active) {
+ mNightMode = UiModeManager.MODE_NIGHT_NO;
+ }
+ updateConfigurationLocked();
+ sendConfigurationLocked();
+ return true;
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
};
void dumpImpl(PrintWriter pw) {
@@ -1022,6 +1116,20 @@
if (state != null) {
mComputedNightMode = state.isNight();
}
+ if (mNightModeOverride == UiModeManager.MODE_NIGHT_YES && !mComputedNightMode) {
+ mComputedNightMode = true;
+ return;
+ }
+ if (mNightModeOverride == UiModeManager.MODE_NIGHT_NO && mComputedNightMode) {
+ mComputedNightMode = false;
+ return;
+ }
+
+ mNightModeOverride = mNightMode;
+ final int user = UserHandle.getCallingUserId();
+ Secure.putIntForUser(getContext().getContentResolver(),
+ OVERRIDE_NIGHT_MODE, mNightModeOverride, user);
+
}
}
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 1432f57..0d28822 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -1289,6 +1289,33 @@
}
protected UserAccounts getUserAccounts(int userId) {
+ try {
+ return getUserAccountsNotChecked(userId);
+ } catch (RuntimeException e) {
+ if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+ // Let it go...
+ throw e;
+ }
+ // User accounts database is corrupted, we must wipe out the whole user, otherwise the
+ // system will crash indefinitely
+ Slog.wtf(TAG, "Removing user " + userId + " due to exception (" + e + ") reading its "
+ + "account database");
+ if (userId == ActivityManager.getCurrentUser() && userId != UserHandle.USER_SYSTEM) {
+ Slog.i(TAG, "Switching to system user first");
+ try {
+ ActivityManager.getService().switchUser(UserHandle.USER_SYSTEM);
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Could not switch to " + UserHandle.USER_SYSTEM + ": " + re);
+ }
+ }
+ if (!getUserManager().removeUserEvenWhenDisallowed(userId)) {
+ Slog.e(TAG, "could not remove user " + userId);
+ }
+ throw e;
+ }
+ }
+
+ private UserAccounts getUserAccountsNotChecked(int userId) {
synchronized (mUsers) {
UserAccounts accounts = mUsers.get(userId);
boolean validateAccounts = false;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5ffdf02..0ee29f3 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5283,7 +5283,7 @@
storageManager.commitChanges();
} catch (Exception e) {
PowerManager pm = (PowerManager)
- mInjector.getContext().getSystemService(Context.POWER_SERVICE);
+ mContext.getSystemService(Context.POWER_SERVICE);
pm.reboot("Checkpoint commit failed");
}
@@ -18030,7 +18030,7 @@
@Override
public int getCurrentUserId() {
- return mUserController.getCurrentUserIdLU();
+ return mUserController.getCurrentUserId();
}
@Override
diff --git a/services/core/java/com/android/server/am/CarUserSwitchingDialog.java b/services/core/java/com/android/server/am/CarUserSwitchingDialog.java
index c7de7b1..ebfc2a0 100644
--- a/services/core/java/com/android/server/am/CarUserSwitchingDialog.java
+++ b/services/core/java/com/android/server/am/CarUserSwitchingDialog.java
@@ -37,6 +37,7 @@
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
+
import com.android.internal.R;
diff --git a/services/core/java/com/android/server/am/OWNERS b/services/core/java/com/android/server/am/OWNERS
index 2f9a5c9..7cc2e8e 100644
--- a/services/core/java/com/android/server/am/OWNERS
+++ b/services/core/java/com/android/server/am/OWNERS
@@ -35,3 +35,5 @@
narayan@google.com
per-file SettingsToPropertiesMapper.java = omakoto@google.com, svetoslavganov@google.com, yamasani@google.com
+
+per-file CarUserSwitchingDialog.java = keunyoung@google.com, felipeal@google.com, gurunagarajan@google.com
diff --git a/services/core/java/com/android/server/am/PendingIntentController.java b/services/core/java/com/android/server/am/PendingIntentController.java
index d75591c..df76713 100644
--- a/services/core/java/com/android/server/am/PendingIntentController.java
+++ b/services/core/java/com/android/server/am/PendingIntentController.java
@@ -43,6 +43,7 @@
import com.android.internal.os.IResultReceiver;
import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.server.AlarmManagerInternal;
import com.android.server.LocalServices;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.SafeActivityOptions;
@@ -293,6 +294,8 @@
PendingIntentController::handlePendingIntentCancelled, this, callbacks);
mH.sendMessage(m);
}
+ final AlarmManagerInternal ami = LocalServices.getService(AlarmManagerInternal.class);
+ ami.remove(new PendingIntent(rec));
}
private void handlePendingIntentCancelled(RemoteCallbackList<IResultReceiver> callbacks) {
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index a2670d8..1d099c8 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -1329,7 +1329,7 @@
final int procCount = procs.size();
for (int i = 0; i < procCount; i++) {
final int procUid = procs.keyAt(i);
- if (UserHandle.isApp(procUid) || !UserHandle.isSameUser(procUid, uid)) {
+ if (!UserHandle.isCore(procUid) || !UserHandle.isSameUser(procUid, uid)) {
// Don't use an app process or different user process for system component.
continue;
}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index b311233..598a68e 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -134,12 +134,12 @@
static final int CONTINUE_USER_SWITCH_MSG = 20;
static final int USER_SWITCH_TIMEOUT_MSG = 30;
static final int START_PROFILES_MSG = 40;
- static final int SYSTEM_USER_START_MSG = 50;
- static final int SYSTEM_USER_CURRENT_MSG = 60;
+ static final int USER_START_MSG = 50;
+ static final int USER_CURRENT_MSG = 60;
static final int FOREGROUND_PROFILE_CHANGED_MSG = 70;
static final int REPORT_USER_SWITCH_COMPLETE_MSG = 80;
static final int USER_SWITCH_CALLBACKS_TIMEOUT_MSG = 90;
- static final int SYSTEM_USER_UNLOCK_MSG = 100;
+ static final int USER_UNLOCK_MSG = 100;
static final int REPORT_LOCKED_BOOT_COMPLETE_MSG = 110;
static final int START_USER_SWITCH_FG_MSG = 120;
@@ -369,16 +369,18 @@
}
}
- mHandler.sendMessage(mHandler.obtainMessage(REPORT_LOCKED_BOOT_COMPLETE_MSG,
- userId, 0));
- Intent intent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED, null);
- intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
- intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
- | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
- mInjector.broadcastIntent(intent, null, resultTo, 0, null, null,
- new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
- AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID,
- Binder.getCallingUid(), Binder.getCallingPid(), userId);
+ if (!mInjector.getUserManager().isPreCreated(userId)) {
+ mHandler.sendMessage(mHandler.obtainMessage(REPORT_LOCKED_BOOT_COMPLETE_MSG,
+ userId, 0));
+ Intent intent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED, null);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+ intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
+ | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+ mInjector.broadcastIntent(intent, null, resultTo, 0, null, null,
+ new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
+ AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID,
+ Binder.getCallingUid(), Binder.getCallingPid(), userId);
+ }
}
// We need to delay unlocking managed profiles until the parent user
@@ -439,8 +441,7 @@
// Dispatch unlocked to system services; when fully dispatched,
// that calls through to the next "unlocked" phase
- mHandler.obtainMessage(SYSTEM_USER_UNLOCK_MSG, userId, 0, uss)
- .sendToTarget();
+ mHandler.obtainMessage(USER_UNLOCK_MSG, userId, 0, uss).sendToTarget();
});
return true;
}
@@ -556,6 +557,17 @@
}
}
+ if (userInfo.preCreated) {
+ Slog.i(TAG, "Stopping pre-created user " + userInfo.toFullString());
+ // Pre-created user was started right after creation so services could properly
+ // intialize it; it should be stopped right away as it's not really a "real" user.
+ // TODO(b/140750212): in the long-term, we should add a onCreateUser() callback
+ // on SystemService instead.
+ stopUser(userInfo.id, /* force= */ true, /* stopUserCallback= */ null,
+ /* keyEvictedCallback= */ null);
+ return;
+ }
+
// Spin up app widgets prior to boot-complete, so they can be ready promptly
mInjector.startUserWidgets(userId);
@@ -808,7 +820,8 @@
mInjector.systemServiceManagerCleanupUser(userId);
mInjector.stackSupervisorRemoveUser(userId);
// Remove the user if it is ephemeral.
- if (getUserInfo(userId).isEphemeral()) {
+ UserInfo userInfo = getUserInfo(userId);
+ if (userInfo.isEphemeral() && !userInfo.preCreated) {
mInjector.getUserManager().removeUserEvenWhenDisallowed(userId);
}
@@ -985,11 +998,13 @@
* <ul>
* <li>{@link Intent#ACTION_USER_STARTED} - sent to registered receivers of the new user
* <li>{@link Intent#ACTION_USER_BACKGROUND} - sent to registered receivers of the outgoing
- * user and all profiles of this user. Sent only if {@code foreground} parameter is true
+ * user and all profiles of this user. Sent only if {@code foreground} parameter is
+ * {@code false}
* <li>{@link Intent#ACTION_USER_FOREGROUND} - sent to registered receivers of the new
- * user and all profiles of this user. Sent only if {@code foreground} parameter is true
+ * user and all profiles of this user. Sent only if {@code foreground} parameter is
+ * {@code true}
* <li>{@link Intent#ACTION_USER_SWITCHED} - sent to registered receivers of the new user.
- * Sent only if {@code foreground} parameter is true
+ * Sent only if {@code foreground} parameter is {@code true}
* <li>{@link Intent#ACTION_USER_STARTING} - ordered broadcast sent to registered receivers
* of the new fg user
* <li>{@link Intent#ACTION_LOCKED_BOOT_COMPLETED} - ordered broadcast sent to receivers of
@@ -1063,6 +1078,11 @@
return false;
}
+ if (foreground && userInfo.preCreated) {
+ Slog.w(TAG, "Cannot start pre-created user #" + userId + " as foreground");
+ return false;
+ }
+
if (foreground && mUserSwitchUiEnabled) {
mInjector.getWindowManager().startFreezingScreen(
R.anim.screen_user_exit, R.anim.screen_user_enter);
@@ -1157,13 +1177,11 @@
// Booting up a new user, need to tell system services about it.
// Note that this is on the same handler as scheduling of broadcasts,
// which is important because it needs to go first.
- mHandler.sendMessage(
- mHandler.obtainMessage(SYSTEM_USER_START_MSG, userId, 0));
+ mHandler.sendMessage(mHandler.obtainMessage(USER_START_MSG, userId, 0));
}
if (foreground) {
- mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_CURRENT_MSG, userId,
- oldUserId));
+ mHandler.sendMessage(mHandler.obtainMessage(USER_CURRENT_MSG, userId, oldUserId));
mHandler.removeMessages(REPORT_USER_SWITCH_MSG);
mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_MSG,
@@ -1172,6 +1190,10 @@
oldUserId, userId, uss), USER_SWITCH_TIMEOUT_MS);
}
+ if (userInfo.preCreated) {
+ needStart = false;
+ }
+
if (needStart) {
// Send USER_STARTED broadcast
Intent intent = new Intent(Intent.ACTION_USER_STARTED);
@@ -2129,13 +2151,13 @@
case START_PROFILES_MSG:
startProfiles();
break;
- case SYSTEM_USER_START_MSG:
+ case USER_START_MSG:
mInjector.batteryStatsServiceNoteEvent(
BatteryStats.HistoryItem.EVENT_USER_RUNNING_START,
Integer.toString(msg.arg1), msg.arg1);
mInjector.getSystemServiceManager().startUser(msg.arg1);
break;
- case SYSTEM_USER_UNLOCK_MSG:
+ case USER_UNLOCK_MSG:
final int userId = msg.arg1;
mInjector.getSystemServiceManager().unlockUser(userId);
// Loads recents on a worker thread that allows disk I/O
@@ -2144,7 +2166,7 @@
});
finishUserUnlocked((UserState) msg.obj);
break;
- case SYSTEM_USER_CURRENT_MSG:
+ case USER_CURRENT_MSG:
mInjector.batteryStatsServiceNoteEvent(
BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_FINISH,
Integer.toString(msg.arg2), msg.arg2);
diff --git a/services/core/java/com/android/server/am/UserSwitchingDialog.java b/services/core/java/com/android/server/am/UserSwitchingDialog.java
index 98f5557..0ec4454 100644
--- a/services/core/java/com/android/server/am/UserSwitchingDialog.java
+++ b/services/core/java/com/android/server/am/UserSwitchingDialog.java
@@ -24,6 +24,7 @@
import android.os.Message;
import android.os.UserHandle;
import android.os.UserManager;
+import android.util.Slog;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewTreeObserver;
@@ -46,6 +47,9 @@
// Time to wait for the onWindowShown() callback before continuing the user switch
private static final int WINDOW_SHOWN_TIMEOUT_MS = 3000;
+ // User switching doesn't happen that frequently, so it doesn't hurt to have it always on
+ protected static final boolean DEBUG = true;
+
private final ActivityManagerService mService;
private final int mUserId;
private static final int MSG_START_USER = 1;
@@ -118,7 +122,7 @@
@Override
public void show() {
- // Slog.v(TAG, "show called");
+ if (DEBUG) Slog.d(TAG, "show called");
super.show();
final View decorView = getWindow().getDecorView();
if (decorView != null) {
@@ -132,13 +136,14 @@
@Override
public void onWindowShown() {
- // Slog.v(TAG, "onWindowShown called");
+ if (DEBUG) Slog.d(TAG, "onWindowShown called");
startUser();
}
void startUser() {
synchronized (this) {
if (!mStartedUser) {
+ Slog.i(TAG, "starting user " + mUserId);
mService.mUserController.startUserInForeground(mUserId);
dismiss();
mStartedUser = true;
@@ -147,6 +152,8 @@
decorView.getViewTreeObserver().removeOnWindowShownListener(this);
}
mHandler.removeMessages(MSG_START_USER);
+ } else {
+ Slog.i(TAG, "user " + mUserId + " already started");
}
}
}
@@ -156,6 +163,8 @@
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_START_USER:
+ Slog.w(TAG, "user switch window not shown in "
+ + WINDOW_SHOWN_TIMEOUT_MS + " ms");
startUser();
break;
}
diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java
index 126beef..765fdb6 100644
--- a/services/core/java/com/android/server/display/BrightnessTracker.java
+++ b/services/core/java/com/android/server/display/BrightnessTracker.java
@@ -94,6 +94,8 @@
static final String TAG = "BrightnessTracker";
static final boolean DEBUG = false;
+ @VisibleForTesting
+ static final boolean ENABLE_COLOR_SAMPLING = false;
private static final String EVENTS_FILE = "brightness_events.xml";
private static final String AMBIENT_BRIGHTNESS_STATS_FILE = "ambient_brightness_stats.xml";
@@ -757,7 +759,8 @@
}
private void enableColorSampling() {
- if (!mInjector.isBrightnessModeAutomatic(mContentResolver)
+ if (!ENABLE_COLOR_SAMPLING
+ || !mInjector.isBrightnessModeAutomatic(mContentResolver)
|| !mInjector.isInteractive(mContext)
|| mColorSamplingEnabled) {
return;
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index 0bf43b6..2dc2cf0 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -589,16 +589,18 @@
if (immediate) {
dtm.setColorMatrix(tintController.getLevel(), to);
} else {
- tintController.setAnimator(ValueAnimator.ofObject(COLOR_MATRIX_EVALUATOR,
- from == null ? MATRIX_IDENTITY : from, to));
- tintController.getAnimator().setDuration(TRANSITION_DURATION);
- tintController.getAnimator().setInterpolator(AnimationUtils.loadInterpolator(
+ TintValueAnimator valueAnimator = TintValueAnimator.ofMatrix(COLOR_MATRIX_EVALUATOR,
+ from == null ? MATRIX_IDENTITY : from, to);
+ tintController.setAnimator(valueAnimator);
+ valueAnimator.setDuration(TRANSITION_DURATION);
+ valueAnimator.setInterpolator(AnimationUtils.loadInterpolator(
getContext(), android.R.interpolator.fast_out_slow_in));
- tintController.getAnimator().addUpdateListener((ValueAnimator animator) -> {
+ valueAnimator.addUpdateListener((ValueAnimator animator) -> {
final float[] value = (float[]) animator.getAnimatedValue();
dtm.setColorMatrix(tintController.getLevel(), value);
+ ((TintValueAnimator) animator).updateMinMaxComponents();
});
- tintController.getAnimator().addListener(new AnimatorListenerAdapter() {
+ valueAnimator.addListener(new AnimatorListenerAdapter() {
private boolean mIsCancelled;
@@ -609,9 +611,14 @@
@Override
public void onAnimationEnd(Animator animator) {
+ TintValueAnimator t = (TintValueAnimator) animator;
Slog.d(TAG, tintController.getClass().getSimpleName()
+ " Animation cancelled: " + mIsCancelled
- + " to matrix: " + TintController.matrixToString(to, 16));
+ + " to matrix: " + TintController.matrixToString(to, 16)
+ + " min matrix coefficients: "
+ + TintController.matrixToString(t.getMin(), 16)
+ + " max matrix coefficients: "
+ + TintController.matrixToString(t.getMax(), 16));
if (!mIsCancelled) {
// Ensure final color matrix is set at the end of the animation. If the
// animation is cancelled then don't set the final color matrix so the new
@@ -621,7 +628,7 @@
tintController.setAnimator(null);
}
});
- tintController.getAnimator().start();
+ valueAnimator.start();
}
}
@@ -1109,6 +1116,51 @@
}
/**
+ * Only animates matrices and saves min and max coefficients for logging.
+ */
+ static class TintValueAnimator extends ValueAnimator {
+ private float[] min;
+ private float[] max;
+
+ public static TintValueAnimator ofMatrix(ColorMatrixEvaluator evaluator,
+ Object... values) {
+ TintValueAnimator anim = new TintValueAnimator();
+ anim.setObjectValues(values);
+ anim.setEvaluator(evaluator);
+ if (values == null || values.length == 0) {
+ return null;
+ }
+ float[] m = (float[]) values[0];
+ anim.min = new float[m.length];
+ anim.max = new float[m.length];
+ for (int i = 0; i < m.length; ++i) {
+ anim.min[i] = Float.MAX_VALUE;
+ anim.max[i] = Float.MIN_VALUE;
+ }
+ return anim;
+ }
+
+ public void updateMinMaxComponents() {
+ float[] value = (float[]) getAnimatedValue();
+ if (value == null) {
+ return;
+ }
+ for (int i = 0; i < value.length; ++i) {
+ min[i] = Math.min(min[i], value[i]);
+ max[i] = Math.max(max[i], value[i]);
+ }
+ }
+
+ public float[] getMin() {
+ return min;
+ }
+
+ public float[] getMax() {
+ return max;
+ }
+ }
+
+ /**
* Interpolates between two 4x4 color transform matrices (in column-major order).
*/
private static class ColorMatrixEvaluator implements TypeEvaluator<float[]> {
diff --git a/services/core/java/com/android/server/display/color/TintController.java b/services/core/java/com/android/server/display/color/TintController.java
index 8d8b9b2..422dd32 100644
--- a/services/core/java/com/android/server/display/color/TintController.java
+++ b/services/core/java/com/android/server/display/color/TintController.java
@@ -24,14 +24,14 @@
abstract class TintController {
- private ValueAnimator mAnimator;
+ private ColorDisplayService.TintValueAnimator mAnimator;
private Boolean mIsActivated;
- public ValueAnimator getAnimator() {
+ public ColorDisplayService.TintValueAnimator getAnimator() {
return mAnimator;
}
- public void setAnimator(ValueAnimator animator) {
+ public void setAnimator(ColorDisplayService.TintValueAnimator animator) {
mAnimator = animator;
}
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index 3fc8438..8d0397e 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -75,6 +75,8 @@
import com.android.internal.location.ProviderRequest;
import com.android.internal.location.gnssmetrics.GnssMetrics;
import com.android.internal.telephony.TelephonyIntents;
+import com.android.server.DeviceIdleController;
+import com.android.server.LocalServices;
import com.android.server.location.GnssSatelliteBlacklistHelper.GnssSatelliteBlacklistCallback;
import com.android.server.location.NtpTimeHelper.InjectNtpTimeCallback;
@@ -178,6 +180,7 @@
private static final int AGPS_SUPL_MODE_MSA = 0x02;
private static final int AGPS_SUPL_MODE_MSB = 0x01;
+ private static final int UPDATE_LOW_POWER_MODE = 1;
private static final int SET_REQUEST = 3;
private static final int INJECT_NTP_TIME = 5;
// PSDS stands for Predicted Satellite Data Service
@@ -371,6 +374,12 @@
private boolean mDisableGpsForPowerManager = false;
/**
+ * True if the device idle controller has determined that the device is stationary. This is only
+ * updated when the device enters idle mode.
+ */
+ private volatile boolean mIsDeviceStationary = false;
+
+ /**
* Properties loaded from PROPERTIES_FILE.
* It must be accessed only inside {@link #mHandler}.
*/
@@ -462,6 +471,15 @@
public GnssNavigationMessageProvider getGnssNavigationMessageProvider() {
return mGnssNavigationMessageProvider;
}
+
+ private final DeviceIdleController.StationaryListener mDeviceIdleStationaryListener =
+ isStationary -> {
+ mIsDeviceStationary = isStationary;
+ // Call updateLowPowerMode on handler thread so it's always called from the same
+ // thread.
+ mHandler.sendEmptyMessage(UPDATE_LOW_POWER_MODE);
+ };
+
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -478,11 +496,22 @@
case ALARM_TIMEOUT:
hibernate();
break;
- case PowerManager.ACTION_POWER_SAVE_MODE_CHANGED:
case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED:
+ DeviceIdleController.LocalService deviceIdleService = LocalServices.getService(
+ DeviceIdleController.LocalService.class);
+ if (mPowerManager.isDeviceIdleMode()) {
+ deviceIdleService.registerStationaryListener(mDeviceIdleStationaryListener);
+ } else {
+ deviceIdleService.unregisterStationaryListener(
+ mDeviceIdleStationaryListener);
+ }
+ // Intentional fall-through.
+ case PowerManager.ACTION_POWER_SAVE_MODE_CHANGED:
case Intent.ACTION_SCREEN_OFF:
case Intent.ACTION_SCREEN_ON:
- updateLowPowerMode();
+ // Call updateLowPowerMode on handler thread so it's always called from the
+ // same thread.
+ mHandler.sendEmptyMessage(UPDATE_LOW_POWER_MODE);
break;
case CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED:
case TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED:
@@ -542,10 +571,9 @@
}
private void updateLowPowerMode() {
- // Disable GPS if we are in device idle mode.
- boolean disableGpsForPowerManager = mPowerManager.isDeviceIdleMode();
- final PowerSaveState result =
- mPowerManager.getPowerSaveState(ServiceType.LOCATION);
+ // Disable GPS if we are in device idle mode and the device is stationary.
+ boolean disableGpsForPowerManager = mPowerManager.isDeviceIdleMode() && mIsDeviceStationary;
+ final PowerSaveState result = mPowerManager.getPowerSaveState(ServiceType.LOCATION);
switch (result.locationMode) {
case PowerManager.LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF:
case PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF:
@@ -2043,6 +2071,9 @@
case REPORT_SV_STATUS:
handleReportSvStatus((SvStatusInfo) msg.obj);
break;
+ case UPDATE_LOW_POWER_MODE:
+ updateLowPowerMode();
+ break;
}
if (msg.arg2 == 1) {
// wakelock was taken for this message, release it
diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java
index 9b9f4de..bc05154 100644
--- a/services/core/java/com/android/server/notification/NotificationComparator.java
+++ b/services/core/java/com/android/server/notification/NotificationComparator.java
@@ -129,6 +129,12 @@
return -1 * Integer.compare(leftPriority, rightPriority);
}
+ final boolean leftInterruptive = left.isInterruptive();
+ final boolean rightInterruptive = right.isInterruptive();
+ if (leftInterruptive != rightInterruptive) {
+ return -1 * Boolean.compare(leftInterruptive, rightInterruptive);
+ }
+
// then break ties by time, most recent first
return -1 * Long.compare(left.getRankingTimeMs(), right.getRankingTimeMs());
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 6f45825..aa8d33d 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -163,6 +163,7 @@
import android.os.IInterface;
import android.os.Looper;
import android.os.Message;
+import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
@@ -281,6 +282,9 @@
public static final boolean ENABLE_CHILD_NOTIFICATIONS
= SystemProperties.getBoolean("debug.child_notifs", true);
+ // pullStats report request: undecorated remote view stats
+ public static final int REPORT_REMOTE_VIEWS = 0x01;
+
static final boolean DEBUG_INTERRUPTIVENESS = SystemProperties.getBoolean(
"debug.notification.interruptiveness", false);
@@ -3733,6 +3737,8 @@
try {
if (filter.stats) {
dumpJson(pw, filter);
+ } else if (filter.rvStats) {
+ dumpRemoteViewStats(pw, filter);
} else if (filter.proto) {
dumpProto(fd, filter);
} else if (filter.criticalPriority) {
@@ -4209,6 +4215,49 @@
new NotificationShellCmd(NotificationManagerService.this)
.exec(this, in, out, err, args, callback, resultReceiver);
}
+
+ /**
+ * Get stats committed after startNs
+ *
+ * @param startNs Report stats committed after this time in nanoseconds.
+ * @param report Indicatess which section to include in the stats.
+ * @param doAgg Whether to aggregate the stats or keep them separated.
+ * @param out List of protos of individual commits or one representing the
+ * aggregate.
+ * @return the report time in nanoseconds, or 0 on error.
+ */
+ @Override
+ public long pullStats(long startNs, int report, boolean doAgg,
+ List<ParcelFileDescriptor> out) {
+ checkCallerIsSystemOrShell();
+ long startMs = TimeUnit.MILLISECONDS.convert(startNs, TimeUnit.NANOSECONDS);
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ switch (report) {
+ case REPORT_REMOTE_VIEWS:
+ Slog.e(TAG, "pullStats REPORT_REMOTE_VIEWS from: "
+ + startMs + " wtih " + doAgg);
+ PulledStats stats = mUsageStats.remoteViewStats(startMs, doAgg);
+ if (stats != null) {
+ out.add(stats.toParcelFileDescriptor(report));
+ Slog.e(TAG, "exiting pullStats with: " + out.size());
+ long endNs = TimeUnit.NANOSECONDS
+ .convert(stats.endTimeMs(), TimeUnit.MILLISECONDS);
+ return endNs;
+ }
+ Slog.e(TAG, "null stats for: " + report);
+ }
+ } catch (IOException e) {
+
+ Slog.e(TAG, "exiting pullStats: on error", e);
+ return 0;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ Slog.e(TAG, "exiting pullStats: bad request");
+ return 0;
+ }
};
@VisibleForTesting
@@ -4424,6 +4473,15 @@
pw.println(dump);
}
+ private void dumpRemoteViewStats(PrintWriter pw, @NonNull DumpFilter filter) {
+ PulledStats stats = mUsageStats.remoteViewStats(filter.since, true);
+ if (stats == null) {
+ pw.println("no remote view stats reported.");
+ return;
+ }
+ stats.dump(REPORT_REMOTE_VIEWS, pw, filter);
+ }
+
private void dumpProto(FileDescriptor fd, @NonNull DumpFilter filter) {
final ProtoOutputStream proto = new ProtoOutputStream(fd);
synchronized (mNotificationLock) {
@@ -5356,16 +5414,8 @@
}
synchronized (mNotificationLock) {
- // If the notification is currently enqueued, repost this runnable so it has a
- // chance to notify listeners
- if ((findNotificationByListLocked(mEnqueuedNotifications, mPkg, mTag, mId, mUserId))
- != null) {
- mHandler.post(this);
- return;
- }
- // Look for the notification in the posted list, since we already checked enqueued.
- NotificationRecord r =
- findNotificationByListLocked(mNotificationList, mPkg, mTag, mId, mUserId);
+ // Look for the notification, searching both the posted and enqueued lists.
+ NotificationRecord r = findNotificationLocked(mPkg, mTag, mId, mUserId);
if (r != null) {
// The notification was found, check if it should be removed.
@@ -5550,7 +5600,9 @@
notification.flags |=
old.getNotification().flags & FLAG_FOREGROUND_SERVICE;
r.isUpdate = true;
- r.setTextChanged(isVisuallyInterruptive(old, r));
+ final boolean isInterruptive = isVisuallyInterruptive(old, r);
+ r.setTextChanged(isInterruptive);
+ r.setInterruptive(isInterruptive);
}
mNotificationsByKey.put(n.getKey(), r);
@@ -5649,7 +5701,6 @@
Notification oldN = old.sbn.getNotification();
Notification newN = r.sbn.getNotification();
-
if (oldN.extras == null || newN.extras == null) {
if (DEBUG_INTERRUPTIVENESS) {
Slog.v(TAG, "INTERRUPTIVENESS: "
@@ -5681,6 +5732,7 @@
}
return true;
}
+
// Do not compare Spannables (will always return false); compare unstyled Strings
final String oldText = String.valueOf(oldN.extras.get(Notification.EXTRA_TEXT));
final String newText = String.valueOf(newN.extras.get(Notification.EXTRA_TEXT));
@@ -5695,6 +5747,7 @@
}
return true;
}
+
if (oldN.hasCompletedProgress() != newN.hasCompletedProgress()) {
if (DEBUG_INTERRUPTIVENESS) {
Slog.v(TAG, "INTERRUPTIVENESS: "
@@ -5702,6 +5755,16 @@
}
return true;
}
+
+ // Fields below are invisible to bubbles.
+ if (r.canBubble()) {
+ if (DEBUG_INTERRUPTIVENESS) {
+ Slog.v(TAG, "INTERRUPTIVENESS: "
+ + r.getKey() + " is not interruptive: bubble");
+ }
+ return false;
+ }
+
// Actions
if (Notification.areActionsVisiblyDifferent(oldN, newN)) {
if (DEBUG_INTERRUPTIVENESS) {
@@ -5735,7 +5798,6 @@
} catch (Exception e) {
Slog.w(TAG, "error recovering builder", e);
}
-
return false;
}
@@ -5930,12 +5992,17 @@
Slog.v(TAG, "INTERRUPTIVENESS: "
+ record.getKey() + " is not interruptive: summary");
}
+ } else if (record.canBubble()) {
+ if (DEBUG_INTERRUPTIVENESS) {
+ Slog.v(TAG, "INTERRUPTIVENESS: "
+ + record.getKey() + " is not interruptive: bubble");
+ }
} else {
+ record.setInterruptive(true);
if (DEBUG_INTERRUPTIVENESS) {
Slog.v(TAG, "INTERRUPTIVENESS: "
+ record.getKey() + " is interruptive: alerted");
}
- record.setInterruptive(true);
}
MetricsLogger.action(record.getLogMaker()
.setCategory(MetricsEvent.NOTIFICATION_ALERT)
@@ -6294,15 +6361,21 @@
int indexBefore = findNotificationRecordIndexLocked(record);
boolean interceptBefore = record.isIntercepted();
int visibilityBefore = record.getPackageVisibilityOverride();
+ boolean interruptiveBefore = record.isInterruptive();
+
recon.applyChangesLocked(record);
applyZenModeLocked(record);
mRankingHelper.sort(mNotificationList);
- int indexAfter = findNotificationRecordIndexLocked(record);
- boolean interceptAfter = record.isIntercepted();
- int visibilityAfter = record.getPackageVisibilityOverride();
- changed = indexBefore != indexAfter || interceptBefore != interceptAfter
- || visibilityBefore != visibilityAfter;
- if (interceptBefore && !interceptAfter
+ boolean indexChanged = indexBefore != findNotificationRecordIndexLocked(record);
+ boolean interceptChanged = interceptBefore != record.isIntercepted();
+ boolean visibilityChanged = visibilityBefore != record.getPackageVisibilityOverride();
+
+ // Broadcast isInterruptive changes for bubbles.
+ boolean interruptiveChanged =
+ record.canBubble() && (interruptiveBefore != record.isInterruptive());
+
+ changed = indexChanged || interceptChanged || visibilityChanged || interruptiveChanged;
+ if (interceptBefore && !record.isIntercepted()
&& record.isNewEnoughForAlerting(System.currentTimeMillis())) {
buzzBeepBlinkLocked(record);
}
@@ -7433,7 +7506,8 @@
record.getSound() != null || record.getVibration() != null,
record.getSystemGeneratedSmartActions(),
record.getSmartReplies(),
- record.canBubble()
+ record.canBubble(),
+ record.isInterruptive()
);
rankings.add(ranking);
}
@@ -8542,6 +8616,7 @@
public boolean zen;
public long since;
public boolean stats;
+ public boolean rvStats;
public boolean redact = true;
public boolean proto = false;
public boolean criticalPriority = false;
@@ -8577,6 +8652,14 @@
} else {
filter.since = 0;
}
+ } else if ("--remote-view-stats".equals(a)) {
+ filter.rvStats = true;
+ if (ai < args.length-1) {
+ ai++;
+ filter.since = Long.parseLong(args[ai]);
+ } else {
+ filter.since = 0;
+ }
} else if (PRIORITY_ARG.equals(a)) {
// Bugreport will call the service twice with priority arguments, first to dump
// critical sections and then non critical ones. Set approriate filters
@@ -8621,6 +8704,7 @@
@VisibleForTesting
void resetAssistantUserSet(int userId) {
+ checkCallerIsSystemOrShell();
mAssistants.setUserSet(userId, false);
handleSavePolicyFile();
}
@@ -8628,12 +8712,14 @@
@VisibleForTesting
@Nullable
ComponentName getApprovedAssistant(int userId) {
+ checkCallerIsSystemOrShell();
List<ComponentName> allowedComponents = mAssistants.getAllowedComponents(userId);
return CollectionUtils.firstOrNull(allowedComponents);
}
@VisibleForTesting
protected void simulatePackageSuspendBroadcast(boolean suspend, String pkg) {
+ checkCallerIsSystemOrShell();
// only use for testing: mimic receive broadcast that package is (un)suspended
// but does not actually (un)suspend the package
final Bundle extras = new Bundle();
@@ -8650,6 +8736,7 @@
@VisibleForTesting
protected void simulatePackageDistractionBroadcast(int flag, String[] pkgs) {
+ checkCallerIsSystemOrShell();
// only use for testing: mimic receive broadcast that package is (un)distracting
// but does not actually register that info with packagemanager
final Bundle extras = new Bundle();
diff --git a/services/core/java/com/android/server/notification/NotificationShellCmd.java b/services/core/java/com/android/server/notification/NotificationShellCmd.java
index dd0f420..c6c9896 100644
--- a/services/core/java/com/android/server/notification/NotificationShellCmd.java
+++ b/services/core/java/com/android/server/notification/NotificationShellCmd.java
@@ -16,6 +16,12 @@
package com.android.server.notification;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_ALARMS;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_UNKNOWN;
+
import android.app.ActivityManager;
import android.app.INotificationManager;
import android.app.Notification;
@@ -26,6 +32,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.res.Resources;
import android.graphics.drawable.BitmapDrawable;
@@ -47,8 +54,8 @@
* Implementation of `cmd notification` in NotificationManagerService.
*/
public class NotificationShellCmd extends ShellCommand {
- private static final String USAGE =
- "usage: cmd notification SUBCMD [args]\n\n"
+ private static final String TAG = "NotifShellCmd";
+ private static final String USAGE = "usage: cmd notification SUBCMD [args]\n\n"
+ "SUBCMDs:\n"
+ " allow_listener COMPONENT [user_id (current user if not specified)]\n"
+ " disallow_listener COMPONENT [user_id (current user if not specified)]\n"
@@ -89,18 +96,19 @@
+ "an <intentspec> is (broadcast|service|activity) <args>\n"
+ " <args> are as described in `am start`";
- public static final int NOTIFICATION_ID = 1138;
- public static final String NOTIFICATION_PACKAGE = "com.android.shell";
- public static final String CHANNEL_ID = "shellcmd";
+ public static final int NOTIFICATION_ID = 2020;
+ public static final String CHANNEL_ID = "shell_cmd";
public static final String CHANNEL_NAME = "Shell command";
public static final int CHANNEL_IMP = NotificationManager.IMPORTANCE_DEFAULT;
private final NotificationManagerService mDirectService;
private final INotificationManager mBinderService;
+ private final PackageManager mPm;
public NotificationShellCmd(NotificationManagerService service) {
mDirectService = service;
mBinderService = service.getBinderService();
+ mPm = mDirectService.getContext().getPackageManager();
}
@Override
@@ -108,9 +116,44 @@
if (cmd == null) {
return handleDefaultCommands(cmd);
}
+ String callingPackage = null;
+ final int callingUid = Binder.getCallingUid();
+ long identity = Binder.clearCallingIdentity();
+ try {
+ String[] packages = mPm.getPackagesForUid(callingUid);
+ if (packages != null && packages.length > 0) {
+ callingPackage = packages[0];
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "failed to get caller pkg", e);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
final PrintWriter pw = getOutPrintWriter();
try {
switch (cmd.replace('-', '_')) {
+ case "set_dnd": {
+ String mode = getNextArgRequired();
+ int interruptionFilter = INTERRUPTION_FILTER_UNKNOWN;
+ switch(mode) {
+ case "none":
+ case "on":
+ interruptionFilter = INTERRUPTION_FILTER_NONE;
+ break;
+ case "priority":
+ interruptionFilter = INTERRUPTION_FILTER_PRIORITY;
+ break;
+ case "alarms":
+ interruptionFilter = INTERRUPTION_FILTER_ALARMS;
+ break;
+ case "all":
+ case "off":
+ interruptionFilter = INTERRUPTION_FILTER_ALL;
+ }
+ final int filter = interruptionFilter;
+ mBinderService.setInterruptionFilter(callingPackage, filter);
+ }
+ break;
case "allow_dnd": {
String packageName = getNextArgRequired();
int userId = ActivityManager.getCurrentUser();
@@ -226,7 +269,7 @@
}
case "post":
case "notify":
- doNotify(pw);
+ doNotify(pw, callingPackage, callingUid);
break;
default:
return handleDefaultCommands(cmd);
@@ -238,27 +281,14 @@
return 0;
}
- void ensureChannel() throws RemoteException {
- final int uid = Binder.getCallingUid();
- final int userid = UserHandle.getCallingUserId();
- final long token = Binder.clearCallingIdentity();
- try {
- if (mBinderService.getNotificationChannelForPackage(NOTIFICATION_PACKAGE,
- uid, CHANNEL_ID, false) == null) {
- final NotificationChannel chan = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME,
- CHANNEL_IMP);
- Slog.v(NotificationManagerService.TAG,
- "creating shell channel for user " + userid + " uid " + uid + ": " + chan);
- mBinderService.createNotificationChannelsForPackage(NOTIFICATION_PACKAGE, uid,
- new ParceledListSlice<NotificationChannel>(
- Collections.singletonList(chan)));
- Slog.v(NotificationManagerService.TAG, "created channel: "
- + mBinderService.getNotificationChannelForPackage(NOTIFICATION_PACKAGE,
- uid, CHANNEL_ID, false));
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ void ensureChannel(String callingPackage, int callingUid) throws RemoteException {
+ final NotificationChannel channel =
+ new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, CHANNEL_IMP);
+ mBinderService.createNotificationChannels(callingPackage,
+ new ParceledListSlice<>(Collections.singletonList(channel)));
+ Slog.v(NotificationManagerService.TAG, "created channel: "
+ + mBinderService.getNotificationChannel(callingPackage,
+ UserHandle.getUserId(callingUid), callingPackage, CHANNEL_ID));
}
Icon parseIcon(Resources res, String encoded) throws IllegalArgumentException {
@@ -287,7 +317,8 @@
return null;
}
- private int doNotify(PrintWriter pw) throws RemoteException, URISyntaxException {
+ private int doNotify(PrintWriter pw, String callingPackage, int callingUid)
+ throws RemoteException, URISyntaxException {
final Context context = mDirectService.getContext();
final Resources res = context.getResources();
final Notification.Builder builder = new Notification.Builder(context, CHANNEL_ID);
@@ -481,26 +512,18 @@
builder.setSmallIcon(smallIcon);
}
- ensureChannel();
+ ensureChannel(callingPackage, callingUid);
final Notification n = builder.build();
pw.println("posting:\n " + n);
Slog.v("NotificationManager", "posting: " + n);
- final int userId = UserHandle.getCallingUserId();
- final long token = Binder.clearCallingIdentity();
- try {
- mBinderService.enqueueNotificationWithTag(
- NOTIFICATION_PACKAGE, "android",
- tag, NOTIFICATION_ID,
- n, userId);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ mBinderService.enqueueNotificationWithTag(callingPackage, callingPackage, tag,
+ NOTIFICATION_ID, n, UserHandle.getUserId(callingUid));
if (verbose) {
NotificationRecord nr = mDirectService.findNotificationLocked(
- NOTIFICATION_PACKAGE, tag, NOTIFICATION_ID, userId);
+ callingPackage, tag, NOTIFICATION_ID, UserHandle.getUserId(callingUid));
for (int tries = 3; tries-- > 0; ) {
if (nr != null) break;
try {
@@ -509,7 +532,7 @@
} catch (InterruptedException e) {
}
nr = mDirectService.findNotificationLocked(
- NOTIFICATION_PACKAGE, tag, NOTIFICATION_ID, userId);
+ callingPackage, tag, NOTIFICATION_ID, UserHandle.getUserId(callingUid));
}
if (nr == null) {
pw.println("warning: couldn't find notification after enqueueing");
diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java
index dd393b8..ba7820e 100644
--- a/services/core/java/com/android/server/notification/NotificationUsageStats.java
+++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java
@@ -150,6 +150,7 @@
stats.numPostedByApp++;
stats.updateInterarrivalEstimate(now);
stats.countApiUse(notification);
+ stats.numUndecoratedRemoteViews += (isUndecoratedRemoteView(notification) ? 1 : 0);
}
releaseAggregatedStatsLocked(aggregatedStatsArray);
if (ENABLE_SQLITE_LOG) {
@@ -158,6 +159,13 @@
}
/**
+ * Does this notification use RemoveViews without a platform decoration?
+ */
+ protected static boolean isUndecoratedRemoteView(NotificationRecord notification) {
+ return (notification.getNotification().getNotificationStyle() == null);
+ }
+
+ /**
* Called when a notification has been updated.
*/
public synchronized void registerUpdatedByApp(NotificationRecord notification,
@@ -327,6 +335,15 @@
return dump;
}
+ public PulledStats remoteViewStats(long startMs, boolean aggregate) {
+ if (ENABLE_SQLITE_LOG) {
+ if (aggregate) {
+ return mSQLiteLog.remoteViewAggStats(startMs);
+ }
+ }
+ return null;
+ }
+
public synchronized void dump(PrintWriter pw, String indent, DumpFilter filter) {
if (ENABLE_AGGREGATED_IN_MEMORY_STATS) {
for (AggregatedStats as : mStats.values()) {
@@ -404,6 +421,7 @@
public int numRateViolations;
public int numAlertViolations;
public int numQuotaViolations;
+ public int numUndecoratedRemoteViews;
public long mLastAccessTime;
public AggregatedStats(Context context, String key) {
@@ -670,6 +688,8 @@
output.append(indentPlusTwo).append(noisyImportance.toString()).append("\n");
output.append(indentPlusTwo).append(quietImportance.toString()).append("\n");
output.append(indentPlusTwo).append(finalImportance.toString()).append("\n");
+ output.append(indentPlusTwo);
+ output.append("numUndecorateRVs=").append(numUndecoratedRemoteViews).append("\n");
output.append(indent).append("}");
return output.toString();
}
@@ -1028,7 +1048,7 @@
private static final int MSG_DISMISS = 4;
private static final String DB_NAME = "notification_log.db";
- private static final int DB_VERSION = 5;
+ private static final int DB_VERSION = 7;
/** Age in ms after which events are pruned from the DB. */
private static final long HORIZON_MS = 7 * 24 * 60 * 60 * 1000L; // 1 week
@@ -1061,6 +1081,7 @@
private static final String COL_FIRST_EXPANSIONTIME_MS = "first_expansion_time_ms";
private static final String COL_AIRTIME_EXPANDED_MS = "expansion_airtime_ms";
private static final String COL_EXPAND_COUNT = "expansion_count";
+ private static final String COL_UNDECORATED = "undecorated";
private static final int EVENT_TYPE_POST = 1;
@@ -1086,12 +1107,20 @@
"COUNT(*) AS cnt, " +
"SUM(" + COL_MUTED + ") as muted, " +
"SUM(" + COL_NOISY + ") as noisy, " +
- "SUM(" + COL_DEMOTED + ") as demoted " +
+ "SUM(" + COL_DEMOTED + ") as demoted, " +
+ "SUM(" + COL_UNDECORATED + ") as undecorated " +
"FROM " + TAB_LOG + " " +
"WHERE " +
COL_EVENT_TYPE + "=" + EVENT_TYPE_POST +
" AND " + COL_EVENT_TIME + " > %d " +
" GROUP BY " + COL_EVENT_USER_ID + ", day, " + COL_PKG;
+ private static final String UNDECORATED_QUERY = "SELECT " +
+ COL_PKG + ", " +
+ "MAX(" + COL_EVENT_TIME + ") as max_time " +
+ "FROM " + TAB_LOG + " " +
+ "WHERE " + COL_UNDECORATED + "> 0 " +
+ " AND " + COL_EVENT_TIME + " > %d " +
+ "GROUP BY " + COL_PKG;
public SQLiteLog(Context context) {
HandlerThread backgroundThread = new HandlerThread("notification-sqlite-log",
@@ -1147,7 +1176,8 @@
COL_AIRTIME_MS + " INT," +
COL_FIRST_EXPANSIONTIME_MS + " INT," +
COL_AIRTIME_EXPANDED_MS + " INT," +
- COL_EXPAND_COUNT + " INT" +
+ COL_EXPAND_COUNT + " INT," +
+ COL_UNDECORATED + " INT" +
")");
}
@@ -1257,6 +1287,7 @@
} else {
putPosttimeVisibility(r, cv);
}
+ cv.put(COL_UNDECORATED, (isUndecoratedRemoteView(r) ? 1 : 0));
SQLiteDatabase db = mHelper.getWritableDatabase();
if (db.insert(TAB_LOG, null, cv) < 0) {
Log.wtf(TAG, "Error while trying to insert values: " + cv);
@@ -1337,5 +1368,22 @@
}
return dump;
}
+
+ public PulledStats remoteViewAggStats(long startMs) {
+ PulledStats stats = new PulledStats(startMs);
+ SQLiteDatabase db = mHelper.getReadableDatabase();
+ String q = String.format(UNDECORATED_QUERY, startMs);
+ Cursor cursor = db.rawQuery(q, null);
+ try {
+ for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
+ String pkg = cursor.getString(0);
+ long maxTimeMs = cursor.getLong(1);
+ stats.addUndecoratedPackage(pkg, maxTimeMs);
+ }
+ } finally {
+ cursor.close();
+ }
+ return stats;
+ }
}
}
diff --git a/services/core/java/com/android/server/notification/PulledStats.java b/services/core/java/com/android/server/notification/PulledStats.java
new file mode 100644
index 0000000..ada890a
--- /dev/null
+++ b/services/core/java/com/android/server/notification/PulledStats.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.notification;
+
+import static com.android.server.notification.NotificationManagerService.REPORT_REMOTE_VIEWS;
+
+import android.os.ParcelFileDescriptor;
+import android.service.notification.NotificationRemoteViewsProto;
+import android.service.notification.PackageRemoteViewInfoProto;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+public class PulledStats {
+ static final String TAG = "PulledStats";
+
+ private final long mTimePeriodStartMs;
+ private long mTimePeriodEndMs;
+ private List<String> mUndecoratedPackageNames;
+
+ public PulledStats(long startMs) {
+ mTimePeriodEndMs = mTimePeriodStartMs = startMs;
+ mUndecoratedPackageNames = new ArrayList<>();
+ }
+
+ ParcelFileDescriptor toParcelFileDescriptor(int report)
+ throws IOException {
+ final ParcelFileDescriptor[] fds = ParcelFileDescriptor.createPipe();
+ switch(report) {
+ case REPORT_REMOTE_VIEWS:
+ Thread thr = new Thread("NotificationManager pulled metric output") {
+ public void run() {
+ try {
+ FileOutputStream fout = new ParcelFileDescriptor.AutoCloseOutputStream(
+ fds[1]);
+ final ProtoOutputStream proto = new ProtoOutputStream(fout);
+ writeToProto(report, proto);
+ proto.flush();
+ fout.close();
+ } catch (IOException e) {
+ Slog.w(TAG, "Failure writing pipe", e);
+ }
+ }
+ };
+ thr.start();
+ break;
+
+ default:
+ Slog.w(TAG, "Unknown pulled stats request: " + report);
+ break;
+ }
+ return fds[0];
+ }
+
+ /*
+ * @return the most recent timestamp in the report, as nanoseconds.
+ */
+ public long endTimeMs() {
+ return mTimePeriodEndMs;
+ }
+
+ public void dump(int report, PrintWriter pw, NotificationManagerService.DumpFilter filter) {
+ switch(report) {
+ case REPORT_REMOTE_VIEWS:
+ pw.print(" Packages with undecordated notifications (");
+ pw.print(mTimePeriodStartMs);
+ pw.print(" - ");
+ pw.print(mTimePeriodEndMs);
+ pw.println("):");
+ if (mUndecoratedPackageNames.size() == 0) {
+ pw.println(" none");
+ } else {
+ for (String pkg : mUndecoratedPackageNames) {
+ if (!filter.filtered || pkg.equals(filter.pkgFilter)) {
+ pw.println(" " + pkg);
+ }
+ }
+ }
+ break;
+
+ default:
+ pw.println("Unknown pulled stats request: " + report);
+ break;
+ }
+ }
+
+ @VisibleForTesting
+ void writeToProto(int report, ProtoOutputStream proto) {
+ switch(report) {
+ case REPORT_REMOTE_VIEWS:
+ for (String pkg: mUndecoratedPackageNames) {
+ long token = proto.start(NotificationRemoteViewsProto.PACKAGE_REMOTE_VIEW_INFO);
+ proto.write(PackageRemoteViewInfoProto.PACKAGE_NAME, pkg);
+ proto.end(token);
+ }
+ break;
+
+ default:
+ Slog.w(TAG, "Unknown pulled stats request: " + report);
+ break;
+ }
+ }
+
+ public void addUndecoratedPackage(String packageName, long timestampMs) {
+ mUndecoratedPackageNames.add(packageName);
+ mTimePeriodEndMs = Math.max(mTimePeriodEndMs, timestampMs);
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 4eddb930..7091c7c 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -2209,8 +2209,10 @@
final boolean success = (returnCode == PackageManager.INSTALL_SUCCEEDED);
// Send broadcast to default launcher only if it's a new install
+ // TODO(b/144270665): Secure the usage of this broadcast.
final boolean isNewInstall = extras == null || !extras.getBoolean(Intent.EXTRA_REPLACING);
- if (success && isNewInstall && mPm.mInstallerService.okToSendBroadcasts()) {
+ if (success && isNewInstall && mPm.mInstallerService.okToSendBroadcasts()
+ && (params.installFlags & PackageManager.INSTALL_DRY_RUN) == 0) {
mPm.sendSessionCommitBroadcast(generateInfo(), userId);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 9791ff3..7d9e2f5 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -23123,6 +23123,13 @@
}
}
+ boolean readPermissionStateForUser(@UserIdInt int userId) {
+ synchronized (mPackages) {
+ mSettings.readPermissionStateForUserSyncLPr(userId);
+ return mSettings.areDefaultRuntimePermissionsGrantedLPr(userId);
+ }
+ }
+
@Override
public VerifierDeviceIdentity getVerifierDeviceIdentity() throws RemoteException {
mContext.enforceCallingOrSelfPermission(
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index e991a91..85bf4d9 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -2376,6 +2376,7 @@
int userId = -1;
int flags = 0;
String opt;
+ boolean preCreateOnly = false;
while ((opt = getNextOption()) != null) {
if ("--profileOf".equals(opt)) {
userId = UserHandle.parseUserArg(getNextArgRequired());
@@ -2389,16 +2390,22 @@
flags |= UserInfo.FLAG_GUEST;
} else if ("--demo".equals(opt)) {
flags |= UserInfo.FLAG_DEMO;
+ } else if ("--pre-create-only".equals(opt)) {
+ preCreateOnly = true;
} else {
getErrPrintWriter().println("Error: unknown option " + opt);
return 1;
}
}
String arg = getNextArg();
- if (arg == null) {
+ if (arg == null && !preCreateOnly) {
getErrPrintWriter().println("Error: no user name specified.");
return 1;
}
+ if (arg != null && preCreateOnly) {
+ getErrPrintWriter().println("Warning: name is ignored for pre-created users");
+ }
+
name = arg;
UserInfo info;
IUserManager um = IUserManager.Stub.asInterface(
@@ -2412,7 +2419,7 @@
accm.addSharedAccountsFromParentUser(parentUserId, userId,
(Process.myUid() == Process.ROOT_UID) ? "root" : "com.android.shell");
} else if (userId < 0) {
- info = um.createUser(name, flags);
+ info = preCreateOnly ? um.preCreateUser(flags) : um.createUser(name, flags);
} else {
info = um.createProfileForUser(name, flags, userId, null);
}
@@ -3307,8 +3314,11 @@
pw.println(" trim-caches DESIRED_FREE_SPACE [internal|UUID]");
pw.println(" Trim cache files to reach the given free space.");
pw.println("");
+ pw.println(" list users");
+ pw.println(" Lists the current users.");
+ pw.println("");
pw.println(" create-user [--profileOf USER_ID] [--managed] [--restricted] [--ephemeral]");
- pw.println(" [--guest] USER_NAME");
+ pw.println(" [--guest] [--pre-create-only] USER_NAME");
pw.println(" Create a new user with the given USER_NAME, printing the new user identifier");
pw.println(" of the user.");
pw.println("");
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index d1e4537..994fca8 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3140,6 +3140,10 @@
return true;
}
+ void readPermissionStateForUserSyncLPr(@UserIdInt int userId) {
+ mRuntimePermissionsPersistence.readStateForUserSyncLPr(userId);
+ }
+
void applyDefaultPreferredAppsLPw(int userId) {
// First pull data from any pre-installed apps.
final PackageManagerInternal pmInternal =
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 31960e4..cc42f62 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -41,6 +41,7 @@
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ShortcutServiceInternal;
import android.content.pm.UserInfo;
+import android.content.pm.UserInfo.UserInfoFlag;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.os.Binder;
@@ -66,6 +67,7 @@
import android.os.ShellCommand;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.UserManager.EnforcingUser;
@@ -83,6 +85,7 @@
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.TimeUtils;
+import android.util.TimingsTraceLog;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
@@ -155,6 +158,7 @@
private static final String ATTR_SERIAL_NO = "serialNumber";
private static final String ATTR_NEXT_SERIAL_NO = "nextSerialNumber";
private static final String ATTR_PARTIAL = "partial";
+ private static final String ATTR_PRE_CREATED = "preCreated";
private static final String ATTR_GUEST_TO_REMOVE = "guestToRemove";
private static final String ATTR_USER_VERSION = "version";
private static final String ATTR_PROFILE_GROUP_ID = "profileGroupId";
@@ -475,6 +479,10 @@
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
mUms.cleanupPartialUsers();
+
+ if (mUms.mPm.isDeviceUpgrading()) {
+ mUms.cleanupPreCreatedUsers();
+ }
}
}
@@ -582,7 +590,8 @@
final int userSize = mUsers.size();
for (int i = 0; i < userSize; i++) {
UserInfo ui = mUsers.valueAt(i).info;
- if ((ui.partial || ui.guestToRemove || ui.isEphemeral()) && i != 0) {
+ if ((ui.partial || ui.guestToRemove || (ui.isEphemeral() && !ui.preCreated))
+ && i != 0) {
partials.add(ui);
addRemovingUserIdLocked(ui.id);
ui.partial = true;
@@ -598,6 +607,33 @@
}
}
+ /**
+ * Removes any pre-created users from the system. Should be invoked after OTAs, to ensure
+ * pre-created users are not stale. New pre-created pool can be re-created after the update.
+ */
+ void cleanupPreCreatedUsers() {
+ final ArrayList<UserInfo> preCreatedUsers;
+ synchronized (mUsersLock) {
+ final int userSize = mUsers.size();
+ preCreatedUsers = new ArrayList<>(userSize);
+ for (int i = 0; i < userSize; i++) {
+ UserInfo ui = mUsers.valueAt(i).info;
+ if (ui.preCreated) {
+ preCreatedUsers.add(ui);
+ addRemovingUserIdLocked(ui.id);
+ ui.flags |= UserInfo.FLAG_DISABLED;
+ ui.partial = true;
+ }
+ }
+ }
+ final int preCreatedSize = preCreatedUsers.size();
+ for (int i = 0; i < preCreatedSize; i++) {
+ UserInfo ui = preCreatedUsers.get(i);
+ Slog.i(LOG_TAG, "Removing pre-created user " + ui.id);
+ removeUserState(ui.id);
+ }
+ }
+
@Override
public String getUserAccount(int userId) {
checkManageUserAndAcrossUsersFullPermission("get user account");
@@ -645,20 +681,25 @@
return null;
}
- @Override
public @NonNull List<UserInfo> getUsers(boolean excludeDying) {
+ return getUsers(/*excludePartial= */ true, excludeDying, /* excludePreCreated= */ true);
+ }
+
+ @Override
+ public @NonNull List<UserInfo> getUsers(boolean excludePartial, boolean excludeDying,
+ boolean excludePreCreated) {
checkManageOrCreateUsersPermission("query users");
synchronized (mUsersLock) {
ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size());
final int userSize = mUsers.size();
for (int i = 0; i < userSize; i++) {
UserInfo ui = mUsers.valueAt(i).info;
- if (ui.partial) {
+ if ((excludePartial && ui.partial)
+ || (excludeDying && mRemovingUserIds.get(ui.id))
+ || (excludePreCreated && ui.preCreated)) {
continue;
}
- if (!excludeDying || !mRemovingUserIds.get(ui.id)) {
- users.add(userWithName(ui));
- }
+ users.add(userWithName(ui));
}
return users;
}
@@ -1179,8 +1220,9 @@
}
}
- private void checkManageOrInteractPermIfCallerInOtherProfileGroup(int userId, String name) {
- int callingUserId = UserHandle.getCallingUserId();
+ private void checkManageOrInteractPermIfCallerInOtherProfileGroup(@UserIdInt int userId,
+ String name) {
+ final int callingUserId = UserHandle.getCallingUserId();
if (callingUserId == userId || isSameProfileGroupNoChecks(callingUserId, userId) ||
hasManageUsersPermission()) {
return;
@@ -1193,8 +1235,8 @@
}
@Override
- public boolean isDemoUser(int userId) {
- int callingUserId = UserHandle.getCallingUserId();
+ public boolean isDemoUser(@UserIdInt int userId) {
+ final int callingUserId = UserHandle.getCallingUserId();
if (callingUserId != userId && !hasManageUsersPermission()) {
throw new SecurityException("You need MANAGE_USERS permission to query if u=" + userId
+ " is a demo user");
@@ -1206,6 +1248,19 @@
}
@Override
+ public boolean isPreCreated(@UserIdInt int userId) {
+ final int callingUserId = UserHandle.getCallingUserId();
+ if (callingUserId != userId && !hasManageUsersPermission()) {
+ throw new SecurityException("You need MANAGE_USERS permission to query if u=" + userId
+ + " is pre-created");
+ }
+ synchronized (mUsersLock) {
+ UserInfo userInfo = getUserInfoLU(userId);
+ return userInfo != null && userInfo.preCreated;
+ }
+ }
+
+ @Override
public boolean isRestricted() {
synchronized (mUsersLock) {
return getUserInfoLU(UserHandle.getCallingUserId()).isRestricted();
@@ -1859,7 +1914,7 @@
// Skip over users being removed
for (int i = 0; i < totalUserCount; i++) {
UserInfo user = mUsers.valueAt(i).info;
- if (!mRemovingUserIds.get(user.id) && !user.isGuest()) {
+ if (!mRemovingUserIds.get(user.id) && !user.isGuest() && !user.preCreated) {
aliveUserCount++;
}
}
@@ -2320,6 +2375,9 @@
if (userInfo.partial) {
serializer.attribute(null, ATTR_PARTIAL, "true");
}
+ if (userInfo.preCreated) {
+ serializer.attribute(null, ATTR_PRE_CREATED, "true");
+ }
if (userInfo.guestToRemove) {
serializer.attribute(null, ATTR_GUEST_TO_REMOVE, "true");
}
@@ -2476,6 +2534,7 @@
int profileBadge = 0;
int restrictedProfileParentId = UserInfo.NO_PROFILE_GROUP_ID;
boolean partial = false;
+ boolean preCreated = false;
boolean guestToRemove = false;
boolean persistSeedData = false;
String seedAccountName = null;
@@ -2520,6 +2579,10 @@
if ("true".equals(valueString)) {
partial = true;
}
+ valueString = parser.getAttributeValue(null, ATTR_PRE_CREATED);
+ if ("true".equals(valueString)) {
+ preCreated = true;
+ }
valueString = parser.getAttributeValue(null, ATTR_GUEST_TO_REMOVE);
if ("true".equals(valueString)) {
guestToRemove = true;
@@ -2573,6 +2636,7 @@
userInfo.lastLoggedInTime = lastLoggedInTime;
userInfo.lastLoggedInFingerprint = lastLoggedInFingerprint;
userInfo.partial = partial;
+ userInfo.preCreated = preCreated;
userInfo.guestToRemove = guestToRemove;
userInfo.profileGroupId = profileGroupId;
userInfo.profileBadge = profileBadge;
@@ -2644,7 +2708,8 @@
public UserInfo createProfileForUserEvenWhenDisallowed(String name, int flags, int userId,
String[] disallowedPackages) {
checkManageOrCreateUsersPermission(flags);
- return createUserInternalUnchecked(name, flags, userId, disallowedPackages);
+ return createUserInternalUnchecked(name, flags, userId, /* preCreate= */ false,
+ disallowedPackages);
}
@Override
@@ -2659,12 +2724,27 @@
return createUserInternal(name, flags, UserHandle.USER_NULL);
}
- private UserInfo createUserInternal(String name, int flags, int parentId) {
+ @Override
+ public UserInfo preCreateUser(int flags) {
+ checkManageOrCreateUsersPermission(flags);
+
+ Preconditions.checkArgument(!UserInfo.isManagedProfile(flags),
+ "cannot pre-create managed profiles");
+
+ Slog.i(LOG_TAG, "Pre-creating user with flags " + UserInfo.flagsToString(flags));
+
+ return createUserInternalUnchecked(/* name= */ null, flags,
+ /* parentId= */ UserHandle.USER_NULL, /* preCreate= */ true,
+ /* disallowedPackages= */ null);
+ }
+
+ private UserInfo createUserInternal(@Nullable String name, @UserInfoFlag int flags,
+ @UserIdInt int parentId) {
return createUserInternal(name, flags, parentId, null);
}
- private UserInfo createUserInternal(String name, int flags, int parentId,
- String[] disallowedPackages) {
+ private UserInfo createUserInternal(@Nullable String name, @UserInfoFlag int flags,
+ @UserIdInt int parentId, @Nullable String[] disallowedPackages) {
String restriction = ((flags & UserInfo.FLAG_MANAGED_PROFILE) != 0)
? UserManager.DISALLOW_ADD_MANAGED_PROFILE
: UserManager.DISALLOW_ADD_USER;
@@ -2672,19 +2752,80 @@
Log.w(LOG_TAG, "Cannot add user. " + restriction + " is enabled.");
return null;
}
- return createUserInternalUnchecked(name, flags, parentId, disallowedPackages);
+ return createUserInternalUnchecked(name, flags, parentId, /* preCreate= */ false,
+ disallowedPackages);
}
- private UserInfo createUserInternalUnchecked(String name, int flags, int parentId,
- String[] disallowedPackages) {
+ private UserInfo createUserInternalUnchecked(@Nullable String name, @UserInfoFlag int flags,
+ @UserIdInt int parentId, boolean preCreate,
+ @Nullable String[] disallowedPackages) {
+ final TimingsTraceLog t = new TimingsTraceLog(LOG_TAG, Trace.TRACE_TAG_SYSTEM_SERVER);
+ t.traceBegin("createUser-" + flags);
+ try {
+ return createUserInternalUncheckedNoTracing(name, flags, parentId, preCreate,
+ disallowedPackages, t);
+ } finally {
+ t.traceEnd();
+ }
+ }
+
+ private UserInfo createUserInternalUncheckedNoTracing(@Nullable String name,
+ @UserInfoFlag int flags, @UserIdInt int parentId, boolean preCreate,
+ @Nullable String[] disallowedPackages, @NonNull TimingsTraceLog t) {
+
+ // First try to use a pre-created user (if available).
+ // NOTE: currently we don't support pre-created managed profiles
+ if (!preCreate && (parentId < 0 && !UserInfo.isManagedProfile(flags))) {
+ final UserData preCreatedUserData;
+ synchronized (mUsersLock) {
+ preCreatedUserData = getPreCreatedUserLU(flags);
+ }
+ if (preCreatedUserData != null) {
+ final UserInfo preCreatedUser = preCreatedUserData.info;
+ if (UserInfo.isGuest(flags) && areGuestUsersEphemeral()) {
+ // TODO(b/143092698): this pre-created user has (persisted) storage keys
+ // that will be removed when the user is stopped and ideally we should
+ // remove them from storage right now, but that's not possible with the
+ // current StorageManager APIs (there are just a
+ // createUserKey(userId, serial, isEphemeral) and destroyUserKey(userId)
+ // methods; we would need a makeUserKeyEphemeral(userId) method)
+ preCreatedUserData.info.flags |= UserInfo.FLAG_EPHEMERAL;
+ }
+ Log.i(LOG_TAG, "Reusing pre-created user " + preCreatedUser.id + " for flags "
+ + UserInfo.flagsToString(flags) + "; new flags: "
+ + UserInfo.flagsToString(preCreatedUserData.info.flags));
+ if (DBG) {
+ Log.d(LOG_TAG, "pre-created user flags: "
+ + UserInfo.flagsToString(preCreatedUser.flags)
+ + " new-user flags: " + UserInfo.flagsToString(flags));
+ }
+ preCreatedUser.name = name;
+ preCreatedUser.preCreated = false;
+ preCreatedUser.creationTime = getCreationTime();
+
+ synchronized (mPackagesLock) {
+ writeUserLP(preCreatedUserData);
+ writeUserListLP();
+ }
+
+ updateUserIds();
+ if (!mPm.readPermissionStateForUser(preCreatedUser.id)) {
+ // Could not read the existing permissions, re-grant them.
+ mPm.onNewUserCreated(preCreatedUser.id);
+ }
+ dispatchUserAddedIntent(preCreatedUser);
+ return preCreatedUser;
+ }
+ }
+
DeviceStorageMonitorInternal dsm = LocalServices
.getService(DeviceStorageMonitorInternal.class);
if (dsm.isMemoryLow()) {
Log.w(LOG_TAG, "Cannot add user. Not enough space on disk.");
return null;
}
- final boolean isGuest = (flags & UserInfo.FLAG_GUEST) != 0;
- final boolean isManagedProfile = (flags & UserInfo.FLAG_MANAGED_PROFILE) != 0;
+ final boolean isGuest = UserInfo.isGuest(flags);
+ final boolean isManagedProfile = UserInfo.isManagedProfile(flags);
final boolean isRestricted = (flags & UserInfo.FLAG_RESTRICTED) != 0;
final boolean isDemo = (flags & UserInfo.FLAG_DEMO) != 0;
final long ident = Binder.clearCallingIdentity();
@@ -2705,13 +2846,13 @@
return null;
}
if (!isGuest && !isManagedProfile && !isDemo && isUserLimitReached()) {
- // If we're not adding a guest/demo user or a managed profile and the limit has
- // been reached, cannot add a user.
+ // If we're not adding a guest/demo user or a managed profile,
+ // and the limit has been reached, cannot add a user.
Log.e(LOG_TAG, "Cannot add user. Maximum user limit is reached.");
return null;
}
// If we're adding a guest and there already exists one, bail.
- if (isGuest && findCurrentGuestUser() != null) {
+ if (isGuest && !preCreate && findCurrentGuestUser() != null) {
Log.e(LOG_TAG, "Cannot add guest user. Guest user already exists.");
return null;
}
@@ -2747,21 +2888,21 @@
userId = getNextAvailableId();
Environment.getUserSystemDirectory(userId).mkdirs();
- boolean ephemeralGuests = Resources.getSystem()
- .getBoolean(com.android.internal.R.bool.config_guestUserEphemeral);
+ boolean ephemeralGuests = areGuestUsersEphemeral();
synchronized (mUsersLock) {
// Add ephemeral flag to guests/users if required. Also inherit it from parent.
- if ((isGuest && ephemeralGuests) || mForceEphemeralUsers
- || (parent != null && parent.info.isEphemeral())) {
+ if (!preCreate && ((isGuest && ephemeralGuests)
+ || mForceEphemeralUsers
+ || (parent != null && parent.info.isEphemeral()))) {
flags |= UserInfo.FLAG_EPHEMERAL;
}
userInfo = new UserInfo(userId, name, null, flags);
userInfo.serialNumber = mNextSerialNumber++;
- long now = System.currentTimeMillis();
- userInfo.creationTime = (now > EPOCH_PLUS_30_YEARS) ? now : 0;
+ userInfo.creationTime = getCreationTime();
userInfo.partial = true;
+ userInfo.preCreated = preCreate;
userInfo.lastLoggedInFingerprint = Build.FINGERPRINT;
if (isManagedProfile && parentId != UserHandle.USER_NULL) {
userInfo.profileBadge = getFreeProfileBadgeLU(parentId);
@@ -2807,19 +2948,95 @@
synchronized (mRestrictionsLock) {
mBaseUserRestrictions.append(userId, restrictions);
}
+
+ t.traceBegin("PM.onNewUserCreated-" + userId);
mPm.onNewUserCreated(userId);
- Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED);
- addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
- mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL,
- android.Manifest.permission.MANAGE_USERS);
- MetricsLogger.count(mContext, isGuest ? TRON_GUEST_CREATED
- : (isDemo ? TRON_DEMO_CREATED : TRON_USER_CREATED), 1);
+ t.traceEnd();
+ if (preCreate) {
+ // Must start user (which will be stopped right away, through
+ // UserController.finishUserUnlockedCompleted) so services can properly
+ // intialize it.
+ // TODO(b/140750212): in the long-term, we should add a onCreateUser() callback
+ // on SystemService instead.
+ Slog.i(LOG_TAG, "starting pre-created user " + userInfo.toFullString());
+ final IActivityManager am = ActivityManager.getService();
+ try {
+ am.startUserInBackground(userId);
+ } catch (RemoteException e) {
+ Slog.w(LOG_TAG, "could not start pre-created user " + userId, e);
+ }
+ } else {
+ dispatchUserAddedIntent(userInfo);
+ }
+
} finally {
Binder.restoreCallingIdentity(ident);
}
+
+ // TODO(b/140750212): it's possible to reach "max users overflow" when the user is created
+ // "from scratch" (i.e., not from a pre-created user) and reaches the maximum number of
+ // users without counting the pre-created one. Then when the pre-created is converted, the
+ // "effective" number of max users is exceeds. Example:
+ // Max: 3 Current: 2 full (u0 and u10) + 1 pre-created (u11)
+ // Step 1: create(/* flags doesn't match u11 */): u12 is created, "effective max" is now 3
+ // (u0, u10, u12) but "real" max is 4 (u0, u10, u11, u12)
+ // Step 2: create(/* flags match u11 */): u11 is converted, now "effective max" is also 4
+ // (u0, u10, u11, u12)
+ // One way to avoid this issue is by removing a pre-created user from the pool when the
+ // "real" max exceeds the max here.
+
return userInfo;
}
+ private long getCreationTime() {
+ final long now = System.currentTimeMillis();
+ return (now > EPOCH_PLUS_30_YEARS) ? now : 0;
+ }
+
+ private void dispatchUserAddedIntent(@NonNull UserInfo userInfo) {
+ Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED);
+ addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userInfo.id);
+ mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL,
+ android.Manifest.permission.MANAGE_USERS);
+ MetricsLogger.count(mContext, userInfo.isGuest() ? TRON_GUEST_CREATED
+ : (userInfo.isDemo() ? TRON_DEMO_CREATED : TRON_USER_CREATED), 1);
+ }
+
+ private boolean areGuestUsersEphemeral() {
+ return Resources.getSystem()
+ .getBoolean(com.android.internal.R.bool.config_guestUserEphemeral);
+ }
+
+ /**
+ * Gets a pre-created user for the given flag.
+ *
+ * <p>Should be used only during user creation, so the pre-created user can be used (instead of
+ * creating and initializing a new user from scratch).
+ */
+ // TODO(b/140750212): add unit test
+ @GuardedBy("mUsersLock")
+ private @Nullable UserData getPreCreatedUserLU(@UserInfoFlag int flags) {
+ if (DBG) {
+ Slog.d(LOG_TAG, "getPreCreatedUser(): flags= " + UserInfo.flagsToString(flags));
+ }
+ final int userSize = mUsers.size();
+ for (int i = 0; i < userSize; i++) {
+ final UserData user = mUsers.valueAt(i);
+ if (DBG) Slog.d(LOG_TAG, i + ":" + user.info.toFullString());
+ if (user.info.preCreated
+ && (user.info.flags & ~UserInfo.FLAG_INITIALIZED) == flags) {
+ if (!user.info.isInitialized()) {
+ Slog.w(LOG_TAG, "found pre-created user for flags "
+ + "" + UserInfo.flagsToString(flags)
+ + ", but it's not initialized yet: " + user.info.toFullString());
+ continue;
+ }
+ return user;
+ }
+ }
+ return null;
+ }
+
@VisibleForTesting
UserData putUserInfo(UserInfo userInfo) {
final UserData userData = new UserData();
@@ -2872,7 +3089,8 @@
final int size = mUsers.size();
for (int i = 0; i < size; i++) {
final UserInfo user = mUsers.valueAt(i).info;
- if (user.isGuest() && !user.guestToRemove && !mRemovingUserIds.get(user.id)) {
+ if (user.isGuest() && !user.guestToRemove && !user.preCreated
+ && !mRemovingUserIds.get(user.id)) {
return user;
}
}
@@ -3426,14 +3644,16 @@
synchronized (mUsersLock) {
final int userSize = mUsers.size();
for (int i = 0; i < userSize; i++) {
- if (!mUsers.valueAt(i).info.partial) {
+ UserInfo userInfo = mUsers.valueAt(i).info;
+ if (!userInfo.partial && !userInfo.preCreated) {
num++;
}
}
final int[] newUsers = new int[num];
int n = 0;
for (int i = 0; i < userSize; i++) {
- if (!mUsers.valueAt(i).info.partial) {
+ UserInfo userInfo = mUsers.valueAt(i).info;
+ if (!userInfo.partial && !userInfo.preCreated) {
newUsers[n++] = mUsers.keyAt(i);
}
}
@@ -3485,7 +3705,10 @@
* recycled.
*/
void reconcileUsers(String volumeUuid) {
- mUserDataPreparer.reconcileUsers(volumeUuid, getUsers(true /* excludeDying */));
+ mUserDataPreparer.reconcileUsers(volumeUuid, getUsers(
+ /* excludePartial= */ true,
+ /* excludeDying= */ true,
+ /* excludePreCreated= */ false));
}
/**
@@ -3651,7 +3874,7 @@
try {
switch(cmd) {
case "list":
- return runList(pw);
+ return runList(pw, shell);
default:
return shell.handleDefaultCommands(cmd);
}
@@ -3661,17 +3884,58 @@
return -1;
}
- private int runList(PrintWriter pw) throws RemoteException {
+ private int runList(PrintWriter pw, Shell shell) throws RemoteException {
+ boolean all = false;
+ boolean verbose = false;
+ String opt;
+ while ((opt = shell.getNextOption()) != null) {
+ switch (opt) {
+ case "-v":
+ verbose = true;
+ break;
+ case "--all":
+ all = true;
+ break;
+ default:
+ pw.println("Invalid option: " + opt);
+ return -1;
+ }
+ }
final IActivityManager am = ActivityManager.getService();
- final List<UserInfo> users = getUsers(false);
+ final List<UserInfo> users = getUsers(/* excludePartial= */ !all,
+ /* excludingDying=*/ false, /* excludePreCreated= */ !all);
if (users == null) {
pw.println("Error: couldn't get users");
return 1;
} else {
- pw.println("Users:");
- for (int i = 0; i < users.size(); i++) {
- String running = am.isUserRunning(users.get(i).id, 0) ? " running" : "";
- pw.println("\t" + users.get(i).toString() + running);
+ final int size = users.size();
+ int currentUser = UserHandle.USER_NULL;
+ if (verbose) {
+ pw.printf("%d users:\n\n", size);
+ currentUser = am.getCurrentUser().id;
+ } else {
+ // NOTE: the standard "list users" command is used by integration tests and
+ // hence should not be changed. If you need to add more info, use the
+ // verbose option.
+ pw.println("Users:");
+ }
+ for (int i = 0; i < size; i++) {
+ final UserInfo user = users.get(i);
+ final boolean running = am.isUserRunning(user.id, 0);
+ final boolean current = user.id == currentUser;
+ if (verbose) {
+ pw.printf("%d: id=%d, name=%s, flags=%s%s%s%s%s\n", i, user.id, user.name,
+ UserInfo.flagsToString(user.flags),
+ running ? " (running)" : "",
+ user.partial ? " (partial)" : "",
+ user.preCreated ? " (pre-created)" : "",
+ current ? " (current)" : "");
+ } else {
+ // NOTE: the standard "list users" command is used by integration tests and
+ // hence should not be changed. If you need to add more info, use the
+ // verbose option.
+ pw.printf("\t%s%s\n", user, running ? " running" : "");
+ }
}
return 0;
}
@@ -3683,6 +3947,16 @@
long now = System.currentTimeMillis();
final long nowRealtime = SystemClock.elapsedRealtime();
+
+ final ActivityManagerInternal amInternal = LocalServices
+ .getService(ActivityManagerInternal.class);
+ pw.print("Current user: ");
+ if (amInternal != null) {
+ pw.println(amInternal.getCurrentUserId());
+ } else {
+ pw.println("N/A");
+ }
+
StringBuilder sb = new StringBuilder();
synchronized (mPackagesLock) {
synchronized (mUsersLock) {
@@ -3696,13 +3970,19 @@
final int userId = userInfo.id;
pw.print(" "); pw.print(userInfo);
pw.print(" serialNo="); pw.print(userInfo.serialNumber);
+ pw.print(" isPrimary="); pw.print(userInfo.isPrimary());
if (mRemovingUserIds.get(userId)) {
pw.print(" <removing> ");
}
if (userInfo.partial) {
pw.print(" <partial>");
}
+ if (userInfo.preCreated) {
+ pw.print(" <pre-created>");
+ }
pw.println();
+ pw.print(" Flags: "); pw.print(userInfo.flags); pw.print(" (");
+ pw.print(UserInfo.flagsToString(userInfo.flags)); pw.println(")");
pw.print(" State: ");
final int state;
synchronized (mUserStates) {
@@ -3778,13 +4058,16 @@
synchronized (mUserStates) {
pw.println(" Started users state: " + mUserStates);
}
- // Dump some capabilities
- pw.println();
- pw.println(" Max users: " + UserManager.getMaxSupportedUsers());
- pw.println(" Supports switchable users: " + UserManager.supportsMultipleUsers());
- pw.println(" All guests ephemeral: " + Resources.getSystem().getBoolean(
- com.android.internal.R.bool.config_guestUserEphemeral));
- }
+ } // synchronized (mPackagesLock)
+
+ // Dump some capabilities
+ pw.println();
+ pw.print(" Max users: " + UserManager.getMaxSupportedUsers());
+ pw.println(" (limit reached: " + isUserLimitReached() + ")");
+ pw.println(" Supports switchable users: " + UserManager.supportsMultipleUsers());
+ pw.println(" All guests ephemeral: " + areGuestUsersEphemeral());
+ pw.println(" Is split-system user: " + UserManager.isSplitSystemUser());
+ pw.println(" User version: " + mUserVersion);
}
private static void dumpTimeAgo(PrintWriter pw, StringBuilder sb, long nowTime, long time) {
@@ -3969,7 +4252,7 @@
public UserInfo createUserEvenWhenDisallowed(String name, int flags,
String[] disallowedPackages) {
UserInfo user = createUserInternalUnchecked(name, flags, UserHandle.USER_NULL,
- disallowedPackages);
+ /* preCreated= */ false, disallowedPackages);
// Keep this in sync with UserManager.createUser
if (user != null && !user.isAdmin() && !user.isDemo()) {
setUserRestriction(UserManager.DISALLOW_SMS, true, user.id);
@@ -4134,7 +4417,7 @@
pw.println(" help");
pw.println(" Print this help text.");
pw.println("");
- pw.println(" list");
+ pw.println(" list [-v] [-all]");
pw.println(" Prints all users on the system.");
}
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 6fced8a..d30afaa 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -48,6 +48,7 @@
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.PermissionWhitelistFlags;
import android.content.pm.PackageManagerInternal;
@@ -2614,7 +2615,7 @@
// Make sure all dynamic permissions have been assigned to a package,
// and make sure there are no dangling permissions.
- flags = updatePermissions(changingPkgName, changingPkg, flags);
+ flags = updatePermissions(changingPkgName, changingPkg, flags, callback);
synchronized (mLock) {
if (mBackgroundPermissions == null) {
@@ -2664,7 +2665,8 @@
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
- private int updatePermissions(String packageName, PackageParser.Package pkg, int flags) {
+ private int updatePermissions(String packageName, PackageParser.Package pkg, int flags,
+ @Nullable PermissionCallback callback) {
Set<BasePermission> needsUpdate = null;
synchronized (mLock) {
final Iterator<BasePermission> it = mSettings.mPermissions.values().iterator();
@@ -2678,6 +2680,44 @@
&& (pkg == null || !hasPermission(pkg, bp.getName()))) {
Slog.i(TAG, "Removing old permission tree: " + bp.getName()
+ " from package " + bp.getSourcePackageName());
+ if (bp.isRuntime()) {
+ final int[] userIds = mUserManagerInt.getUserIds();
+ final int numUserIds = userIds.length;
+ for (int userIdNum = 0; userIdNum < numUserIds; userIdNum++) {
+ final int userId = userIds[userIdNum];
+
+ mPackageManagerInt.forEachPackage((Package p) -> {
+ final String pName = p.packageName;
+ final ApplicationInfo appInfo =
+ mPackageManagerInt.getApplicationInfo(pName, 0,
+ Process.SYSTEM_UID, UserHandle.USER_SYSTEM);
+ if (appInfo != null
+ && appInfo.targetSdkVersion < Build.VERSION_CODES.M) {
+ return;
+ }
+
+ final String permissionName = bp.getName();
+ if (checkPermission(permissionName, pName, Process.SYSTEM_UID,
+ userId) == PackageManager.PERMISSION_GRANTED) {
+ try {
+ revokeRuntimePermission(
+ permissionName,
+ pName,
+ false,
+ userId,
+ callback);
+ } catch (IllegalArgumentException e) {
+ Slog.e(TAG,
+ "Failed to revoke "
+ + permissionName
+ + " from "
+ + pName,
+ e);
+ }
+ }
+ });
+ }
+ }
flags |= UPDATE_PERMISSIONS_ALL;
it.remove();
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 48056b4..7270616 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -23,6 +23,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.content.Context.CONTEXT_RESTRICTED;
import static android.content.Context.WINDOW_SERVICE;
+import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE;
import static android.content.pm.PackageManager.FEATURE_HDMI_CEC;
import static android.content.pm.PackageManager.FEATURE_LEANBACK;
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
@@ -384,6 +385,7 @@
BurnInProtectionHelper mBurnInProtectionHelper;
private DisplayFoldController mDisplayFoldController;
AppOpsManager mAppOpsManager;
+ private boolean mHasFeatureAuto;
private boolean mHasFeatureWatch;
private boolean mHasFeatureLeanback;
private boolean mHasFeatureHdmiCec;
@@ -1752,6 +1754,7 @@
mDisplayManager = mContext.getSystemService(DisplayManager.class);
mHasFeatureWatch = mContext.getPackageManager().hasSystemFeature(FEATURE_WATCH);
mHasFeatureLeanback = mContext.getPackageManager().hasSystemFeature(FEATURE_LEANBACK);
+ mHasFeatureAuto = mContext.getPackageManager().hasSystemFeature(FEATURE_AUTOMOTIVE);
mHasFeatureHdmiCec = mContext.getPackageManager().hasSystemFeature(FEATURE_HDMI_CEC);
mAccessibilityShortcutController =
new AccessibilityShortcutController(mContext, new Handler(), mCurrentUserId);
@@ -5215,7 +5218,7 @@
awakenDreams();
}
- if (!isUserSetupComplete()) {
+ if (!mHasFeatureAuto && !isUserSetupComplete()) {
Slog.i(TAG, "Not going home because user setup is in progress.");
return;
}
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index c76bbb0..d70b628 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -41,6 +41,7 @@
import android.app.AppOpsManager.HistoricalOpsRequest;
import android.app.AppOpsManager.HistoricalPackageOps;
import android.app.AppOpsManager.HistoricalUidOps;
+import android.app.INotificationManager;
import android.app.ProcessMemoryState;
import android.app.StatsManager;
import android.bluetooth.BluetoothActivityEnergyInfo;
@@ -140,6 +141,7 @@
import com.android.server.SystemServiceManager;
import com.android.server.am.MemoryStatUtil.IonAllocations;
import com.android.server.am.MemoryStatUtil.MemoryStat;
+import com.android.server.notification.NotificationManagerService;
import com.android.server.role.RoleManagerInternal;
import com.android.server.storage.DiskStatsFileLogger;
import com.android.server.storage.DiskStatsLoggingService;
@@ -1625,14 +1627,7 @@
if (statsFiles.size() != 1) {
return;
}
- InputStream stream = new ParcelFileDescriptor.AutoCloseInputStream(
- statsFiles.get(0));
- int[] len = new int[1];
- byte[] stats = readFully(stream, len);
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos,
- wallClockNanos);
- e.writeStorage(Arrays.copyOf(stats, len[0]));
- pulledData.add(e);
+ unpackStreamedData(tagId, elapsedNanos, wallClockNanos, pulledData, statsFiles);
new File(mBaseDir.getAbsolutePath() + "/" + section + "_"
+ lastHighWaterMark).delete();
new File(
@@ -1648,6 +1643,52 @@
}
}
+ private INotificationManager mNotificationManager =
+ INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+
+ private void pullNotificationStats(int reportId, int tagId, long elapsedNanos,
+ long wallClockNanos,
+ List<StatsLogEventWrapper> pulledData) {
+ final long callingToken = Binder.clearCallingIdentity();
+ try {
+ // determine last pull tine. Copy file trick from pullProcessStats?
+ long lastNotificationStatsNs = wallClockNanos -
+ TimeUnit.NANOSECONDS.convert(1, TimeUnit.DAYS);
+
+ List<ParcelFileDescriptor> statsFiles = new ArrayList<>();
+ long notificationStatsNs = mNotificationManager.pullStats(
+ lastNotificationStatsNs, reportId, true, statsFiles);
+ if (statsFiles.size() != 1) {
+ return;
+ }
+ unpackStreamedData(tagId, elapsedNanos, wallClockNanos, pulledData, statsFiles);
+ } catch (IOException e) {
+ Log.e(TAG, "Getting notistats failed: ", e);
+
+ } catch (RemoteException e) {
+ Log.e(TAG, "Getting notistats failed: ", e);
+ } catch (SecurityException e) {
+ Log.e(TAG, "Getting notistats failed: ", e);
+ } finally {
+ Binder.restoreCallingIdentity(callingToken);
+ }
+
+ }
+
+ static void unpackStreamedData(int tagId, long elapsedNanos, long wallClockNanos,
+ List<StatsLogEventWrapper> pulledData, List<ParcelFileDescriptor> statsFiles)
+ throws IOException {
+ InputStream stream = new ParcelFileDescriptor.AutoCloseInputStream(
+ statsFiles.get(0));
+ int[] len = new int[1];
+ byte[] stats = readFully(stream, len);
+ StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos,
+ wallClockNanos);
+ e.writeStorage(Arrays.copyOf(stats, len[0]));
+ pulledData.add(e);
+ }
+
static byte[] readFully(InputStream stream, int[] outLen) throws IOException {
int pos = 0;
final int initialAvail = stream.available();
@@ -2477,6 +2518,11 @@
pullAppOps(elapsedNanos, wallClockNanos, ret);
break;
}
+ case StatsLog.NOTIFICATION_REMOTE_VIEWS: {
+ pullNotificationStats(NotificationManagerService.REPORT_REMOTE_VIEWS,
+ tagId, elapsedNanos, wallClockNanos, ret);
+ break;
+ }
default:
Slog.w(TAG, "No such tagId data as " + tagId);
return null;
diff --git a/services/core/java/com/android/server/wallpaper/GLHelper.java b/services/core/java/com/android/server/wallpaper/GLHelper.java
new file mode 100644
index 0000000..1d733f5
--- /dev/null
+++ b/services/core/java/com/android/server/wallpaper/GLHelper.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wallpaper;
+
+import static android.opengl.EGL14.EGL_ALPHA_SIZE;
+import static android.opengl.EGL14.EGL_BLUE_SIZE;
+import static android.opengl.EGL14.EGL_CONFIG_CAVEAT;
+import static android.opengl.EGL14.EGL_CONTEXT_CLIENT_VERSION;
+import static android.opengl.EGL14.EGL_DEFAULT_DISPLAY;
+import static android.opengl.EGL14.EGL_DEPTH_SIZE;
+import static android.opengl.EGL14.EGL_GREEN_SIZE;
+import static android.opengl.EGL14.EGL_HEIGHT;
+import static android.opengl.EGL14.EGL_NONE;
+import static android.opengl.EGL14.EGL_NO_CONTEXT;
+import static android.opengl.EGL14.EGL_NO_DISPLAY;
+import static android.opengl.EGL14.EGL_NO_SURFACE;
+import static android.opengl.EGL14.EGL_OPENGL_ES2_BIT;
+import static android.opengl.EGL14.EGL_RED_SIZE;
+import static android.opengl.EGL14.EGL_RENDERABLE_TYPE;
+import static android.opengl.EGL14.EGL_STENCIL_SIZE;
+import static android.opengl.EGL14.EGL_WIDTH;
+import static android.opengl.EGL14.eglChooseConfig;
+import static android.opengl.EGL14.eglCreateContext;
+import static android.opengl.EGL14.eglCreatePbufferSurface;
+import static android.opengl.EGL14.eglDestroyContext;
+import static android.opengl.EGL14.eglDestroySurface;
+import static android.opengl.EGL14.eglGetDisplay;
+import static android.opengl.EGL14.eglGetError;
+import static android.opengl.EGL14.eglInitialize;
+import static android.opengl.EGL14.eglMakeCurrent;
+import static android.opengl.EGL14.eglTerminate;
+import static android.opengl.GLES20.GL_MAX_TEXTURE_SIZE;
+import static android.opengl.GLES20.glGetIntegerv;
+
+import android.opengl.EGLConfig;
+import android.opengl.EGLContext;
+import android.opengl.EGLDisplay;
+import android.opengl.EGLSurface;
+import android.opengl.GLUtils;
+import android.os.SystemProperties;
+import android.util.Log;
+
+class GLHelper {
+ private static final String TAG = GLHelper.class.getSimpleName();
+ private static final int sMaxTextureSize;
+
+ static {
+ int maxTextureSize = SystemProperties.getInt("sys.max_texture_size", 0);
+ sMaxTextureSize = maxTextureSize > 0 ? maxTextureSize : retrieveTextureSizeFromGL();
+ }
+
+ private static int retrieveTextureSizeFromGL() {
+ try {
+ String err;
+
+ // Before we can retrieve info from GL,
+ // we have to create EGLContext, EGLConfig and EGLDisplay first.
+ // We will fail at querying info from GL once one of above failed.
+ // When this happens, we will use defValue instead.
+ EGLDisplay eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ if (eglDisplay == null || eglDisplay == EGL_NO_DISPLAY) {
+ err = "eglGetDisplay failed: " + GLUtils.getEGLErrorString(eglGetError());
+ throw new RuntimeException(err);
+ }
+
+ if (!eglInitialize(eglDisplay, null, 0 /* majorOffset */, null, 1 /* minorOffset */)) {
+ err = "eglInitialize failed: " + GLUtils.getEGLErrorString(eglGetError());
+ throw new RuntimeException(err);
+ }
+
+ EGLConfig eglConfig = null;
+ int[] configsCount = new int[1];
+ EGLConfig[] configs = new EGLConfig[1];
+ int[] configSpec = new int[] {
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ EGL_RED_SIZE, 8,
+ EGL_GREEN_SIZE, 8,
+ EGL_BLUE_SIZE, 8,
+ EGL_ALPHA_SIZE, 0,
+ EGL_DEPTH_SIZE, 0,
+ EGL_STENCIL_SIZE, 0,
+ EGL_CONFIG_CAVEAT, EGL_NONE,
+ EGL_NONE
+ };
+
+ if (!eglChooseConfig(eglDisplay, configSpec, 0 /* attrib_listOffset */,
+ configs, 0 /* configOffset */, 1 /* config_size */,
+ configsCount, 0 /* num_configOffset */)) {
+ err = "eglChooseConfig failed: " + GLUtils.getEGLErrorString(eglGetError());
+ throw new RuntimeException(err);
+ } else if (configsCount[0] > 0) {
+ eglConfig = configs[0];
+ }
+
+ if (eglConfig == null) {
+ throw new RuntimeException("eglConfig not initialized!");
+ }
+
+ int[] attr_list = new int[] {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
+ EGLContext eglContext = eglCreateContext(
+ eglDisplay, eglConfig, EGL_NO_CONTEXT, attr_list, 0 /* offset */);
+
+ if (eglContext == null || eglContext == EGL_NO_CONTEXT) {
+ err = "eglCreateContext failed: " + GLUtils.getEGLErrorString(eglGetError());
+ throw new RuntimeException(err);
+ }
+
+ // We create a push buffer temporarily for querying info from GL.
+ int[] attrs = {EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE};
+ EGLSurface eglSurface =
+ eglCreatePbufferSurface(eglDisplay, eglConfig, attrs, 0 /* offset */);
+ eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);
+
+ // Now, we are ready to query the info from GL.
+ int[] maxSize = new int[1];
+ glGetIntegerv(GL_MAX_TEXTURE_SIZE, maxSize, 0 /* offset */);
+
+ // We have got the info we want, release all egl resources.
+ eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+ eglDestroySurface(eglDisplay, eglSurface);
+ eglDestroyContext(eglDisplay, eglContext);
+ eglTerminate(eglDisplay);
+ return maxSize[0];
+ } catch (RuntimeException e) {
+ Log.w(TAG, "Retrieve from GL failed", e);
+ return Integer.MAX_VALUE;
+ }
+ }
+
+ static int getMaxTextureSize() {
+ return sMaxTextureSize;
+ }
+}
+
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index b0f1e5d..4e136af 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -134,6 +134,9 @@
private static final boolean DEBUG = false;
private static final boolean DEBUG_LIVE = true;
+ // This 100MB limitation is defined in RecordingCanvas.
+ private static final int MAX_BITMAP_SIZE = 100 * 1024 * 1024;
+
public static class Lifecycle extends SystemService {
private IWallpaperManagerService mService;
@@ -572,7 +575,9 @@
// Only generate crop for default display.
final DisplayData wpData = getDisplayDataOrCreate(DEFAULT_DISPLAY);
- Rect cropHint = new Rect(wallpaper.cropHint);
+ final Rect cropHint = new Rect(wallpaper.cropHint);
+ final DisplayInfo displayInfo = new DisplayInfo();
+ mDisplayManager.getDisplay(DEFAULT_DISPLAY).getDisplayInfo(displayInfo);
if (DEBUG) {
Slog.v(TAG, "Generating crop for new wallpaper(s): 0x"
@@ -618,12 +623,12 @@
}
// scale if the crop height winds up not matching the recommended metrics
- needScale = (wpData.mHeight != cropHint.height());
+ needScale = wpData.mHeight != cropHint.height()
+ || cropHint.height() > GLHelper.getMaxTextureSize()
+ || cropHint.width() > GLHelper.getMaxTextureSize();
//make sure screen aspect ratio is preserved if width is scaled under screen size
if (needScale) {
- final DisplayInfo displayInfo = new DisplayInfo();
- mDisplayManager.getDisplay(DEFAULT_DISPLAY).getDisplayInfo(displayInfo);
final float scaleByHeight = (float) wpData.mHeight / (float) cropHint.height();
final int newWidth = (int) (cropHint.width() * scaleByHeight);
if (newWidth < displayInfo.logicalWidth) {
@@ -644,14 +649,29 @@
if (!needCrop && !needScale) {
// Simple case: the nominal crop fits what we want, so we take
// the whole thing and just copy the image file directly.
- if (DEBUG) {
- Slog.v(TAG, "Null crop of new wallpaper; copying");
+
+ // TODO: It is not accurate to estimate bitmap size without decoding it,
+ // may be we can try to remove this optimized way in the future,
+ // that means, we will always go into the 'else' block.
+
+ // This is just a quick estimation, may be smaller than it is.
+ long estimateSize = options.outWidth * options.outHeight * 4;
+
+ // A bitmap over than MAX_BITMAP_SIZE will make drawBitmap() fail.
+ // Please see: RecordingCanvas#throwIfCannotDraw.
+ if (estimateSize < MAX_BITMAP_SIZE) {
+ success = FileUtils.copyFile(wallpaper.wallpaperFile, wallpaper.cropFile);
}
- success = FileUtils.copyFile(wallpaper.wallpaperFile, wallpaper.cropFile);
+
if (!success) {
wallpaper.cropFile.delete();
// TODO: fall back to default wallpaper in this case
}
+
+ if (DEBUG) {
+ Slog.v(TAG, "Null crop of new wallpaper, estimate size="
+ + estimateSize + ", success=" + success);
+ }
} else {
// Fancy case: crop and scale. First, we decode and scale down if appropriate.
FileOutputStream f = null;
@@ -665,49 +685,78 @@
// We calculate the largest power-of-two under the actual ratio rather than
// just let the decode take care of it because we also want to remap where the
// cropHint rectangle lies in the decoded [super]rect.
- final BitmapFactory.Options scaler;
final int actualScale = cropHint.height() / wpData.mHeight;
int scale = 1;
- while (2*scale < actualScale) {
+ while (2 * scale <= actualScale) {
scale *= 2;
}
- if (scale > 1) {
- scaler = new BitmapFactory.Options();
- scaler.inSampleSize = scale;
+ options.inSampleSize = scale;
+ options.inJustDecodeBounds = false;
+
+ final Rect estimateCrop = new Rect(cropHint);
+ estimateCrop.scale(1f / options.inSampleSize);
+ final float hRatio = (float) wpData.mHeight / estimateCrop.height();
+ final int destHeight = (int) (estimateCrop.height() * hRatio);
+ final int destWidth = (int) (estimateCrop.width() * hRatio);
+
+ // We estimated an invalid crop, try to adjust the cropHint to get a valid one.
+ if (destWidth > GLHelper.getMaxTextureSize()) {
+ int newHeight = (int) (wpData.mHeight / hRatio);
+ int newWidth = (int) (wpData.mWidth / hRatio);
+
if (DEBUG) {
- Slog.v(TAG, "Downsampling cropped rect with scale " + scale);
+ Slog.v(TAG, "Invalid crop dimensions, trying to adjust.");
}
- } else {
- scaler = null;
+
+ estimateCrop.set(cropHint);
+ estimateCrop.left += (cropHint.width() - newWidth) / 2;
+ estimateCrop.top += (cropHint.height() - newHeight) / 2;
+ estimateCrop.right = estimateCrop.left + newWidth;
+ estimateCrop.bottom = estimateCrop.top + newHeight;
+ cropHint.set(estimateCrop);
+ estimateCrop.scale(1f / options.inSampleSize);
}
- Bitmap cropped = decoder.decodeRegion(cropHint, scaler);
+
+ // We've got the safe cropHint; now we want to scale it properly to
+ // the desired rectangle.
+ // That's a height-biased operation: make it fit the hinted height.
+ final int safeHeight = (int) (estimateCrop.height() * hRatio);
+ final int safeWidth = (int) (estimateCrop.width() * hRatio);
+
+ if (DEBUG) {
+ Slog.v(TAG, "Decode parameters:");
+ Slog.v(TAG, " cropHint=" + cropHint + ", estimateCrop=" + estimateCrop);
+ Slog.v(TAG, " down sampling=" + options.inSampleSize
+ + ", hRatio=" + hRatio);
+ Slog.v(TAG, " dest=" + destWidth + "x" + destHeight);
+ Slog.v(TAG, " safe=" + safeWidth + "x" + safeHeight);
+ Slog.v(TAG, " maxTextureSize=" + GLHelper.getMaxTextureSize());
+ }
+
+ Bitmap cropped = decoder.decodeRegion(cropHint, options);
decoder.recycle();
if (cropped == null) {
Slog.e(TAG, "Could not decode new wallpaper");
} else {
- // We've got the extracted crop; now we want to scale it properly to
- // the desired rectangle. That's a height-biased operation: make it
- // fit the hinted height, and accept whatever width we end up with.
- cropHint.offsetTo(0, 0);
- cropHint.right /= scale; // adjust by downsampling factor
- cropHint.bottom /= scale;
- final float heightR =
- ((float) wpData.mHeight) / ((float) cropHint.height());
- if (DEBUG) {
- Slog.v(TAG, "scale " + heightR + ", extracting " + cropHint);
- }
- final int destWidth = (int)(cropHint.width() * heightR);
+ // We are safe to create final crop with safe dimensions now.
final Bitmap finalCrop = Bitmap.createScaledBitmap(cropped,
- destWidth, wpData.mHeight, true);
+ safeWidth, safeHeight, true);
if (DEBUG) {
Slog.v(TAG, "Final extract:");
Slog.v(TAG, " dims: w=" + wpData.mWidth
+ " h=" + wpData.mHeight);
- Slog.v(TAG, " out: w=" + finalCrop.getWidth()
+ Slog.v(TAG, " out: w=" + finalCrop.getWidth()
+ " h=" + finalCrop.getHeight());
}
+ // A bitmap over than MAX_BITMAP_SIZE will make drawBitmap() fail.
+ // Please see: RecordingCanvas#throwIfCannotDraw.
+ if (finalCrop.getByteCount() > MAX_BITMAP_SIZE) {
+ throw new RuntimeException(
+ "Too large bitmap, limit=" + MAX_BITMAP_SIZE);
+ }
+
f = new FileOutputStream(wallpaper.cropFile);
bos = new BufferedOutputStream(f, 32*1024);
finalCrop.compress(Bitmap.CompressFormat.JPEG, 100, bos);
@@ -1237,6 +1286,7 @@
saveSettingsLocked(mWallpaper.userId);
}
FgThread.getHandler().removeCallbacks(mResetRunnable);
+ mContext.getMainThreadHandler().removeCallbacks(this::tryToRebind);
}
}
}
@@ -1279,6 +1329,34 @@
}
}
+ private void tryToRebind() {
+ synchronized (mLock) {
+ if (mWallpaper.wallpaperUpdating) {
+ return;
+ }
+ final ComponentName wpService = mWallpaper.wallpaperComponent;
+ // The broadcast of package update could be delayed after service disconnected. Try
+ // to re-bind the service for 10 seconds.
+ if (bindWallpaperComponentLocked(
+ wpService, true, false, mWallpaper, null)) {
+ mWallpaper.connection.scheduleTimeoutLocked();
+ } else if (SystemClock.uptimeMillis() - mWallpaper.lastDiedTime
+ < WALLPAPER_RECONNECT_TIMEOUT_MS) {
+ // Bind fail without timeout, schedule rebind
+ Slog.w(TAG, "Rebind fail! Try again later");
+ mContext.getMainThreadHandler().postDelayed(this::tryToRebind, 1000);
+ } else {
+ // Timeout
+ Slog.w(TAG, "Reverting to built-in wallpaper!");
+ clearWallpaperLocked(true, FLAG_SYSTEM, mWallpaper.userId, null);
+ final String flattened = wpService.flattenToString();
+ EventLog.writeEvent(EventLogTags.WP_WALLPAPER_CRASHED,
+ flattened.substring(0, Math.min(flattened.length(),
+ MAX_WALLPAPER_COMPONENT_LOG_LENGTH)));
+ }
+ }
+ }
+
private void processDisconnect(final ServiceConnection connection) {
synchronized (mLock) {
// The wallpaper disappeared. If this isn't a system-default one, track
@@ -1302,20 +1380,8 @@
clearWallpaperLocked(true, FLAG_SYSTEM, mWallpaper.userId, null);
} else {
mWallpaper.lastDiedTime = SystemClock.uptimeMillis();
-
- clearWallpaperComponentLocked(mWallpaper);
- if (bindWallpaperComponentLocked(
- wpService, false, false, mWallpaper, null)) {
- mWallpaper.connection.scheduleTimeoutLocked();
- } else {
- Slog.w(TAG, "Reverting to built-in wallpaper!");
- clearWallpaperLocked(true, FLAG_SYSTEM, mWallpaper.userId, null);
- }
+ tryToRebind();
}
- final String flattened = wpService.flattenToString();
- EventLog.writeEvent(EventLogTags.WP_WALLPAPER_CRASHED,
- flattened.substring(0, Math.min(flattened.length(),
- MAX_WALLPAPER_COMPONENT_LOG_LENGTH)));
}
} else {
if (DEBUG_LIVE) {
@@ -1981,6 +2047,11 @@
if (!isWallpaperSupported(callingPackage)) {
return;
}
+
+ // Make sure both width and height are not larger than max texture size.
+ width = Math.min(width, GLHelper.getMaxTextureSize());
+ height = Math.min(height, GLHelper.getMaxTextureSize());
+
synchronized (mLock) {
int userId = UserHandle.getCallingUserId();
WallpaperData wallpaper = getWallpaperSafeLocked(userId, FLAG_SYSTEM);
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 99a9db3..b37c779 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -2812,7 +2812,11 @@
mHandler.post(() -> {
final int displayId = getDisplayId();
getStatusBarManagerInternal().onDisplayReady(displayId);
- LocalServices.getService(WallpaperManagerInternal.class).onDisplayReady(displayId);
+ final WallpaperManagerInternal wpMgr = LocalServices
+ .getService(WallpaperManagerInternal.class);
+ if (wpMgr != null) {
+ wpMgr.onDisplayReady(displayId);
+ }
});
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 2528063..ae773ca 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1512,6 +1512,8 @@
traceBeginAndSlog("StartWallpaperManagerService");
mSystemServiceManager.startService(WALLPAPER_SERVICE_CLASS);
traceEnd();
+ } else {
+ Slog.i(TAG, "Wallpaper service disabled by config");
}
traceBeginAndSlog("StartAudioService");
diff --git a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
index 1e29ed6..fafd4e8 100644
--- a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
@@ -24,6 +24,7 @@
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doThrow;
@@ -35,6 +36,7 @@
import static com.android.server.AlarmManagerService.ACTIVE_INDEX;
import static com.android.server.AlarmManagerService.AlarmHandler.APP_STANDBY_BUCKET_CHANGED;
import static com.android.server.AlarmManagerService.AlarmHandler.APP_STANDBY_PAROLE_CHANGED;
+import static com.android.server.AlarmManagerService.AlarmHandler.REMOVE_FOR_CANCELED;
import static com.android.server.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_LONG_TIME;
import static com.android.server.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_SHORT_TIME;
import static com.android.server.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION;
@@ -79,9 +81,9 @@
import android.util.Log;
import android.util.SparseArray;
-import androidx.test.filters.FlakyTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.dx.mockito.inline.extended.MockedVoidMethod;
import com.android.internal.annotations.GuardedBy;
import org.junit.After;
@@ -166,7 +168,6 @@
}
public class Injector extends AlarmManagerService.Injector {
- boolean mIsAutomotiveOverride;
Injector(Context context) {
super(context);
@@ -256,6 +257,9 @@
.when(() -> LocalServices.getService(DeviceIdleController.LocalService.class));
doReturn(mUsageStatsManagerInternal).when(
() -> LocalServices.getService(UsageStatsManagerInternal.class));
+ doCallRealMethod().when((MockedVoidMethod) () ->
+ LocalServices.addService(eq(AlarmManagerInternal.class), any()));
+ doCallRealMethod().when(() -> LocalServices.getService(AlarmManagerInternal.class));
when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE),
eq(UserHandle.getUserId(TEST_CALLING_UID)), anyLong()))
.thenReturn(STANDBY_BUCKET_ACTIVE);
@@ -443,7 +447,6 @@
assertEquals(expectedTriggerTime, mTestTimer.getElapsed());
}
- @FlakyTest(bugId = 130313408)
@Test
public void testEarliestAlarmSet() {
final PendingIntent pi6 = getNewMockPendingIntent();
@@ -661,11 +664,15 @@
anyLong())).thenReturn(bucket);
mAppStandbyListener.onAppIdleStateChanged(TEST_CALLING_PACKAGE,
UserHandle.getUserId(TEST_CALLING_UID), false, bucket, 0);
+ assertAndHandleMessageSync(APP_STANDBY_BUCKET_CHANGED);
+ }
+
+ private void assertAndHandleMessageSync(int what) {
final ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
verify(mService.mHandler, atLeastOnce()).sendMessage(messageCaptor.capture());
final Message lastMessage = messageCaptor.getValue();
assertEquals("Unexpected message send to handler", lastMessage.what,
- APP_STANDBY_BUCKET_CHANGED);
+ what);
mService.mHandler.handleMessage(lastMessage);
}
@@ -725,12 +732,7 @@
private void assertAndHandleParoleChanged(boolean parole) {
mAppStandbyListener.onParoleStateChanged(parole);
- final ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
- verify(mService.mHandler, atLeastOnce()).sendMessage(messageCaptor.capture());
- final Message lastMessage = messageCaptor.getValue();
- assertEquals("Unexpected message send to handler", lastMessage.what,
- APP_STANDBY_PAROLE_CHANGED);
- mService.mHandler.handleMessage(lastMessage);
+ assertAndHandleMessageSync(APP_STANDBY_PAROLE_CHANGED);
}
@Test
@@ -1033,12 +1035,13 @@
}
@Test
- public void alarmCountOnPendingIntentCancel() {
+ public void alarmCountOnRemoveForCanceled() {
+ final AlarmManagerInternal ami = LocalServices.getService(AlarmManagerInternal.class);
final PendingIntent pi = getNewMockPendingIntent();
- setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 123, pi);
- verify(pi).registerCancelListener(mService.mOperationCancelListener);
+ setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + 12345, pi);
assertEquals(1, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
- mService.mOperationCancelListener.onCancelled(pi);
+ ami.remove(pi);
+ assertAndHandleMessageSync(REMOVE_FOR_CANCELED);
assertEquals(0, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
}
@@ -1047,5 +1050,6 @@
if (mMockingSession != null) {
mMockingSession.finishMocking();
}
+ LocalServices.removeServiceForTest(AlarmManagerInternal.class);
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
index e51ee94..88273b7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
@@ -17,6 +17,7 @@
import static androidx.test.InstrumentationRegistry.getContext;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
@@ -31,6 +32,7 @@
import static com.android.server.DeviceIdleController.LIGHT_STATE_OVERRIDE;
import static com.android.server.DeviceIdleController.LIGHT_STATE_PRE_IDLE;
import static com.android.server.DeviceIdleController.LIGHT_STATE_WAITING_FOR_NETWORK;
+import static com.android.server.DeviceIdleController.MSG_REPORT_STATIONARY_STATUS;
import static com.android.server.DeviceIdleController.STATE_ACTIVE;
import static com.android.server.DeviceIdleController.STATE_IDLE;
import static com.android.server.DeviceIdleController.STATE_IDLE_MAINTENANCE;
@@ -51,6 +53,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -70,6 +73,7 @@
import android.net.NetworkInfo;
import android.os.Handler;
import android.os.Looper;
+import android.os.Message;
import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
@@ -85,11 +89,13 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Answers;
+import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoSession;
+import org.mockito.invocation.InvocationOnMock;
import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
/**
* Tests for {@link com.android.server.DeviceIdleController}.
@@ -97,6 +103,7 @@
@RunWith(AndroidJUnit4.class)
public class DeviceIdleControllerTest {
private DeviceIdleController mDeviceIdleController;
+ private DeviceIdleController.MyHandler mHandler;
private AnyMotionDetectorForTest mAnyMotionDetector;
private AppStateTrackerForTest mAppStateTracker;
private DeviceIdleController.Constants mConstants;
@@ -110,8 +117,6 @@
@Mock
private ContentResolver mContentResolver;
@Mock
- private DeviceIdleController.MyHandler mHandler;
- @Mock
private IActivityManager mIActivityManager;
@Mock
private LocationManager mLocationManager;
@@ -128,6 +133,8 @@
ConnectivityService connectivityService;
LocationManager locationManager;
ConstraintController constraintController;
+ // Freeze time for testing.
+ long nowElapsed;
InjectorForTest(Context ctx) {
super(ctx);
@@ -155,12 +162,34 @@
}
@Override
+ long getElapsedRealtime() {
+ return nowElapsed;
+ }
+
+ @Override
LocationManager getLocationManager() {
return locationManager;
}
@Override
DeviceIdleController.MyHandler getHandler(DeviceIdleController controller) {
+ if (mHandler == null) {
+ mHandler = controller.new MyHandler(getContext().getMainLooper());
+ spyOn(mHandler);
+ doNothing().when(mHandler).handleMessage(argThat((message) ->
+ message.what != MSG_REPORT_STATIONARY_STATUS));
+ doAnswer(new Answer<Boolean>() {
+ @Override
+ public Boolean answer(InvocationOnMock invocation) throws Throwable {
+ Message msg = invocation.getArgument(0);
+ mHandler.handleMessage(msg);
+ return true;
+ }
+ }).when(mHandler).sendMessageDelayed(
+ argThat((message) -> message.what == MSG_REPORT_STATIONARY_STATUS),
+ anyLong());
+ }
+
return mHandler;
}
@@ -226,6 +255,19 @@
}
}
+ private class StationaryListenerForTest implements DeviceIdleController.StationaryListener {
+ boolean motionExpected = false;
+ boolean isStationary = false;
+
+ @Override
+ public void onDeviceStationaryChanged(boolean isStationary) {
+ if (isStationary == motionExpected) {
+ fail("Unexpected device stationary status: " + isStationary);
+ }
+ this.isStationary = isStationary;
+ }
+ }
+
@Before
public void setUp() {
mMockingSession = mockitoSession()
@@ -255,8 +297,6 @@
doReturn(true).when(mSensorManager).registerListener(any(), any(), anyInt());
mAppStateTracker = new AppStateTrackerForTest(getContext(), Looper.getMainLooper());
mAnyMotionDetector = new AnyMotionDetectorForTest();
- mHandler = mock(DeviceIdleController.MyHandler.class, Answers.RETURNS_DEEP_STUBS);
- doNothing().when(mHandler).handleMessage(any());
mInjector = new InjectorForTest(getContext());
doNothing().when(mContentResolver).registerContentObserver(any(), anyBoolean(), any());
@@ -1607,6 +1647,86 @@
1.0f, curfactor, delta);
}
+ @Test
+ public void testStationaryDetection_QuickDozeOff() {
+ setQuickDozeEnabled(false);
+ enterDeepState(STATE_IDLE);
+ // Regular progression through states, so time should have increased appropriately.
+ mInjector.nowElapsed += mConstants.IDLE_AFTER_INACTIVE_TIMEOUT + mConstants.SENSING_TIMEOUT
+ + mConstants.LOCATING_TIMEOUT;
+
+ StationaryListenerForTest stationaryListener = new StationaryListenerForTest();
+
+ mDeviceIdleController.registerStationaryListener(stationaryListener);
+
+ // Go to IDLE_MAINTENANCE
+ mDeviceIdleController.stepIdleStateLocked("testing");
+
+ // Back to IDLE
+ mDeviceIdleController.stepIdleStateLocked("testing");
+ assertTrue(stationaryListener.isStationary);
+
+ // Test motion
+ stationaryListener.motionExpected = true;
+ mDeviceIdleController.mMotionListener.onTrigger(null);
+ assertFalse(stationaryListener.isStationary);
+ }
+
+ @Test
+ public void testStationaryDetection_QuickDozeOn() {
+ setAlarmSoon(false);
+ enterDeepState(STATE_QUICK_DOZE_DELAY);
+ mDeviceIdleController.stepIdleStateLocked("testing");
+ verifyStateConditions(STATE_IDLE);
+ // Quick doze progression through states, so time should have increased appropriately.
+ mInjector.nowElapsed += mConstants.QUICK_DOZE_DELAY_TIMEOUT;
+ final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListener = ArgumentCaptor
+ .forClass(AlarmManager.OnAlarmListener.class);
+ doNothing().when(mAlarmManager).set(anyInt(), anyLong(), eq("DeviceIdleController.motion"),
+ alarmListener.capture(), any());
+
+ StationaryListenerForTest stationaryListener = new StationaryListenerForTest();
+
+ stationaryListener.motionExpected = true;
+ mDeviceIdleController.registerStationaryListener(stationaryListener);
+ assertFalse(stationaryListener.isStationary);
+
+ // Go to IDLE_MAINTENANCE
+ mDeviceIdleController.stepIdleStateLocked("testing");
+
+ mInjector.nowElapsed += mConstants.MOTION_INACTIVE_TIMEOUT / 2;
+
+ // Back to IDLE
+ mDeviceIdleController.stepIdleStateLocked("testing");
+
+ // Now enough time has passed.
+ mInjector.nowElapsed += mConstants.MOTION_INACTIVE_TIMEOUT / 2;
+ stationaryListener.motionExpected = false;
+ alarmListener.getValue().onAlarm();
+ assertTrue(stationaryListener.isStationary);
+
+ stationaryListener.motionExpected = true;
+ mDeviceIdleController.mMotionListener.onSensorChanged(null);
+ assertFalse(stationaryListener.isStationary);
+
+ // Since we're in quick doze, the device shouldn't stop idling.
+ verifyStateConditions(STATE_IDLE);
+
+ // Go to IDLE_MAINTENANCE
+ mDeviceIdleController.stepIdleStateLocked("testing");
+
+ mInjector.nowElapsed += mConstants.MOTION_INACTIVE_TIMEOUT / 2;
+
+ // Back to IDLE
+ mDeviceIdleController.stepIdleStateLocked("testing");
+
+ // Now enough time has passed.
+ mInjector.nowElapsed += mConstants.MOTION_INACTIVE_TIMEOUT / 2;
+ stationaryListener.motionExpected = false;
+ alarmListener.getValue().onAlarm();
+ assertTrue(stationaryListener.isStationary);
+ }
+
private void enterDeepState(int state) {
switch (state) {
case STATE_ACTIVE:
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java
new file mode 100644
index 0000000..3975f0b
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.AppGlobals;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.content.pm.IPackageManager;
+import android.os.Looper;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.AlarmManagerInternal;
+import com.android.server.LocalServices;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+@RunWith(AndroidJUnit4.class)
+public class PendingIntentControllerTest {
+ private static final String TEST_PACKAGE_NAME = "test-package-1";
+ private static final int TEST_CALLING_UID = android.os.Process.myUid();
+ private static final Intent[] TEST_INTENTS = new Intent[]{new Intent("com.test.intent")};
+
+ @Mock
+ private UserController mUserController;
+ @Mock
+ private AlarmManagerInternal mAlarmManagerInternal;
+ @Mock
+ private ActivityManagerInternal mActivityManagerInternal;
+ @Mock
+ private IPackageManager mIPackageManager;
+
+ private MockitoSession mMockingSession;
+ private PendingIntentController mPendingIntentController;
+
+ @Before
+ public void setUp() throws Exception {
+ mMockingSession = mockitoSession()
+ .initMocks(this)
+ .mockStatic(LocalServices.class)
+ .mockStatic(AppGlobals.class)
+ .strictness(Strictness.LENIENT) // Needed to stub LocalServices.getService twice
+ .startMocking();
+ doReturn(mAlarmManagerInternal).when(
+ () -> LocalServices.getService(AlarmManagerInternal.class));
+ doReturn(mActivityManagerInternal).when(
+ () -> LocalServices.getService(ActivityManagerInternal.class));
+ doReturn(mIPackageManager).when(() -> AppGlobals.getPackageManager());
+ when(mIPackageManager.getPackageUid(eq(TEST_PACKAGE_NAME), anyInt(), anyInt())).thenReturn(
+ TEST_CALLING_UID);
+ mPendingIntentController = new PendingIntentController(Looper.getMainLooper(),
+ mUserController);
+ mPendingIntentController.onActivityManagerInternalAdded();
+ }
+
+ private PendingIntentRecord createPendingIntentRecord(int flags) {
+ return mPendingIntentController.getIntentSender(ActivityManager.INTENT_SENDER_BROADCAST,
+ TEST_PACKAGE_NAME, TEST_CALLING_UID, 0, null, null, 0, TEST_INTENTS, null, flags,
+ null);
+ }
+
+ @Test
+ public void alarmsRemovedOnCancel() {
+ final PendingIntentRecord pir = createPendingIntentRecord(0);
+ mPendingIntentController.cancelIntentSender(pir);
+ final ArgumentCaptor<PendingIntent> piCaptor = ArgumentCaptor.forClass(PendingIntent.class);
+ verify(mAlarmManagerInternal).remove(piCaptor.capture());
+ assertEquals("Wrong target for pending intent passed to alarm manager", pir,
+ piCaptor.getValue().getTarget());
+ }
+
+ @Test
+ public void alarmsRemovedOnRecreateWithCancelCurrent() {
+ final PendingIntentRecord pir = createPendingIntentRecord(0);
+ createPendingIntentRecord(PendingIntent.FLAG_CANCEL_CURRENT);
+ final ArgumentCaptor<PendingIntent> piCaptor = ArgumentCaptor.forClass(PendingIntent.class);
+ verify(mAlarmManagerInternal).remove(piCaptor.capture());
+ assertEquals("Wrong target for pending intent passed to alarm manager", pir,
+ piCaptor.getValue().getTarget());
+ }
+
+ @Test
+ public void alarmsRemovedOnSendingOneShot() {
+ final PendingIntentRecord pir = createPendingIntentRecord(PendingIntent.FLAG_ONE_SHOT);
+ pir.send(0, null, null, null, null, null, null);
+ final ArgumentCaptor<PendingIntent> piCaptor = ArgumentCaptor.forClass(PendingIntent.class);
+ verify(mAlarmManagerInternal).remove(piCaptor.capture());
+ assertEquals("Wrong target for pending intent passed to alarm manager", pir,
+ piCaptor.getValue().getTarget());
+ }
+
+ @After
+ public void tearDown() {
+ if (mMockingSession != null) {
+ mMockingSession.finishMocking();
+ }
+ }
+}
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index eecdeed..f1ceb3c 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -22,6 +22,7 @@
"services.appwidget",
"services.autofill",
"services.backup",
+ "services.contentsuggestions",
"services.core",
"services.devicepolicy",
"services.net",
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
index f336497..ab23b29 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
@@ -39,6 +39,7 @@
import android.os.RemoteException;
import android.os.UserHandle;
+import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
import org.junit.After;
@@ -67,6 +68,7 @@
@Mock AccessibilityServiceInfo mMockServiceInfo;
@Mock ResolveInfo mMockResolveInfo;
@Mock AccessibilityManagerService.SecurityPolicy mMockSecurityPolicy;
+ @Mock ActivityTaskManagerInternal mMockActivityTaskManagerInternal;
@Mock AbstractAccessibilityServiceConnection.SystemSupport mMockSystemSupport;
@Mock WindowManagerInternal mMockWindowManagerInternal;
@Mock GlobalActionPerformer mMockGlobalActionPerformer;
@@ -89,7 +91,8 @@
mConnection = new AccessibilityServiceConnection(mMockUserState, mMockContext,
COMPONENT_NAME, mMockServiceInfo, SERVICE_ID, mHandler, new Object(),
mMockSecurityPolicy, mMockSystemSupport, mMockWindowManagerInternal,
- mMockGlobalActionPerformer);
+ mMockGlobalActionPerformer, mMockActivityTaskManagerInternal);
+ when(mMockSecurityPolicy.canPerformGestures(mConnection)).thenReturn(true);
}
@After
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 231025c..a7c943e 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -25,8 +25,8 @@
import static com.android.server.am.UserController.REPORT_LOCKED_BOOT_COMPLETE_MSG;
import static com.android.server.am.UserController.REPORT_USER_SWITCH_COMPLETE_MSG;
import static com.android.server.am.UserController.REPORT_USER_SWITCH_MSG;
-import static com.android.server.am.UserController.SYSTEM_USER_CURRENT_MSG;
-import static com.android.server.am.UserController.SYSTEM_USER_START_MSG;
+import static com.android.server.am.UserController.USER_CURRENT_MSG;
+import static com.android.server.am.UserController.USER_START_MSG;
import static com.android.server.am.UserController.USER_SWITCH_TIMEOUT_MSG;
import static com.google.android.collect.Lists.newArrayList;
@@ -53,11 +53,13 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.annotation.UserIdInt;
import android.app.IUserSwitchObserver;
import android.content.Context;
import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.pm.UserInfo;
+import android.content.pm.UserInfo.UserInfoFlag;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -107,6 +109,10 @@
private static final int TEST_USER_ID1 = 101;
private static final int TEST_USER_ID2 = 102;
private static final int NONEXIST_USER_ID = 2;
+ private static final int TEST_PRE_CREATED_USER_ID = 103;
+
+ private static final int NO_USERINFO_FLAGS = 0;
+
private static final String TAG = UserControllerTest.class.getSimpleName();
private static final long HANDLER_WAIT_TIME_MS = 100;
@@ -128,11 +134,11 @@
private static final Set<Integer> START_FOREGROUND_USER_MESSAGE_CODES = newHashSet(
REPORT_USER_SWITCH_MSG,
USER_SWITCH_TIMEOUT_MSG,
- SYSTEM_USER_START_MSG,
- SYSTEM_USER_CURRENT_MSG);
+ USER_START_MSG,
+ USER_CURRENT_MSG);
private static final Set<Integer> START_BACKGROUND_USER_MESSAGE_CODES = newHashSet(
- SYSTEM_USER_START_MSG,
+ USER_START_MSG,
REPORT_LOCKED_BOOT_COMPLETE_MSG);
@Before
@@ -149,7 +155,8 @@
doNothing().when(mInjector).clearBroadcastQueueForUser(anyInt());
doNothing().when(mInjector).stackSupervisorRemoveUser(anyInt());
mUserController = new UserController(mInjector);
- setUpUser(TEST_USER_ID, 0);
+ setUpUser(TEST_USER_ID, NO_USERINFO_FLAGS);
+ setUpUser(TEST_PRE_CREATED_USER_ID, NO_USERINFO_FLAGS, /* preCreated=*/ true);
});
}
@@ -190,6 +197,31 @@
startForegroundUserAssertions();
}
+ @Test
+ public void testStartPreCreatedUser_foreground() {
+ assertFalse(mUserController.startUser(TEST_PRE_CREATED_USER_ID, /* foreground= */ true));
+ }
+
+ @Test
+ public void testStartPreCreatedUser_background() throws Exception {
+ assertTrue(mUserController.startUser(TEST_PRE_CREATED_USER_ID, /* foreground= */ false));
+
+ verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
+ verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
+ verify(mInjector, never()).clearAllLockedTasks(anyString());
+
+ assertWithMessage("should not have received intents")
+ .that(getActions(mInjector.mSentIntents)).isEmpty();
+ // TODO(b/140868593): should have received a USER_UNLOCK_MSG message as well, but it doesn't
+ // because StorageManager.isUserKeyUnlocked(TEST_PRE_CREATED_USER_ID) returns false - to
+ // properly fix it, we'd need to move this class to FrameworksMockingServicesTests so we can
+ // mock static methods (but moving this class would involve changing the presubmit tests,
+ // and the cascade effect goes on...). In fact, a better approach would to not assert the
+ // binder calls, but their side effects (in this case, that the user is stopped right away)
+ assertWithMessage("wrong binder message calls").that(mInjector.mHandler.getMessageCodes())
+ .containsExactly(USER_START_MSG);
+ }
+
private void startUserAssertions(
List<String> expectedActions, Set<Integer> expectedMessageCodes) {
assertEquals(expectedActions, getActions(mInjector.mSentIntents));
@@ -469,9 +501,15 @@
continueUserSwitchAssertions(newUserId, expectOldUserStopping);
}
- private void setUpUser(int userId, int flags) {
+ private void setUpUser(@UserIdInt int userId, @UserInfoFlag int flags) {
+ setUpUser(userId, flags, /* preCreated= */ false);
+ }
+
+ private void setUpUser(@UserIdInt int userId, @UserInfoFlag int flags, boolean preCreated) {
UserInfo userInfo = new UserInfo(userId, "User" + userId, flags);
+ userInfo.preCreated = preCreated;
when(mInjector.mUserManagerMock.getUserInfo(eq(userId))).thenReturn(userInfo);
+ when(mInjector.mUserManagerMock.isPreCreated(userId)).thenReturn(preCreated);
}
private static List<String> getActions(List<Intent> intents) {
diff --git a/services/tests/servicestests/src/com/android/server/contentsuggestions/ContentSuggestionsPerUserServiceTest.java b/services/tests/servicestests/src/com/android/server/contentsuggestions/ContentSuggestionsPerUserServiceTest.java
new file mode 100644
index 0000000..80cf6ad
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/contentsuggestions/ContentSuggestionsPerUserServiceTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.contentsuggestions;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.contentsuggestions.ContentSuggestionsManager;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.UserManagerInternal;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.LocalServices;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ContentSuggestionsPerUserServiceTest {
+ private int mUserId;
+ private ContentSuggestionsPerUserService mPerUserService;
+ private ActivityTaskManagerInternal mActivityTaskManagerInternal;
+
+ @Before
+ public void setup() {
+ UserManagerInternal umi = mock(UserManagerInternal.class);
+ LocalServices.removeServiceForTest(UserManagerInternal.class);
+ LocalServices.addService(UserManagerInternal.class, umi);
+
+ mActivityTaskManagerInternal = mock(ActivityTaskManagerInternal.class);
+ LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
+ LocalServices.addService(ActivityTaskManagerInternal.class, mActivityTaskManagerInternal);
+
+ ContentSuggestionsManagerService contentSuggestionsManagerService =
+ new ContentSuggestionsManagerService(getContext());
+ mUserId = 1;
+ mPerUserService = new ContentSuggestionsPerUserService(contentSuggestionsManagerService,
+ new Object(),
+ mUserId);
+ }
+
+ // Tests TaskSnapshot is taken when the key ContentSuggestionsManager.EXTRA_BITMAP is missing
+ // from imageContextRequestExtras provided.
+ @Test
+ public void testProvideContextImageLocked_noBitmapInBundle() {
+ Bundle imageContextRequestExtras = Bundle.EMPTY;
+ mPerUserService.provideContextImageLocked(mUserId, imageContextRequestExtras);
+ verify(mActivityTaskManagerInternal, times(1)).getTaskSnapshotNoRestore(anyInt(),
+ anyBoolean());
+ }
+
+ // Tests TaskSnapshot is not taken when the key ContentSuggestionsManager.EXTRA_BITMAP is
+ // provided in imageContextRequestExtras.
+ @Test
+ public void testProvideContextImageLocked_bitmapInBundle() {
+ Bundle imageContextRequestExtras = new Bundle();
+ imageContextRequestExtras.putParcelable(ContentSuggestionsManager.EXTRA_BITMAP,
+ Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888));
+ mPerUserService.provideContextImageLocked(mUserId, imageContextRequestExtras);
+ verify(mActivityTaskManagerInternal, times(0))
+ .getTaskSnapshotNoRestore(anyInt(), anyBoolean());
+ }
+}
+
+
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
index 7081d2e..75fac7c 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
@@ -120,7 +120,7 @@
assertTrue(mInjector.mIdleScheduled);
mInjector.sendScreenChange(/*screen on */ true);
assertNotNull(mInjector.mSensorListener);
- assertTrue(mInjector.mColorSamplingEnabled);
+ assertEquals(BrightnessTracker.ENABLE_COLOR_SAMPLING, mInjector.mColorSamplingEnabled);
mInjector.sendScreenChange(/*screen on */ false);
assertNull(mInjector.mSensorListener);
@@ -141,7 +141,7 @@
// Turn on screen while brightness mode is automatic.
mInjector.sendScreenChange(/*screen on */ true);
assertNotNull(mInjector.mSensorListener);
- assertTrue(mInjector.mColorSamplingEnabled);
+ assertEquals(BrightnessTracker.ENABLE_COLOR_SAMPLING, mInjector.mColorSamplingEnabled);
mTracker.stop();
assertNull(mInjector.mSensorListener);
@@ -184,6 +184,9 @@
@Test
public void testColorSampling_FrameRateChange() {
+ if (!BrightnessTracker.ENABLE_COLOR_SAMPLING) {
+ return;
+ }
startTracker(mTracker);
assertTrue(mInjector.mColorSamplingEnabled);
assertNotNull(mInjector.mDisplayListener);
@@ -211,8 +214,10 @@
mInjector.setBrightnessMode(/*isBrightnessModeAutomatic*/ true);
assertNotNull(mInjector.mSensorListener);
- assertTrue(mInjector.mColorSamplingEnabled);
- assertNotNull(mInjector.mDisplayListener);
+ assertEquals(BrightnessTracker.ENABLE_COLOR_SAMPLING, mInjector.mColorSamplingEnabled);
+ if (BrightnessTracker.ENABLE_COLOR_SAMPLING) {
+ assertNotNull(mInjector.mDisplayListener);
+ }
SensorEventListener listener = mInjector.mSensorListener;
DisplayManager.DisplayListener displayListener = mInjector.mDisplayListener;
@@ -226,8 +231,10 @@
assertFalse(mInjector.mColorSamplingEnabled);
assertNull(mInjector.mDisplayListener);
mInjector.mSensorListener = listener;
- mInjector.mDisplayListener = displayListener;
- mInjector.mColorSamplingEnabled = true;
+ if (BrightnessTracker.ENABLE_COLOR_SAMPLING) {
+ mInjector.mDisplayListener = displayListener;
+ mInjector.mColorSamplingEnabled = true;
+ }
mInjector.setBrightnessMode(/*isBrightnessModeAutomatic*/ false);
assertNull(mInjector.mSensorListener);
@@ -301,8 +308,11 @@
assertEquals(3333, event.colorTemperature);
assertEquals("a.package", event.packageName);
assertEquals(0, event.userId);
- assertArrayEquals(new long[] {1, 10, 100, 1000, 300, 30, 10, 1}, event.colorValueBuckets);
- assertEquals(10000, event.colorSampleDuration);
+ if (BrightnessTracker.ENABLE_COLOR_SAMPLING) {
+ assertArrayEquals(new long[]{1, 10, 100, 1000, 300, 30, 10, 1},
+ event.colorValueBuckets);
+ assertEquals(10000, event.colorSampleDuration);
+ }
assertEquals(1, eventsNoPackage.size());
assertNull(eventsNoPackage.get(0).packageName);
@@ -559,8 +569,11 @@
assertEquals(0.5f, event.powerBrightnessFactor, FLOAT_DELTA);
assertTrue(event.isUserSetBrightness);
assertFalse(event.isDefaultBrightnessConfig);
- assertArrayEquals(new long[] {1, 10, 100, 1000, 300, 30, 10, 1}, event.colorValueBuckets);
- assertEquals(10000, event.colorSampleDuration);
+ if (BrightnessTracker.ENABLE_COLOR_SAMPLING) {
+ assertArrayEquals(new long[]{1, 10, 100, 1000, 300, 30, 10, 1},
+ event.colorValueBuckets);
+ assertEquals(10000, event.colorSampleDuration);
+ }
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
index 806c71a..6d5b994 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
@@ -132,6 +132,7 @@
user.profileBadge = 2;
user.partial = true;
user.guestToRemove = true;
+ user.preCreated = true;
return user;
}
@@ -147,5 +148,6 @@
assertEquals("profile badge not preseved", one.profileBadge, two.profileBadge);
assertEquals("partial not preseved", one.partial, two.partial);
assertEquals("guestToRemove not preseved", one.guestToRemove, two.guestToRemove);
+ assertEquals("preCreated not preseved", one.preCreated, two.preCreated);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java b/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java
index 36504ac..4a13dce 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java
@@ -28,7 +28,6 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import android.app.usage.UsageStatsManager;
import android.os.FileUtils;
import android.test.AndroidTestCase;
@@ -150,4 +149,21 @@
aih = new AppIdleHistory(mStorageDir, 5000);
assertEquals(REASON_MAIN_TIMEOUT, aih.getAppStandbyReason(PACKAGE_1, USER_ID, 5000));
}
+
+ public void testNullPackage() throws Exception {
+ AppIdleHistory aih = new AppIdleHistory(mStorageDir, 1000);
+ // Report usage of a package
+ aih.reportUsage(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
+ REASON_SUB_USAGE_MOVE_TO_FOREGROUND, 2000, 0);
+ // "Accidentally" report usage against a null named package
+ aih.reportUsage(null, USER_ID, STANDBY_BUCKET_ACTIVE,
+ REASON_SUB_USAGE_MOVE_TO_FOREGROUND, 2000, 0);
+ // Persist data
+ aih.writeAppIdleTimes(USER_ID);
+ // Recover data from disk
+ aih = new AppIdleHistory(mStorageDir, 5000);
+ // Verify data is intact
+ assertEquals(REASON_MAIN_USAGE | REASON_SUB_USAGE_MOVE_TO_FOREGROUND,
+ aih.getAppStandbyReason(PACKAGE_1, USER_ID, 3000));
+ }
}
\ No newline at end of file
diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp
index 92198fa..f608bab 100644
--- a/services/tests/uiservicestests/Android.bp
+++ b/services/tests/uiservicestests/Android.bp
@@ -20,6 +20,7 @@
"androidx.test.rules", "hamcrest-library",
"mockito-target-inline-minus-junit4",
"platform-test-annotations",
+ "platformprotosnano",
"hamcrest-library",
"testables",
"truth-prebuilt",
diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
new file mode 100644
index 0000000..338f837
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 20019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.app.IUiModeManager;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import com.android.server.twilight.TwilightManager;
+import com.android.server.wm.WindowManagerInternal;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import static android.app.UiModeManager.MODE_NIGHT_AUTO;
+import static android.app.UiModeManager.MODE_NIGHT_NO;
+import static android.app.UiModeManager.MODE_NIGHT_YES;
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class UiModeManagerServiceTest extends UiServiceTestCase {
+ private UiModeManagerService mUiManagerService;
+ private IUiModeManager mService;
+ @Mock
+ private ContentResolver mContentResolver;
+ @Mock
+ private WindowManagerInternal mWindowManager;
+ @Mock
+ private Context mContext;
+ @Mock
+ private Resources mResources;
+ @Mock
+ TwilightManager mTwilightManager;
+ @Mock
+ PowerManager.WakeLock mWakeLock;
+ private Set<BroadcastReceiver> mScreenOffRecievers;
+
+ @Before
+ public void setUp() {
+ mUiManagerService = new UiModeManagerService(mContext, mWindowManager, mWakeLock,
+ mTwilightManager, true);
+ mScreenOffRecievers = new HashSet<>();
+ mService = mUiManagerService.getService();
+ when(mContext.checkCallingOrSelfPermission(anyString()))
+ .thenReturn(PackageManager.PERMISSION_GRANTED);
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mContext.getContentResolver()).thenReturn(mContentResolver);
+ when(mContext.registerReceiver(any(), any())).then(inv -> {
+ mScreenOffRecievers.add(inv.getArgument(0));
+ return null;
+ });
+ }
+
+ @Test
+ public void setAutoMode_screenOffRegistered() throws RemoteException {
+ try {
+ mService.setNightMode(MODE_NIGHT_NO);
+ } catch (SecurityException e) { /* we should ignore this update config exception*/ }
+ mService.setNightMode(MODE_NIGHT_AUTO);
+ verify(mContext).registerReceiver(any(BroadcastReceiver.class), any());
+ }
+
+ @Test
+ public void setAutoMode_screenOffUnRegistered() throws RemoteException {
+ try {
+ mService.setNightMode(MODE_NIGHT_AUTO);
+ } catch (SecurityException e) { /* we should ignore this update config exception*/ }
+ try {
+ mService.setNightMode(MODE_NIGHT_NO);
+ } catch (SecurityException e) { /*we should ignore this update config exception*/ }
+ given(mContext.registerReceiver(any(), any())).willThrow(SecurityException.class);
+ verify(mContext).unregisterReceiver(any(BroadcastReceiver.class));
+ }
+
+ @Test
+ public void setNightModeActive_fromNightModeYesToNoWhenFalse() throws RemoteException {
+ try {
+ mService.setNightMode(MODE_NIGHT_YES);
+ } catch (SecurityException e) { /* we should ignore this update config exception*/ }
+ try {
+ mService.setNightModeActivated(false);
+ } catch (SecurityException e) { /* we should ignore this update config exception*/ }
+ assertEquals(MODE_NIGHT_NO, mService.getNightMode());
+ }
+
+ @Test
+ public void setNightModeActive_fromNightModeNoToYesWhenTrue() throws RemoteException {
+ try {
+ mService.setNightMode(MODE_NIGHT_NO);
+ } catch (SecurityException e) { /* we should ignore this update config exception*/ }
+ try {
+ mService.setNightModeActivated(true);
+ } catch (SecurityException e) { /* we should ignore this update config exception*/ }
+ assertEquals(MODE_NIGHT_YES, mService.getNightMode());
+ }
+
+ @Test
+ public void setNightModeActive_autoNightModeNoChanges() throws RemoteException {
+ try {
+ mService.setNightMode(MODE_NIGHT_AUTO);
+ } catch (SecurityException e) { /* we should ignore this update config exception*/ }
+ try {
+ mService.setNightModeActivated(true);
+ } catch (SecurityException e) { /* we should ignore this update config exception*/ }
+ assertEquals(MODE_NIGHT_AUTO, mService.getNightMode());
+ }
+
+ @Test
+ public void isNightModeActive_nightModeYes() throws RemoteException {
+ try {
+ mService.setNightMode(MODE_NIGHT_YES);
+ } catch (SecurityException e) { /* we should ignore this update config exception*/ }
+ assertTrue(isNightModeActivated());
+ }
+
+ @Test
+ public void isNightModeActive_nightModeNo() throws RemoteException {
+ try {
+ mService.setNightMode(MODE_NIGHT_NO);
+ } catch (SecurityException e) { /* we should ignore this update config exception*/ }
+ assertFalse(isNightModeActivated());
+ }
+
+ private boolean isNightModeActivated() {
+ return (mUiManagerService.getConfiguration().uiMode
+ & Configuration.UI_MODE_NIGHT_YES) != 0;
+ }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
index e15af3d..0b4760d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
@@ -68,6 +68,7 @@
private final int uid2 = 1111111;
private static final String TEST_CHANNEL_ID = "test_channel_id";
+ private NotificationRecord mRecordMinCallNonInterruptive;
private NotificationRecord mRecordMinCall;
private NotificationRecord mRecordHighCall;
private NotificationRecord mRecordDefaultMedia;
@@ -105,6 +106,18 @@
smsPkg = Settings.Secure.getString(mContext.getContentResolver(),
Settings.Secure.SMS_DEFAULT_APPLICATION);
+ Notification nonInterruptiveNotif = new Notification.Builder(mContext, TEST_CHANNEL_ID)
+ .setCategory(Notification.CATEGORY_CALL)
+ .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
+ .build();
+ mRecordMinCallNonInterruptive = new NotificationRecord(mContext,
+ new StatusBarNotification(callPkg,
+ callPkg, 1, "mRecordMinCallNonInterruptive", callUid, callUid,
+ nonInterruptiveNotif,
+ new UserHandle(userId), "", 2000), getDefaultChannel());
+ mRecordMinCallNonInterruptive.setSystemImportance(NotificationManager.IMPORTANCE_MIN);
+ mRecordMinCallNonInterruptive.setInterruptive(false);
+
Notification n1 = new Notification.Builder(mContext, TEST_CHANNEL_ID)
.setCategory(Notification.CATEGORY_CALL)
.setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
@@ -113,6 +126,7 @@
callPkg, 1, "minCall", callUid, callUid, n1,
new UserHandle(userId), "", 2000), getDefaultChannel());
mRecordMinCall.setSystemImportance(NotificationManager.IMPORTANCE_MIN);
+ mRecordMinCall.setInterruptive(true);
Notification n2 = new Notification.Builder(mContext, TEST_CHANNEL_ID)
.setCategory(Notification.CATEGORY_CALL)
@@ -245,6 +259,7 @@
expected.add(mRecordCheater);
expected.add(mRecordCheaterColorized);
expected.add(mRecordMinCall);
+ expected.add(mRecordMinCallNonInterruptive);
List<NotificationRecord> actual = new ArrayList<>();
actual.addAll(expected);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
index 397d215..a9fe1a6 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -51,6 +51,8 @@
import android.service.notification.SnoozeCriterion;
import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.server.UiServiceTestCase;
import org.junit.After;
@@ -61,8 +63,6 @@
import java.util.ArrayList;
import java.util.List;
-import androidx.test.runner.AndroidJUnit4;
-
@SmallTest
@RunWith(AndroidJUnit4.class)
public class NotificationListenerServiceTest extends UiServiceTestCase {
@@ -116,6 +116,7 @@
assertActionsEqual(getSmartActions(key, i), ranking.getSmartActions());
assertEquals(getSmartReplies(key, i), ranking.getSmartReplies());
assertEquals(canBubble(i), ranking.canBubble());
+ assertEquals(visuallyInterruptive(i), ranking.visuallyInterruptive());
}
}
@@ -182,7 +183,8 @@
tweak.isNoisy(),
(ArrayList) tweak.getSmartActions(),
(ArrayList) tweak.getSmartReplies(),
- tweak.canBubble()
+ tweak.canBubble(),
+ tweak.visuallyInterruptive()
);
assertNotEquals(nru, nru2);
}
@@ -258,7 +260,8 @@
getNoisy(i),
getSmartActions(key, i),
getSmartReplies(key, i),
- canBubble(i)
+ canBubble(i),
+ visuallyInterruptive(i)
);
rankings[i] = ranking;
}
@@ -363,6 +366,10 @@
return index % 4 == 0;
}
+ private boolean visuallyInterruptive(int index) {
+ return index % 4 == 0;
+ }
+
private void assertActionsEqual(
List<Notification.Action> expecteds, List<Notification.Action> actuals) {
assertEquals(expecteds.size(), actuals.size());
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index b5542af..389b68a 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -1017,20 +1017,6 @@
}
@Test
- public void testCancelImmediatelyAfterEnqueueNotifiesListeners_ForegroundServiceFlag()
- throws Exception {
- final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
- sbn.getNotification().flags =
- Notification.FLAG_ONGOING_EVENT | FLAG_FOREGROUND_SERVICE;
- mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
- sbn.getId(), sbn.getNotification(), sbn.getUserId());
- mBinderService.cancelNotificationWithTag(PKG, "tag", sbn.getId(), sbn.getUserId());
- waitForIdle();
- verify(mListeners, times(1)).notifyPostedLocked(any(), any());
- verify(mListeners, times(1)).notifyRemovedLocked(any(), anyInt(), any());
- }
-
- @Test
public void testUserInitiatedClearAll_noLeak() throws Exception {
final NotificationRecord n = generateNotificationRecord(
mTestNotificationChannel, 1, "group", true);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationShellCmdTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationShellCmdTest.java
index fa90b29..0d44318 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationShellCmdTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationShellCmdTest.java
@@ -116,8 +116,8 @@
ArgumentCaptor<Notification> notificationCaptor =
ArgumentCaptor.forClass(Notification.class);
verify(mMockBinderService).enqueueNotificationWithTag(
- eq(NotificationShellCmd.NOTIFICATION_PACKAGE),
- eq("android"),
+ eq(getContext().getPackageName()),
+ eq(getContext().getPackageName()),
eq(aTag),
eq(NotificationShellCmd.NOTIFICATION_ID),
notificationCaptor.capture(),
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PulledStatsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PulledStatsTest.java
new file mode 100644
index 0000000..f685c68
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PulledStatsTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.server.notification;
+
+import static com.android.server.notification.NotificationManagerService.REPORT_REMOTE_VIEWS;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotSame;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.service.notification.nano.NotificationRemoteViewsProto;
+import android.test.MoreAsserts;
+import android.util.proto.ProtoOutputStream;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.UiServiceTestCase;
+
+import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
+
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+public class PulledStatsTest extends UiServiceTestCase {
+
+ @Test
+ public void testPulledStats_Empty() {
+ PulledStats stats = new PulledStats(0L);
+ assertEquals(0L, stats.endTimeMs());
+ }
+
+ @Test
+ public void testPulledStats_UnknownReport() {
+ PulledStats stats = new PulledStats(0L);
+ stats.addUndecoratedPackage("foo", 456);
+ stats.addUndecoratedPackage("bar", 123);
+
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ final ProtoOutputStream proto = new ProtoOutputStream(bytes);
+ stats.writeToProto(1023123, proto); // a very large number
+ proto.flush();
+
+ // expect empty output in response to an unrecognized request
+ assertEquals(0L, bytes.size());
+ }
+
+ @Test
+ public void testPulledStats_RemoteViewReportPackages() {
+ List<String> expectedPkgs = new ArrayList<>(2);
+ expectedPkgs.add("foo");
+ expectedPkgs.add("bar");
+
+ PulledStats stats = new PulledStats(0L);
+ for(String pkg: expectedPkgs) {
+ stats.addUndecoratedPackage(pkg, 111);
+ }
+
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ final ProtoOutputStream protoStream = new ProtoOutputStream(bytes);
+ stats.writeToProto(REPORT_REMOTE_VIEWS, protoStream);
+ protoStream.flush();
+
+ try {
+ NotificationRemoteViewsProto proto =
+ NotificationRemoteViewsProto.parseFrom(bytes.toByteArray());
+ List<String> actualPkgs = new ArrayList<>(2);
+ for(int i = 0 ; i < proto.packageRemoteViewInfo.length; i++) {
+ actualPkgs.add(proto.packageRemoteViewInfo[i].packageName);
+ }
+ assertEquals(2, actualPkgs.size());
+ assertTrue("missing packages", actualPkgs.containsAll(expectedPkgs));
+ assertTrue("unexpected packages", expectedPkgs.containsAll(actualPkgs));
+ } catch (InvalidProtocolBufferNanoException e) {
+ e.printStackTrace();
+ fail("writeToProto generated unparsable output");
+ }
+
+ }
+ @Test
+ public void testPulledStats_RemoteViewReportEndTime() {
+ List<String> expectedPkgs = new ArrayList<>(2);
+ expectedPkgs.add("foo");
+ expectedPkgs.add("bar");
+
+ PulledStats stats = new PulledStats(0L);
+ long t = 111;
+ for(String pkg: expectedPkgs) {
+ t += 1000;
+ stats.addUndecoratedPackage(pkg, t);
+ }
+ assertEquals(t, stats.endTimeMs());
+ }
+
+}
diff --git a/services/usage/java/com/android/server/usage/AppIdleHistory.java b/services/usage/java/com/android/server/usage/AppIdleHistory.java
index 1e4861a..82292cf 100644
--- a/services/usage/java/com/android/server/usage/AppIdleHistory.java
+++ b/services/usage/java/com/android/server/usage/AppIdleHistory.java
@@ -578,7 +578,7 @@
}
}
} catch (IOException | XmlPullParserException e) {
- Slog.e(TAG, "Unable to read app idle file for user " + userId);
+ Slog.e(TAG, "Unable to read app idle file for user " + userId, e);
} finally {
IoUtils.closeQuietly(fis);
}
@@ -608,6 +608,11 @@
final int N = userHistory.size();
for (int i = 0; i < N; i++) {
String packageName = userHistory.keyAt(i);
+ // Skip any unexpected null package names
+ if (packageName == null) {
+ Slog.w(TAG, "Skipping App Idle write for unexpected null package");
+ continue;
+ }
AppUsageHistory history = userHistory.valueAt(i);
xml.startTag(null, TAG_PACKAGE);
xml.attribute(null, ATTR_NAME, packageName);
@@ -641,7 +646,7 @@
appIdleFile.finishWrite(fos);
} catch (Exception e) {
appIdleFile.failWrite(fos);
- Slog.e(TAG, "Error writing app idle file for user " + userId);
+ Slog.e(TAG, "Error writing app idle file for user " + userId, e);
}
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 4071e95..d3f069f 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -2930,6 +2930,54 @@
"ping_test_before_data_switch_bool";
/**
+ * Controls whether to switch data to primary from opportunistic subscription
+ * if primary is out of service. This control only affects system or 1st party app
+ * initiated data switch, but will not override data switch initiated by privileged carrier apps
+ * This carrier config is used to disable this feature.
+ * @hide
+ */
+ public static final String KEY_SWITCH_DATA_TO_PRIMARY_IF_PRIMARY_IS_OOS_BOOL =
+ "switch_data_to_primary_if_primary_is_oos_bool";
+
+ /**
+ * Controls the ping pong determination of opportunistic network.
+ * If opportunistic network is determined as out of service or below
+ * #KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSRP_INT or
+ * #KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSSNR_INT within
+ * #KEY_OPPORTUNISTIC_NETWORK_PING_PONG_TIME_LONG of switching to opportunistic network,
+ * it will be determined as ping pong situation by system app or 1st party app.
+ * @hide
+ */
+ public static final String KEY_OPPORTUNISTIC_NETWORK_PING_PONG_TIME_LONG =
+ "opportunistic_network_ping_pong_time_long";
+ /**
+ * Controls back off time in milli seconds for switching back to
+ * opportunistic subscription. This time will be added to
+ * {@link CarrierConfigManager#KEY_OPPORTUNISTIC_NETWORK_DATA_SWITCH_HYSTERESIS_TIME_LONG} to
+ * determine hysteresis time if there is ping pong situation
+ * (determined by system app or 1st party app) between primary and opportunistic
+ * subscription. Ping ping situation is defined in
+ * #KEY_OPPORTUNISTIC_NETWORK_PING_PONG_TIME_LONG.
+ * If ping pong situation continuous #KEY_OPPORTUNISTIC_NETWORK_BACKOFF_TIME_LONG
+ * will be added to previously determined hysteresis time.
+ * @hide
+ */
+ public static final String KEY_OPPORTUNISTIC_NETWORK_BACKOFF_TIME_LONG =
+ "opportunistic_network_backoff_time_long";
+
+ /**
+ * Controls the max back off time in milli seconds for switching back to
+ * opportunistic subscription.
+ * This time will be the max hysteresis that can be determined irrespective of there is
+ * continuous ping pong situation or not as described in
+ * #KEY_OPPORTUNISTIC_NETWORK_PING_PONG_TIME_LONG and
+ * #KEY_OPPORTUNISTIC_NETWORK_BACKOFF_TIME_LONG.
+ * @hide
+ */
+ public static final String KEY_OPPORTUNISTIC_NETWORK_MAX_BACKOFF_TIME_LONG =
+ "opportunistic_network_max_backoff_time_long";
+
+ /**
* Indicates zero or more emergency number prefix(es), because some carrier requires
* if users dial an emergency number address with a specific prefix, the combination of the
* prefix and the address is also a valid emergency number to dial. For example, an emergency
@@ -3762,6 +3810,13 @@
/* Default value is 3 seconds. */
sDefaults.putLong(KEY_OPPORTUNISTIC_NETWORK_DATA_SWITCH_EXIT_HYSTERESIS_TIME_LONG, 3000);
sDefaults.putBoolean(KEY_PING_TEST_BEFORE_DATA_SWITCH_BOOL, true);
+ sDefaults.putBoolean(KEY_SWITCH_DATA_TO_PRIMARY_IF_PRIMARY_IS_OOS_BOOL, true);
+ /* Default value is 60 seconds. */
+ sDefaults.putLong(KEY_OPPORTUNISTIC_NETWORK_PING_PONG_TIME_LONG, 60000);
+ /* Default value is 10 seconds. */
+ sDefaults.putLong(KEY_OPPORTUNISTIC_NETWORK_BACKOFF_TIME_LONG, 10000);
+ /* Default value is 60 seconds. */
+ sDefaults.putLong(KEY_OPPORTUNISTIC_NETWORK_MAX_BACKOFF_TIME_LONG, 60000);
sDefaults.putAll(Gps.getDefaults());
sDefaults.putAll(Wifi.getDefaults());
sDefaults.putIntArray(KEY_CDMA_ENHANCED_ROAMING_INDICATOR_FOR_HOME_NETWORK_INT_ARRAY,
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index 9eff809..ebb5175 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -89,8 +89,8 @@
private int mCarrierId;
/**
- * The source of the name, NAME_SOURCE_DEFAULT_SOURCE, NAME_SOURCE_SIM_SOURCE or
- * NAME_SOURCE_USER_INPUT.
+ * The source of the name, NAME_SOURCE_DEFAULT_SOURCE, NAME_SOURCE_SIM_SPN,
+ * NAME_SOURCE_SIM_PNN, or NAME_SOURCE_USER_INPUT.
*/
private int mNameSource;
@@ -334,7 +334,7 @@
}
/**
- * @return the source of the name, eg NAME_SOURCE_DEFAULT_SOURCE, NAME_SOURCE_SIM_SOURCE or
+ * @return the source of the name, eg NAME_SOURCE_DEFAULT_SOURCE, NAME_SOURCE_SIM_SPN or
* NAME_SOURCE_USER_INPUT.
* @hide
*/
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index fbbc951..16415ea 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -400,19 +400,19 @@
public static final String NAME_SOURCE = "name_source";
/**
- * The name_source is the default
+ * The name_source is the default, which is from the carrier id.
* @hide
*/
public static final int NAME_SOURCE_DEFAULT_SOURCE = 0;
/**
- * The name_source is from the SIM
+ * The name_source is from SIM EF_SPN.
* @hide
*/
- public static final int NAME_SOURCE_SIM_SOURCE = 1;
+ public static final int NAME_SOURCE_SIM_SPN = 1;
/**
- * The name_source is from the user
+ * The name_source is from user input
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
@@ -425,6 +425,24 @@
public static final int NAME_SOURCE_CARRIER = 3;
/**
+ * The name_source is from SIM EF_PNN.
+ * @hide
+ */
+ public static final int NAME_SOURCE_SIM_PNN = 4;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"NAME_SOURCE_"},
+ value = {
+ NAME_SOURCE_DEFAULT_SOURCE,
+ NAME_SOURCE_SIM_SPN,
+ NAME_SOURCE_USER_INPUT,
+ NAME_SOURCE_CARRIER,
+ NAME_SOURCE_SIM_PNN
+ })
+ public @interface SimDisplayNameSource {}
+
+ /**
* TelephonyProvider column name for the color of a SIM.
* <P>Type: INTEGER (int)</P>
*/
@@ -1660,13 +1678,12 @@
* Set display name by simInfo index with name source
* @param displayName the display name of SIM card
* @param subId the unique SubscriptionInfo index in database
- * @param nameSource 0: NAME_SOURCE_DEFAULT_SOURCE, 1: NAME_SOURCE_SIM_SOURCE,
- * 2: NAME_SOURCE_USER_INPUT
+ * @param nameSource SIM display name source
* @return the number of records updated or < 0 if invalid subId
* @hide
*/
@UnsupportedAppUsage
- public int setDisplayName(String displayName, int subId, int nameSource) {
+ public int setDisplayName(String displayName, int subId, @SimDisplayNameSource int nameSource) {
if (VDBG) {
logd("[setDisplayName]+ displayName:" + displayName + " subId:" + subId
+ " nameSource:" + nameSource);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 4fb26fd..97a3273 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -38,6 +38,9 @@
import android.annotation.WorkerThread;
import android.app.ActivityThread;
import android.app.PendingIntent;
+import android.compat.Compatibility;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -142,6 +145,14 @@
private static final String TAG = "TelephonyManager";
/**
+ * To expand the error codes for {@link TelephonyManager#updateAvailableNetworks} and
+ * {@link TelephonyManager#setPreferredOpportunisticDataSubscription}.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+ public static final long CALLBACK_ON_MORE_ERROR_CODE_CHANGE = 130595455L;
+
+ /**
* The key to use when placing the result of {@link #requestModemActivityInfo(ResultReceiver)}
* into the ResultReceiver Bundle.
* @hide
@@ -11017,7 +11028,9 @@
@IntDef(prefix = {"SET_OPPORTUNISTIC_SUB"}, value = {
SET_OPPORTUNISTIC_SUB_SUCCESS,
SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED,
- SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION})
+ SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION,
+ SET_OPPORTUNISTIC_SUB_NO_OPPORTUNISTIC_SUB_AVAILABLE,
+ SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION})
public @interface SetOpportunisticSubscriptionResult {}
/**
@@ -11035,6 +11048,16 @@
*/
public static final int SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION = 2;
+ /**
+ * The subscription is not valid. It must be an opportunistic subscription.
+ */
+ public static final int SET_OPPORTUNISTIC_SUB_NO_OPPORTUNISTIC_SUB_AVAILABLE = 3;
+
+ /**
+ * Subscription service happened remote exception.
+ */
+ public static final int SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION = 4;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"UPDATE_AVAILABLE_NETWORKS"}, value = {
@@ -11042,7 +11065,13 @@
UPDATE_AVAILABLE_NETWORKS_UNKNOWN_FAILURE,
UPDATE_AVAILABLE_NETWORKS_ABORTED,
UPDATE_AVAILABLE_NETWORKS_INVALID_ARGUMENTS,
- UPDATE_AVAILABLE_NETWORKS_NO_CARRIER_PRIVILEGE})
+ UPDATE_AVAILABLE_NETWORKS_NO_CARRIER_PRIVILEGE,
+ UPDATE_AVAILABLE_NETWORKS_DISABLE_MODEM_FAIL,
+ UPDATE_AVAILABLE_NETWORKS_ENABLE_MODEM_FAIL,
+ UPDATE_AVAILABLE_NETWORKS_MULTIPLE_NETWORKS_NOT_SUPPORTED,
+ UPDATE_AVAILABLE_NETWORKS_NO_OPPORTUNISTIC_SUB_AVAILABLE,
+ UPDATE_AVAILABLE_NETWORKS_REMOTE_SERVICE_EXCEPTION,
+ UPDATE_AVAILABLE_NETWORKS_SERVICE_IS_DISABLED})
public @interface UpdateAvailableNetworksResult {}
/**
@@ -11071,6 +11100,36 @@
public static final int UPDATE_AVAILABLE_NETWORKS_NO_CARRIER_PRIVILEGE = 4;
/**
+ * Disable modem fail.
+ */
+ public static final int UPDATE_AVAILABLE_NETWORKS_DISABLE_MODEM_FAIL = 5;
+
+ /**
+ * Enable modem fail.
+ */
+ public static final int UPDATE_AVAILABLE_NETWORKS_ENABLE_MODEM_FAIL = 6;
+
+ /**
+ * Carrier app does not support multiple available networks.
+ */
+ public static final int UPDATE_AVAILABLE_NETWORKS_MULTIPLE_NETWORKS_NOT_SUPPORTED = 7;
+
+ /**
+ * The subscription is not valid. It must be an opportunistic subscription.
+ */
+ public static final int UPDATE_AVAILABLE_NETWORKS_NO_OPPORTUNISTIC_SUB_AVAILABLE = 8;
+
+ /**
+ * There is no OpportunisticNetworkService.
+ */
+ public static final int UPDATE_AVAILABLE_NETWORKS_REMOTE_SERVICE_EXCEPTION = 9;
+
+ /**
+ * OpportunisticNetworkService is disabled.
+ */
+ public static final int UPDATE_AVAILABLE_NETWORKS_SERVICE_IS_DISABLED = 10;
+
+ /**
* Set preferred opportunistic data subscription id.
*
* Switch internet data to preferred opportunistic data subscription id. This api
@@ -11102,7 +11161,11 @@
final long identity = Binder.clearCallingIdentity();
try {
executor.execute(() -> {
- callback.accept(SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION);
+ if (Compatibility.isChangeEnabled(CALLBACK_ON_MORE_ERROR_CODE_CHANGE)) {
+ callback.accept(SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION);
+ } else {
+ callback.accept(SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION);
+ }
});
} finally {
Binder.restoreCallingIdentity(identity);
@@ -11196,9 +11259,12 @@
if (iOpportunisticNetworkService == null) {
final long identity = Binder.clearCallingIdentity();
try {
- /* Todo<b/130595455> passing unknown due to lack of good error codes */
executor.execute(() -> {
- callback.accept(UPDATE_AVAILABLE_NETWORKS_UNKNOWN_FAILURE);
+ if (Compatibility.isChangeEnabled(CALLBACK_ON_MORE_ERROR_CODE_CHANGE)) {
+ callback.accept(UPDATE_AVAILABLE_NETWORKS_REMOTE_SERVICE_EXCEPTION);
+ } else {
+ callback.accept(UPDATE_AVAILABLE_NETWORKS_UNKNOWN_FAILURE);
+ }
});
} finally {
Binder.restoreCallingIdentity(identity);
diff --git a/telephony/java/android/telephony/ims/ImsReasonInfo.java b/telephony/java/android/telephony/ims/ImsReasonInfo.java
index 76b214b..f4b2cef 100644
--- a/telephony/java/android/telephony/ims/ImsReasonInfo.java
+++ b/telephony/java/android/telephony/ims/ImsReasonInfo.java
@@ -889,6 +889,12 @@
*/
public static final int CODE_WFC_SERVICE_NOT_AVAILABLE_IN_THIS_LOCATION = 1623;
+ /**
+ * The dialed RTT call should be retried without RTT
+ * @hide
+ */
+ public static final int CODE_RETRY_ON_IMS_WITHOUT_RTT = 3001;
+
/*
* OEM specific error codes. To be used by OEMs when they don't want to reveal error code which
* would be replaced by ERROR_UNSPECIFIED.
@@ -1069,6 +1075,7 @@
CODE_REJECT_VT_AVPF_NOT_ALLOWED,
CODE_REJECT_ONGOING_ENCRYPTED_CALL,
CODE_REJECT_ONGOING_CS_CALL,
+ CODE_RETRY_ON_IMS_WITHOUT_RTT,
CODE_OEM_CAUSE_1,
CODE_OEM_CAUSE_2,
CODE_OEM_CAUSE_3,
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java b/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
index bdd1fab..515687f 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
@@ -225,7 +225,7 @@
private static Pair<Integer, List<Geometry>> parseWarningAreaCoordinates(
byte[] pdu, int wacOffset) {
// little-endian
- int wacDataLength = (pdu[wacOffset + 1] << 8) | pdu[wacOffset];
+ int wacDataLength = ((pdu[wacOffset + 1] & 0xff) << 8) | (pdu[wacOffset] & 0xff);
int offset = wacOffset + 2;
if (offset + wacDataLength > pdu.length) {
diff --git a/tests/testables/src/android/testing/TestableSettingsProvider.java b/tests/testables/src/android/testing/TestableSettingsProvider.java
index b158476..fd92c65 100644
--- a/tests/testables/src/android/testing/TestableSettingsProvider.java
+++ b/tests/testables/src/android/testing/TestableSettingsProvider.java
@@ -14,6 +14,8 @@
package android.testing;
+import static org.junit.Assert.assertEquals;
+
import android.content.ContentProviderClient;
import android.content.Context;
import android.os.Bundle;
@@ -25,8 +27,6 @@
import java.util.HashMap;
-import static org.junit.Assert.*;
-
/**
* Allows calls to android.provider.Settings to be tested easier.
*
@@ -71,7 +71,7 @@
public Bundle call(String method, String arg, Bundle extras) {
// Methods are "GET_system", "GET_global", "PUT_secure", etc.
- final int userId = extras.getInt(Settings.CALL_METHOD_USER_KEY, 0);
+ final int userId = extras.getInt(Settings.CALL_METHOD_USER_KEY, UserHandle.myUserId());
final String[] commands = method.split("_", 2);
final String op = commands[0];
final String table = commands[1];
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 671b3c7..7ce83f9 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1776,12 +1776,13 @@
}
/**
- * Remove the Passpoint configuration identified by its FQDN (Fully Qualified Domain Name).
+ * Remove the Passpoint configuration identified by its FQDN (Fully Qualified Domain Name) added
+ * by the caller.
*
- * @param fqdn The FQDN of the Passpoint configuration to be removed
+ * @param fqdn The FQDN of the Passpoint configuration added by the caller to be removed
* @throws IllegalArgumentException if no configuration is associated with the given FQDN or
* Passpoint is not enabled on the device.
- * @deprecated This is no longer supported.
+ * @deprecated This will be non-functional in a future release.
*/
@Deprecated
@RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
@@ -1796,12 +1797,12 @@
}
/**
- * Return the list of installed Passpoint configurations.
+ * Return the list of installed Passpoint configurations added by the caller.
*
* An empty list will be returned when no configurations are installed.
*
- * @return A list of {@link PasspointConfiguration}
- * @deprecated This is no longer supported.
+ * @return A list of {@link PasspointConfiguration} added by the caller
+ * @deprecated This will be non-functional in a future release.
*/
@Deprecated
@RequiresPermission(anyOf = {
@@ -2068,69 +2069,69 @@
}
/** @hide */
- public static final int WIFI_FEATURE_INFRA = 0x0001; // Basic infrastructure mode
+ public static final long WIFI_FEATURE_INFRA = 0x0001L; // Basic infrastructure mode
/** @hide */
- public static final int WIFI_FEATURE_INFRA_5G = 0x0002; // Support for 5 GHz Band
+ public static final long WIFI_FEATURE_INFRA_5G = 0x0002L; // Support for 5 GHz Band
/** @hide */
- public static final int WIFI_FEATURE_PASSPOINT = 0x0004; // Support for GAS/ANQP
+ public static final long WIFI_FEATURE_PASSPOINT = 0x0004L; // Support for GAS/ANQP
/** @hide */
- public static final int WIFI_FEATURE_P2P = 0x0008; // Wifi-Direct
+ public static final long WIFI_FEATURE_P2P = 0x0008L; // Wifi-Direct
/** @hide */
- public static final int WIFI_FEATURE_MOBILE_HOTSPOT = 0x0010; // Soft AP
+ public static final long WIFI_FEATURE_MOBILE_HOTSPOT = 0x0010L; // Soft AP
/** @hide */
- public static final int WIFI_FEATURE_SCANNER = 0x0020; // WifiScanner APIs
+ public static final long WIFI_FEATURE_SCANNER = 0x0020L; // WifiScanner APIs
/** @hide */
- public static final int WIFI_FEATURE_AWARE = 0x0040; // Wi-Fi AWare networking
+ public static final long WIFI_FEATURE_AWARE = 0x0040L; // Wi-Fi AWare networking
/** @hide */
- public static final int WIFI_FEATURE_D2D_RTT = 0x0080; // Device-to-device RTT
+ public static final long WIFI_FEATURE_D2D_RTT = 0x0080L; // Device-to-device RTT
/** @hide */
- public static final int WIFI_FEATURE_D2AP_RTT = 0x0100; // Device-to-AP RTT
+ public static final long WIFI_FEATURE_D2AP_RTT = 0x0100L; // Device-to-AP RTT
/** @hide */
- public static final int WIFI_FEATURE_BATCH_SCAN = 0x0200; // Batched Scan (deprecated)
+ public static final long WIFI_FEATURE_BATCH_SCAN = 0x0200L; // Batched Scan (deprecated)
/** @hide */
- public static final int WIFI_FEATURE_PNO = 0x0400; // Preferred network offload
+ public static final long WIFI_FEATURE_PNO = 0x0400L; // Preferred network offload
/** @hide */
- public static final int WIFI_FEATURE_ADDITIONAL_STA = 0x0800; // Support for two STAs
+ public static final long WIFI_FEATURE_ADDITIONAL_STA = 0x0800L; // Support for two STAs
/** @hide */
- public static final int WIFI_FEATURE_TDLS = 0x1000; // Tunnel directed link setup
+ public static final long WIFI_FEATURE_TDLS = 0x1000L; // Tunnel directed link setup
/** @hide */
- public static final int WIFI_FEATURE_TDLS_OFFCHANNEL = 0x2000; // Support for TDLS off channel
+ public static final long WIFI_FEATURE_TDLS_OFFCHANNEL = 0x2000L; // TDLS off channel
/** @hide */
- public static final int WIFI_FEATURE_EPR = 0x4000; // Enhanced power reporting
+ public static final long WIFI_FEATURE_EPR = 0x4000L; // Enhanced power reporting
/** @hide */
- public static final int WIFI_FEATURE_AP_STA = 0x8000; // AP STA Concurrency
+ public static final long WIFI_FEATURE_AP_STA = 0x8000L; // AP STA Concurrency
/** @hide */
- public static final int WIFI_FEATURE_LINK_LAYER_STATS = 0x10000; // Link layer stats collection
+ public static final long WIFI_FEATURE_LINK_LAYER_STATS = 0x10000L; // Link layer stats
/** @hide */
- public static final int WIFI_FEATURE_LOGGER = 0x20000; // WiFi Logger
+ public static final long WIFI_FEATURE_LOGGER = 0x20000L; // WiFi Logger
/** @hide */
- public static final int WIFI_FEATURE_HAL_EPNO = 0x40000; // Enhanced PNO
+ public static final long WIFI_FEATURE_HAL_EPNO = 0x40000L; // Enhanced PNO
/** @hide */
- public static final int WIFI_FEATURE_RSSI_MONITOR = 0x80000; // RSSI Monitor
+ public static final long WIFI_FEATURE_RSSI_MONITOR = 0x80000L; // RSSI Monitor
/** @hide */
- public static final int WIFI_FEATURE_MKEEP_ALIVE = 0x100000; // mkeep_alive
+ public static final long WIFI_FEATURE_MKEEP_ALIVE = 0x100000L; // mkeep_alive
/** @hide */
- public static final int WIFI_FEATURE_CONFIG_NDO = 0x200000; // ND offload
+ public static final long WIFI_FEATURE_CONFIG_NDO = 0x200000L; // ND offload
/** @hide */
- public static final int WIFI_FEATURE_TRANSMIT_POWER = 0x400000; // Capture transmit power
+ public static final long WIFI_FEATURE_TRANSMIT_POWER = 0x400000L; // Capture transmit power
/** @hide */
- public static final int WIFI_FEATURE_CONTROL_ROAMING = 0x800000; // Control firmware roaming
+ public static final long WIFI_FEATURE_CONTROL_ROAMING = 0x800000L; // Control firmware roaming
/** @hide */
- public static final int WIFI_FEATURE_IE_WHITELIST = 0x1000000; // Probe IE white listing
+ public static final long WIFI_FEATURE_IE_WHITELIST = 0x1000000L; // Probe IE white listing
/** @hide */
- public static final int WIFI_FEATURE_SCAN_RAND = 0x2000000; // Random MAC & Probe seq
+ public static final long WIFI_FEATURE_SCAN_RAND = 0x2000000L; // Random MAC & Probe seq
/** @hide */
- public static final int WIFI_FEATURE_TX_POWER_LIMIT = 0x4000000; // Set Tx power limit
+ public static final long WIFI_FEATURE_TX_POWER_LIMIT = 0x4000000L; // Set Tx power limit
/** @hide */
- public static final int WIFI_FEATURE_WPA3_SAE = 0x8000000; // WPA3-Personal SAE
+ public static final long WIFI_FEATURE_WPA3_SAE = 0x8000000L; // WPA3-Personal SAE
/** @hide */
- public static final int WIFI_FEATURE_WPA3_SUITE_B = 0x10000000; // WPA3-Enterprise Suite-B
+ public static final long WIFI_FEATURE_WPA3_SUITE_B = 0x10000000L; // WPA3-Enterprise Suite-B
/** @hide */
- public static final int WIFI_FEATURE_OWE = 0x20000000; // Enhanced Open
+ public static final long WIFI_FEATURE_OWE = 0x20000000L; // Enhanced Open
/** @hide */
- public static final int WIFI_FEATURE_LOW_LATENCY = 0x40000000; // Low Latency modes
+ public static final long WIFI_FEATURE_LOW_LATENCY = 0x40000000L; // Low Latency modes
/** @hide */
- public static final int WIFI_FEATURE_DPP = 0x80000000; // DPP (Easy-Connect)
+ public static final long WIFI_FEATURE_DPP = 0x80000000L; // DPP (Easy-Connect)
/** @hide */
public static final long WIFI_FEATURE_P2P_RAND_MAC = 0x100000000L; // Random P2P MAC
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareNetworkInfo.java b/wifi/java/android/net/wifi/aware/WifiAwareNetworkInfo.java
index fd26817b..e9fb37e 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareNetworkInfo.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareNetworkInfo.java
@@ -120,10 +120,12 @@
new Creator<WifiAwareNetworkInfo>() {
@Override
public WifiAwareNetworkInfo createFromParcel(Parcel in) {
+ byte[] addr = in.createByteArray();
+ String interfaceName = in.readString();
+ int port = in.readInt();
+ int transportProtocol = in.readInt();
Inet6Address ipv6Addr;
try {
- byte[] addr = in.createByteArray();
- String interfaceName = in.readString();
NetworkInterface ni = null;
if (interfaceName != null) {
try {
@@ -137,9 +139,6 @@
e.printStackTrace();
return null;
}
- int port = in.readInt();
- int transportProtocol = in.readInt();
-
return new WifiAwareNetworkInfo(ipv6Addr, port, transportProtocol);
}
diff --git a/wifi/java/android/net/wifi/rtt/package.html b/wifi/java/android/net/wifi/rtt/package.html
index e639282..4a32f52 100644
--- a/wifi/java/android/net/wifi/rtt/package.html
+++ b/wifi/java/android/net/wifi/rtt/package.html
@@ -37,5 +37,9 @@
<pre>
getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_RTT)
</pre>
+
+<p>For an example of this functionality, see
+<a href="{@docRoot}guide/topics/connectivity/wifi-rtt" class="external">Wi-Fi location: ranging
+with RTT</a>.</p>
</BODY>
</HTML>