Merge "Use new UnsupportedAppUsage annotation." am: 70129fc863 am: fe3063fb71
Change-Id: Iea82f97f0ba766ce3f435630175aea48ca47f12f
diff --git a/api/current.txt b/api/current.txt
index cf9a292..b99db90 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -45562,6 +45562,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
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/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 1c7180f..64b7aae 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -266,7 +266,9 @@
IResultReceiver::asInterface(data.readStrongBinder());
err = command(in, out, err, args, resultReceiver);
- resultReceiver->send(err);
+ if (resultReceiver != nullptr) {
+ resultReceiver->send(err);
+ }
return NO_ERROR;
}
default: { return BnStatsManager::onTransact(code, data, reply, flags); }
@@ -411,13 +413,20 @@
return cmd_trigger_active_config_broadcast(out, args);
}
if (!args[0].compare(String8("data-subscribe"))) {
- if (mShellSubscriber == nullptr) {
- mShellSubscriber = new ShellSubscriber(mUidMap, mPullerManager);
+ {
+ std::lock_guard<std::mutex> lock(mShellSubscriberMutex);
+ if (mShellSubscriber == nullptr) {
+ mShellSubscriber = new ShellSubscriber(mUidMap, mPullerManager);
+ }
}
int timeoutSec = -1;
if (argCount >= 2) {
timeoutSec = atoi(args[1].c_str());
}
+ if (resultReceiver == nullptr) {
+ ALOGI("Null resultReceiver given, no subscription will be started");
+ return UNEXPECTED_NULL;
+ }
mShellSubscriber->startNewSubscription(in, out, resultReceiver, timeoutSec);
return NO_ERROR;
}
@@ -1385,7 +1394,10 @@
Status StatsService::sendWatchdogRollbackOccurredAtom(const int32_t rollbackTypeIn,
const android::String16& packageNameIn,
- const int64_t packageVersionCodeIn) {
+ const int64_t packageVersionCodeIn,
+ const int32_t rollbackReasonIn,
+ const android::String16&
+ failingPackageNameIn) {
// Note: We skip the usage stats op check here since we do not have a package name.
// This is ok since we are overloading the usage_stats permission.
// This method only sends data, it does not receive it.
@@ -1407,7 +1419,8 @@
}
android::util::stats_write(android::util::WATCHDOG_ROLLBACK_OCCURRED,
- rollbackTypeIn, String8(packageNameIn).string(), packageVersionCodeIn);
+ rollbackTypeIn, String8(packageNameIn).string(), packageVersionCodeIn,
+ rollbackReasonIn, String8(failingPackageNameIn).string());
// Fast return to save disk read.
if (rollbackTypeIn != android::util::WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 53b6ce9..5f1335e 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -200,7 +200,9 @@
virtual Status sendWatchdogRollbackOccurredAtom(
const int32_t rollbackTypeIn,
const android::String16& packageNameIn,
- const int64_t packageVersionCodeIn) override;
+ const int64_t packageVersionCodeIn,
+ const int32_t rollbackReasonIn,
+ const android::String16& failingPackageNameIn) override;
/**
* Binder call to get registered experiment IDs.
@@ -432,6 +434,10 @@
sp<ShellSubscriber> mShellSubscriber;
+ /**
+ * Mutex for setting the shell subscriber
+ */
+ mutable mutex mShellSubscriberMutex;
std::shared_ptr<LogEventQueue> mEventQueue;
FRIEND_TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart);
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index c923156..4eb38cc 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.
@@ -1689,6 +1692,19 @@
optional string package_name = 2;
optional int32 package_version_code = 3;
+
+ enum RollbackReasonType {
+ REASON_UNKNOWN = 0;
+ REASON_NATIVE_CRASH = 1;
+ REASON_EXPLICIT_HEALTH_CHECK = 2;
+ REASON_APP_CRASH = 3;
+ REASON_APP_NOT_RESPONDING = 4;
+ }
+ optional RollbackReasonType rollback_reason = 4;
+
+ // Set by RollbackPackageHealthObserver to be the package that is failing when a rollback
+ // is initiated. Empty if the package is unknown.
+ optional string failing_package_name = 5;
}
/**
@@ -3713,6 +3729,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 +4770,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 +6919,6 @@
// Where it was logged from.
optional Source source = 4;
-
}
/**
@@ -6894,3 +6954,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 7ecaaca..6519366 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/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 726a619..06e8b8e 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -2904,7 +2904,7 @@
public static final int IMPORTANCE_CANT_SAVE_STATE_PRE_26 = 170;
/**
- * Constant for {@link #importance}: This process is contains services
+ * Constant for {@link #importance}: This process contains services
* that should remain running. These are background services apps have
* started, not something the user is aware of, so they may be killed by
* the system relatively freely (though it is generally desired that they
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 6acbf21..bef8b04 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 82df702..dfd6462 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -706,11 +706,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 6582d24..3633064 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 102de95..7a6e314 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 12d3c59..3fc735b 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 323c7d1..7396a11 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -1107,6 +1107,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
*
* @return Battery level in percents from 0 to 100, or {@link #BATTERY_LEVEL_UNKNOWN} if
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/ComponentName.java b/core/java/android/content/ComponentName.java
index 18147b5..2a9b4918 100644
--- a/core/java/android/content/ComponentName.java
+++ b/core/java/android/content/ComponentName.java
@@ -305,6 +305,12 @@
proto.end(token);
}
+ /**
+ * {@inheritDoc}
+ *
+ * <p>Two components are considered to be equal if the packages in which they reside have the
+ * same name, and if the classes that implement each component also have the same name.
+ */
@Override
public boolean equals(Object obj) {
try {
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index a6867ff..d177de4 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2689,6 +2689,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/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
index 6eaf54b..23f18a8 100644
--- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
+++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
@@ -1163,7 +1163,7 @@
if (orderedPreviewSizes != null) {
for (Size size : orderedPreviewSizes) {
if ((mDisplaySize.getWidth() >= size.getWidth()) &&
- (mDisplaySize.getWidth() >= size.getHeight())) {
+ (mDisplaySize.getHeight() >= size.getHeight())) {
return size;
}
}
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 46b497d..e73eba5 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -245,7 +245,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/IStatsManager.aidl b/core/java/android/os/IStatsManager.aidl
index e3f9326..36ea1bc 100644
--- a/core/java/android/os/IStatsManager.aidl
+++ b/core/java/android/os/IStatsManager.aidl
@@ -224,7 +224,7 @@
* Logs an event for watchdog rollbacks.
*/
oneway void sendWatchdogRollbackOccurredAtom(in int rollbackType, in String packageName,
- in long packageVersionCode);
+ in long packageVersionCode, in int rollbackReason, in String failingPackageName);
/**
* Returns the most recently registered experiment IDs.
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/PowerManager.java b/core/java/android/os/PowerManager.java
index c618dbc..092f438 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -45,70 +45,10 @@
* <p>
* <b>Device battery life will be significantly affected by the use of this API.</b>
* Do not acquire {@link WakeLock}s unless you really need them, use the minimum levels
- * possible, and be sure to release them as soon as possible.
- * </p><p>
- * The primary API you'll use is {@link #newWakeLock(int, String) newWakeLock()}.
- * This will create a {@link PowerManager.WakeLock} object. You can then use methods
- * on the wake lock object to control the power state of the device.
- * </p><p>
- * In practice it's quite simple:
- * {@samplecode
- * PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
- * PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "My Tag");
- * wl.acquire();
- * ..screen will stay on during this section..
- * wl.release();
- * }
- * </p><p>
- * The following wake lock levels are defined, with varying effects on system power.
- * <i>These levels are mutually exclusive - you may only specify one of them.</i>
+ * possible, and be sure to release them as soon as possible. In most cases,
+ * you'll want to use
+ * {@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON} instead.
*
- * <table>
- * <tr><th>Flag Value</th>
- * <th>CPU</th> <th>Screen</th> <th>Keyboard</th></tr>
- *
- * <tr><td>{@link #PARTIAL_WAKE_LOCK}</td>
- * <td>On*</td> <td>Off</td> <td>Off</td>
- * </tr>
- *
- * <tr><td>{@link #SCREEN_DIM_WAKE_LOCK}</td>
- * <td>On</td> <td>Dim</td> <td>Off</td>
- * </tr>
- *
- * <tr><td>{@link #SCREEN_BRIGHT_WAKE_LOCK}</td>
- * <td>On</td> <td>Bright</td> <td>Off</td>
- * </tr>
- *
- * <tr><td>{@link #FULL_WAKE_LOCK}</td>
- * <td>On</td> <td>Bright</td> <td>Bright</td>
- * </tr>
- * </table>
- * </p><p>
- * *<i>If you hold a partial wake lock, the CPU will continue to run, regardless of any
- * display timeouts or the state of the screen and even after the user presses the power button.
- * In all other wake locks, the CPU will run, but the user can still put the device to sleep
- * using the power button.</i>
- * </p><p>
- * In addition, you can add two more flags, which affect behavior of the screen only.
- * <i>These flags have no effect when combined with a {@link #PARTIAL_WAKE_LOCK}.</i></p>
- *
- * <table>
- * <tr><th>Flag Value</th> <th>Description</th></tr>
- *
- * <tr><td>{@link #ACQUIRE_CAUSES_WAKEUP}</td>
- * <td>Normal wake locks don't actually turn on the illumination. Instead, they cause
- * the illumination to remain on once it turns on (e.g. from user activity). This flag
- * will force the screen and/or keyboard to turn on immediately, when the WakeLock is
- * acquired. A typical use would be for notifications which are important for the user to
- * see immediately.</td>
- * </tr>
- *
- * <tr><td>{@link #ON_AFTER_RELEASE}</td>
- * <td>If this flag is set, the user activity timer will be reset when the WakeLock is
- * released, causing the illumination to remain on a bit longer. This can be used to
- * reduce flicker if you are cycling between wake lock conditions.</td>
- * </tr>
- * </table>
* <p>
* Any application using a WakeLock must request the {@code android.permission.WAKE_LOCK}
* permission in an {@code <uses-permission>} element of the application's manifest.
@@ -923,7 +863,8 @@
* {@link #FULL_WAKE_LOCK}, {@link #SCREEN_DIM_WAKE_LOCK}
* and {@link #SCREEN_BRIGHT_WAKE_LOCK}. Exactly one wake lock level must be
* specified as part of the {@code levelAndFlags} parameter.
- * </p><p>
+ * </p>
+ * <p>
* The wake lock flags are: {@link #ACQUIRE_CAUSES_WAKEUP}
* and {@link #ON_AFTER_RELEASE}. Multiple flags can be combined as part of the
* {@code levelAndFlags} parameters.
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 2622254..6476bcf 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -662,6 +662,22 @@
"android.settings.NIGHT_DISPLAY_SETTINGS";
/**
+ * Activity Action: Show settings to allow configuration of Dark theme.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_DARK_THEME_SETTINGS =
+ "android.settings.DARK_THEME_SETTINGS";
+
+ /**
* Activity Action: Show settings to allow configuration of locale.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
@@ -7859,6 +7875,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.
@@ -8939,6 +8968,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
*/
@@ -8955,6 +8992,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
@@ -9036,8 +9081,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,
@@ -9045,9 +9088,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,
@@ -9080,12 +9120,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,
@@ -9096,7 +9133,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
};
/**
@@ -9291,6 +9330,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);
}
/**
@@ -9864,14 +9906,35 @@
* List of ISO country codes in which eUICC UI is shown. Country codes should be separated
* by comma.
*
- * <p>Used to hide eUICC UI from users who are currently in countries no carriers support
- * eUICC.
+ * Note: if {@link #EUICC_SUPPORTED_COUNTRIES} is empty, then {@link
+ * #EUICC_UNSUPPORTED_COUNTRIES} is used.
+ *
+ * <p>Used to hide eUICC UI from users who are currently in countries where no carriers
+ * support eUICC.
+ *
* @hide
*/
//TODO(b/77914569) Changes this to System Api.
public static final String EUICC_SUPPORTED_COUNTRIES = "euicc_supported_countries";
/**
+ * List of ISO country codes in which eUICC UI is not shown. Country codes should be
+ * separated by comma.
+ *
+ * Note: if {@link #EUICC_SUPPORTED_COUNTRIES} is empty, then {@link
+ * #EUICC_UNSUPPORTED_COUNTRIES} is used.
+ *
+ * <p>Used to hide eUICC UI from users who are currently in countries where no carriers
+ * support eUICC.
+ *
+ * @hide
+ */
+ //TODO(b/77914569) Changes this to System Api.
+ public static final String EUICC_UNSUPPORTED_COUNTRIES = "euicc_unsupported_countries";
+ private static final Validator EUICC_UNSUPPORTED_COUNTRIES_VALIDATOR =
+ new SettingsValidators.ComponentNameListValidator(",");
+
+ /**
* Whether any activity can be resized. When this is true, any
* activity, regardless of manifest values, can be resized for multi-window.
* (0 = false, 1 = true)
@@ -13840,6 +13903,7 @@
VALIDATORS.put(DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD,
DYNAMIC_POWER_SAVINGS_VALIDATOR);
VALIDATORS.put(BLUETOOTH_ON, BLUETOOTH_ON_VALIDATOR);
+ VALIDATORS.put(EUICC_UNSUPPORTED_COUNTRIES, EUICC_UNSUPPORTED_COUNTRIES_VALIDATOR);
VALIDATORS.put(PRIVATE_DNS_MODE, PRIVATE_DNS_MODE_VALIDATOR);
VALIDATORS.put(PRIVATE_DNS_SPECIFIER, PRIVATE_DNS_SPECIFIER_VALIDATOR);
VALIDATORS.put(SOFT_AP_TIMEOUT_ENABLED, SOFT_AP_TIMEOUT_ENABLED_VALIDATOR);
diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java
index 91f77ea..63a38dd 100644
--- a/core/java/android/service/autofill/FillRequest.java
+++ b/core/java/android/service/autofill/FillRequest.java
@@ -71,12 +71,21 @@
*/
public static final int FLAG_COMPATIBILITY_MODE_REQUEST = 0x2;
+ /**
+ * Indicates the request came from a password field.
+ *
+ * (TODO: b/141703197) Temporary fix for augmented autofill showing passwords.
+ *
+ * @hide
+ */
+ public static final @RequestFlags int FLAG_PASSWORD_INPUT_TYPE = 0x4;
+
/** @hide */
public static final int INVALID_REQUEST_ID = Integer.MIN_VALUE;
/** @hide */
@IntDef(flag = true, prefix = { "FLAG_" }, value = {
- FLAG_MANUAL_REQUEST, FLAG_COMPATIBILITY_MODE_REQUEST
+ FLAG_MANUAL_REQUEST, FLAG_COMPATIBILITY_MODE_REQUEST, FLAG_PASSWORD_INPUT_TYPE
})
@Retention(RetentionPolicy.SOURCE)
@interface RequestFlags{}
@@ -100,7 +109,7 @@
@Nullable Bundle clientState, @RequestFlags int flags) {
mId = id;
mFlags = Preconditions.checkFlagsArgument(flags,
- FLAG_MANUAL_REQUEST | FLAG_COMPATIBILITY_MODE_REQUEST);
+ FLAG_MANUAL_REQUEST | FLAG_COMPATIBILITY_MODE_REQUEST | FLAG_PASSWORD_INPUT_TYPE);
mContexts = Preconditions.checkCollectionElementsNotNull(contexts, "contexts");
mClientState = clientState;
}
diff --git a/core/java/android/service/autofill/augmented/FillWindow.java b/core/java/android/service/autofill/augmented/FillWindow.java
index 6a29d48..5d00370 100644
--- a/core/java/android/service/autofill/augmented/FillWindow.java
+++ b/core/java/android/service/autofill/augmented/FillWindow.java
@@ -242,6 +242,7 @@
synchronized (mLock) {
if (mDestroyed) return;
if (mUpdateCalled) {
+ mFillView.setOnClickListener(null);
hide();
mProxy.report(AutofillProxy.REPORT_EVENT_UI_DESTROYED);
}
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..ae489c3 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);
}
/**
@@ -1876,7 +1887,8 @@
&& ((mSmartActions == null ? 0 : mSmartActions.size())
== (other.mSmartActions == null ? 0 : other.mSmartActions.size()))
&& Objects.equals(mSmartReplies, other.mSmartReplies)
- && Objects.equals(mCanBubble, other.mCanBubble);
+ && Objects.equals(mCanBubble, other.mCanBubble)
+ && Objects.equals(mVisuallyInterruptive, 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/util/StatsLog.java b/core/java/android/util/StatsLog.java
index ae9966b..5f43ee1 100644
--- a/core/java/android/util/StatsLog.java
+++ b/core/java/android/util/StatsLog.java
@@ -179,6 +179,8 @@
* @param rollbackType state of the rollback.
* @param packageName package name being rolled back.
* @param packageVersionCode version of the package being rolled back.
+ * @param rollbackReason reason the package is being rolled back.
+ * @param failingPackageName the package name causing the failure.
*
* @return True if the log request was sent to statsd.
*
@@ -186,7 +188,7 @@
*/
@RequiresPermission(allOf = {DUMP, PACKAGE_USAGE_STATS})
public static boolean logWatchdogRollbackOccurred(int rollbackType, String packageName,
- long packageVersionCode) {
+ long packageVersionCode, int rollbackReason, String failingPackageName) {
synchronized (sLogLock) {
try {
IStatsManager service = getIStatsManagerLocked();
@@ -198,7 +200,7 @@
}
service.sendWatchdogRollbackOccurredAtom(rollbackType, packageName,
- packageVersionCode);
+ packageVersionCode, rollbackReason, failingPackageName);
return true;
} catch (RemoteException e) {
sService = null;
diff --git a/core/java/android/view/CompositionSamplingListener.java b/core/java/android/view/CompositionSamplingListener.java
index e4309a6..f23bb40 100644
--- a/core/java/android/view/CompositionSamplingListener.java
+++ b/core/java/android/view/CompositionSamplingListener.java
@@ -29,7 +29,7 @@
*/
public abstract class CompositionSamplingListener {
- private final long mNativeListener;
+ private long mNativeListener;
private final Executor mExecutor;
public CompositionSamplingListener(Executor executor) {
@@ -37,13 +37,19 @@
mNativeListener = nativeCreate(this);
}
+ public void destroy() {
+ if (mNativeListener == 0) {
+ return;
+ }
+ unregister(this);
+ nativeDestroy(mNativeListener);
+ mNativeListener = 0;
+ }
+
@Override
protected void finalize() throws Throwable {
try {
- if (mNativeListener != 0) {
- unregister(this);
- nativeDestroy(mNativeListener);
- }
+ destroy();
} finally {
super.finalize();
}
@@ -59,6 +65,9 @@
*/
public static void register(CompositionSamplingListener listener,
int displayId, IBinder stopLayer, Rect samplingArea) {
+ if (listener.mNativeListener == 0) {
+ return;
+ }
Preconditions.checkArgument(displayId == Display.DEFAULT_DISPLAY,
"default display only for now");
nativeRegister(listener.mNativeListener, stopLayer, samplingArea.left, samplingArea.top,
@@ -69,6 +78,9 @@
* Unregisters a sampling listener.
*/
public static void unregister(CompositionSamplingListener listener) {
+ if (listener.mNativeListener == 0) {
+ return;
+ }
nativeUnregister(listener.mNativeListener);
}
diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java
index 715181f..e77d719 100644
--- a/core/java/android/view/DisplayCutout.java
+++ b/core/java/android/view/DisplayCutout.java
@@ -66,6 +66,7 @@
private static final String BOTTOM_MARKER = "@bottom";
private static final String DP_MARKER = "@dp";
private static final String RIGHT_MARKER = "@right";
+ private static final String LEFT_MARKER = "@left";
/**
* Category for overlays that allow emulating a display cutout on devices that don't have
@@ -642,6 +643,9 @@
if (spec.endsWith(RIGHT_MARKER)) {
offsetX = displayWidth;
spec = spec.substring(0, spec.length() - RIGHT_MARKER.length()).trim();
+ } else if (spec.endsWith(LEFT_MARKER)) {
+ offsetX = 0;
+ spec = spec.substring(0, spec.length() - LEFT_MARKER.length()).trim();
} else {
offsetX = displayWidth / 2f;
}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 2313b13..399b5cf 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -190,8 +190,7 @@
private static native void nativeSetInputWindowInfo(long transactionObj, long nativeObject,
InputWindowHandle handle);
- private static native void nativeTransferTouchFocus(long transactionObj, IBinder fromToken,
- IBinder toToken);
+
private static native boolean nativeGetProtectedContentSupport();
private static native void nativeSetMetadata(long transactionObj, long nativeObject, int key,
Parcel data);
@@ -2249,22 +2248,6 @@
}
/**
- * Transfers touch focus from one window to another. It is possible for multiple windows to
- * have touch focus if they support split touch dispatch
- * {@link android.view.WindowManager.LayoutParams#FLAG_SPLIT_TOUCH} but this
- * method only transfers touch focus of the specified window without affecting
- * other windows that may also have touch focus at the same time.
- * @param fromToken The token of a window that currently has touch focus.
- * @param toToken The token of the window that should receive touch focus in
- * place of the first.
- * @hide
- */
- public Transaction transferTouchFocus(IBinder fromToken, IBinder toToken) {
- nativeTransferTouchFocus(mNativeObject, fromToken, toToken);
- return this;
- }
-
- /**
* Waits until any changes to input windows have been sent from SurfaceFlinger to
* InputFlinger before returning.
*
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 9d2040c..c6cea37 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.
*
@@ -16136,7 +16136,7 @@
* by the most recent call to {@link #measure(int, int)}. This result is a bit mask
* as defined by {@link #MEASURED_SIZE_MASK} and {@link #MEASURED_STATE_TOO_SMALL}.
* This should be used during measurement and layout calculations only. Use
- * {@link #getHeight()} to see how wide a view is after layout.
+ * {@link #getHeight()} to see how high a view is after layout.
*
* @return The measured height of this view as a bit mask.
*/
@@ -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/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/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 1f7ae0e..bfa34ed 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -17,6 +17,7 @@
package android.view.autofill;
import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
+import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE;
import static android.view.autofill.Helper.sDebug;
import static android.view.autofill.Helper.sVerbose;
import static android.view.autofill.Helper.toList;
@@ -60,6 +61,7 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeProvider;
import android.view.accessibility.AccessibilityWindowInfo;
+import android.widget.TextView;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
@@ -966,6 +968,10 @@
if (!isClientDisablingEnterExitEvent()) {
final AutofillValue value = view.getAutofillValue();
+ if (view instanceof TextView && ((TextView) view).isAnyPasswordInputType()) {
+ flags |= FLAG_PASSWORD_INPUT_TYPE;
+ }
+
if (!isActiveLocked()) {
// Starts new session.
startSessionLocked(id, null, value, flags);
@@ -1130,6 +1136,10 @@
} else {
// don't notify entered when Activity is already in background
if (!isClientDisablingEnterExitEvent()) {
+ if (view instanceof TextView && ((TextView) view).isAnyPasswordInputType()) {
+ flags |= FLAG_PASSWORD_INPUT_TYPE;
+ }
+
if (!isActiveLocked()) {
// Starts new session.
startSessionLocked(id, bounds, null, flags);
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/view/textclassifier/ActionsModelParamsSupplier.java b/core/java/android/view/textclassifier/ActionsModelParamsSupplier.java
index 6b90588..3164567 100644
--- a/core/java/android/view/textclassifier/ActionsModelParamsSupplier.java
+++ b/core/java/android/view/textclassifier/ActionsModelParamsSupplier.java
@@ -60,7 +60,9 @@
private boolean mParsed = true;
public ActionsModelParamsSupplier(Context context, @Nullable Runnable onChangedListener) {
- mAppContext = Preconditions.checkNotNull(context).getApplicationContext();
+ final Context appContext = Preconditions.checkNotNull(context).getApplicationContext();
+ // Some contexts don't have an app context.
+ mAppContext = appContext != null ? appContext : context;
mOnChangedListener = onChangedListener == null ? () -> {} : onChangedListener;
mSettingsObserver = new SettingsObserver(mAppContext, () -> {
synchronized (mLock) {
diff --git a/core/java/android/view/textclassifier/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java
index aeb99b8..7c527ba 100644
--- a/core/java/android/view/textclassifier/ConversationActions.java
+++ b/core/java/android/view/textclassifier/ConversationActions.java
@@ -21,10 +21,12 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringDef;
+import android.annotation.UserIdInt;
import android.app.Person;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.UserHandle;
import android.text.SpannedString;
import com.android.internal.annotations.VisibleForTesting;
@@ -316,6 +318,8 @@
private final List<String> mHints;
@Nullable
private String mCallingPackageName;
+ @UserIdInt
+ private int mUserId = UserHandle.USER_NULL;
@NonNull
private Bundle mExtras;
@@ -340,6 +344,7 @@
List<String> hints = new ArrayList<>();
in.readStringList(hints);
String callingPackageName = in.readString();
+ int userId = in.readInt();
Bundle extras = in.readBundle();
Request request = new Request(
conversation,
@@ -348,6 +353,7 @@
hints,
extras);
request.setCallingPackageName(callingPackageName);
+ request.setUserId(userId);
return request;
}
@@ -358,6 +364,7 @@
parcel.writeInt(mMaxSuggestions);
parcel.writeStringList(mHints);
parcel.writeString(mCallingPackageName);
+ parcel.writeInt(mUserId);
parcel.writeBundle(mExtras);
}
@@ -428,6 +435,24 @@
}
/**
+ * Sets the id of the user that sent this request.
+ * <p>
+ * Package-private for SystemTextClassifier's use.
+ */
+ void setUserId(@UserIdInt int userId) {
+ mUserId = userId;
+ }
+
+ /**
+ * Returns the id of the user that sent this request.
+ * @hide
+ */
+ @UserIdInt
+ public int getUserId() {
+ return mUserId;
+ }
+
+ /**
* Returns the extended data related to this request.
*
* <p><b>NOTE: </b>Do not modify this bundle.
diff --git a/core/java/android/view/textclassifier/SelectionEvent.java b/core/java/android/view/textclassifier/SelectionEvent.java
index 9ae0c65..ae9d5c6 100644
--- a/core/java/android/view/textclassifier/SelectionEvent.java
+++ b/core/java/android/view/textclassifier/SelectionEvent.java
@@ -19,8 +19,10 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.UserHandle;
import android.view.textclassifier.TextClassifier.EntityType;
import android.view.textclassifier.TextClassifier.WidgetType;
@@ -127,6 +129,7 @@
private String mWidgetType = TextClassifier.WIDGET_TYPE_UNKNOWN;
private @InvocationMethod int mInvocationMethod;
@Nullable private String mWidgetVersion;
+ private @UserIdInt int mUserId = UserHandle.USER_NULL;
@Nullable private String mResultId;
private long mEventTime;
private long mDurationSinceSessionStart;
@@ -171,6 +174,7 @@
mEnd = in.readInt();
mSmartStart = in.readInt();
mSmartEnd = in.readInt();
+ mUserId = in.readInt();
}
@Override
@@ -199,6 +203,7 @@
dest.writeInt(mEnd);
dest.writeInt(mSmartStart);
dest.writeInt(mSmartEnd);
+ dest.writeInt(mUserId);
}
@Override
@@ -401,6 +406,24 @@
}
/**
+ * Sets the id of this event's user.
+ * <p>
+ * Package-private for SystemTextClassifier's use.
+ */
+ void setUserId(@UserIdInt int userId) {
+ mUserId = userId;
+ }
+
+ /**
+ * Returns the id of this event's user.
+ * @hide
+ */
+ @UserIdInt
+ public int getUserId() {
+ return mUserId;
+ }
+
+ /**
* Returns the type of widget that was involved in triggering this event.
*/
@WidgetType
@@ -426,6 +449,7 @@
mPackageName = context.getPackageName();
mWidgetType = context.getWidgetType();
mWidgetVersion = context.getWidgetVersion();
+ mUserId = context.getUserId();
}
/**
@@ -612,7 +636,7 @@
@Override
public int hashCode() {
return Objects.hash(mAbsoluteStart, mAbsoluteEnd, mEventType, mEntityType,
- mWidgetVersion, mPackageName, mWidgetType, mInvocationMethod, mResultId,
+ mWidgetVersion, mPackageName, mUserId, mWidgetType, mInvocationMethod, mResultId,
mEventTime, mDurationSinceSessionStart, mDurationSincePreviousEvent,
mEventIndex, mSessionId, mStart, mEnd, mSmartStart, mSmartEnd);
}
@@ -633,6 +657,7 @@
&& Objects.equals(mEntityType, other.mEntityType)
&& Objects.equals(mWidgetVersion, other.mWidgetVersion)
&& Objects.equals(mPackageName, other.mPackageName)
+ && mUserId == other.mUserId
&& Objects.equals(mWidgetType, other.mWidgetType)
&& mInvocationMethod == other.mInvocationMethod
&& Objects.equals(mResultId, other.mResultId)
@@ -652,12 +677,12 @@
return String.format(Locale.US,
"SelectionEvent {absoluteStart=%d, absoluteEnd=%d, eventType=%d, entityType=%s, "
+ "widgetVersion=%s, packageName=%s, widgetType=%s, invocationMethod=%s, "
- + "resultId=%s, eventTime=%d, durationSinceSessionStart=%d, "
+ + "userId=%d, resultId=%s, eventTime=%d, durationSinceSessionStart=%d, "
+ "durationSincePreviousEvent=%d, eventIndex=%d,"
+ "sessionId=%s, start=%d, end=%d, smartStart=%d, smartEnd=%d}",
mAbsoluteStart, mAbsoluteEnd, mEventType, mEntityType,
mWidgetVersion, mPackageName, mWidgetType, mInvocationMethod,
- mResultId, mEventTime, mDurationSinceSessionStart,
+ mUserId, mResultId, mEventTime, mDurationSinceSessionStart,
mDurationSincePreviousEvent, mEventIndex,
mSessionId, mStart, mEnd, mSmartStart, mSmartEnd);
}
diff --git a/core/java/android/view/textclassifier/SystemTextClassifier.java b/core/java/android/view/textclassifier/SystemTextClassifier.java
index 8f8766e..a97c330 100644
--- a/core/java/android/view/textclassifier/SystemTextClassifier.java
+++ b/core/java/android/view/textclassifier/SystemTextClassifier.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.annotation.WorkerThread;
import android.content.Context;
import android.os.Bundle;
@@ -50,6 +51,10 @@
private final TextClassificationConstants mSettings;
private final TextClassifier mFallback;
private final String mPackageName;
+ // NOTE: Always set this before sending a request to the manager service otherwise the manager
+ // service will throw a remote exception.
+ @UserIdInt
+ private final int mUserId;
private TextClassificationSessionId mSessionId;
public SystemTextClassifier(Context context, TextClassificationConstants settings)
@@ -60,6 +65,7 @@
mFallback = context.getSystemService(TextClassificationManager.class)
.getTextClassifier(TextClassifier.LOCAL);
mPackageName = Preconditions.checkNotNull(context.getOpPackageName());
+ mUserId = context.getUserId();
}
/**
@@ -72,6 +78,7 @@
Utils.checkMainThread();
try {
request.setCallingPackageName(mPackageName);
+ request.setUserId(mUserId);
final BlockingCallback<TextSelection> callback =
new BlockingCallback<>("textselection");
mManagerService.onSuggestSelection(mSessionId, request, callback);
@@ -95,6 +102,7 @@
Utils.checkMainThread();
try {
request.setCallingPackageName(mPackageName);
+ request.setUserId(mUserId);
final BlockingCallback<TextClassification> callback =
new BlockingCallback<>("textclassification");
mManagerService.onClassifyText(mSessionId, request, callback);
@@ -123,6 +131,7 @@
try {
request.setCallingPackageName(mPackageName);
+ request.setUserId(mUserId);
final BlockingCallback<TextLinks> callback =
new BlockingCallback<>("textlinks");
mManagerService.onGenerateLinks(mSessionId, request, callback);
@@ -142,6 +151,7 @@
Utils.checkMainThread();
try {
+ event.setUserId(mUserId);
mManagerService.onSelectionEvent(mSessionId, event);
} catch (RemoteException e) {
Log.e(LOG_TAG, "Error reporting selection event.", e);
@@ -154,6 +164,12 @@
Utils.checkMainThread();
try {
+ final TextClassificationContext tcContext = event.getEventContext() == null
+ ? new TextClassificationContext.Builder(mPackageName, WIDGET_TYPE_UNKNOWN)
+ .build()
+ : event.getEventContext();
+ tcContext.setUserId(mUserId);
+ event.setEventContext(tcContext);
mManagerService.onTextClassifierEvent(mSessionId, event);
} catch (RemoteException e) {
Log.e(LOG_TAG, "Error reporting textclassifier event.", e);
@@ -167,6 +183,7 @@
try {
request.setCallingPackageName(mPackageName);
+ request.setUserId(mUserId);
final BlockingCallback<TextLanguage> callback =
new BlockingCallback<>("textlanguage");
mManagerService.onDetectLanguage(mSessionId, request, callback);
@@ -187,6 +204,7 @@
try {
request.setCallingPackageName(mPackageName);
+ request.setUserId(mUserId);
final BlockingCallback<ConversationActions> callback =
new BlockingCallback<>("conversation-actions");
mManagerService.onSuggestConversationActions(mSessionId, request, callback);
@@ -228,6 +246,7 @@
printWriter.printPair("mFallback", mFallback);
printWriter.printPair("mPackageName", mPackageName);
printWriter.printPair("mSessionId", mSessionId);
+ printWriter.printPair("mUserId", mUserId);
printWriter.decreaseIndent();
printWriter.println();
}
@@ -243,6 +262,7 @@
@NonNull TextClassificationSessionId sessionId) {
mSessionId = Preconditions.checkNotNull(sessionId);
try {
+ classificationContext.setUserId(mUserId);
mManagerService.onCreateTextClassificationSession(classificationContext, mSessionId);
} catch (RemoteException e) {
Log.e(LOG_TAG, "Error starting a new classification session.", e);
diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java
index 6321051..975f3ba 100644
--- a/core/java/android/view/textclassifier/TextClassification.java
+++ b/core/java/android/view/textclassifier/TextClassification.java
@@ -21,6 +21,7 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.app.RemoteAction;
import android.content.Context;
@@ -35,6 +36,7 @@
import android.os.LocaleList;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.UserHandle;
import android.text.SpannedString;
import android.util.ArrayMap;
import android.view.View.OnClickListener;
@@ -551,6 +553,8 @@
@Nullable private final ZonedDateTime mReferenceTime;
@NonNull private final Bundle mExtras;
@Nullable private String mCallingPackageName;
+ @UserIdInt
+ private int mUserId = UserHandle.USER_NULL;
private Request(
CharSequence text,
@@ -631,6 +635,24 @@
}
/**
+ * Sets the id of the user that sent this request.
+ * <p>
+ * Package-private for SystemTextClassifier's use.
+ */
+ void setUserId(@UserIdInt int userId) {
+ mUserId = userId;
+ }
+
+ /**
+ * Returns the id of the user that sent this request.
+ * @hide
+ */
+ @UserIdInt
+ public int getUserId() {
+ return mUserId;
+ }
+
+ /**
* Returns the extended data.
*
* <p><b>NOTE: </b>Do not modify this bundle.
@@ -730,6 +752,7 @@
dest.writeParcelable(mDefaultLocales, flags);
dest.writeString(mReferenceTime == null ? null : mReferenceTime.toString());
dest.writeString(mCallingPackageName);
+ dest.writeInt(mUserId);
dest.writeBundle(mExtras);
}
@@ -742,11 +765,13 @@
final ZonedDateTime referenceTime = referenceTimeString == null
? null : ZonedDateTime.parse(referenceTimeString);
final String callingPackageName = in.readString();
+ final int userId = in.readInt();
final Bundle extras = in.readBundle();
final Request request = new Request(text, startIndex, endIndex,
defaultLocales, referenceTime, extras);
request.setCallingPackageName(callingPackageName);
+ request.setUserId(userId);
return request;
}
diff --git a/core/java/android/view/textclassifier/TextClassificationContext.java b/core/java/android/view/textclassifier/TextClassificationContext.java
index 3bf8e9b..db07685 100644
--- a/core/java/android/view/textclassifier/TextClassificationContext.java
+++ b/core/java/android/view/textclassifier/TextClassificationContext.java
@@ -18,8 +18,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.UserHandle;
import android.view.textclassifier.TextClassifier.WidgetType;
import com.android.internal.util.Preconditions;
@@ -35,6 +37,8 @@
private final String mPackageName;
private final String mWidgetType;
@Nullable private final String mWidgetVersion;
+ @UserIdInt
+ private int mUserId = UserHandle.USER_NULL;
private TextClassificationContext(
String packageName,
@@ -54,6 +58,24 @@
}
/**
+ * Sets the id of this context's user.
+ * <p>
+ * Package-private for SystemTextClassifier's use.
+ */
+ void setUserId(@UserIdInt int userId) {
+ mUserId = userId;
+ }
+
+ /**
+ * Returns the id of this context's user.
+ * @hide
+ */
+ @UserIdInt
+ public int getUserId() {
+ return mUserId;
+ }
+
+ /**
* Returns the widget type for this classification context.
*/
@NonNull
@@ -75,8 +97,8 @@
@Override
public String toString() {
return String.format(Locale.US, "TextClassificationContext{"
- + "packageName=%s, widgetType=%s, widgetVersion=%s}",
- mPackageName, mWidgetType, mWidgetVersion);
+ + "packageName=%s, widgetType=%s, widgetVersion=%s, userId=%d}",
+ mPackageName, mWidgetType, mWidgetVersion, mUserId);
}
/**
@@ -133,12 +155,14 @@
parcel.writeString(mPackageName);
parcel.writeString(mWidgetType);
parcel.writeString(mWidgetVersion);
+ parcel.writeInt(mUserId);
}
private TextClassificationContext(Parcel in) {
mPackageName = in.readString();
mWidgetType = in.readString();
mWidgetVersion = in.readString();
+ mUserId = in.readInt();
}
public static final @android.annotation.NonNull Parcelable.Creator<TextClassificationContext> CREATOR =
diff --git a/core/java/android/view/textclassifier/TextClassifierEvent.java b/core/java/android/view/textclassifier/TextClassifierEvent.java
index 57da829..a041296 100644
--- a/core/java/android/view/textclassifier/TextClassifierEvent.java
+++ b/core/java/android/view/textclassifier/TextClassifierEvent.java
@@ -139,7 +139,7 @@
@Nullable
private final String[] mEntityTypes;
@Nullable
- private final TextClassificationContext mEventContext;
+ private TextClassificationContext mEventContext;
@Nullable
private final String mResultId;
private final int mEventIndex;
@@ -289,6 +289,15 @@
}
/**
+ * Sets the event context.
+ * <p>
+ * Package-private for SystemTextClassifier's use.
+ */
+ void setEventContext(@Nullable TextClassificationContext eventContext) {
+ mEventContext = eventContext;
+ }
+
+ /**
* Returns the id of the text classifier result related to this event.
*/
@Nullable
diff --git a/core/java/android/view/textclassifier/TextLanguage.java b/core/java/android/view/textclassifier/TextLanguage.java
index 6c75ffb..3e3dc72 100644
--- a/core/java/android/view/textclassifier/TextLanguage.java
+++ b/core/java/android/view/textclassifier/TextLanguage.java
@@ -20,10 +20,12 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.icu.util.ULocale;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.UserHandle;
import android.util.ArrayMap;
import com.android.internal.annotations.VisibleForTesting;
@@ -226,6 +228,8 @@
private final CharSequence mText;
private final Bundle mExtra;
@Nullable private String mCallingPackageName;
+ @UserIdInt
+ private int mUserId = UserHandle.USER_NULL;
private Request(CharSequence text, Bundle bundle) {
mText = text;
@@ -260,6 +264,24 @@
}
/**
+ * Sets the id of the user that sent this request.
+ * <p>
+ * Package-private for SystemTextClassifier's use.
+ */
+ void setUserId(@UserIdInt int userId) {
+ mUserId = userId;
+ }
+
+ /**
+ * Returns the id of the user that sent this request.
+ * @hide
+ */
+ @UserIdInt
+ public int getUserId() {
+ return mUserId;
+ }
+
+ /**
* Returns a bundle containing non-structured extra information about this request.
*
* <p><b>NOTE: </b>Do not modify this bundle.
@@ -278,16 +300,19 @@
public void writeToParcel(Parcel dest, int flags) {
dest.writeCharSequence(mText);
dest.writeString(mCallingPackageName);
+ dest.writeInt(mUserId);
dest.writeBundle(mExtra);
}
private static Request readFromParcel(Parcel in) {
final CharSequence text = in.readCharSequence();
final String callingPackageName = in.readString();
+ final int userId = in.readInt();
final Bundle extra = in.readBundle();
final Request request = new Request(text, extra);
request.setCallingPackageName(callingPackageName);
+ request.setUserId(userId);
return request;
}
diff --git a/core/java/android/view/textclassifier/TextLinks.java b/core/java/android/view/textclassifier/TextLinks.java
index f3e0dc1..d7ac524 100644
--- a/core/java/android/view/textclassifier/TextLinks.java
+++ b/core/java/android/view/textclassifier/TextLinks.java
@@ -20,11 +20,13 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.content.Context;
import android.os.Bundle;
import android.os.LocaleList;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.UserHandle;
import android.text.Spannable;
import android.text.method.MovementMethod;
import android.text.style.ClickableSpan;
@@ -339,6 +341,8 @@
private final boolean mLegacyFallback;
@Nullable private String mCallingPackageName;
private final Bundle mExtras;
+ @UserIdInt
+ private int mUserId = UserHandle.USER_NULL;
private Request(
CharSequence text,
@@ -410,6 +414,24 @@
}
/**
+ * Sets the id of the user that sent this request.
+ * <p>
+ * Package-private for SystemTextClassifier's use.
+ */
+ void setUserId(@UserIdInt int userId) {
+ mUserId = userId;
+ }
+
+ /**
+ * Returns the id of the user that sent this request.
+ * @hide
+ */
+ @UserIdInt
+ public int getUserId() {
+ return mUserId;
+ }
+
+ /**
* Returns the extended data.
*
* <p><b>NOTE: </b>Do not modify this bundle.
@@ -509,6 +531,7 @@
dest.writeParcelable(mDefaultLocales, flags);
dest.writeParcelable(mEntityConfig, flags);
dest.writeString(mCallingPackageName);
+ dest.writeInt(mUserId);
dest.writeBundle(mExtras);
}
@@ -517,11 +540,13 @@
final LocaleList defaultLocales = in.readParcelable(null);
final EntityConfig entityConfig = in.readParcelable(null);
final String callingPackageName = in.readString();
+ final int userId = in.readInt();
final Bundle extras = in.readBundle();
final Request request = new Request(text, defaultLocales, entityConfig,
/* legacyFallback= */ true, extras);
request.setCallingPackageName(callingPackageName);
+ request.setUserId(userId);
return request;
}
diff --git a/core/java/android/view/textclassifier/TextSelection.java b/core/java/android/view/textclassifier/TextSelection.java
index 75c27bd..94e0bc3 100644
--- a/core/java/android/view/textclassifier/TextSelection.java
+++ b/core/java/android/view/textclassifier/TextSelection.java
@@ -20,10 +20,12 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.os.Bundle;
import android.os.LocaleList;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.UserHandle;
import android.text.SpannedString;
import android.util.ArrayMap;
import android.view.textclassifier.TextClassifier.EntityType;
@@ -211,6 +213,8 @@
private final boolean mDarkLaunchAllowed;
private final Bundle mExtras;
@Nullable private String mCallingPackageName;
+ @UserIdInt
+ private int mUserId = UserHandle.USER_NULL;
private Request(
CharSequence text,
@@ -292,6 +296,24 @@
}
/**
+ * Sets the id of the user that sent this request.
+ * <p>
+ * Package-private for SystemTextClassifier's use.
+ */
+ void setUserId(@UserIdInt int userId) {
+ mUserId = userId;
+ }
+
+ /**
+ * Returns the id of the user that sent this request.
+ * @hide
+ */
+ @UserIdInt
+ public int getUserId() {
+ return mUserId;
+ }
+
+ /**
* Returns the extended data.
*
* <p><b>NOTE: </b>Do not modify this bundle.
@@ -394,6 +416,7 @@
dest.writeInt(mEndIndex);
dest.writeParcelable(mDefaultLocales, flags);
dest.writeString(mCallingPackageName);
+ dest.writeInt(mUserId);
dest.writeBundle(mExtras);
}
@@ -403,11 +426,13 @@
final int endIndex = in.readInt();
final LocaleList defaultLocales = in.readParcelable(null);
final String callingPackageName = in.readString();
+ final int userId = in.readInt();
final Bundle extras = in.readBundle();
final Request request = new Request(text, startIndex, endIndex, defaultLocales,
/* darkLaunchAllowed= */ false, extras);
request.setCallingPackageName(callingPackageName);
+ request.setUserId(userId);
return request;
}
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/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 0918c5f..b202167 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -6582,6 +6582,16 @@
return mTransformation instanceof PasswordTransformationMethod;
}
+ /**
+ * Returns true if the current inputType is any type of password.
+ *
+ * @hide
+ */
+ public boolean isAnyPasswordInputType() {
+ final int inputType = getInputType();
+ return isPasswordInputType(inputType) || isVisiblePasswordInputType(inputType);
+ }
+
static boolean isPasswordInputType(int inputType) {
final int variation =
inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
@@ -11283,6 +11293,12 @@
}
@Nullable
+ final TextClassificationManager getTextClassificationManagerForUser() {
+ return getServiceManagerForUser(
+ getContext().getPackageName(), TextClassificationManager.class);
+ }
+
+ @Nullable
final <T> T getServiceManagerForUser(String packageName, Class<T> managerClazz) {
if (mTextOperationUser == null) {
return getContext().getSystemService(managerClazz);
@@ -12383,8 +12399,7 @@
@NonNull
public TextClassifier getTextClassifier() {
if (mTextClassifier == null) {
- final TextClassificationManager tcm =
- mContext.getSystemService(TextClassificationManager.class);
+ final TextClassificationManager tcm = getTextClassificationManagerForUser();
if (tcm != null) {
return tcm.getTextClassifier();
}
@@ -12400,8 +12415,7 @@
@NonNull
TextClassifier getTextClassificationSession() {
if (mTextClassificationSession == null || mTextClassificationSession.isDestroyed()) {
- final TextClassificationManager tcm =
- mContext.getSystemService(TextClassificationManager.class);
+ final TextClassificationManager tcm = getTextClassificationManagerForUser();
if (tcm != null) {
final String widgetType;
if (isTextEditable()) {
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..0bf2d64 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;
@@ -54,6 +55,7 @@
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.database.Cursor;
import android.database.DataSetObserver;
import android.graphics.Bitmap;
@@ -69,6 +71,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 +81,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 +109,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 +126,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 +164,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 +267,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 +533,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 +623,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 +756,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.
*/
@@ -778,12 +820,19 @@
return new PackageMonitor() {
@Override
public void onSomePackagesChanged() {
- mAdapter.handlePackagesChanged();
- bindProfileView();
+ handlePackagesChanged();
}
};
}
+ /**
+ * Update UI to reflect changes in data.
+ */
+ public void handlePackagesChanged() {
+ mAdapter.handlePackagesChanged();
+ bindProfileView();
+ }
+
private void onCopyButtonClicked(View v) {
Intent targetIntent = getTargetIntent();
if (targetIntent == null) {
@@ -868,6 +917,108 @@
}
}
+ 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, PackageManager.GET_META_DATA);
+ if (ri == null || ri.activityInfo == null) {
+ Log.e(TAG, "Device-specified nearby sharing component (" + cn
+ + ") not available");
+ return null;
+ }
+
+ // Allow the nearby sharing component to provide a more appropriate icon and label
+ // for the chip.
+ CharSequence name = null;
+ Drawable icon = null;
+ final Bundle metaData = ri.activityInfo.metaData;
+ if (metaData != null) {
+ try {
+ final Resources pkgRes = getPackageManager().getResourcesForActivity(cn);
+ final int nameResId = metaData.getInt(CHIP_LABEL_METADATA_KEY);
+ name = pkgRes.getString(nameResId);
+ final int resId = metaData.getInt(CHIP_ICON_METADATA_KEY);
+ icon = pkgRes.getDrawable(resId);
+ } catch (Resources.NotFoundException ex) {
+ } catch (NameNotFoundException ex) {
+ }
+ }
+ if (TextUtils.isEmpty(name)) {
+ name = ri.loadLabel(getPackageManager());
+ }
+ if (icon == null) {
+ icon = ri.loadIcon(getPackageManager());
+ }
+
+ final DisplayResolveInfo dri = new DisplayResolveInfo(
+ originalIntent, ri, name, "", null);
+ dri.setDisplayIcon(icon);
+ 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);
+ finish();
+ }
+ );
+ }
+
+ 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 +1052,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 +1213,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 +1431,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 +1666,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 +2111,12 @@
}
return false;
}
+
+ @Override
+ public boolean isComponentPinned(ComponentName name) {
+ return mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
+ }
+
}
@Override
@@ -2089,6 +2250,10 @@
public boolean isSuspended() {
return false;
}
+
+ public boolean isPinned() {
+ return false;
+ }
}
final class PlaceHolderTargetInfo extends NotSelectableTargetInfo {
@@ -2177,6 +2342,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 +2597,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..16e9fca 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);
+ 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);
+ }
+
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);
+ + 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();
-
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..aec4bfa 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
+ ((ChooserActivity) getActivity()).handlePackagesChanged();
+ 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/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 3bd0fd2..3cc771a 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -1870,6 +1870,7 @@
mCount = computeCurrentCountLocked();
mUnpluggedReportedTotalTime = mCurrentReportedTotalTime = 0;
mUnpluggedReportedCount = mCurrentReportedCount = 0;
+ mTrackingReportedValues = false;
}
public void setUpdateVersion(int version) {
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/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 030d3be..b2aef78 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -463,15 +463,6 @@
transaction->setInputWindowInfo(ctrl, *handle->getInfo());
}
-static void nativeTransferTouchFocus(JNIEnv* env, jclass clazz, jlong transactionObj,
- jobject fromTokenObj, jobject toTokenObj) {
- auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
-
- sp<IBinder> fromToken(ibinderForJavaObject(env, fromTokenObj));
- sp<IBinder> toToken(ibinderForJavaObject(env, toTokenObj));
- transaction->transferTouchFocus(fromToken, toToken);
-}
-
static void nativeSyncInputWindows(JNIEnv* env, jclass clazz, jlong transactionObj) {
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
transaction->syncInputWindows();
@@ -1381,8 +1372,6 @@
(void*)nativeCaptureLayers },
{"nativeSetInputWindowInfo", "(JJLandroid/view/InputWindowHandle;)V",
(void*)nativeSetInputWindowInfo },
- {"nativeTransferTouchFocus", "(JLandroid/os/IBinder;Landroid/os/IBinder;)V",
- (void*)nativeTransferTouchFocus },
{"nativeSetMetadata", "(JJILandroid/os/Parcel;)V",
(void*)nativeSetMetadata },
{"nativeGetDisplayedContentSamplingAttributes",
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/global.proto b/core/proto/android/providers/settings/global.proto
index ca71314..3d99b72 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -385,6 +385,7 @@
optional SettingProto provisioned = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto factory_reset_timeout_millis = 2 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto unsupported_countries = 3 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Euicc euicc = 52;
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/AndroidManifest.xml b/core/res/AndroidManifest.xml
index a42ba17..dd23e3b 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -346,6 +346,10 @@
<protected-broadcast android:name="com.android.server.am.DELETE_DUMPHEAP" />
<protected-broadcast android:name="com.android.server.net.action.SNOOZE_WARNING" />
<protected-broadcast android:name="com.android.server.net.action.SNOOZE_RAPID" />
+ <protected-broadcast android:name="com.android.server.wifi.ACTION_SHOW_SET_RANDOMIZATION_DETAILS" />
+ <protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_ALLOWED_APP" />
+ <protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_DISALLOWED_APP" />
+ <protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_DISMISSED" />
<protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.USER_DISMISSED_NOTIFICATION" />
<protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.CONNECT_TO_NETWORK" />
<protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.PICK_WIFI_NETWORK" />
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..8c9f1c5 100644
--- a/core/res/res/drawable/ic_menu_copy_material.xml
+++ b/core/res/res/drawable/ic_menu_copy_material.xml
@@ -1,26 +1,27 @@
-<!--
-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"
+ android:autoMirrored="true"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@color/white"
+ 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..119b2e9
--- /dev/null
+++ b/core/res/res/layout/chooser_action_button.xml
@@ -0,0 +1,30 @@
+<!--
+ ~ 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"
+ android:drawableTint="?android:attr/colorControlNormal"
+ android:drawableTintMode="src_in"
+ />
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..2a39215 100644
--- a/core/res/res/layout/chooser_grid_preview_file.xml
+++ b/core/res/res/layout/chooser_grid_preview_file.xml
@@ -65,13 +65,15 @@
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"
+ android:layout_gravity="center"
+ />
</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..62df165 100644
--- a/core/res/res/layout/chooser_grid_preview_image.xml
+++ b/core/res/res/layout/chooser_grid_preview_image.xml
@@ -78,5 +78,15 @@
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"
+ android:layout_gravity="center"
+ />
+
</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..0029174 100644
--- a/core/res/res/layout/chooser_grid_preview_text.xml
+++ b/core/res/res/layout/chooser_grid_preview_text.xml
@@ -37,50 +37,27 @@
<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"/>
+ android:maxLines="2"
+ android:focusable="true"/>
- <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"
+ android:layout_gravity="center"
+ />
+
<!-- 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 04d1eef..17bd132 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3235,7 +3235,9 @@
The path is assumed to be specified in display coordinates with pixel units and in
the display's native orientation, with the origin of the coordinate system at the
- center top of the display.
+ center top of the display. Optionally, you can append either `@left` or `@right` to the
+ end of the path string, in order to change the path origin to either the top left,
+ or top right of the display.
To facilitate writing device-independent emulation overlays, the marker `@dp` can be
appended after the path string to interpret coordinates in dp instead of px units.
@@ -4322,4 +4324,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 4aa44fc..f78a53e 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1045,47 +1045,26 @@
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_readContacts">read your contacts</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permdesc_readContacts" product="tablet">Allows the app to read
- data about your contacts stored on your tablet, including the frequency
- with which you\'ve called, emailed, or communicated in other ways with
- specific individuals. This permission allows apps to save your contact
- data, and malicious apps may share contact data without your
- knowledge.</string>
+ <string name="permdesc_readContacts" product="tablet">Allows the app to read data about your contacts stored on your tablet.
+ This permission allows apps to save your contact data, and malicious apps may share contact data without your knowledge.</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permdesc_readContacts" product="tv">Allows the app to read
- data about your contacts stored on your TV, including the frequency
- with which you\'ve called, emailed, or communicated in other ways with
- specific individuals. This permission allows apps to save your contact
- data, and malicious apps may share contact data without your
- knowledge.</string>
+ <string name="permdesc_readContacts" product="tv">Allows the app to read data about your contacts stored on your TV.
+ This permission allows apps to save your contact data, and malicious apps may share contact data without your knowledge.</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permdesc_readContacts" product="default">Allows the app to
- read data about your contacts stored on your phone, including the
- frequency with which you\'ve called, emailed, or communicated in other ways
- with specific individuals. This permission allows apps to save your
- contact data, and malicious apps may share contact data without your
- knowledge.</string>
+ <string name="permdesc_readContacts" product="default">Allows the app to read data about your contacts stored on your phone.
+ This permission allows apps to save your contact data, and malicious apps may share contact data without your knowledge.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_writeContacts">modify your contacts</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permdesc_writeContacts" product="tablet">Allows the app to
- modify the data about your contacts stored on your tablet, including the
- frequency with which you\'ve called, emailed, or communicated in other ways
- with specific contacts. This permission allows apps to delete contact
- data.</string>
+ <string name="permdesc_writeContacts" product="tablet">Allows the app to modify the data about your contacts stored on your tablet.
+ This permission allows apps to delete contact data.</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permdesc_writeContacts" product="tv">Allows the app to
- modify the data about your contacts stored on your TV, including the
- frequency with which you\'ve called, emailed, or communicated in other ways
- with specific contacts. This permission allows apps to delete contact
- data.</string>
+ <string name="permdesc_writeContacts" product="tv">Allows the app to modify the data about your contacts stored on your TV.
+ This permission allows apps to delete contact data.</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permdesc_writeContacts" product="default">Allows the app to
- modify the data about your contacts stored on your phone, including the
- frequency with which you\'ve called, emailed, or communicated in other ways
- with specific contacts. This permission allows apps to delete contact
- data.</string>
+ <string name="permdesc_writeContacts" product="default">Allows the app to modify the data about your contacts stored on your phone.
+ This permission allows apps to delete contact data.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_readCallLog">read call log</string>
@@ -1148,6 +1127,8 @@
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_accessCoarseLocation" product="tv">This app can get your location based on network sources such as cell towers and Wi-Fi networks, but only when when the app is in the foreground. These location services must be turned on and available on your TV for the app to be able to use them.</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_accessCoarseLocation" product="automotive">This app can get your approximate location only when it is in the foreground. These location services must be turned on and available on your car for the app to be able to use them.</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_accessCoarseLocation" product="default">This app can get your location based on network sources such as cell towers and Wi-Fi networks, but only when the app is in the foreground. These location services must be turned on and available on your phone for the app to be able to use them.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
@@ -3377,6 +3358,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>
@@ -4852,10 +4853,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>
@@ -5085,6 +5086,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 383fcd4..32749a2 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" />
@@ -2067,6 +2066,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" />
@@ -3051,7 +3058,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..83f3c31 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -264,6 +264,7 @@
Settings.Global.ERROR_LOGCAT_PREFIX,
Settings.Global.EUICC_PROVISIONED,
Settings.Global.EUICC_SUPPORTED_COUNTRIES,
+ Settings.Global.EUICC_UNSUPPORTED_COUNTRIES,
Settings.Global.EUICC_FACTORY_RESET_TIMEOUT_MILLIS,
Settings.Global.EUICC_REMOVING_INVISIBLE_PROFILES_TIMEOUT_MILLIS,
Settings.Global.FANCY_IME_ANIMATIONS,
@@ -595,7 +596,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 +727,13 @@
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.NEARBY_SHARING_COMPONENT, // not user configurable
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 d7cd054..4363568 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/AudioManager.java b/media/java/android/media/AudioManager.java
index f797da7..9ad7f2a 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -908,6 +908,7 @@
/** @hide */
@UnsupportedAppUsage
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
public void setMasterMute(boolean mute, int flags) {
final IAudioService service = getService();
try {
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/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index c9d7978..b7149b9 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -362,7 +362,8 @@
</tr>
<tr>
<td>FLAC</td>
- <td>mandatory metadata block (called the STREAMINFO block),<br>
+ <td>"fLaC", the FLAC stream marker in ASCII,<br>
+ followed by the STREAMINFO block (the mandatory metadata block),<br>
optionally followed by any number of other metadata blocks</td>
<td class=NA>Not Used</td>
<td class=NA>Not Used</td>
diff --git a/packages/CarSystemUI/AndroidManifest.xml b/packages/CarSystemUI/AndroidManifest.xml
index 261b9f5..c5da135 100644
--- a/packages/CarSystemUI/AndroidManifest.xml
+++ b/packages/CarSystemUI/AndroidManifest.xml
@@ -25,4 +25,6 @@
<uses-permission android:name="android.car.permission.CAR_ENROLL_TRUST"/>
<!-- This permission is required to get bluetooth broadcast. -->
<uses-permission android:name="android.permission.BLUETOOTH" />
+ <!-- Allows us to defer some actions until after the BOOT_COMPLETED event -->
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
</manifest>
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/car/SUWProgressController.java b/packages/CarSystemUI/src/com/android/systemui/car/SUWProgressController.java
new file mode 100644
index 0000000..d952939
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/car/SUWProgressController.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.systemui.car;
+
+import android.app.ActivityManager;
+import android.car.settings.CarSettings;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.provider.Settings;
+
+import com.android.systemui.Dependency;
+
+import java.util.ArrayList;
+
+/**
+ * A controller that monitors the status of SUW progress for each user.
+ */
+public class SUWProgressController {
+ private static final Uri USER_SETUP_IN_PROGRESS_URI = Settings.Secure.getUriFor(
+ CarSettings.Secure.KEY_SETUP_WIZARD_IN_PROGRESS);
+ private final ArrayList<SUWProgressListener> mListeners = new ArrayList<>();
+ private final ContentObserver mCarSettingsObserver = new ContentObserver(
+ Dependency.get(Dependency.MAIN_HANDLER)) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri, int userId) {
+ if (USER_SETUP_IN_PROGRESS_URI.equals(uri)) {
+ notifyUserSetupInProgressChanged();
+ }
+ }
+ };
+ private final ContentResolver mContentResolver;
+
+ public SUWProgressController(Context context) {
+ mContentResolver = context.getContentResolver();
+ }
+
+ /**
+ * Returns {@code true} then SUW is in progress for the given user.
+ */
+ public boolean isUserSetupInProgress(int user) {
+ return Settings.Secure.getIntForUser(mContentResolver,
+ CarSettings.Secure.KEY_SETUP_WIZARD_IN_PROGRESS, /* def= */ 0, user) != 0;
+ }
+
+ /**
+ * Returns {@code true} then SUW is in progress for the current user.
+ */
+ public boolean isCurrentUserSetupInProgress() {
+ return isUserSetupInProgress(ActivityManager.getCurrentUser());
+ }
+
+ /**
+ * Adds a {@link SUWProgressListener} callback.
+ */
+ public void addCallback(SUWProgressListener listener) {
+ mListeners.add(listener);
+ if (mListeners.size() == 1) {
+ startListening(ActivityManager.getCurrentUser());
+ }
+ listener.onUserSetupInProgressChanged();
+ }
+
+ /**
+ * Removes a {@link SUWProgressListener} callback.
+ */
+ public void removeCallback(SUWProgressListener listener) {
+ mListeners.remove(listener);
+ if (mListeners.size() == 0) {
+ stopListening();
+ }
+ }
+
+ private void startListening(int user) {
+ mContentResolver.registerContentObserver(
+ USER_SETUP_IN_PROGRESS_URI, /* notifyForDescendants= */ true, mCarSettingsObserver,
+ user);
+ }
+
+ private void stopListening() {
+ mContentResolver.unregisterContentObserver(mCarSettingsObserver);
+ }
+
+ /**
+ * Allows SUWProgressController to switch its listeners to observe SUW progress for new user.
+ */
+ public void onUserSwitched() {
+ if (mListeners.size() == 0) {
+ return;
+ }
+
+ mContentResolver.unregisterContentObserver(mCarSettingsObserver);
+ mContentResolver.registerContentObserver(
+ USER_SETUP_IN_PROGRESS_URI, /* notifyForDescendants= */ true, mCarSettingsObserver,
+ ActivityManager.getCurrentUser());
+ }
+
+ private void notifyUserSetupInProgressChanged() {
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ mListeners.get(i).onUserSetupInProgressChanged();
+ }
+ }
+
+ /**
+ * A listener that listens for changes in SUW progress for a user.
+ */
+ public interface SUWProgressListener {
+ /**
+ * A callback for when a change occurs in SUW progress for a user.
+ */
+ default void onUserSetupInProgressChanged() {
+ }
+ }
+}
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..90d20ba 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,32 @@
mComponentNames = componentNameString.split(FACET_FILTER_DELIMITER);
}
- setOnClickListener(v -> {
- intent.putExtra(EXTRA_FACET_LAUNCH_PICKER, mSelected);
- mContext.startActivityAsUser(intent, UserHandle.CURRENT);
- });
+ intent.putExtra(EXTRA_FACET_LAUNCH_PICKER, mSelected);
+ 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 -> 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..0046c20 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,24 @@
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.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.inputmethodservice.InputMethodService;
+import android.media.AudioManager;
+import android.os.Build;
+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 +69,48 @@
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.car.SUWProgressController;
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.NetworkController;
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 +131,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 +142,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 +158,9 @@
private CarFacetButtonController mCarFacetButtonController;
private ActivityManagerWrapper mActivityManagerWrapper;
private DeviceProvisionedController mDeviceProvisionedController;
- private boolean mDeviceIsProvisioned = true;
+ private SUWProgressController mSUWProgressController;
+ private boolean mDeviceIsSetUpForUser = true;
+ private boolean mIsUserSetupInProgress = false;
private HvacController mHvacController;
private DrivingStateHelper mDrivingStateHelper;
private PowerManagerHelper mPowerManagerHelper;
@@ -132,6 +169,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 +183,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 +216,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 +232,55 @@
}
};
+ 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
+ }
+ };
+
+ private boolean mBootCompleted = false;
+ private final BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mBootCompleted = true;
+ String action = intent.getAction();
+ if (action != null && action.equals(Intent.ACTION_BOOT_COMPLETED)) {
+ initHvac();
+ }
+ }
+ };
+
@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);
+
+ // Defer some actions for CarStatusBar initialization until after boot complete event
+ registerBootCompletedReceiver();
+
// 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();
+ mSUWProgressController = new SUWProgressController(mContext);
+ mDeviceIsSetUpForUser = mDeviceProvisionedController.isCurrentUserSetup();
+ mIsUserSetupInProgress = mSUWProgressController.isCurrentUserSetupInProgress();
// Keyboard related setup, before nav bars are created.
mHideNavBarForKeyboard = mContext.getResources().getBoolean(
@@ -205,6 +292,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 +333,30 @@
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();
- }
- });
- }
- });
- }
+ mSUWProgressController.addCallback(
+ new SUWProgressController.SUWProgressListener() {
+ @Override
+ public void onUserSetupInProgressChanged() {
+ mHandler.post(() -> restartNavBarsIfNecessary());
+ }
+ });
+ mDeviceProvisionedController.addCallback(
+ new DeviceProvisionedController.DeviceProvisionedListener() {
+ @Override
+ public void onUserSetupChanged() {
+ mHandler.post(() -> restartNavBarsIfNecessary());
+ }
+
+ @Override
+ public void onUserSwitched() {
+ mSUWProgressController.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 +365,84 @@
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);
+ }
+ });
+ }
+
+ @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);
+ if (Build.IS_USERDEBUG) {
+ mNetworkController = Dependency.get(NetworkController.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.
+ }
+
+ private void registerBootCompletedReceiver() {
+ IntentFilter bootCompletedFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
+ bootCompletedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+ mContext.registerReceiver(mBootCompletedReceiver, bootCompletedFilter);
+ }
+
+ private void unregisterBootCompletedListener() {
+ mContext.unregisterReceiver(mBootCompletedReceiver);
+ }
+
+ private void restartNavBarsIfNecessary() {
+ boolean currentUserSetup = mDeviceProvisionedController.isCurrentUserSetup();
+ boolean currentUserSetupInProgress = mSUWProgressController.isCurrentUserSetupInProgress();
+ if (mIsUserSetupInProgress != currentUserSetupInProgress
+ || mDeviceIsSetUpForUser != currentUserSetup) {
+ mDeviceIsSetUpForUser = currentUserSetup;
+ mIsUserSetupInProgress = currentUserSetupInProgress;
+ restartNavBars();
+ }
}
/**
@@ -260,9 +452,11 @@
private void restartNavBars() {
// remove and reattach all hvac components such that we don't keep a reference to unused
// ui elements
- mHvacController.removeAllComponents();
- addTemperatureViewToController(mStatusBarWindow);
+ if (mHvacController != null) {
+ mHvacController.removeAllComponents();
+ }
mCarFacetButtonController.removeAll();
+
if (mNavigationBarWindow != null) {
mNavigationBarWindow.removeAllViews();
mNavigationBarView = null;
@@ -279,6 +473,9 @@
}
buildNavBarContent();
+ if (mBootCompleted) {
+ initHvac();
+ }
// If the UI was rebuilt (day/night change) while the keyguard was up we need to
// correctly respect that state.
if (mIsKeyguard) {
@@ -290,6 +487,8 @@
}
private void addTemperatureViewToController(View v) {
+ if (v == null) return;
+
if (v instanceof TemperatureView) {
mHvacController.addHvacTextView((TemperatureView) v);
} else if (v instanceof ViewGroup) {
@@ -299,7 +498,6 @@
}
}
}
-
/**
* Allows for showing or hiding just the navigation bars. This is indented to be used when
* the full screen user selector is shown.
@@ -356,7 +554,6 @@
@Override
protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {
super.makeStatusBarView(result);
- mHvacController = new HvacController(mContext);
CarSystemUIFactory factory = SystemUIFactory.getInstance();
mCarFacetButtonController = factory.getCarDependencyComponent()
@@ -381,7 +578,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 +612,18 @@
GestureDetector handleBarCloseNotificationGestureDetector = new GestureDetector(mContext,
new HandleBarCloseNotificationGestureListener());
+ mTopNavBarNotificationTouchListener = (v, event) -> {
+ if (!mDeviceIsSetUpForUser || mIsUserSetupInProgress) {
+ 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 +634,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 +642,16 @@
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(() -> {
+ if (mNavigationBarView != null && mNotificationDataManager != null) {
+ onUseenCountUpdate(mNotificationDataManager.getUnseenNotificationCount());
+ }
+ });
mEnableHeadsUpNotificationWhenNotificationShadeOpen = mContext.getResources().getBoolean(
R.bool.config_enableHeadsUpNotificationWhenNotificationShadeOpen);
@@ -482,7 +660,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 +755,45 @@
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 automatically 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 onUseenCountUpdate(int unseenNotificationCount) {
+ boolean hasUnseen = unseenNotificationCount > 0;
+
+ if (mNavigationBarView != null) {
+ mNavigationBarView.toggleNotificationUnseenIndicator(hasUnseen);
+ }
+
+ if (mLeftNavigationBarView != null) {
+ mLeftNavigationBarView.toggleNotificationUnseenIndicator(hasUnseen);
+ }
+
+ if (mRightNavigationBarView != null) {
+ mRightNavigationBarView.toggleNotificationUnseenIndicator(hasUnseen);
+ }
}
/**
@@ -745,23 +954,32 @@
}
private void buildNavBarContent() {
+ boolean shouldBuildNavBarContent = mDeviceIsSetUpForUser && !mIsUserSetupInProgress;
+
+ // Always build top bar.
+ buildTopBar(shouldBuildNavBarContent ? R.layout.car_top_navigation_bar :
+ R.layout.car_top_navigation_bar_unprovisioned);
+
if (mShowBottom) {
- buildBottomBar((mDeviceIsProvisioned) ? R.layout.car_navigation_bar :
+ buildBottomBar(shouldBuildNavBarContent ? R.layout.car_navigation_bar :
R.layout.car_navigation_bar_unprovisioned);
}
if (mShowLeft) {
- buildLeft((mDeviceIsProvisioned) ? R.layout.car_left_navigation_bar :
+ buildLeft(shouldBuildNavBarContent ? R.layout.car_left_navigation_bar :
R.layout.car_left_navigation_bar_unprovisioned);
}
if (mShowRight) {
- buildRight((mDeviceIsProvisioned) ? R.layout.car_right_navigation_bar :
+ buildRight(shouldBuildNavBarContent ? 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 +1012,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 +1068,31 @@
}
}
- /**
- * 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);
+ mTopNavigationBarView.setStatusBarWindowTouchListener(mTopNavBarNotificationTouchListener);
}
private void buildBottomBar(int layout) {
@@ -891,10 +1103,9 @@
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);
mNavigationBarView.setStatusBarWindowTouchListener(mNavBarNotificationTouchListener);
}
@@ -902,11 +1113,10 @@
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);
- addTemperatureViewToController(mLeftNavigationBarView);
mLeftNavigationBarView.setStatusBarWindowTouchListener(mNavBarNotificationTouchListener);
}
@@ -915,14 +1125,24 @@
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);
- addTemperatureViewToController(mRightNavigationBarView);
mRightNavigationBarView.setStatusBarWindowTouchListener(mNavBarNotificationTouchListener);
}
+ private void initHvac() {
+ if (mHvacController == null) {
+ mHvacController = Dependency.get(HvacController.class);
+ mHvacController.connectToCarService();
+ }
+ addTemperatureViewToController(mTopNavigationBarView);
+ addTemperatureViewToController(mNavigationBarView);
+ addTemperatureViewToController(mLeftNavigationBarView);
+ addTemperatureViewToController(mRightNavigationBarView);
+ }
+
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
//When executing dump() function simultaneously, we need to serialize them
@@ -1030,12 +1250,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();
}
@@ -1125,6 +1341,12 @@
mScrimController.setScrimBehindDrawable(mNotificationPanelBackground);
}
+ @Override
+ public void onLocaleListChanged() {
+ restartNavBars();
+ connectNotificationsUI();
+ }
+
/**
* Returns the {@link Drawable} that represents the wallpaper that the user has currently set.
*/
@@ -1146,17 +1368,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(
@@ -1373,8 +1600,10 @@
@Override
protected void setHeadsUpVisible() {
- // if the Notifications panel is showing don't show the Heads up
- if (!mEnableHeadsUpNotificationWhenNotificationShadeOpen && mPanelExpanded) {
+ // if the Notifications panel is showing or SUW for user is in progress then don't show
+ // heads up notifications
+ if ((!mEnableHeadsUpNotificationWhenNotificationShadeOpen && mPanelExpanded)
+ || !mDeviceIsSetUpForUser || mIsUserSetupInProgress) {
return;
}
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..92834e8 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,10 @@
import androidx.recyclerview.widget.GridLayoutManager;
+import com.android.internal.widget.LockPatternUtils;
+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 +46,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 +67,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 +85,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 +117,19 @@
/* 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 (hasTrustedDevice(initialUser)) {
+
+ // If the initial user has screen lock and trusted device, display the unlock dialog on the
+ // keyguard.
+ if (hasScreenLock(initialUser) && 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() {
@@ -162,7 +162,7 @@
*/
private void onUserSelected(UserGridRecyclerView.UserRecord record) {
mSelectedUser = record;
- if (hasTrustedDevice(record.mInfo.id)) {
+ if (hasScreenLock(record.mInfo.id) && hasTrustedDevice(record.mInfo.id)) {
mUnlockDialogHelper.showUnlockDialog(record.mInfo.id, mOnHideListener);
return;
}
@@ -200,7 +200,15 @@
}
+ private boolean hasScreenLock(int uid) {
+ LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext);
+ return lockPatternUtils.isSecure(uid);
+ }
+
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/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 386ff65..09c8f73 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -177,6 +177,8 @@
static final String KEY_CARRIER_AP_EAP_TYPE = "key_carrier_ap_eap_type";
static final String KEY_CARRIER_NAME = "key_carrier_name";
static final String KEY_EAPTYPE = "eap_psktype";
+ static final String KEY_IS_PSK_SAE_TRANSITION_MODE = "key_is_psk_sae_transition_mode";
+ static final String KEY_IS_OWE_TRANSITION_MODE = "key_is_owe_transition_mode";
static final AtomicInteger sLastId = new AtomicInteger(0);
/*
@@ -191,15 +193,12 @@
public static final int SECURITY_OWE = 4;
public static final int SECURITY_SAE = 5;
public static final int SECURITY_EAP_SUITE_B = 6;
- public static final int SECURITY_PSK_SAE_TRANSITION = 7;
- public static final int SECURITY_OWE_TRANSITION = 8;
- public static final int SECURITY_MAX_VAL = 9; // Has to be the last
+ public static final int SECURITY_MAX_VAL = 7; // Has to be the last
private static final int PSK_UNKNOWN = 0;
private static final int PSK_WPA = 1;
private static final int PSK_WPA2 = 2;
private static final int PSK_WPA_WPA2 = 3;
- private static final int PSK_SAE = 4;
private static final int EAP_UNKNOWN = 0;
private static final int EAP_WPA = 1; // WPA-EAP
@@ -260,6 +259,9 @@
private String mOsuFailure;
private boolean mOsuProvisioningComplete = false;
+ private boolean mIsPskSaeTransitionMode = false;
+ private boolean mIsOweTransitionMode = false;
+
/**
* The EAP type {@link WifiEnterpriseConfig.Eap} associated with this AP if it is a carrier AP.
*/
@@ -323,6 +325,13 @@
if (savedState.containsKey(KEY_CARRIER_NAME)) {
mCarrierName = savedState.getString(KEY_CARRIER_NAME);
}
+ if (savedState.containsKey(KEY_IS_PSK_SAE_TRANSITION_MODE)) {
+ mIsPskSaeTransitionMode = savedState.getBoolean(KEY_IS_PSK_SAE_TRANSITION_MODE);
+ }
+ if (savedState.containsKey(KEY_IS_OWE_TRANSITION_MODE)) {
+ mIsOweTransitionMode = savedState.getBoolean(KEY_IS_OWE_TRANSITION_MODE);
+ }
+
update(mConfig, mInfo, mNetworkInfo);
// Calculate required fields
@@ -648,8 +657,15 @@
return oldMetering == mIsScoredNetworkMetered;
}
- public static String getKey(ScanResult result) {
- return getKey(result.SSID, result.BSSID, getSecurity(result));
+ /**
+ * Generates an AccessPoint key for a given scan result
+ *
+ * @param context
+ * @param result Scan result
+ * @return AccessPoint key
+ */
+ public static String getKey(Context context, ScanResult result) {
+ return getKey(result.SSID, result.BSSID, getSecurity(context, result));
}
/**
@@ -707,7 +723,42 @@
* Determines if the other AccessPoint represents the same network as this AccessPoint
*/
public boolean matches(AccessPoint other) {
- return getKey().equals(other.getKey());
+ if (isPasspoint() || isPasspointConfig() || isOsuProvider()) {
+ return getKey().equals(other.getKey());
+ }
+
+ if (!isSameSsidOrBssid(other)) {
+ return false;
+ }
+
+ final int otherApSecurity = other.getSecurity();
+ if (mIsPskSaeTransitionMode) {
+ if (otherApSecurity == SECURITY_SAE && getWifiManager().isWpa3SaeSupported()) {
+ return true;
+ } else if (otherApSecurity == SECURITY_PSK) {
+ return true;
+ }
+ } else {
+ if ((security == SECURITY_SAE || security == SECURITY_PSK)
+ && other.isPskSaeTransitionMode()) {
+ return true;
+ }
+ }
+
+ if (mIsOweTransitionMode) {
+ if (otherApSecurity == SECURITY_OWE && getWifiManager().isEnhancedOpenSupported()) {
+ return true;
+ } else if (otherApSecurity == SECURITY_NONE) {
+ return true;
+ }
+ } else {
+ if ((security == SECURITY_OWE || security == SECURITY_NONE)
+ && other.isOweTransitionMode()) {
+ return true;
+ }
+ }
+
+ return security == other.getSecurity();
}
public boolean matches(WifiConfiguration config) {
@@ -721,18 +772,77 @@
}
final int configSecurity = getSecurity(config);
- final WifiManager wifiManager = getWifiManager();
- switch (security) {
- case SECURITY_PSK_SAE_TRANSITION:
- return configSecurity == SECURITY_PSK
- || (wifiManager.isWpa3SaeSupported() && configSecurity == SECURITY_SAE);
- case SECURITY_OWE_TRANSITION:
- return configSecurity == SECURITY_NONE
- || (wifiManager.isEnhancedOpenSupported()
- && configSecurity == SECURITY_OWE);
- default:
- return security == configSecurity;
+ if (mIsPskSaeTransitionMode) {
+ if (configSecurity == SECURITY_SAE && getWifiManager().isWpa3SaeSupported()) {
+ return true;
+ } else if (configSecurity == SECURITY_PSK) {
+ return true;
+ }
}
+
+ if (mIsOweTransitionMode) {
+ if (configSecurity == SECURITY_OWE && getWifiManager().isEnhancedOpenSupported()) {
+ return true;
+ } else if (configSecurity == SECURITY_NONE) {
+ return true;
+ }
+ }
+
+ return security == getSecurity(config);
+ }
+
+ private boolean matches(WifiConfiguration config, WifiInfo wifiInfo) {
+ if (config == null || wifiInfo == null) {
+ return false;
+ }
+ if (!config.isPasspoint() && !isSameSsidOrBssid(wifiInfo)) {
+ return false;
+ }
+ return matches(config);
+ }
+
+ @VisibleForTesting
+ boolean matches(ScanResult scanResult) {
+ if (scanResult == null) {
+ return false;
+ }
+ if (isPasspoint() || isOsuProvider()) {
+ throw new IllegalStateException("Should not matches a Passpoint by ScanResult");
+ }
+
+ if (!isSameSsidOrBssid(scanResult)) {
+ return false;
+ }
+
+ if (mIsPskSaeTransitionMode) {
+ if (scanResult.capabilities.contains("SAE")
+ && getWifiManager().isWpa3SaeSupported()) {
+ return true;
+ } else if (scanResult.capabilities.contains("PSK")) {
+ return true;
+ }
+ } else {
+ if ((security == SECURITY_SAE || security == SECURITY_PSK)
+ && AccessPoint.isPskSaeTransitionMode(scanResult)) {
+ return true;
+ }
+ }
+
+ if (mIsOweTransitionMode) {
+ final int scanResultSccurity = getSecurity(mContext, scanResult);
+ if (scanResultSccurity == SECURITY_OWE && getWifiManager().isEnhancedOpenSupported()) {
+ return true;
+ } else if (scanResultSccurity == SECURITY_NONE) {
+ return true;
+ }
+ } else {
+ if ((security == SECURITY_OWE || security == SECURITY_NONE)
+ && AccessPoint.isOweTransitionMode(scanResult)) {
+ return true;
+ }
+ }
+
+ return security == getSecurity(mContext, scanResult);
}
public WifiConfiguration getConfig() {
@@ -819,14 +929,17 @@
if (bestResult != null) {
ssid = bestResult.SSID;
bssid = bestResult.BSSID;
- security = getSecurity(bestResult);
- if (security == SECURITY_PSK || security == SECURITY_SAE
- || security == SECURITY_PSK_SAE_TRANSITION) {
+ security = getSecurity(mContext, bestResult);
+ if (security == SECURITY_PSK || security == SECURITY_SAE) {
pskType = getPskType(bestResult);
}
if (security == SECURITY_EAP) {
mEapType = getEapType(bestResult);
}
+
+ mIsPskSaeTransitionMode = AccessPoint.isPskSaeTransitionMode(bestResult);
+ mIsOweTransitionMode = AccessPoint.isOweTransitionMode(bestResult);
+
mIsCarrierAp = bestResult.isCarrierAp;
mCarrierApEapType = bestResult.carrierApEapType;
mCarrierName = bestResult.carrierName;
@@ -859,6 +972,12 @@
return concise ? context.getString(R.string.wifi_security_short_eap) :
context.getString(R.string.wifi_security_eap);
}
+
+ if (mIsPskSaeTransitionMode) {
+ return concise ? context.getString(R.string.wifi_security_short_psk_sae) :
+ context.getString(R.string.wifi_security_psk_sae);
+ }
+
switch(security) {
case SECURITY_EAP:
switch (mEapType) {
@@ -898,20 +1017,8 @@
return concise ? context.getString(R.string.wifi_security_short_wep) :
context.getString(R.string.wifi_security_wep);
case SECURITY_SAE:
- case SECURITY_PSK_SAE_TRANSITION:
- if (pskType == PSK_SAE) {
- return concise ? context.getString(R.string.wifi_security_short_psk_sae) :
- context.getString(R.string.wifi_security_psk_sae);
- } else {
- return concise ? context.getString(R.string.wifi_security_short_sae) :
- context.getString(R.string.wifi_security_sae);
- }
- case SECURITY_OWE_TRANSITION:
- if (mConfig != null && getSecurity(mConfig) == SECURITY_OWE) {
- return concise ? context.getString(R.string.wifi_security_short_owe) :
- context.getString(R.string.wifi_security_owe);
- }
- return concise ? "" : context.getString(R.string.wifi_security_none);
+ return concise ? context.getString(R.string.wifi_security_short_sae) :
+ context.getString(R.string.wifi_security_sae);
case SECURITY_OWE:
return concise ? context.getString(R.string.wifi_security_short_owe) :
context.getString(R.string.wifi_security_owe);
@@ -1195,7 +1302,7 @@
if (networkId != WifiConfiguration.INVALID_NETWORK_ID) {
return networkId == info.getNetworkId();
} else if (config != null) {
- return isKeyEqual(getKey(config));
+ return matches(config, info);
} else {
// Might be an ephemeral connection with no WifiConfiguration. Try matching on SSID.
// (Note that we only do this if the WifiConfiguration explicitly equals INVALID).
@@ -1221,8 +1328,7 @@
* Can only be called for unsecured networks.
*/
public void generateOpenNetworkConfig() {
- if ((security != SECURITY_NONE) && (security != SECURITY_OWE)
- && (security != SECURITY_OWE_TRANSITION)) {
+ if ((security != SECURITY_NONE) && (security != SECURITY_OWE)) {
throw new IllegalStateException();
}
if (mConfig != null)
@@ -1265,43 +1371,14 @@
savedState.putBoolean(KEY_IS_CARRIER_AP, mIsCarrierAp);
savedState.putInt(KEY_CARRIER_AP_EAP_TYPE, mCarrierApEapType);
savedState.putString(KEY_CARRIER_NAME, mCarrierName);
+ savedState.putBoolean(KEY_IS_PSK_SAE_TRANSITION_MODE, mIsPskSaeTransitionMode);
+ savedState.putBoolean(KEY_IS_OWE_TRANSITION_MODE, mIsOweTransitionMode);
}
public void setListener(AccessPointListener listener) {
mAccessPointListener = listener;
}
- private static final String sPskSuffix = "," + String.valueOf(SECURITY_PSK);
- private static final String sSaeSuffix = "," + String.valueOf(SECURITY_SAE);
- private static final String sPskSaeSuffix = "," + String.valueOf(SECURITY_PSK_SAE_TRANSITION);
- private static final String sOweSuffix = "," + String.valueOf(SECURITY_OWE);
- private static final String sOpenSuffix = "," + String.valueOf(SECURITY_NONE);
- private static final String sOweTransSuffix = "," + String.valueOf(SECURITY_OWE_TRANSITION);
-
- private boolean isKeyEqual(String compareTo) {
- if (mKey == null) {
- return false;
- }
-
- if (compareTo.endsWith(sPskSuffix) || compareTo.endsWith(sSaeSuffix)) {
- if (mKey.endsWith(sPskSaeSuffix)) {
- // Special handling for PSK-SAE transition mode. If the AP has advertised both,
- // we compare the key with both PSK and SAE for a match.
- return TextUtils.equals(mKey.substring(0, mKey.lastIndexOf(',')),
- compareTo.substring(0, compareTo.lastIndexOf(',')));
- }
- }
- if (compareTo.endsWith(sOpenSuffix) || compareTo.endsWith(sOweSuffix)) {
- if (mKey.endsWith(sOweTransSuffix)) {
- // Special handling for OWE/Open networks. If AP advertises OWE in transition mode
- // and we have an Open network saved, allow this connection to be established.
- return TextUtils.equals(mKey.substring(0, mKey.lastIndexOf(',')),
- compareTo.substring(0, compareTo.lastIndexOf(',')));
- }
- }
- return mKey.equals(compareTo);
- }
-
/**
* Sets {@link #mScanResults} to the given collection and updates info based on the best RSSI
* scan result.
@@ -1318,11 +1395,10 @@
// Passpoint networks are not bound to a specific SSID/BSSID, so skip this for passpoint.
if (mKey != null && !isPasspoint() && !isOsuProvider()) {
for (ScanResult result : scanResults) {
- String scanResultKey = AccessPoint.getKey(result);
- if (!isKeyEqual(scanResultKey)) {
+ if (!matches(result)) {
Log.d(TAG, String.format(
- "ScanResult %s\nkey of %s did not match current AP key %s",
- result, scanResultKey, mKey));
+ "ScanResult %s\nkey of %s did not match current AP key %s",
+ result, getKey(mContext, result), mKey));
return;
}
}
@@ -1601,11 +1677,8 @@
private static int getPskType(ScanResult result) {
boolean wpa = result.capabilities.contains("WPA-PSK");
boolean wpa2 = result.capabilities.contains("RSN-PSK");
- boolean wpa3TransitionMode = result.capabilities.contains("PSK+SAE");
boolean wpa3 = result.capabilities.contains("RSN-SAE");
- if (wpa3TransitionMode) {
- return PSK_SAE;
- } else if (wpa2 && wpa) {
+ if (wpa2 && wpa) {
return PSK_WPA_WPA2;
} else if (wpa2) {
return PSK_WPA2;
@@ -1632,22 +1705,37 @@
return EAP_UNKNOWN;
}
- private static int getSecurity(ScanResult result) {
- if (result.capabilities.contains("WEP")) {
+ private static int getSecurity(Context context, ScanResult result) {
+ final boolean isWep = result.capabilities.contains("WEP");
+ final boolean isSae = result.capabilities.contains("SAE");
+ final boolean isPsk = result.capabilities.contains("PSK");
+ final boolean isEapSuiteB192 = result.capabilities.contains("EAP_SUITE_B_192");
+ final boolean isEap = result.capabilities.contains("EAP");
+ final boolean isOwe = result.capabilities.contains("OWE");
+ final boolean isOweTransition = result.capabilities.contains("OWE_TRANSITION");
+
+ if (isSae && isPsk) {
+ final WifiManager wifiManager = (WifiManager)
+ context.getSystemService(Context.WIFI_SERVICE);
+ return wifiManager.isWpa3SaeSupported() ? SECURITY_SAE : SECURITY_PSK;
+ }
+ if (isOweTransition) {
+ final WifiManager wifiManager = (WifiManager)
+ context.getSystemService(Context.WIFI_SERVICE);
+ return wifiManager.isEnhancedOpenSupported() ? SECURITY_OWE : SECURITY_NONE;
+ }
+
+ if (isWep) {
return SECURITY_WEP;
- } else if (result.capabilities.contains("PSK+SAE")) {
- return SECURITY_PSK_SAE_TRANSITION;
- } else if (result.capabilities.contains("SAE")) {
+ } else if (isSae) {
return SECURITY_SAE;
- } else if (result.capabilities.contains("PSK")) {
+ } else if (isPsk) {
return SECURITY_PSK;
- } else if (result.capabilities.contains("EAP_SUITE_B_192")) {
+ } else if (isEapSuiteB192) {
return SECURITY_EAP_SUITE_B;
- } else if (result.capabilities.contains("EAP")) {
+ } else if (isEap) {
return SECURITY_EAP;
- } else if (result.capabilities.contains("OWE_TRANSITION")) {
- return SECURITY_OWE_TRANSITION;
- } else if (result.capabilities.contains("OWE")) {
+ } else if (isOwe) {
return SECURITY_OWE;
}
return SECURITY_NONE;
@@ -1693,10 +1781,6 @@
return "SUITE_B";
} else if (security == SECURITY_OWE) {
return "OWE";
- } else if (security == SECURITY_PSK_SAE_TRANSITION) {
- return "PSK+SAE";
- } else if (security == SECURITY_OWE_TRANSITION) {
- return "OWE_TRANSITION";
}
return "NONE";
}
@@ -1866,4 +1950,61 @@
}
}
}
+
+ public boolean isPskSaeTransitionMode() {
+ return mIsPskSaeTransitionMode;
+ }
+
+ public boolean isOweTransitionMode() {
+ return mIsOweTransitionMode;
+ }
+
+ private static boolean isPskSaeTransitionMode(ScanResult scanResult) {
+ return scanResult.capabilities.contains("PSK")
+ && scanResult.capabilities.contains("SAE");
+ }
+
+ private static boolean isOweTransitionMode(ScanResult scanResult) {
+ return scanResult.capabilities.contains("OWE_TRANSITION");
+ }
+
+ private boolean isSameSsidOrBssid(ScanResult scanResult) {
+ if (scanResult == null) {
+ return false;
+ }
+
+ if (TextUtils.equals(ssid, scanResult.SSID)) {
+ return true;
+ } else if (scanResult.BSSID != null && TextUtils.equals(bssid, scanResult.BSSID)) {
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isSameSsidOrBssid(WifiInfo wifiInfo) {
+ if (wifiInfo == null) {
+ return false;
+ }
+
+ if (TextUtils.equals(ssid, removeDoubleQuotes(wifiInfo.getSSID()))) {
+ return true;
+ } else if (wifiInfo.getBSSID() != null && TextUtils.equals(bssid, wifiInfo.getBSSID())) {
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isSameSsidOrBssid(AccessPoint accessPoint) {
+ if (accessPoint == null) {
+ return false;
+ }
+
+ if (TextUtils.equals(ssid, accessPoint.getSsid())) {
+ return true;
+ } else if (accessPoint.getBssid() != null
+ && TextUtils.equals(bssid, accessPoint.getBssid())) {
+ return true;
+ }
+ return false;
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
index dae5464..6269a71 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
@@ -201,8 +201,7 @@
return;
}
if ((mAccessPoint.getSecurity() != AccessPoint.SECURITY_NONE)
- && (mAccessPoint.getSecurity() != AccessPoint.SECURITY_OWE)
- && (mAccessPoint.getSecurity() != AccessPoint.SECURITY_OWE_TRANSITION)) {
+ && (mAccessPoint.getSecurity() != AccessPoint.SECURITY_OWE)) {
mFrictionSld.setState(STATE_SECURED);
} else if (mAccessPoint.isMetered()) {
mFrictionSld.setState(STATE_METERED);
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index 3e359d2..f1c5f46 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -70,8 +70,10 @@
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Collectors;
/**
* Tracks saved or available wifi networks and their state.
@@ -475,7 +477,7 @@
continue;
}
- String apKey = AccessPoint.getKey(result);
+ String apKey = AccessPoint.getKey(mContext, result);
List<ScanResult> resultList;
if (scanResultsByApKey.containsKey(apKey)) {
resultList = scanResultsByApKey.get(apKey);
@@ -548,14 +550,6 @@
private void updateAccessPoints(final List<ScanResult> newScanResults,
List<WifiConfiguration> configs) {
- // Map configs and scan results necessary to make AccessPoints
- final Map<String, WifiConfiguration> configsByKey = new ArrayMap(configs.size());
- if (configs != null) {
- for (WifiConfiguration config : configs) {
- configsByKey.put(AccessPoint.getKey(config), config);
- }
- }
-
WifiConfiguration connectionConfig = null;
if (mLastInfo != null) {
connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId(), configs);
@@ -587,7 +581,26 @@
getCachedOrCreate(entry.getValue(), cachedAccessPoints);
// Update the matching config if there is one, to populate saved network info
- accessPoint.update(configsByKey.get(entry.getKey()));
+ final List<WifiConfiguration> matchedConfigs = configs.stream()
+ .filter(config -> accessPoint.matches(config))
+ .collect(Collectors.toList());
+
+ final int matchedConfigCount = matchedConfigs.size();
+ if (matchedConfigCount == 0) {
+ accessPoint.update(null);
+ } else if (matchedConfigCount == 1) {
+ accessPoint.update(matchedConfigs.get(0));
+ } else {
+ // We may have 2 matched configured WifiCongiguration if the AccessPoint is
+ // of PSK/SAE transition mode or open/OWE transition mode.
+ Optional<WifiConfiguration> preferredConfig = matchedConfigs.stream()
+ .filter(config -> isSaeOrOwe(config)).findFirst();
+ if (preferredConfig.isPresent()) {
+ accessPoint.update(preferredConfig.get());
+ } else {
+ accessPoint.update(matchedConfigs.get(0));
+ }
+ }
accessPoints.add(accessPoint);
}
@@ -653,6 +666,11 @@
conditionallyNotifyListeners();
}
+ private static boolean isSaeOrOwe(WifiConfiguration config) {
+ final int security = AccessPoint.getSecurity(config);
+ return security == AccessPoint.SECURITY_SAE || security == AccessPoint.SECURITY_OWE;
+ }
+
@VisibleForTesting
List<AccessPoint> updatePasspointAccessPoints(
List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> passpointConfigsAndScans,
@@ -701,7 +719,8 @@
private AccessPoint getCachedOrCreate(
List<ScanResult> scanResults,
List<AccessPoint> cache) {
- AccessPoint accessPoint = getCachedByKey(cache, AccessPoint.getKey(scanResults.get(0)));
+ AccessPoint accessPoint = getCachedByKey(cache,
+ AccessPoint.getKey(mContext, scanResults.get(0)));
if (accessPoint == null) {
accessPoint = new AccessPoint(mContext, scanResults);
} else {
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
index af4704c..77473f5 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
@@ -39,6 +39,7 @@
import android.net.WifiKey;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiConfiguration.KeyMgmt;
import android.net.wifi.WifiEnterpriseConfig;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
@@ -1272,7 +1273,7 @@
@Test
public void testGetKey_matchesKeysCorrectly() {
AccessPoint ap = new AccessPoint(mContext, mScanResults);
- assertThat(ap.getKey()).isEqualTo(AccessPoint.getKey(mScanResults.get(0)));
+ assertThat(ap.getKey()).isEqualTo(AccessPoint.getKey(mContext, mScanResults.get(0)));
WifiConfiguration spyConfig = spy(new WifiConfiguration());
when(spyConfig.isPasspoint()).thenReturn(true);
@@ -1294,6 +1295,44 @@
}
/**
+ * Test that getKey returns a key of SAE type for a PSK/SAE transition mode ScanResult.
+ */
+ @Test
+ public void testGetKey_supportSaeTransitionMode_shouldGetSaeKey() {
+ ScanResult scanResult = createScanResult(TEST_SSID, TEST_BSSID, DEFAULT_RSSI);
+ scanResult.capabilities =
+ "[WPA2-FT/PSK-CCMP][RSN-FT/PSK+PSK-SHA256+SAE+FT/SAE-CCMP][ESS][WPS]";
+ when(mMockWifiManager.isWpa3SaeSupported()).thenReturn(true);
+ when(mMockContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mMockWifiManager);
+ StringBuilder key = new StringBuilder();
+ key.append(AccessPoint.KEY_PREFIX_AP);
+ key.append(TEST_SSID);
+ key.append(',');
+ key.append(AccessPoint.SECURITY_SAE);
+
+ assertThat(AccessPoint.getKey(mMockContext, scanResult)).isEqualTo(key.toString());
+ }
+
+ /**
+ * Test that getKey returns a key of PSK type for a PSK/SAE transition mode ScanResult.
+ */
+ @Test
+ public void testGetKey_notSupportSaeTransitionMode_shouldGetPskKey() {
+ ScanResult scanResult = createScanResult(TEST_SSID, TEST_BSSID, DEFAULT_RSSI);
+ scanResult.capabilities =
+ "[WPA2-FT/PSK-CCMP][RSN-FT/PSK+PSK-SHA256+SAE+FT/SAE-CCMP][ESS][WPS]";
+ when(mMockWifiManager.isWpa3SaeSupported()).thenReturn(false);
+ when(mMockContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mMockWifiManager);
+ StringBuilder key = new StringBuilder();
+ key.append(AccessPoint.KEY_PREFIX_AP);
+ key.append(TEST_SSID);
+ key.append(',');
+ key.append(AccessPoint.SECURITY_PSK);
+
+ assertThat(AccessPoint.getKey(mMockContext, scanResult)).isEqualTo(key.toString());
+ }
+
+ /**
* Verifies that the Passpoint AccessPoint constructor creates AccessPoints whose isPasspoint()
* returns true.
*/
@@ -1522,4 +1561,113 @@
verify(mMockConnectListener).onFailure(anyInt());
}
+
+ /**
+ * Verifies that matches(AccessPoint other) matches a PSK/SAE transition mode AP to a PSK or a
+ * SAE AP.
+ */
+ @Test
+ public void testMatches1_transitionModeApMatchesNotTransitionModeAp_shouldMatchCorrectly() {
+ when(mMockContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mMockWifiManager);
+ when(mMockWifiManager.isWpa3SaeSupported()).thenReturn(true);
+ AccessPoint pskSaeTransitionModeAp = getPskSaeTransitionModeAp();
+
+ // Transition mode AP matches a SAE AP.
+ AccessPoint saeAccessPoint = new TestAccessPointBuilder(mContext)
+ .setSsid(AccessPoint.removeDoubleQuotes(TEST_SSID))
+ .setSecurity(AccessPoint.SECURITY_SAE)
+ .build();
+ assertThat(pskSaeTransitionModeAp.matches(saeAccessPoint)).isTrue();
+
+ // Transition mode AP matches a PSK AP.
+ AccessPoint pskAccessPoint = new TestAccessPointBuilder(mContext)
+ .setSsid(AccessPoint.removeDoubleQuotes(TEST_SSID))
+ .setSecurity(AccessPoint.SECURITY_PSK)
+ .build();
+
+ assertThat(pskSaeTransitionModeAp.matches(pskAccessPoint)).isTrue();
+
+ // Transition mode AP does not match a SAE AP if the device does not support SAE.
+ when(mMockWifiManager.isWpa3SaeSupported()).thenReturn(false);
+ pskSaeTransitionModeAp = getPskSaeTransitionModeAp();
+ saeAccessPoint = new TestAccessPointBuilder(mContext)
+ .setSsid(AccessPoint.removeDoubleQuotes(TEST_SSID))
+ .setSecurity(AccessPoint.SECURITY_SAE)
+ .build();
+
+ assertThat(pskSaeTransitionModeAp.matches(saeAccessPoint)).isFalse();
+ }
+
+ /**
+ * Verifies that matches(WifiConfiguration config) matches a PSK/SAE transition mode AP to a PSK
+ * or a SAE WifiConfiguration.
+ */
+ @Test
+ public void testMatches2_transitionModeApMatchesNotTransitionModeAp_shouldMatchCorrectly() {
+ when(mMockContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mMockWifiManager);
+ when(mMockWifiManager.isWpa3SaeSupported()).thenReturn(true);
+ AccessPoint pskSaeTransitionModeAp = getPskSaeTransitionModeAp();
+
+ // Transition mode AP matches a SAE WifiConfiguration.
+ WifiConfiguration saeConfig = new WifiConfiguration();
+ saeConfig.SSID = TEST_SSID;
+ saeConfig.allowedKeyManagement.set(KeyMgmt.SAE);
+
+ assertThat(pskSaeTransitionModeAp.matches(saeConfig)).isTrue();
+
+ // Transition mode AP matches a PSK WifiConfiguration.
+ WifiConfiguration pskConfig = new WifiConfiguration();
+ pskConfig.SSID = TEST_SSID;
+ pskConfig.allowedKeyManagement.set(KeyMgmt.WPA_PSK);
+
+ assertThat(pskSaeTransitionModeAp.matches(pskConfig)).isTrue();
+
+ // Transition mode AP does not matches a SAE WifiConfiguration if the device does not
+ // support SAE.
+ when(mMockWifiManager.isWpa3SaeSupported()).thenReturn(false);
+ pskSaeTransitionModeAp = getPskSaeTransitionModeAp();
+
+ assertThat(pskSaeTransitionModeAp.matches(saeConfig)).isFalse();
+ }
+
+ /**
+ * Verifies that matches(ScanResult scanResult) matches a PSK/SAE transition mode AP to a PSK
+ * or a SAE ScanResult.
+ */
+ @Test
+ public void testMatches3_transitionModeApMatchesNotTransitionModeAp_shouldMatchCorrectly() {
+ when(mMockContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mMockWifiManager);
+ when(mMockWifiManager.isWpa3SaeSupported()).thenReturn(true);
+ AccessPoint pskSaeTransitionModeAp = getPskSaeTransitionModeAp();
+
+ // Transition mode AP matches a SAE ScanResult.
+ ScanResult saeScanResult = createScanResult(AccessPoint.removeDoubleQuotes(TEST_SSID),
+ TEST_BSSID, DEFAULT_RSSI);
+ saeScanResult.capabilities = "[SAE-CCMP][ESS][WPS]";
+
+ assertThat(pskSaeTransitionModeAp.matches(saeScanResult)).isTrue();
+
+ // Transition mode AP matches a PSK ScanResult.
+ ScanResult pskScanResult = createScanResult(AccessPoint.removeDoubleQuotes(TEST_SSID),
+ TEST_BSSID, DEFAULT_RSSI);
+ pskScanResult.capabilities = "[RSN-PSK-CCMP][ESS][WPS]";
+
+ assertThat(pskSaeTransitionModeAp.matches(pskScanResult)).isTrue();
+
+ // Transition mode AP does not matches a SAE ScanResult if the device does not support SAE.
+ when(mMockWifiManager.isWpa3SaeSupported()).thenReturn(false);
+ pskSaeTransitionModeAp = getPskSaeTransitionModeAp();
+
+ assertThat(pskSaeTransitionModeAp.matches(saeScanResult)).isFalse();
+ }
+
+ private AccessPoint getPskSaeTransitionModeAp() {
+ ScanResult scanResult = createScanResult(AccessPoint.removeDoubleQuotes(TEST_SSID),
+ TEST_BSSID, DEFAULT_RSSI);
+ scanResult.capabilities =
+ "[WPA2-FT/PSK-CCMP][RSN-FT/PSK+PSK-SHA256+SAE+FT/SAE-CCMP][ESS][WPS]";
+ return new TestAccessPointBuilder(mMockContext)
+ .setScanResults(new ArrayList<ScanResult>(Arrays.asList(scanResult)))
+ .build();
+ }
}
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..21129f9 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -641,6 +641,9 @@
dumpSetting(s, p,
Settings.Global.EUICC_FACTORY_RESET_TIMEOUT_MILLIS,
GlobalSettingsProto.Euicc.FACTORY_RESET_TIMEOUT_MILLIS);
+ dumpSetting(s, p,
+ Settings.Global.EUICC_UNSUPPORTED_COUNTRIES,
+ GlobalSettingsProto.Euicc.UNSUPPORTED_COUNTRIES);
p.end(euiccToken);
dumpSetting(s, p,
@@ -1969,6 +1972,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/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index 1bfc4c0..c2788a8 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -24,7 +24,35 @@
android:outlineProvider="none"
android:elevation="5dp" > <!-- Put it above the status bar header -->
- <include layout="@layout/keyguard_indication_area_overlay" />
+ <LinearLayout
+ android:id="@+id/keyguard_indication_area"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/keyguard_indication_margin_bottom"
+ android:layout_gravity="bottom|center_horizontal"
+ android:orientation="vertical">
+
+ <com.android.systemui.statusbar.phone.KeyguardIndicationTextView
+ android:id="@+id/keyguard_indication_enterprise_disclosure"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:paddingStart="@dimen/keyguard_indication_text_padding"
+ android:paddingEnd="@dimen/keyguard_indication_text_padding"
+ android:textAppearance="@style/TextAppearance.Keyguard.BottomArea"
+ android:visibility="gone"/>
+
+ <com.android.systemui.statusbar.phone.KeyguardIndicationTextView
+ android:id="@+id/keyguard_indication_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:paddingStart="@dimen/keyguard_indication_text_padding"
+ android:paddingEnd="@dimen/keyguard_indication_text_padding"
+ android:textAppearance="@style/TextAppearance.Keyguard.BottomArea"
+ android:accessibilityLiveRegion="polite"/>
+
+ </LinearLayout>
<FrameLayout
android:id="@+id/preview_container"
diff --git a/packages/SystemUI/res/layout/keyguard_indication_area_overlay.xml b/packages/SystemUI/res/layout/keyguard_indication_area_overlay.xml
deleted file mode 100644
index cc30a68..0000000
--- a/packages/SystemUI/res/layout/keyguard_indication_area_overlay.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/keyguard_indication_area"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginBottom="@dimen/keyguard_indication_margin_bottom"
- android:layout_gravity="bottom|center_horizontal"
- android:orientation="vertical">
-
- <include layout="@layout/keyguard_indication_text_view" />
-
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/keyguard_indication_text_view.xml b/packages/SystemUI/res/layout/keyguard_indication_text_view.xml
deleted file mode 100644
index 2b2100c..0000000
--- a/packages/SystemUI/res/layout/keyguard_indication_text_view.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
-
- <com.android.systemui.statusbar.phone.KeyguardIndicationTextView
- android:id="@+id/keyguard_indication_enterprise_disclosure"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:paddingStart="@dimen/keyguard_indication_text_padding"
- android:paddingEnd="@dimen/keyguard_indication_text_padding"
- android:textAppearance="@style/TextAppearance.Keyguard.BottomArea"
- android:visibility="gone"/>
-
- <com.android.systemui.statusbar.phone.KeyguardIndicationTextView
- android:id="@+id/keyguard_indication_text"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:paddingStart="@dimen/keyguard_indication_text_padding"
- android:paddingEnd="@dimen/keyguard_indication_text_padding"
- android:textAppearance="@style/TextAppearance.Keyguard.BottomArea"
- android:accessibilityLiveRegion="polite"/>
-</merge>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d58f83f..207fe26 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -892,8 +892,12 @@
<string name="quick_settings_secondary_label_until">Until <xliff:g id="time" example="7 am">%s</xliff:g></string>
<!-- QuickSettings: Label for the toggle to activate dark theme (A.K.A Dark Mode). [CHAR LIMIT=20] -->
<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 the dark theme tile when enabled by battery saver. [CHAR LIMIT=20] -->
+ <string name="quick_settings_dark_mode_secondary_label_battery_saver">Battery 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/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/doze/DozeWallpaperState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
index 35c8b74..b409129 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
@@ -16,6 +16,7 @@
package com.android.systemui.doze;
+import android.annotation.Nullable;
import android.app.IWallpaperManager;
import android.content.Context;
import android.os.RemoteException;
@@ -37,6 +38,7 @@
private static final String TAG = "DozeWallpaperState";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ @Nullable
private final IWallpaperManager mWallpaperManagerService;
private final DozeParameters mDozeParameters;
private final BiometricUnlockController mBiometricUnlockController;
@@ -88,16 +90,18 @@
if (isAmbientMode != mIsAmbientMode) {
mIsAmbientMode = isAmbientMode;
- try {
- long duration = animated ? StackStateAnimator.ANIMATION_DURATION_WAKEUP : 0L;
- if (DEBUG) {
- Log.i(TAG, "AOD wallpaper state changed to: " + mIsAmbientMode
+ if (mWallpaperManagerService != null) {
+ try {
+ long duration = animated ? StackStateAnimator.ANIMATION_DURATION_WAKEUP : 0L;
+ if (DEBUG) {
+ Log.i(TAG, "AOD wallpaper state changed to: " + mIsAmbientMode
+ ", animationDuration: " + duration);
+ }
+ mWallpaperManagerService.setInAmbientMode(mIsAmbientMode, duration);
+ } catch (RemoteException e) {
+ // Cannot notify wallpaper manager service, but it's fine, let's just skip it.
+ Log.w(TAG, "Cannot notify state to WallpaperManagerService: " + mIsAmbientMode);
}
- mWallpaperManagerService.setInAmbientMode(mIsAmbientMode, duration);
- } catch (RemoteException e) {
- // Cannot notify wallpaper manager service, but it's fine, let's just skip it.
- Log.w(TAG, "Cannot notify state to WallpaperManagerService: " + mIsAmbientMode);
}
}
}
@@ -106,5 +110,6 @@
public void dump(PrintWriter pw) {
pw.println("DozeWallpaperState:");
pw.println(" isAmbientMode: " + mIsAmbientMode);
+ pw.println(" hasWallpaperService: " + (mWallpaperManagerService != null));
}
}
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..107958f 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,34 @@
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 (powerSave) {
+ state.secondaryLabel = mContext.getResources().getString(
+ R.string.quick_settings_dark_mode_secondary_label_battery_saver);
+ } else 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.label = mContext.getString(R.string.quick_settings_ui_mode_night_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 {
@@ -112,7 +122,7 @@
@Override
public Intent getLongClickIntent() {
- return new Intent(Settings.ACTION_DISPLAY_SETTINGS);
+ return new Intent(Settings.ACTION_DARK_THEME_SETTINGS);
}
@Override
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/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index a37367e..da0f83d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -89,7 +89,6 @@
private NotificationRowBinder mNotificationRowBinder;
private NotificationPresenter mPresenter;
- private NotificationListenerService.RankingMap mLatestRankingMap;
@VisibleForTesting
protected NotificationData mNotificationData;
@@ -168,8 +167,7 @@
/** Adds a {@link NotificationLifetimeExtender}. */
public void addNotificationLifetimeExtender(NotificationLifetimeExtender extender) {
mNotificationLifetimeExtenders.add(extender);
- extender.setCallback(key -> removeNotification(key, mLatestRankingMap,
- UNDEFINED_DISMISS_REASON));
+ extender.setCallback(key -> removeNotification(key, null, UNDEFINED_DISMISS_REASON));
}
public NotificationData getNotificationData() {
@@ -307,7 +305,6 @@
if (!forceRemove && !entryDismissed) {
for (NotificationLifetimeExtender extender : mNotificationLifetimeExtenders) {
if (extender.shouldExtendLifetime(entry)) {
- mLatestRankingMap = ranking;
extendLifetime(entry, extender);
lifetimeExtended = true;
break;
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..7b4ed3f 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) {
@@ -201,6 +200,9 @@
removed = mEntries.remove(key);
}
if (removed == null) return null;
+ // NEM may pass us a null ranking map if removing a lifetime-extended notification,
+ // so use the most recent ranking
+ if (ranking == null) ranking = mRankingMap;
mGroupManager.onEntryRemoved(removed);
updateRankingAndSort(ranking);
return removed;
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/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/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/NavBarTintController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavBarTintController.java
index bfd17b9..68ea81d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavBarTintController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavBarTintController.java
@@ -107,6 +107,11 @@
requestUpdateSamplingListener();
}
+ void stopAndDestroy() {
+ stop();
+ mSamplingListener.destroy();
+ }
+
@Override
public void onViewAttachedToWindow(View view) {
requestUpdateSamplingListener();
@@ -114,8 +119,7 @@
@Override
public void onViewDetachedFromWindow(View view) {
- // Defer calling updateSamplingListener the attach info has not yet been reset
- requestUpdateSamplingListener();
+ stopAndDestroy();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index f5f2dd9..d3a56ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -137,6 +137,7 @@
private final MetricsLogger mMetricsLogger;
private final DeviceProvisionedController mDeviceProvisionedController;
private final StatusBarStateController mStatusBarStateController;
+ private final NavigationModeController mNavigationModeController;
protected NavigationBarView mNavigationBarView = null;
@@ -253,6 +254,7 @@
mAssistManager = assistManager;
mAssistantAvailable = mAssistManager.getAssistInfoForUser(UserHandle.USER_CURRENT) != null;
mOverviewProxyService = overviewProxyService;
+ mNavigationModeController = navigationModeController;
mNavBarMode = navigationModeController.addListener(this);
}
@@ -292,6 +294,7 @@
@Override
public void onDestroy() {
super.onDestroy();
+ mNavigationModeController.removeListener(this);
mAccessibilityManagerWrapper.removeCallback(mAccessibilityListener);
mContentResolver.unregisterContentObserver(mMagnificationObserver);
mContentResolver.unregisterContentObserver(mAssistContentObserver);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 17c200e..a0e7d02 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -379,8 +379,11 @@
mContext.getString(R.string.accessibility_quick_settings_bluetooth_on);
boolean bluetoothVisible = false;
if (mBluetooth != null) {
- if (mBluetooth.isBluetoothConnected()) {
- contentDescription = mContext.getString(R.string.accessibility_bluetooth_connected);
+ if (mBluetooth.isBluetoothConnected()
+ && (mBluetooth.isBluetoothAudioActive()
+ || !mBluetooth.isBluetoothAudioProfileOnly())) {
+ contentDescription = mContext.getString(
+ R.string.accessibility_bluetooth_connected);
bluetoothVisible = mBluetooth.isBluetoothEnabled();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java
index 2b0bb21..26cc74d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java
@@ -127,6 +127,11 @@
updateSamplingListener();
}
+ void stopAndDestroy() {
+ stop();
+ mSamplingListener.destroy();
+ }
+
@Override
public void onViewAttachedToWindow(View view) {
updateSamplingListener();
@@ -134,9 +139,7 @@
@Override
public void onViewDetachedFromWindow(View view) {
- // isAttachedToWindow is only changed after this call to the listeners, so let's post it
- // instead
- postUpdateSamplingListener();
+ stopAndDestroy();
}
@Override
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..c6dee5e 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,
@@ -707,6 +694,8 @@
mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
mFalsingManager = Dependency.get(FalsingManager.class);
+ mWallpaperSupported =
+ mContext.getSystemService(WallpaperManager.class).isWallpaperSupported();
// Connect in to the status bar manager service
mCommandQueue = getComponent(CommandQueue.class);
@@ -721,11 +710,15 @@
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);
+ 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 +753,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 +891,7 @@
createNavigationBar(result);
- if (ENABLE_LOCKSCREEN_WALLPAPER) {
+ if (ENABLE_LOCKSCREEN_WALLPAPER && mWallpaperSupported) {
mLockscreenWallpaper = new LockscreenWallpaper(mContext, this, mHandler);
}
@@ -910,20 +905,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 +962,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 +1062,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 +1094,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 +1981,9 @@
mStatusBarWindow.cancelExpandHelper();
mStatusBarView.collapsePanel(true /* animate */, delayed, speedUpFactor);
} else {
- mBubbleController.collapseStack();
+ if (mBubbleController != null) {
+ mBubbleController.collapseStack();
+ }
}
}
@@ -2318,6 +2365,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 +2638,7 @@
if (mRemoteInputManager.getController() != null) {
mRemoteInputManager.getController().closeRemoteInputs();
}
- if (mBubbleController.isStackExpanded()) {
+ if (mBubbleController != null && mBubbleController.isStackExpanded()) {
mBubbleController.collapseStack();
}
if (mLockscreenUserManager.isCurrentProfile(getSendingUserId())) {
@@ -2606,7 +2654,7 @@
if (mStatusBarWindowController != null) {
mStatusBarWindowController.setNotTouchable(false);
}
- if (mBubbleController.isStackExpanded()) {
+ if (mBubbleController != null && mBubbleController.isStackExpanded()) {
mBubbleController.collapseStack();
}
finishBarAnimations();
@@ -2700,9 +2748,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 +3036,9 @@
mStatusBarStateController.setLeaveOpenOnKeyguardHide(false);
mPendingRemoteInputView = null;
updateIsKeyguard();
- mAssistManager.onLockscreenShown();
+ if (mAssistManager != null) {
+ mAssistManager.onLockscreenShown();
+ }
}
public boolean hideKeyguard() {
@@ -3000,10 +3054,21 @@
return mState == StatusBarState.FULLSCREEN_USER_SWITCHER;
}
+ private boolean isAutomotive() {
+ return mContext != null
+ && mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+ }
+
private boolean updateIsKeyguard() {
boolean wakeAndUnlocking = mBiometricUnlockController.getMode()
== BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
+ if (mScreenLifecycle == null && isAutomotive()) {
+ // TODO(b/146144370): workaround to avoid NPE when device goes into STR (Suspend to RAM)
+ Log.w(TAG, "updateIsKeyguard(): mScreenLifeCycle not set yet");
+ mScreenLifecycle = Dependency.get(ScreenLifecycle.class);
+ }
+
// For dozing, keyguard needs to be shown whenever the device is non-interactive. Otherwise
// there's no surface we can show to the user. Note that the device goes fully interactive
// late in the transition, so we also allow the device to start dozing once the screen has
@@ -3030,7 +3095,7 @@
public void showKeyguardImpl() {
mIsKeyguard = true;
- if (mKeyguardMonitor.isLaunchTransitionFadingAway()) {
+ if (mKeyguardMonitor != null && mKeyguardMonitor.isLaunchTransitionFadingAway()) {
mNotificationPanel.animate().cancel();
onLaunchTransitionFadingEnded();
}
@@ -3334,7 +3399,9 @@
if (mNotificationPanel.canPanelBeCollapsed()) {
animateCollapsePanels();
} else {
- mBubbleController.performBackPressIfNeeded();
+ if (mBubbleController != null) {
+ mBubbleController.performBackPressIfNeeded();
+ }
}
return true;
}
@@ -3495,9 +3562,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 +3955,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 +3984,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 +4286,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/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/BluetoothController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
index 42e02d5..0c5b851 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
@@ -31,6 +31,8 @@
boolean isBluetoothConnected();
boolean isBluetoothConnecting();
+ boolean isBluetoothAudioProfileOnly();
+ boolean isBluetoothAudioActive();
String getConnectedDeviceName();
void setBluetoothEnabled(boolean enabled);
Collection<CachedBluetoothDevice> getDevices();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 78e845a..351579a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -34,6 +34,7 @@
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfile;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import java.io.FileDescriptor;
@@ -66,6 +67,8 @@
private boolean mEnabled;
private int mConnectionState = BluetoothAdapter.STATE_DISCONNECTED;
+ private boolean mAudioProfileOnly;
+ private boolean mIsActive;
private final H mHandler = new H(Looper.getMainLooper());
private int mState;
@@ -103,6 +106,8 @@
}
pw.print(" mEnabled="); pw.println(mEnabled);
pw.print(" mConnectionState="); pw.println(stateToString(mConnectionState));
+ pw.print(" mAudioProfileOnly="); pw.println(mAudioProfileOnly);
+ pw.print(" mIsActive="); pw.println(mIsActive);
pw.print(" mConnectedDevices="); pw.println(mConnectedDevices);
pw.print(" mCallbacks.size="); pw.println(mHandler.mCallbacks.size());
pw.println(" Bluetooth Devices:");
@@ -176,6 +181,16 @@
}
@Override
+ public boolean isBluetoothAudioProfileOnly() {
+ return mAudioProfileOnly;
+ }
+
+ @Override
+ public boolean isBluetoothAudioActive() {
+ return mIsActive;
+ }
+
+ @Override
public void setBluetoothEnabled(boolean enabled) {
if (mLocalBluetoothManager != null) {
mLocalBluetoothManager.getBluetoothAdapter().setBluetoothEnabled(enabled);
@@ -239,6 +254,48 @@
mConnectionState = state;
mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
}
+ updateAudioProfile();
+ }
+
+ private void updateActive() {
+ boolean isActive = false;
+
+ for (CachedBluetoothDevice device : getDevices()) {
+ isActive |= device.isActiveDevice(BluetoothProfile.HEADSET)
+ || device.isActiveDevice(BluetoothProfile.A2DP)
+ || device.isActiveDevice(BluetoothProfile.HEARING_AID);
+ }
+
+ if (mIsActive != isActive) {
+ mIsActive = isActive;
+ mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
+ }
+ }
+
+ private void updateAudioProfile() {
+ boolean audioProfileConnected = false;
+ boolean otherProfileConnected = false;
+
+ for (CachedBluetoothDevice device : getDevices()) {
+ for (LocalBluetoothProfile profile : device.getProfiles()) {
+ int profileId = profile.getProfileId();
+ boolean isConnected = device.isConnectedProfile(profile);
+ if (profileId == BluetoothProfile.HEADSET
+ || profileId == BluetoothProfile.A2DP
+ || profileId == BluetoothProfile.HEARING_AID) {
+ audioProfileConnected |= isConnected;
+ } else {
+ otherProfileConnected |= isConnected;
+ }
+ }
+ }
+
+ boolean audioProfileOnly = (audioProfileConnected && !otherProfileConnected);
+ if (audioProfileOnly != mAudioProfileOnly) {
+ mAudioProfileOnly = audioProfileOnly;
+ mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
+ }
+
}
@Override
@@ -294,6 +351,16 @@
}
@Override
+ public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) {
+ if (DEBUG) {
+ Log.d(TAG, "ActiveDeviceChanged=" + activeDevice.getAddress()
+ + " profileId=" + bluetoothProfile);
+ }
+ updateActive();
+ mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
+ }
+
+ @Override
public void onAclConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
if (DEBUG) {
Log.d(TAG, "ACLConnectionStateChanged=" + cachedDevice.getAddress() + " "
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/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/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/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
index 766ad97..7fa094b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
@@ -39,6 +39,7 @@
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfile;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.systemui.SysuiTestCase;
@@ -229,4 +230,28 @@
assertTrue(mBluetoothControllerImpl.isBluetoothConnected());
verify(callback, atLeastOnce()).onBluetoothStateChange(anyBoolean());
}
+
+ @Test
+ public void testOnActiveDeviceChanged_updatesAudioActive() {
+ assertFalse(mBluetoothControllerImpl.isBluetoothAudioActive());
+ assertFalse(mBluetoothControllerImpl.isBluetoothAudioProfileOnly());
+
+ CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
+ mDevices.add(device);
+ when(device.isActiveDevice(BluetoothProfile.HEADSET)).thenReturn(true);
+
+ List<LocalBluetoothProfile> profiles = new ArrayList<>();
+ LocalBluetoothProfile profile = mock(LocalBluetoothProfile.class);
+ profiles.add(profile);
+ when(profile.getProfileId()).thenReturn(BluetoothProfile.HEADSET);
+ when(device.getProfiles()).thenReturn(profiles);
+ when(device.isConnectedProfile(profile)).thenReturn(true);
+
+ mBluetoothControllerImpl.onAclConnectionStateChanged(device,
+ BluetoothProfile.STATE_CONNECTED);
+ mBluetoothControllerImpl.onActiveDeviceChanged(device, BluetoothProfile.HEADSET);
+
+ assertTrue(mBluetoothControllerImpl.isBluetoothAudioActive());
+ assertTrue(mBluetoothControllerImpl.isBluetoothAudioProfileOnly());
+ }
}
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/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
index cac6bf7..6cbd175 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
@@ -57,6 +57,16 @@
}
@Override
+ public boolean isBluetoothAudioProfileOnly() {
+ return false;
+ }
+
+ @Override
+ public boolean isBluetoothAudioActive() {
+ return false;
+ }
+
+ @Override
public String getConnectedDeviceName() {
return null;
}
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/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 87991be..ce2bc82 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -18,6 +18,7 @@
import static android.service.autofill.AutofillFieldClassificationService.EXTRA_SCORES;
import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
+import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE;
import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
import static android.view.autofill.AutofillManager.ACTION_START_SESSION;
import static android.view.autofill.AutofillManager.ACTION_VALUE_CHANGED;
@@ -581,7 +582,7 @@
+ ", flags=" + flags + ")");
}
mForAugmentedAutofillOnly = true;
- triggerAugmentedAutofillLocked();
+ triggerAugmentedAutofillLocked(flags);
return;
}
viewState.setState(newState);
@@ -780,7 +781,7 @@
id, mCompatMode);
}
// Although "standard" autofill is disabled, it might still trigger augmented autofill
- if (triggerAugmentedAutofillLocked() != null) {
+ if (triggerAugmentedAutofillLocked(requestFlags) != null) {
mForAugmentedAutofillOnly = true;
if (sDebug) {
Slog.d(TAG, "Service disabled autofill for " + mComponentName
@@ -2424,7 +2425,7 @@
if (sDebug) Slog.d(TAG, "updateLocked(" + id + "): augmented-autofillable");
// ...then trigger the augmented autofill UI
- triggerAugmentedAutofillLocked();
+ triggerAugmentedAutofillLocked(flags);
return;
}
@@ -2688,8 +2689,8 @@
// The default autofill service cannot fullfill the request, let's check if the augmented
// autofill service can.
- mAugmentedAutofillDestroyer = triggerAugmentedAutofillLocked();
- if (mAugmentedAutofillDestroyer == null) {
+ mAugmentedAutofillDestroyer = triggerAugmentedAutofillLocked(flags);
+ if (mAugmentedAutofillDestroyer == null && ((flags & FLAG_PASSWORD_INPUT_TYPE) == 0)) {
if (sVerbose) {
Slog.v(TAG, "canceling session " + id + " when service returned null and it cannot "
+ "be augmented. AutofillableIds: " + autofillableIds);
@@ -2699,8 +2700,14 @@
removeSelf();
} else {
if (sVerbose) {
- Slog.v(TAG, "keeping session " + id + " when service returned null but "
- + "it can be augmented. AutofillableIds: " + autofillableIds);
+ if ((flags & FLAG_PASSWORD_INPUT_TYPE) != 0) {
+ Slog.v(TAG, "keeping session " + id + " when service returned null and "
+ + "augmented service is disabled for password fields. "
+ + "AutofillableIds: " + autofillableIds);
+ } else {
+ Slog.v(TAG, "keeping session " + id + " when service returned null but "
+ + "it can be augmented. AutofillableIds: " + autofillableIds);
+ }
}
mAugmentedAutofillableIds = autofillableIds;
try {
@@ -2719,7 +2726,12 @@
// TODO(b/123099468): might need to call it in other places, like when the service returns a
// non-null response but without datasets (for example, just SaveInfo)
@GuardedBy("mLock")
- private Runnable triggerAugmentedAutofillLocked() {
+ private Runnable triggerAugmentedAutofillLocked(int flags) {
+ // (TODO: b/141703197) Fix later by passing info to service.
+ if ((flags & FLAG_PASSWORD_INPUT_TYPE) != 0) {
+ return null;
+ }
+
// Check if Smart Suggestions is supported...
final @SmartSuggestionMode int supportedModes = mService
.getSupportedSmartSuggestionModesLocked();
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..1fd4a0c 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,30 @@
}
};
+ /** AlarmListener to start monitoring motion if there are registered stationary listeners. */
+ private final AlarmManager.OnAlarmListener mMotionRegistrationAlarmListener = () -> {
+ synchronized (DeviceIdleController.this) {
+ if (mStationaryListeners.size() > 0) {
+ startMonitoringMotionLocked();
+ }
+ }
+ };
+
+ 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 +686,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;
}
@@ -694,6 +784,7 @@
}
if (success) {
active = true;
+ activatedTimeElapsed = mInjector.getElapsedRealtime();
} else {
Slog.e(TAG, "Unable to register for " + mMotionSensor);
}
@@ -1307,6 +1398,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 +1536,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 +1745,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 +1823,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 +1882,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);
@@ -1745,6 +1898,27 @@
return controller.new MyHandler(BackgroundThread.getHandler().getLooper());
}
+ Sensor getMotionSensor() {
+ final SensorManager sensorManager = getSensorManager();
+ Sensor motionSensor = null;
+ int sigMotionSensorId = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_autoPowerModeAnyMotionSensor);
+ if (sigMotionSensorId > 0) {
+ motionSensor = sensorManager.getDefaultSensor(sigMotionSensorId, true);
+ }
+ if (motionSensor == null && mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_autoPowerModePreferWristTilt)) {
+ motionSensor = sensorManager.getDefaultSensor(
+ Sensor.TYPE_WRIST_TILT_GESTURE, true);
+ }
+ if (motionSensor == null) {
+ // As a last ditch, fall back to SMD.
+ motionSensor = sensorManager.getDefaultSensor(
+ Sensor.TYPE_SIGNIFICANT_MOTION, true);
+ }
+ return motionSensor;
+ }
+
PowerManager getPowerManager() {
return mContext.getSystemService(PowerManager.class);
}
@@ -1896,21 +2070,7 @@
mSensorManager = mInjector.getSensorManager();
if (mUseMotionSensor) {
- int sigMotionSensorId = getContext().getResources().getInteger(
- com.android.internal.R.integer.config_autoPowerModeAnyMotionSensor);
- if (sigMotionSensorId > 0) {
- mMotionSensor = mSensorManager.getDefaultSensor(sigMotionSensorId, true);
- }
- if (mMotionSensor == null && getContext().getResources().getBoolean(
- com.android.internal.R.bool.config_autoPowerModePreferWristTilt)) {
- mMotionSensor = mSensorManager.getDefaultSensor(
- Sensor.TYPE_WRIST_TILT_GESTURE, true);
- }
- if (mMotionSensor == null) {
- // As a last ditch, fall back to SMD.
- mMotionSensor = mSensorManager.getDefaultSensor(
- Sensor.TYPE_SIGNIFICANT_MOTION, true);
- }
+ mMotionSensor = mInjector.getMotionSensor();
}
if (getContext().getResources().getBoolean(
@@ -2601,6 +2761,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 +2929,11 @@
mNextIdleDelay = 0;
mNextLightIdleDelay = 0;
mIdleStartTime = 0;
+ mQuickDozeActivatedWhileIdling = false;
cancelAlarmLocked();
cancelSensingTimeoutAlarmLocked();
cancelLocatingLocked();
- stopMonitoringMotionLocked();
+ maybeStopMonitoringMotionLocked();
mAnyMotionDetector.stop();
updateActiveConstraintsLocked();
}
@@ -3270,11 +3433,27 @@
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();
+ // We need to re-register the motion listener, but we don't want the sensors to be
+ // constantly active or to churn the CPU by registering too early, register after some
+ // delay.
+ scheduleMotionRegistrationAlarmLocked();
+ }
+ 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 +3505,18 @@
}
}
- void stopMonitoringMotionLocked() {
- if (DEBUG) Slog.d(TAG, "stopMonitoringMotionLocked()");
- if (mMotionSensor != null && mMotionListener.active) {
- mMotionListener.unregisterLocked();
+ /**
+ * 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 && mStationaryListeners.size() == 0) {
+ if (mMotionListener.active) {
+ mMotionListener.unregisterLocked();
+ cancelMotionTimeoutAlarmLocked();
+ }
+ cancelMotionRegistrationAlarmLocked();
}
}
@@ -3356,6 +3543,14 @@
}
}
+ private void cancelMotionTimeoutAlarmLocked() {
+ mAlarmManager.cancel(mMotionTimeoutAlarmListener);
+ }
+
+ private void cancelMotionRegistrationAlarmLocked() {
+ mAlarmManager.cancel(mMotionRegistrationAlarmListener);
+ }
+
void cancelSensingTimeoutAlarmLocked() {
if (mNextSensingTimeoutAlarmTime != 0) {
mNextSensingTimeoutAlarmTime = 0;
@@ -3402,6 +3597,23 @@
mNextLightAlarmTime, "DeviceIdleController.light", mLightAlarmListener, mHandler);
}
+ private void scheduleMotionRegistrationAlarmLocked() {
+ if (DEBUG) Slog.d(TAG, "scheduleMotionRegistrationAlarmLocked");
+ long nextMotionRegistrationAlarmTime =
+ mInjector.getElapsedRealtime() + mConstants.MOTION_INACTIVE_TIMEOUT / 2;
+ mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextMotionRegistrationAlarmTime,
+ "DeviceIdleController.motion_registration", mMotionRegistrationAlarmListener,
+ 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 +4534,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/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index ea3dd3d..fc140a2 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -56,6 +56,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
@@ -80,6 +81,22 @@
static final String PROPERTY_WATCHDOG_EXPLICIT_HEALTH_CHECK_ENABLED =
"watchdog_explicit_health_check_enabled";
+ public static final int FAILURE_REASON_UNKNOWN = 0;
+ public static final int FAILURE_REASON_NATIVE_CRASH = 1;
+ public static final int FAILURE_REASON_EXPLICIT_HEALTH_CHECK = 2;
+ public static final int FAILURE_REASON_APP_CRASH = 3;
+ public static final int FAILURE_REASON_APP_NOT_RESPONDING = 4;
+
+ @IntDef(prefix = { "FAILURE_REASON_" }, value = {
+ FAILURE_REASON_UNKNOWN,
+ FAILURE_REASON_NATIVE_CRASH,
+ FAILURE_REASON_EXPLICIT_HEALTH_CHECK,
+ FAILURE_REASON_APP_CRASH,
+ FAILURE_REASON_APP_NOT_RESPONDING
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FailureReasons {}
+
// Duration to count package failures before it resets to 0
private static final int DEFAULT_TRIGGER_FAILURE_DURATION_MS =
(int) TimeUnit.MINUTES.toMillis(1);
@@ -295,14 +312,15 @@
}
/**
- * Called when a process fails either due to a crash or ANR.
+ * Called when a process fails due to a crash, ANR or explicit health check.
*
* <p>For each package contained in the process, one registered observer with the least user
* impact will be notified for mitigation.
*
* <p>This method could be called frequently if there is a severe problem on the device.
*/
- public void onPackageFailure(List<VersionedPackage> packages) {
+ public void onPackageFailure(List<VersionedPackage> packages,
+ @FailureReasons int failureReason) {
mLongTaskHandler.post(() -> {
synchronized (mLock) {
if (mAllObservers.isEmpty()) {
@@ -333,7 +351,7 @@
// Execute action with least user impact
if (currentObserverToNotify != null) {
- currentObserverToNotify.execute(versionedPackage);
+ currentObserverToNotify.execute(versionedPackage, failureReason);
}
}
}
@@ -404,7 +422,7 @@
*
* @return {@code true} if action was executed successfully, {@code false} otherwise
*/
- boolean execute(VersionedPackage versionedPackage);
+ boolean execute(VersionedPackage versionedPackage, @FailureReasons int failureReason);
// TODO(b/120598832): Ensure uniqueness?
/**
@@ -648,7 +666,8 @@
// the tests don't install any packages
versionedPkg = new VersionedPackage(failedPackage, 0L);
}
- registeredObserver.execute(versionedPkg);
+ registeredObserver.execute(versionedPkg,
+ PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
}
}
}
@@ -759,7 +778,7 @@
final List<VersionedPackage> pkgList = Collections.singletonList(pkg);
final long failureCount = getTriggerFailureCount();
for (int i = 0; i < failureCount; i++) {
- onPackageFailure(pkgList);
+ onPackageFailure(pkgList, FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
}
});
}
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 9d71896..7ad04728 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -58,6 +58,7 @@
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 +66,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;
@@ -85,15 +87,24 @@
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;
private boolean mCharging = false;
private boolean mPowerSave = false;
+ // Do not change configuration now. wait until screen turns off.
+ // This prevents jank and activity restart when the user
+ // is actively using the device
+ private boolean mWaitForScreenOff = false;
private int mDefaultUiModeType;
private boolean mCarModeKeepsScreenOn;
private boolean mDeskModeKeepsScreenOn;
private boolean mTelevision;
+ private boolean mCar;
private boolean mWatch;
private boolean mVrHeadset;
private boolean mComputedNightMode;
@@ -119,6 +130,7 @@
private TwilightManager mTwilightManager;
private NotificationManager mNotificationManager;
private StatusBarManager mStatusBarManager;
+ private WindowManagerInternal mWindowManager;
private PowerManager.WakeLock mWakeLock;
@@ -128,6 +140,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,13 +212,31 @@
public void onTwilightStateChanged(@Nullable TwilightState state) {
synchronized (mLock) {
if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
- updateComputedNightModeLocked();
- updateLocked(0, 0);
+ if (mCar) {
+ updateLocked(0, 0);
+ } else {
+ registerScreenOffEvent();
+ }
}
}
}
};
+ /**
+ * DO NOT USE DIRECTLY
+ * see register registerScreenOffEvent and unregisterScreenOffEvent
+ */
+ private final BroadcastReceiver mOnScreenOffHandler = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ synchronized (mLock) {
+ // must unregister first before updating
+ unregisterScreenOffEvent();
+ updateLocked(0, 0);
+ }
+ }
+ };
+
private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
@Override
public void onVrStateChanged(boolean enabled) {
@@ -227,8 +268,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 : mode;
SystemProperties.set(SYSTEM_PROPERTY_DEVICE_THEME, Integer.toString(mode));
}
};
@@ -247,6 +290,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
@@ -290,6 +334,7 @@
final PackageManager pm = context.getPackageManager();
mTelevision = pm.hasSystemFeature(PackageManager.FEATURE_TELEVISION)
|| pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK);
+ mCar = pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
mWatch = pm.hasSystemFeature(PackageManager.FEATURE_WATCH);
updateNightModeFromSettings(context, res, UserHandle.getCallingUserId());
@@ -298,7 +343,7 @@
SystemServerInitThreadPool.get().submit(() -> {
synchronized (mLock) {
updateConfigurationLocked();
- sendConfigurationLocked();
+ applyConfigurationExternallyLocked();
}
}, TAG + ".onStart");
@@ -313,6 +358,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,13 +402,32 @@
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;
}
+ private void registerScreenOffEvent() {
+ mWaitForScreenOff = true;
+ final IntentFilter intentFilter =
+ new IntentFilter(Intent.ACTION_SCREEN_OFF);
+ getContext().registerReceiver(mOnScreenOffHandler, intentFilter);
+ }
+
+ private void unregisterScreenOffEvent() {
+ mWaitForScreenOff = false;
+ try {
+ getContext().unregisterReceiver(mOnScreenOffHandler);
+ } catch (IllegalArgumentException e) {
+ // we ignore this exception if the receiver is unregistered already.
+ }
+ }
+
private final IUiModeManager.Stub mService = new IUiModeManager.Stub() {
@Override
public void enableCarMode(@UiModeManager.EnableCarMode int flags,
@@ -474,14 +548,23 @@
try {
synchronized (mLock) {
if (mNightMode != mode) {
- // Only persist setting if not in car mode
- if (!mCarModeEnabled) {
- Secure.putIntForUser(getContext().getContentResolver(),
- Secure.UI_NIGHT_MODE, mode, user);
+ if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
+ unregisterScreenOffEvent();
}
mNightMode = mode;
- updateLocked(0, 0);
+ mNightModeOverride = mode;
+
+ // Only persist setting if not in car mode
+ if (!mCarModeEnabled) {
+ persistNightMode(user);
+ }
+ // on screen off will update configuration instead
+ if (mNightMode != UiModeManager.MODE_NIGHT_AUTO || mCar) {
+ updateLocked(0, 0);
+ } else {
+ registerScreenOffEvent();
+ }
}
}
} finally {
@@ -521,6 +604,33 @@
if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
dumpImpl(pw);
}
+
+ @Override
+ public boolean setNightModeActivated(boolean active) {
+ synchronized (mLock) {
+ final int user = UserHandle.getCallingUserId();
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
+ unregisterScreenOffEvent();
+ 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();
+ applyConfigurationExternallyLocked();
+ persistNightMode(user);
+ return true;
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
};
void dumpImpl(PrintWriter pw) {
@@ -724,6 +834,13 @@
}
}
+ private void persistNightMode(int user) {
+ Secure.putIntForUser(getContext().getContentResolver(),
+ Secure.UI_NIGHT_MODE, mNightMode, user);
+ Secure.putIntForUser(getContext().getContentResolver(),
+ OVERRIDE_NIGHT_MODE, mNightModeOverride, user);
+ }
+
private void updateConfigurationLocked() {
int uiMode = mDefaultUiModeType;
if (mUiModeLocked) {
@@ -769,15 +886,14 @@
}
mCurUiMode = uiMode;
- if (!mHoldingConfiguration) {
+ if (!mHoldingConfiguration || !mWaitForScreenOff) {
mConfiguration.uiMode = uiMode;
}
}
- private void sendConfigurationLocked() {
+ private void applyConfigurationExternallyLocked() {
if (mSetUiMode != mConfiguration.uiMode) {
mSetUiMode = mConfiguration.uiMode;
-
try {
ActivityTaskManager.getService().updateConfiguration(mConfiguration);
} catch (RemoteException e) {
@@ -957,7 +1073,7 @@
}
// Send the new configuration.
- sendConfigurationLocked();
+ applyConfigurationExternallyLocked();
// If we did not start a dock app, then start dreaming if supported.
if (category != null && !dockAppStarted) {
@@ -1022,6 +1138,19 @@
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 2af04ae..d7b7165 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -18055,7 +18055,7 @@
@Override
public int getCurrentUserId() {
- return mUserController.getCurrentUserIdLU();
+ return mUserController.getCurrentUserId();
}
@Override
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index 1ff6f4d..a4c6950 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -430,7 +430,8 @@
RescueParty.noteAppCrash(mContext, r.uid);
}
- mPackageWatchdog.onPackageFailure(r.getPackageListWithVersionCode());
+ mPackageWatchdog.onPackageFailure(r.getPackageListWithVersionCode(),
+ PackageWatchdog.FAILURE_REASON_APP_CRASH);
}
final int relaunchReason = r != null
@@ -884,7 +885,8 @@
}
// Notify PackageWatchdog without the lock held
if (packageList != null) {
- mPackageWatchdog.onPackageFailure(packageList);
+ mPackageWatchdog.onPackageFailure(packageList,
+ PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING);
}
}
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..7cc2e8eb 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/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/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 25fdf64..e17a610 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -2814,6 +2814,7 @@
}
public void setMasterMute(boolean mute, int flags, String callingPackage, int userId) {
+ enforceModifyAudioRoutingPermission();
setMasterMuteInternal(mute, flags, callingPackage, Binder.getCallingUid(),
userId);
}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
index 85ca627..2d96d89 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
@@ -233,18 +233,24 @@
}
private void fanoutAidlCallbackLocked(AidlCallbackRunnable runnable) {
+ List<TunerSession> deadSessions = null;
for (TunerSession tunerSession : mAidlTunerSessions) {
try {
runnable.run(tunerSession.mCallback);
} catch (DeadObjectException ex) {
- // The other side died without calling close(), so just purge it from our
- // records.
+ // The other side died without calling close(), so just purge it from our records.
Slog.e(TAG, "Removing dead TunerSession");
- mAidlTunerSessions.remove(tunerSession);
+ if (deadSessions == null) {
+ deadSessions = new ArrayList<>();
+ }
+ deadSessions.add(tunerSession);
} catch (RemoteException ex) {
Slog.e(TAG, "Failed to invoke ITunerCallback: ", ex);
}
}
+ if (deadSessions != null) {
+ mAidlTunerSessions.removeAll(deadSessions);
+ }
}
public android.hardware.radio.ICloseHandle addAnnouncementListener(@NonNull int[] enabledTypes,
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 31632dc..177e2d8 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -41,6 +41,7 @@
import android.util.Slog;
import android.util.TimeUtils;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
import com.android.server.EventLogTags;
@@ -215,7 +216,9 @@
private IActivityTaskManager mActivityTaskManager;
private PackageManager mPackageManager;
- public AutomaticBrightnessController(Callbacks callbacks, Looper looper,
+ private final Injector mInjector;
+
+ AutomaticBrightnessController(Callbacks callbacks, Looper looper,
SensorManager sensorManager, Sensor lightSensor, BrightnessMappingStrategy mapper,
int lightSensorWarmUpTime, int brightnessMin, int brightnessMax, float dozeScaleFactor,
int lightSensorRate, int initialLightSensorRate, long brighteningLightDebounceConfig,
@@ -223,6 +226,24 @@
HysteresisLevels ambientBrightnessThresholds,
HysteresisLevels screenBrightnessThresholds, long shortTermModelTimeout,
PackageManager packageManager) {
+ this(new Injector(), callbacks, looper, sensorManager, lightSensor, mapper,
+ lightSensorWarmUpTime, brightnessMin, brightnessMax, dozeScaleFactor,
+ lightSensorRate, initialLightSensorRate, brighteningLightDebounceConfig,
+ darkeningLightDebounceConfig, resetAmbientLuxAfterWarmUpConfig,
+ ambientBrightnessThresholds, screenBrightnessThresholds, shortTermModelTimeout,
+ packageManager);
+ }
+
+ @VisibleForTesting
+ AutomaticBrightnessController(Injector injector, Callbacks callbacks, Looper looper,
+ SensorManager sensorManager, Sensor lightSensor, BrightnessMappingStrategy mapper,
+ int lightSensorWarmUpTime, int brightnessMin, int brightnessMax, float dozeScaleFactor,
+ int lightSensorRate, int initialLightSensorRate, long brighteningLightDebounceConfig,
+ long darkeningLightDebounceConfig, boolean resetAmbientLuxAfterWarmUpConfig,
+ HysteresisLevels ambientBrightnessThresholds,
+ HysteresisLevels screenBrightnessThresholds, long shortTermModelTimeout,
+ PackageManager packageManager) {
+ mInjector = injector;
mCallbacks = callbacks;
mSensorManager = sensorManager;
mBrightnessMapper = mapper;
@@ -725,8 +746,8 @@
float value = mBrightnessMapper.getBrightness(mAmbientLux, mForegroundAppPackageName,
mForegroundAppCategory);
- int newScreenAutoBrightness =
- clampScreenBrightness(Math.round(value * PowerManager.BRIGHTNESS_ON));
+ int newScreenAutoBrightness = Math.round(clampScreenBrightness(
+ value * PowerManager.BRIGHTNESS_ON));
// If screenAutoBrightness is set, we should have screen{Brightening,Darkening}Threshold,
// in which case we ignore the new screen brightness if it doesn't differ enough from the
@@ -750,10 +771,10 @@
}
mScreenAutoBrightness = newScreenAutoBrightness;
- mScreenBrighteningThreshold =
- mScreenBrightnessThresholds.getBrighteningThreshold(newScreenAutoBrightness);
- mScreenDarkeningThreshold =
- mScreenBrightnessThresholds.getDarkeningThreshold(newScreenAutoBrightness);
+ mScreenBrighteningThreshold = clampScreenBrightness(
+ mScreenBrightnessThresholds.getBrighteningThreshold(newScreenAutoBrightness));
+ mScreenDarkeningThreshold = clampScreenBrightness(
+ mScreenBrightnessThresholds.getDarkeningThreshold(newScreenAutoBrightness));
if (sendUpdate) {
mCallbacks.updateBrightness();
@@ -761,7 +782,7 @@
}
}
- private int clampScreenBrightness(int value) {
+ private float clampScreenBrightness(float value) {
return MathUtils.constrain(value,
mScreenBrightnessRangeMinimum, mScreenBrightnessRangeMaximum);
}
@@ -839,7 +860,7 @@
}
// The ActivityTaskManager's lock tends to get contended, so this is done in a background
// thread and applied via this thread's handler synchronously.
- BackgroundThread.getHandler().post(new Runnable() {
+ mInjector.getBackgroundThreadHandler().post(new Runnable() {
public void run() {
try {
// The foreground app is the top activity of the focused tasks stack.
@@ -965,6 +986,9 @@
private int mCount;
public AmbientLightRingBuffer(long lightSensorRate, int ambientLightHorizon) {
+ if (lightSensorRate <= 0) {
+ throw new IllegalArgumentException("lightSensorRate must be above 0");
+ }
mCapacity = (int) Math.ceil(ambientLightHorizon * BUFFER_SLACK / lightSensorRate);
mRingLux = new float[mCapacity];
mRingTime = new long[mCapacity];
@@ -1076,4 +1100,10 @@
return index;
}
}
+
+ public static class Injector {
+ public Handler getBackgroundThreadHandler() {
+ return BackgroundThread.getHandler();
+ }
+ }
}
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/HysteresisLevels.java b/services/core/java/com/android/server/display/HysteresisLevels.java
index 2db1d03..f0a505d 100644
--- a/services/core/java/com/android/server/display/HysteresisLevels.java
+++ b/services/core/java/com/android/server/display/HysteresisLevels.java
@@ -18,13 +18,16 @@
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.io.PrintWriter;
import java.util.Arrays;
/**
* A helper class for handling access to illuminance hysteresis level values.
*/
-final class HysteresisLevels {
+@VisibleForTesting
+public class HysteresisLevels {
private static final String TAG = "HysteresisLevels";
// Default hysteresis constraints for brightening or darkening.
@@ -60,7 +63,7 @@
/**
* Return the brightening hysteresis threshold for the given value level.
*/
- float getBrighteningThreshold(float value) {
+ public float getBrighteningThreshold(float value) {
float brightConstant = getReferenceLevel(value, mBrighteningThresholds);
float brightThreshold = value * (1.0f + brightConstant);
if (DEBUG) {
@@ -73,7 +76,7 @@
/**
* Return the darkening hysteresis threshold for the given value level.
*/
- float getDarkeningThreshold(float value) {
+ public float getDarkeningThreshold(float value) {
float darkConstant = getReferenceLevel(value, mDarkeningThresholds);
float darkThreshold = value * (1.0f - darkConstant);
if (DEBUG) {
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/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index b338705..7e6e668 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -221,6 +221,8 @@
private static native void nativeSetFocusedApplication(long ptr,
int displayId, InputApplicationHandle application);
private static native void nativeSetFocusedDisplay(long ptr, int displayId);
+ private static native boolean nativeTransferTouchFocus(long ptr,
+ InputChannel fromChannel, InputChannel toChannel);
private static native void nativeSetPointerSpeed(long ptr, int speed);
private static native void nativeSetShowTouches(long ptr, boolean enabled);
private static native void nativeSetInteractive(long ptr, boolean interactive);
@@ -1543,6 +1545,29 @@
nativeSetSystemUiVisibility(mPtr, visibility);
}
+ /**
+ * Atomically transfers touch focus from one window to another as identified by
+ * their input channels. It is possible for multiple windows to have
+ * touch focus if they support split touch dispatch
+ * {@link android.view.WindowManager.LayoutParams#FLAG_SPLIT_TOUCH} but this
+ * method only transfers touch focus of the specified window without affecting
+ * other windows that may also have touch focus at the same time.
+ * @param fromChannel The channel of a window that currently has touch focus.
+ * @param toChannel The channel of the window that should receive touch focus in
+ * place of the first.
+ * @return True if the transfer was successful. False if the window with the
+ * specified channel did not actually have touch focus at the time of the request.
+ */
+ public boolean transferTouchFocus(InputChannel fromChannel, InputChannel toChannel) {
+ if (fromChannel == null) {
+ throw new IllegalArgumentException("fromChannel must not be null.");
+ }
+ if (toChannel == null) {
+ throw new IllegalArgumentException("toChannel must not be null.");
+ }
+ return nativeTransferTouchFocus(mPtr, fromChannel, toChannel);
+ }
+
@Override // Binder call
public void tryPointerSpeed(int speed) {
if (!checkCallingPermission(android.Manifest.permission.SET_POINTER_SPEED,
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/location/GnssVisibilityControl.java b/services/core/java/com/android/server/location/GnssVisibilityControl.java
index ea4f9c4..dd522b9 100644
--- a/services/core/java/com/android/server/location/GnssVisibilityControl.java
+++ b/services/core/java/com/android/server/location/GnssVisibilityControl.java
@@ -637,7 +637,7 @@
return new Notification.Builder(context, SystemNotificationChannels.NETWORK_ALERTS)
.setSmallIcon(com.android.internal.R.drawable.stat_sys_gps_on)
.setWhen(0)
- .setOngoing(true)
+ .setOngoing(false)
.setAutoCancel(true)
.setColor(context.getColor(
com.android.internal.R.color.system_notification_accent_color))
diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java
index f295ed3..7098435 100644
--- a/services/core/java/com/android/server/notification/NotificationComparator.java
+++ b/services/core/java/com/android/server/notification/NotificationComparator.java
@@ -123,6 +123,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..0fe0971 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) {
@@ -5550,7 +5608,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 +5709,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 +5740,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 +5755,7 @@
}
return true;
}
+
if (oldN.hasCompletedProgress() != newN.hasCompletedProgress()) {
if (DEBUG_INTERRUPTIVENESS) {
Slog.v(TAG, "INTERRUPTIVENESS: "
@@ -5702,6 +5763,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 +5806,6 @@
} catch (Exception e) {
Slog.w(TAG, "error recovering builder", e);
}
-
return false;
}
@@ -5930,12 +6000,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 +6369,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 +7514,8 @@
record.getSound() != null || record.getVibration() != null,
record.getSystemGeneratedSmartActions(),
record.getSmartReplies(),
- record.canBubble()
+ record.canBubble(),
+ record.isInterruptive()
);
rankings.add(ranking);
}
@@ -8542,6 +8624,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 +8660,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 +8712,7 @@
@VisibleForTesting
void resetAssistantUserSet(int userId) {
+ checkCallerIsSystemOrShell();
mAssistants.setUserSet(userId, false);
handleSavePolicyFile();
}
@@ -8628,12 +8720,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 +8744,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/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 3c2169a..c46aad9 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -1289,6 +1289,18 @@
return getLogMaker().setCategory(MetricsEvent.NOTIFICATION_ITEM);
}
+ public boolean hasUndecoratedRemoteView() {
+ Notification notification = getNotification();
+ Class<? extends Notification.Style> style = notification.getNotificationStyle();
+ boolean hasDecoratedStyle = style != null
+ && (Notification.DecoratedCustomViewStyle.class.equals(style)
+ || Notification.DecoratedMediaCustomViewStyle.class.equals(style));
+ boolean hasCustomRemoteView = notification.contentView != null
+ || notification.bigContentView != null
+ || notification.headsUpContentView != null;
+ return hasCustomRemoteView && !hasDecoratedStyle;
+ }
+
@VisibleForTesting
static final class Light {
public final int color;
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..fbf9934 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 += (notification.hasUndecoratedRemoteView() ? 1 : 0);
}
releaseAggregatedStatsLocked(aggregatedStatsArray);
if (ENABLE_SQLITE_LOG) {
@@ -327,6 +328,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 +414,7 @@
public int numRateViolations;
public int numAlertViolations;
public int numQuotaViolations;
+ public int numUndecoratedRemoteViews;
public long mLastAccessTime;
public AggregatedStats(Context context, String key) {
@@ -670,6 +681,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 +1041,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 +1074,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 +1100,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 +1169,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 +1280,7 @@
} else {
putPosttimeVisibility(r, cv);
}
+ cv.put(COL_UNDECORATED, (r.hasUndecoratedRemoteView() ? 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 +1361,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/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 0329e2c..fd8db4b 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -259,6 +259,10 @@
// Don't hold mSessions lock when calling restoreSession, since it might trigger an APK
// atomic install which needs to query sessions, which requires lock on mSessions.
for (PackageInstallerSession session : stagedSessionsToRestore) {
+ if (mPm.isDeviceUpgrading() && !session.isStagedAndInTerminalState()) {
+ session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
+ "Build fingerprint has changed");
+ }
mStagingManager.restoreSession(session);
}
// Broadcasts are not sent while we restore sessions on boot, since no processes would be
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 7e7822c..7a5bcc5 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -23122,6 +23122,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/power/batterysaver/BatterySaverStateMachine.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
index ff91299..a62bb74 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
@@ -773,9 +773,9 @@
// Handle triggering the notification to show/hide when appropriate
if (intReason == BatterySaverController.REASON_DYNAMIC_POWER_SAVINGS_AUTOMATIC_ON) {
- runOnBgThread(this::triggerDynamicModeNotification);
+ triggerDynamicModeNotification();
} else if (!enable) {
- runOnBgThread(this::hideDynamicModeNotification);
+ hideDynamicModeNotification();
}
if (DEBUG) {
@@ -787,33 +787,42 @@
@VisibleForTesting
void triggerDynamicModeNotification() {
- NotificationManager manager = mContext.getSystemService(NotificationManager.class);
- ensureNotificationChannelExists(manager, DYNAMIC_MODE_NOTIF_CHANNEL_ID,
- R.string.dynamic_mode_notification_channel_name);
+ // The current lock is the PowerManager lock, which sits very low in the service lock
+ // hierarchy. We shouldn't call out to NotificationManager with the PowerManager lock.
+ runOnBgThread(() -> {
+ NotificationManager manager = mContext.getSystemService(NotificationManager.class);
+ ensureNotificationChannelExists(manager, DYNAMIC_MODE_NOTIF_CHANNEL_ID,
+ R.string.dynamic_mode_notification_channel_name);
- manager.notifyAsUser(TAG, DYNAMIC_MODE_NOTIFICATION_ID,
- buildNotification(DYNAMIC_MODE_NOTIF_CHANNEL_ID,
- mContext.getResources().getString(R.string.dynamic_mode_notification_title),
- R.string.dynamic_mode_notification_summary,
- Intent.ACTION_POWER_USAGE_SUMMARY),
- UserHandle.ALL);
+ manager.notifyAsUser(TAG, DYNAMIC_MODE_NOTIFICATION_ID,
+ buildNotification(DYNAMIC_MODE_NOTIF_CHANNEL_ID,
+ mContext.getResources().getString(
+ R.string.dynamic_mode_notification_title),
+ R.string.dynamic_mode_notification_summary,
+ Intent.ACTION_POWER_USAGE_SUMMARY),
+ UserHandle.ALL);
+ });
}
@VisibleForTesting
void triggerStickyDisabledNotification() {
- NotificationManager manager = mContext.getSystemService(NotificationManager.class);
- ensureNotificationChannelExists(manager, BATTERY_SAVER_NOTIF_CHANNEL_ID,
- R.string.battery_saver_notification_channel_name);
+ // The current lock is the PowerManager lock, which sits very low in the service lock
+ // hierarchy. We shouldn't call out to NotificationManager with the PowerManager lock.
+ runOnBgThread(() -> {
+ NotificationManager manager = mContext.getSystemService(NotificationManager.class);
+ ensureNotificationChannelExists(manager, BATTERY_SAVER_NOTIF_CHANNEL_ID,
+ R.string.battery_saver_notification_channel_name);
- final String percentage = NumberFormat.getPercentInstance()
- .format((double) mBatteryLevel / 100.0);
- manager.notifyAsUser(TAG, STICKY_AUTO_DISABLED_NOTIFICATION_ID,
- buildNotification(BATTERY_SAVER_NOTIF_CHANNEL_ID,
- mContext.getResources().getString(
- R.string.battery_saver_charged_notification_title, percentage),
- R.string.battery_saver_off_notification_summary,
- Settings.ACTION_BATTERY_SAVER_SETTINGS),
- UserHandle.ALL);
+ final String percentage = NumberFormat.getPercentInstance()
+ .format((double) mBatteryLevel / 100.0);
+ manager.notifyAsUser(TAG, STICKY_AUTO_DISABLED_NOTIFICATION_ID,
+ buildNotification(BATTERY_SAVER_NOTIF_CHANNEL_ID,
+ mContext.getResources().getString(
+ R.string.battery_saver_charged_notification_title, percentage),
+ R.string.battery_saver_off_notification_summary,
+ Settings.ACTION_BATTERY_SAVER_SETTINGS),
+ UserHandle.ALL);
+ });
}
private void ensureNotificationChannelExists(NotificationManager manager,
@@ -854,8 +863,12 @@
}
private void hideNotification(int notificationId) {
- NotificationManager manager = mContext.getSystemService(NotificationManager.class);
- manager.cancel(notificationId);
+ // The current lock is the PowerManager lock, which sits very low in the service lock
+ // hierarchy. We shouldn't call out to NotificationManager with the PowerManager lock.
+ runOnBgThread(() -> {
+ NotificationManager manager = mContext.getSystemService(NotificationManager.class);
+ manager.cancelAsUser(TAG, notificationId, UserHandle.ALL);
+ });
}
private void setStickyActive(boolean active) {
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 83891f6..ce6a954 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -16,6 +16,13 @@
package com.android.server.rollback;
+import static android.util.StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH;
+import static android.util.StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING;
+import static android.util.StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK;
+import static android.util.StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH;
+import static android.util.StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN;
+
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -41,6 +48,7 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.server.PackageWatchdog;
+import com.android.server.PackageWatchdog.FailureReasons;
import com.android.server.PackageWatchdog.PackageHealthObserver;
import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
@@ -106,11 +114,19 @@
}
@Override
- public boolean execute(VersionedPackage failedPackage) {
+ public boolean execute(VersionedPackage failedPackage, @FailureReasons int rollbackReason) {
RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
VersionedPackage moduleMetadataPackage = getModuleMetadataPackage();
RollbackInfo rollback = getAvailableRollback(rollbackManager, failedPackage);
+ int reasonToLog = mapFailureReasonToMetric(rollbackReason);
+ final String failedPackageToLog;
+ if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
+ failedPackageToLog = SystemProperties.get(
+ "ro.init.updatable_crashing_process_name", "");
+ } else {
+ failedPackageToLog = failedPackage.getPackageName();
+ }
if (rollback == null) {
Slog.w(TAG, "Expected rollback but no valid rollback found for package: [ "
+ failedPackage.getPackageName() + "] with versionCode: ["
@@ -119,7 +135,8 @@
}
logEvent(moduleMetadataPackage,
- StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE);
+ StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE,
+ reasonToLog, failedPackageToLog);
LocalIntentReceiver rollbackReceiver = new LocalIntentReceiver((Intent result) -> {
int status = result.getIntExtra(RollbackManager.EXTRA_STATUS,
RollbackManager.STATUS_FAILURE);
@@ -136,11 +153,13 @@
moduleMetadataPackage);
} else {
logEvent(moduleMetadataPackage,
- StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS);
+ StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS,
+ reasonToLog, failedPackageToLog);
}
} else {
logEvent(moduleMetadataPackage,
- StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE);
+ StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
+ reasonToLog, failedPackageToLog);
}
});
@@ -219,12 +238,14 @@
}
if (sessionInfo.isStagedSessionApplied()) {
logEvent(oldModuleMetadataPackage,
- StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS);
+ StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS,
+ WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN, "");
} else if (sessionInfo.isStagedSessionReady()) {
// TODO: What do for staged session ready but not applied
} else {
logEvent(oldModuleMetadataPackage,
- StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE);
+ StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
+ WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN, "");
}
}
@@ -303,12 +324,16 @@
saveLastStagedRollbackId(rollbackId);
logEvent(moduleMetadataPackage,
StatsLog
- .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED);
+ .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED,
+ WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN,
+ "");
mContext.getSystemService(PowerManager.class).reboot("Rollback staged install");
} else if (sessionInfo.isStagedSessionFailed()
&& markStagedSessionHandled(rollbackId)) {
logEvent(moduleMetadataPackage,
- StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE);
+ StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
+ WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN,
+ "");
mContext.unregisterReceiver(listener);
}
}
@@ -355,11 +380,12 @@
return rollbackId;
}
- private static void logEvent(@Nullable VersionedPackage moduleMetadataPackage, int type) {
+ private static void logEvent(@Nullable VersionedPackage moduleMetadataPackage, int type,
+ int rollbackReason, @NonNull String failingPackageName) {
Slog.i(TAG, "Watchdog event occurred of type: " + type);
if (moduleMetadataPackage != null) {
StatsLog.logWatchdogRollbackOccurred(type, moduleMetadataPackage.getPackageName(),
- moduleMetadataPackage.getVersionCode());
+ moduleMetadataPackage.getVersionCode(), rollbackReason, failingPackageName);
}
}
@@ -371,7 +397,7 @@
mNumberOfNativeCrashPollsRemaining--;
// Check if native watchdog reported a crash
if ("1".equals(SystemProperties.get("sys.init.updatable_crashing"))) {
- execute(getModuleMetadataPackage());
+ execute(getModuleMetadataPackage(), PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
// we stop polling after an attempt to execute rollback, regardless of whether the
// attempt succeeds or not
} else {
@@ -392,4 +418,20 @@
+ "and mitigate native crashes");
mHandler.post(()->checkAndMitigateNativeCrashes());
}
+
+ private int mapFailureReasonToMetric(@FailureReasons int failureReason) {
+ switch (failureReason) {
+ case PackageWatchdog.FAILURE_REASON_NATIVE_CRASH:
+ return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH;
+ case PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK:
+ return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK;
+ case PackageWatchdog.FAILURE_REASON_APP_CRASH:
+ return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH;
+ case PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING:
+ return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING;
+ default:
+ return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN;
+ }
+ }
+
}
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index 0d28b46..cb5ddfe 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;
@@ -1624,14 +1626,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(
@@ -1647,6 +1642,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();
@@ -2476,6 +2517,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/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
index 5f00148..89a5305 100644
--- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
+++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
@@ -30,6 +30,7 @@
import android.service.textclassifier.ITextClassifierCallback;
import android.service.textclassifier.ITextClassifierService;
import android.service.textclassifier.TextClassifierService;
+import android.util.ArrayMap;
import android.util.Slog;
import android.util.SparseArray;
import android.view.textclassifier.ConversationActions;
@@ -54,6 +55,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayDeque;
+import java.util.Map;
import java.util.Queue;
/**
@@ -119,6 +121,8 @@
private final Object mLock;
@GuardedBy("mLock")
final SparseArray<UserState> mUserStates = new SparseArray<>();
+ @GuardedBy("mLock")
+ private final Map<TextClassificationSessionId, Integer> mSessionUserIds = new ArrayMap<>();
private TextClassificationManagerService(Context context) {
mContext = Preconditions.checkNotNull(context);
@@ -127,15 +131,16 @@
@Override
public void onSuggestSelection(
- TextClassificationSessionId sessionId,
+ @Nullable TextClassificationSessionId sessionId,
TextSelection.Request request, ITextClassifierCallback callback)
throws RemoteException {
Preconditions.checkNotNull(request);
Preconditions.checkNotNull(callback);
- validateInput(mContext, request.getCallingPackageName());
+ final int userId = request.getUserId();
+ validateInput(mContext, request.getCallingPackageName(), userId);
synchronized (mLock) {
- UserState userState = getCallingUserStateLocked();
+ UserState userState = getUserStateLocked(userId);
if (!userState.bindLocked()) {
callback.onFailure();
} else if (userState.isBoundLocked()) {
@@ -150,15 +155,16 @@
@Override
public void onClassifyText(
- TextClassificationSessionId sessionId,
+ @Nullable TextClassificationSessionId sessionId,
TextClassification.Request request, ITextClassifierCallback callback)
throws RemoteException {
Preconditions.checkNotNull(request);
Preconditions.checkNotNull(callback);
- validateInput(mContext, request.getCallingPackageName());
+ final int userId = request.getUserId();
+ validateInput(mContext, request.getCallingPackageName(), userId);
synchronized (mLock) {
- UserState userState = getCallingUserStateLocked();
+ UserState userState = getUserStateLocked(userId);
if (!userState.bindLocked()) {
callback.onFailure();
} else if (userState.isBoundLocked()) {
@@ -173,15 +179,16 @@
@Override
public void onGenerateLinks(
- TextClassificationSessionId sessionId,
+ @Nullable TextClassificationSessionId sessionId,
TextLinks.Request request, ITextClassifierCallback callback)
throws RemoteException {
Preconditions.checkNotNull(request);
Preconditions.checkNotNull(callback);
- validateInput(mContext, request.getCallingPackageName());
+ final int userId = request.getUserId();
+ validateInput(mContext, request.getCallingPackageName(), userId);
synchronized (mLock) {
- UserState userState = getCallingUserStateLocked();
+ UserState userState = getUserStateLocked(userId);
if (!userState.bindLocked()) {
callback.onFailure();
} else if (userState.isBoundLocked()) {
@@ -196,12 +203,14 @@
@Override
public void onSelectionEvent(
- TextClassificationSessionId sessionId, SelectionEvent event) throws RemoteException {
+ @Nullable TextClassificationSessionId sessionId, SelectionEvent event)
+ throws RemoteException {
Preconditions.checkNotNull(event);
- validateInput(mContext, event.getPackageName());
+ final int userId = event.getUserId();
+ validateInput(mContext, event.getPackageName(), userId);
synchronized (mLock) {
- UserState userState = getCallingUserStateLocked();
+ UserState userState = getUserStateLocked(userId);
if (userState.isBoundLocked()) {
userState.mService.onSelectionEvent(sessionId, event);
} else {
@@ -213,16 +222,19 @@
}
@Override
public void onTextClassifierEvent(
- TextClassificationSessionId sessionId,
+ @Nullable TextClassificationSessionId sessionId,
TextClassifierEvent event) throws RemoteException {
Preconditions.checkNotNull(event);
final String packageName = event.getEventContext() == null
? null
: event.getEventContext().getPackageName();
- validateInput(mContext, packageName);
+ final int userId = event.getEventContext() == null
+ ? UserHandle.getCallingUserId()
+ : event.getEventContext().getUserId();
+ validateInput(mContext, packageName, userId);
synchronized (mLock) {
- UserState userState = getCallingUserStateLocked();
+ UserState userState = getUserStateLocked(userId);
if (userState.isBoundLocked()) {
userState.mService.onTextClassifierEvent(sessionId, event);
} else {
@@ -235,15 +247,16 @@
@Override
public void onDetectLanguage(
- TextClassificationSessionId sessionId,
+ @Nullable TextClassificationSessionId sessionId,
TextLanguage.Request request,
ITextClassifierCallback callback) throws RemoteException {
Preconditions.checkNotNull(request);
Preconditions.checkNotNull(callback);
- validateInput(mContext, request.getCallingPackageName());
+ final int userId = request.getUserId();
+ validateInput(mContext, request.getCallingPackageName(), userId);
synchronized (mLock) {
- UserState userState = getCallingUserStateLocked();
+ UserState userState = getUserStateLocked(userId);
if (!userState.bindLocked()) {
callback.onFailure();
} else if (userState.isBoundLocked()) {
@@ -258,15 +271,16 @@
@Override
public void onSuggestConversationActions(
- TextClassificationSessionId sessionId,
+ @Nullable TextClassificationSessionId sessionId,
ConversationActions.Request request,
ITextClassifierCallback callback) throws RemoteException {
Preconditions.checkNotNull(request);
Preconditions.checkNotNull(callback);
- validateInput(mContext, request.getCallingPackageName());
+ final int userId = request.getUserId();
+ validateInput(mContext, request.getCallingPackageName(), userId);
synchronized (mLock) {
- UserState userState = getCallingUserStateLocked();
+ UserState userState = getUserStateLocked(userId);
if (!userState.bindLocked()) {
callback.onFailure();
} else if (userState.isBoundLocked()) {
@@ -285,13 +299,15 @@
throws RemoteException {
Preconditions.checkNotNull(sessionId);
Preconditions.checkNotNull(classificationContext);
- validateInput(mContext, classificationContext.getPackageName());
+ final int userId = classificationContext.getUserId();
+ validateInput(mContext, classificationContext.getPackageName(), userId);
synchronized (mLock) {
- UserState userState = getCallingUserStateLocked();
+ UserState userState = getUserStateLocked(userId);
if (userState.isBoundLocked()) {
userState.mService.onCreateTextClassificationSession(
classificationContext, sessionId);
+ mSessionUserIds.put(sessionId, userId);
} else {
userState.mPendingRequests.add(new PendingRequest(
() -> onCreateTextClassificationSession(classificationContext, sessionId),
@@ -306,9 +322,15 @@
Preconditions.checkNotNull(sessionId);
synchronized (mLock) {
- UserState userState = getCallingUserStateLocked();
+ final int userId = mSessionUserIds.containsKey(sessionId)
+ ? mSessionUserIds.get(sessionId)
+ : UserHandle.getCallingUserId();
+ validateInput(mContext, null /* packageName */, userId);
+
+ UserState userState = getUserStateLocked(userId);
if (userState.isBoundLocked()) {
userState.mService.onDestroyTextClassificationSession(sessionId);
+ mSessionUserIds.remove(sessionId);
} else {
userState.mPendingRequests.add(new PendingRequest(
() -> onDestroyTextClassificationSession(sessionId),
@@ -318,11 +340,6 @@
}
@GuardedBy("mLock")
- private UserState getCallingUserStateLocked() {
- return getUserStateLocked(UserHandle.getCallingUserId());
- }
-
- @GuardedBy("mLock")
private UserState getUserStateLocked(int userId) {
UserState result = mUserStates.get(userId);
if (result == null) {
@@ -356,6 +373,7 @@
pw.decreaseIndent();
}
}
+ pw.println("Number of active sessions: " + mSessionUserIds.size());
}
}
@@ -420,20 +438,32 @@
e -> Slog.d(LOG_TAG, "Error " + opDesc + ": " + e.getMessage()));
}
- private static void validateInput(Context context, @Nullable String packageName)
+ private static void validateInput(
+ Context context, @Nullable String packageName, @UserIdInt int userId)
throws RemoteException {
- if (packageName == null) return;
try {
- final int packageUid = context.getPackageManager()
- .getPackageUidAsUser(packageName, UserHandle.getCallingUserId());
- final int callingUid = Binder.getCallingUid();
- Preconditions.checkArgument(callingUid == packageUid
- // Trust the system process:
- || callingUid == android.os.Process.SYSTEM_UID);
+ if (packageName != null) {
+ final int packageUid = context.getPackageManager()
+ .getPackageUidAsUser(packageName, UserHandle.getCallingUserId());
+ final int callingUid = Binder.getCallingUid();
+ Preconditions.checkArgument(callingUid == packageUid
+ // Trust the system process:
+ || callingUid == android.os.Process.SYSTEM_UID,
+ "Invalid package name. Package=" + packageName
+ + ", CallingUid=" + callingUid);
+ }
+
+ Preconditions.checkArgument(userId != UserHandle.USER_NULL, "Null userId");
+ final int callingUserId = UserHandle.getCallingUserId();
+ if (callingUserId != userId) {
+ context.enforceCallingOrSelfPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ "Invalid userId. UserId=" + userId + ", CallingUserId=" + callingUserId);
+ }
} catch (Exception e) {
- throw new RemoteException(
- String.format("Invalid package: name=%s, error=%s", packageName, e));
+ throw new RemoteException("Invalid request: " + e.getMessage(), e,
+ /* enableSuppression */ true, /* writableStackTrace */ true);
}
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 991c09a..4e136af 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1286,6 +1286,7 @@
saveSettingsLocked(mWallpaper.userId);
}
FgThread.getHandler().removeCallbacks(mResetRunnable);
+ mContext.getMainThreadHandler().removeCallbacks(this::tryToRebind);
}
}
}
@@ -1328,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
@@ -1351,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) {
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 99a9db3..2d835d3 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -865,6 +865,8 @@
if (canToastShowWhenLocked(callingPid)) {
attrs.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
}
+ // Toasts can't be clickable
+ attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
break;
}
@@ -2812,7 +2814,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/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index f8f6334..d5f403f 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -113,7 +113,7 @@
final WindowState callingWin = mService.windowForClientLocked(
null, window, false);
- if (callingWin == null) {
+ if (callingWin == null || callingWin.cantReceiveTouchInput()) {
Slog.w(TAG_WM, "Bad requesting window " + window);
return null; // !!! TODO: throw here?
}
@@ -167,8 +167,7 @@
final SurfaceControl surfaceControl = mDragState.mSurfaceControl;
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM, ">>> OPEN TRANSACTION performDrag");
- final SurfaceControl.Transaction transaction =
- callingWin.getPendingTransaction();
+ final SurfaceControl.Transaction transaction = mDragState.mTransaction;
transaction.setAlpha(surfaceControl, mDragState.mOriginalAlpha);
transaction.setPosition(
surfaceControl, touchX - thumbCenterX, touchY - thumbCenterY);
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 553b0ff..bdb706e47 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -120,7 +120,7 @@
// A surface used to catch input events for the drag-and-drop operation.
SurfaceControl mInputSurface;
- private final SurfaceControl.Transaction mTransaction;
+ final SurfaceControl.Transaction mTransaction;
private final Rect mTmpClipRect = new Rect();
@@ -129,7 +129,6 @@
* {@code true} when {@link #closeLocked()} is called.
*/
private boolean mIsClosing;
- IBinder mTransferTouchFromToken;
DragState(WindowManagerService service, DragDropController controller, IBinder token,
SurfaceControl surface, int flags, IBinder localWin) {
@@ -173,10 +172,9 @@
mTmpClipRect.set(0, 0, mDisplaySize.x, mDisplaySize.y);
mTransaction.setWindowCrop(mInputSurface, mTmpClipRect);
- mTransaction.transferTouchFocus(mTransferTouchFromToken, h.token);
- mTransferTouchFromToken = null;
- // syncInputWindows here to ensure the input channel isn't removed before the transfer.
+ // syncInputWindows here to ensure the input window info is sent before the
+ // transferTouchFocus is called.
mTransaction.syncInputWindows();
mTransaction.apply();
}
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 2fc64ea..7d50ac6 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -50,6 +50,7 @@
import android.view.InputEvent;
import android.view.InputWindowHandle;
import android.view.MotionEvent;
+import android.view.SurfaceControl;
import android.view.WindowManager;
import com.android.internal.annotations.VisibleForTesting;
@@ -307,7 +308,8 @@
mDisplayContent.pauseRotationLocked();
// Notify InputMonitor to take mDragWindowHandle.
- mDisplayContent.getInputMonitor().updateInputWindowsLw(true /*force*/);
+ mDisplayContent.getInputMonitor().updateInputWindowsImmediately();
+ new SurfaceControl.Transaction().syncInputWindows().apply();
mSideMargin = dipToPixel(SIDE_MARGIN_DIP, mDisplayMetrics);
mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, mDisplayMetrics);
diff --git a/services/core/java/com/android/server/wm/TaskPositioningController.java b/services/core/java/com/android/server/wm/TaskPositioningController.java
index 2441954..56b3bba 100644
--- a/services/core/java/com/android/server/wm/TaskPositioningController.java
+++ b/services/core/java/com/android/server/wm/TaskPositioningController.java
@@ -24,7 +24,6 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Handler;
-import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Slog;
@@ -51,7 +50,6 @@
private @Nullable TaskPositioner mTaskPositioner;
private final Rect mTmpClipRect = new Rect();
- private IBinder mTransferTouchFromToken;
boolean isPositioningLocked() {
return mTaskPositioner != null;
@@ -104,8 +102,6 @@
mTmpClipRect.set(0, 0, p.x, p.y);
t.setWindowCrop(mInputSurface, mTmpClipRect);
- t.transferTouchFocus(mTransferTouchFromToken, h.token);
- mTransferTouchFromToken = null;
}
boolean startMovingTask(IWindow window, float startX, float startY) {
@@ -168,6 +164,7 @@
mPositioningDisplay = displayContent;
mTaskPositioner = TaskPositioner.create(mService);
+ mTaskPositioner.register(displayContent);
// We need to grab the touch focus so that the touch events during the
// resizing/scrolling are not sent to the app. 'win' is the main window
@@ -178,8 +175,12 @@
&& displayContent.mCurrentFocus.mAppToken == win.mAppToken) {
transferFocusFromWin = displayContent.mCurrentFocus;
}
- mTransferTouchFromToken = transferFocusFromWin.mInputChannel.getToken();
- mTaskPositioner.register(displayContent);
+ if (!mInputManager.transferTouchFocus(
+ transferFocusFromWin.mInputChannel, mTaskPositioner.mServerChannel)) {
+ Slog.e(TAG_WM, "startPositioningLocked: Unable to transfer touch focus");
+ cleanUpTaskPositioner();
+ return false;
+ }
mTaskPositioner.startDrag(win, resize, preserveOrientation, startX, startY);
return true;
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 40bec14..b407ac5 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -156,9 +156,8 @@
default boolean registerInputChannel(
DragState state, Display display, InputManagerService service,
InputChannel source) {
- state.mTransferTouchFromToken = source.getToken();
state.register(display);
- return true;
+ return service.transferTouchFocus(source, state.getInputChannel());
}
/**
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index fb3076b..d331d1f 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -1571,6 +1571,27 @@
im->setSystemUiVisibility(visibility);
}
+static jboolean nativeTransferTouchFocus(JNIEnv* env,
+ jclass /* clazz */, jlong ptr, jobject fromChannelObj, jobject toChannelObj) {
+ NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+ sp<InputChannel> fromChannel =
+ android_view_InputChannel_getInputChannel(env, fromChannelObj);
+ sp<InputChannel> toChannel =
+ android_view_InputChannel_getInputChannel(env, toChannelObj);
+
+ if (fromChannel == nullptr || toChannel == nullptr) {
+ return JNI_FALSE;
+ }
+
+ if (im->getInputManager()->getDispatcher()->
+ transferTouchFocus(fromChannel->getToken(), toChannel->getToken())) {
+ return JNI_TRUE;
+ } else {
+ return JNI_FALSE;
+ }
+}
+
static void nativeSetPointerSpeed(JNIEnv* /* env */,
jclass /* clazz */, jlong ptr, jint speed) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
@@ -1770,6 +1791,8 @@
(void*) nativeSetInputDispatchMode },
{ "nativeSetSystemUiVisibility", "(JI)V",
(void*) nativeSetSystemUiVisibility },
+ { "nativeTransferTouchFocus", "(JLandroid/view/InputChannel;Landroid/view/InputChannel;)Z",
+ (void*) nativeTransferTouchFocus },
{ "nativeSetPointerSpeed", "(JI)V",
(void*) nativeSetPointerSpeed },
{ "nativeSetShowTouches", "(JZ)V",
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index b5b21f4..e58f513 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1513,6 +1513,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..fe15ff4 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,9 +53,11 @@
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;
+import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import android.app.ActivityManagerInternal;
@@ -63,13 +67,18 @@
import android.content.Context;
import android.content.Intent;
import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
+import android.hardware.TriggerEvent;
+import android.hardware.TriggerEventListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.net.ConnectivityManager;
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 +94,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 +108,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 +122,6 @@
@Mock
private ContentResolver mContentResolver;
@Mock
- private DeviceIdleController.MyHandler mHandler;
- @Mock
private IActivityManager mIActivityManager;
@Mock
private LocationManager mLocationManager;
@@ -122,12 +132,16 @@
@Mock
private PowerManagerInternal mPowerManagerInternal;
@Mock
+ private Sensor mMotionSensor;
+ @Mock
private SensorManager mSensorManager;
class InjectorForTest extends DeviceIdleController.Injector {
ConnectivityService connectivityService;
LocationManager locationManager;
ConstraintController constraintController;
+ // Freeze time for testing.
+ long nowElapsed;
InjectorForTest(Context ctx) {
super(ctx);
@@ -155,16 +169,43 @@
}
@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;
}
@Override
+ Sensor getMotionSensor() {
+ return mMotionSensor;
+ }
+
+ @Override
PowerManager getPowerManager() {
return mPowerManager;
}
@@ -226,6 +267,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 +309,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 +1659,202 @@
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_NoMotion() {
+ // Short timeout for testing.
+ mConstants.MOTION_INACTIVE_TIMEOUT = 6000L;
+ doReturn(Sensor.REPORTING_MODE_ONE_SHOT).when(mMotionSensor).getReportingMode();
+ doReturn(true).when(mSensorManager)
+ .requestTriggerSensor(eq(mDeviceIdleController.mMotionListener), eq(mMotionSensor));
+ 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> motionAlarmListener = ArgumentCaptor
+ .forClass(AlarmManager.OnAlarmListener.class);
+ final ArgumentCaptor<AlarmManager.OnAlarmListener> motionRegistrationAlarmListener =
+ ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+ doNothing().when(mAlarmManager).set(anyInt(), anyLong(), eq("DeviceIdleController.motion"),
+ motionAlarmListener.capture(), any());
+ doNothing().when(mAlarmManager).set(anyInt(), anyLong(),
+ eq("DeviceIdleController.motion_registration"),
+ motionRegistrationAlarmListener.capture(), any());
+
+ StationaryListenerForTest stationaryListener = new StationaryListenerForTest();
+ spyOn(stationaryListener);
+ InOrder inOrder = inOrder(stationaryListener);
+
+ stationaryListener.motionExpected = true;
+ mDeviceIdleController.registerStationaryListener(stationaryListener);
+ inOrder.verify(stationaryListener, timeout(1000L).times(1))
+ .onDeviceStationaryChanged(eq(false));
+ 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;
+ stationaryListener.motionExpected = false;
+ motionAlarmListener.getValue().onAlarm();
+ inOrder.verify(stationaryListener, timeout(1000L).times(1))
+ .onDeviceStationaryChanged(eq(true));
+ assertTrue(stationaryListener.isStationary);
+
+ stationaryListener.motionExpected = true;
+ mDeviceIdleController.mMotionListener.onTrigger(null);
+ inOrder.verify(stationaryListener, timeout(1000L).times(1))
+ .onDeviceStationaryChanged(eq(false));
+ 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");
+
+ motionRegistrationAlarmListener.getValue().onAlarm();
+ mInjector.nowElapsed += mConstants.MOTION_INACTIVE_TIMEOUT / 2;
+
+ // Back to IDLE
+ stationaryListener.motionExpected = false;
+ mDeviceIdleController.stepIdleStateLocked("testing");
+ verify(mSensorManager,
+ timeout(mConstants.MOTION_INACTIVE_TIMEOUT).times(2))
+ .requestTriggerSensor(eq(mDeviceIdleController.mMotionListener), eq(mMotionSensor));
+
+ // Now enough time has passed.
+ mInjector.nowElapsed += mConstants.MOTION_INACTIVE_TIMEOUT;
+ motionAlarmListener.getValue().onAlarm();
+ inOrder.verify(stationaryListener,
+ timeout(mConstants.MOTION_INACTIVE_TIMEOUT).times(1))
+ .onDeviceStationaryChanged(eq(true));
+ assertTrue(stationaryListener.isStationary);
+ }
+
+ @Test
+ public void testStationaryDetection_QuickDozeOn_OneShot() {
+ // Short timeout for testing.
+ mConstants.MOTION_INACTIVE_TIMEOUT = 6000L;
+ doReturn(Sensor.REPORTING_MODE_ONE_SHOT).when(mMotionSensor).getReportingMode();
+ 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"), any(), any());
+ doNothing().when(mAlarmManager).set(anyInt(), anyLong(),
+ eq("DeviceIdleController.motion_registration"),
+ alarmListener.capture(), any());
+ ArgumentCaptor<TriggerEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(TriggerEventListener.class);
+
+ StationaryListenerForTest stationaryListener = new StationaryListenerForTest();
+ spyOn(stationaryListener);
+ InOrder inOrder = inOrder(stationaryListener, mSensorManager);
+
+ stationaryListener.motionExpected = true;
+ mDeviceIdleController.registerStationaryListener(stationaryListener);
+ inOrder.verify(stationaryListener, timeout(1000L).times(1))
+ .onDeviceStationaryChanged(eq(false));
+ assertFalse(stationaryListener.isStationary);
+ inOrder.verify(mSensorManager)
+ .requestTriggerSensor(listenerCaptor.capture(), eq(mMotionSensor));
+ final TriggerEventListener listener = listenerCaptor.getValue();
+
+ // Trigger motion
+ listener.onTrigger(mock(TriggerEvent.class));
+ inOrder.verify(stationaryListener, timeout(1000L).times(1))
+ .onDeviceStationaryChanged(eq(false));
+
+ // Make sure the listener is re-registered.
+ alarmListener.getValue().onAlarm();
+ inOrder.verify(mSensorManager).requestTriggerSensor(eq(listener), eq(mMotionSensor));
+ }
+
+ @Test
+ public void testStationaryDetection_QuickDozeOn_MultiShot() {
+ // Short timeout for testing.
+ mConstants.MOTION_INACTIVE_TIMEOUT = 6000L;
+ doReturn(Sensor.REPORTING_MODE_CONTINUOUS).when(mMotionSensor).getReportingMode();
+ 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"), any(), any());
+ doNothing().when(mAlarmManager).set(anyInt(), anyLong(),
+ eq("DeviceIdleController.motion_registration"),
+ alarmListener.capture(), any());
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+
+ StationaryListenerForTest stationaryListener = new StationaryListenerForTest();
+ spyOn(stationaryListener);
+ InOrder inOrder = inOrder(stationaryListener, mSensorManager);
+
+ stationaryListener.motionExpected = true;
+ mDeviceIdleController.registerStationaryListener(stationaryListener);
+ inOrder.verify(stationaryListener, timeout(1000L).times(1))
+ .onDeviceStationaryChanged(eq(false));
+ assertFalse(stationaryListener.isStationary);
+ inOrder.verify(mSensorManager)
+ .registerListener(listenerCaptor.capture(), eq(mMotionSensor),
+ eq(SensorManager.SENSOR_DELAY_NORMAL));
+ final SensorEventListener listener = listenerCaptor.getValue();
+
+ // Trigger motion
+ listener.onSensorChanged(mock(SensorEvent.class));
+ inOrder.verify(stationaryListener, timeout(1000L).times(1))
+ .onDeviceStationaryChanged(eq(false));
+
+ // Make sure the listener is re-registered.
+ alarmListener.getValue().onAlarm();
+ inOrder.verify(mSensorManager)
+ .registerListener(eq(listener), eq(mMotionSensor),
+ eq(SensorManager.SENSOR_DELAY_NORMAL));
+ }
+
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 d4dd245..5c75a46 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/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
new file mode 100644
index 0000000..f6c4d3a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -0,0 +1,192 @@
+/*
+ * 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.display;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyFloat;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.Sensor;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+import android.os.Handler;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AutomaticBrightnessControllerTest {
+
+ private static final int BRIGHTNESS_MIN = 1;
+ private static final int BRIGHTNESS_MAX = 255;
+ private static final int LIGHT_SENSOR_RATE = 20;
+ private static final int INITIAL_LIGHT_SENSOR_RATE = 20;
+ private static final int BRIGHTENING_LIGHT_DEBOUNCE_CONFIG = 0;
+ private static final int DARKENING_LIGHT_DEBOUNCE_CONFIG = 0;
+ private static final int SHORT_TERM_MODEL_TIMEOUT = 0;
+ private static final float DOZE_SCALE_FACTOR = 0.0f;
+ private static final boolean RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG = false;
+
+ private Context mContext;
+ @Mock SensorManager mSensorManager;
+ @Mock BrightnessMappingStrategy mBrightnessMappingStrategy;
+ @Mock HysteresisLevels mAmbientBrightnessThresholds;
+ @Mock HysteresisLevels mScreenBrightnessThresholds;
+ @Mock PackageManager mPackageManager;
+ @Mock Handler mNoopHandler;
+
+ private static final int LIGHT_SENSOR_WARMUP_TIME = 0;
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = InstrumentationRegistry.getContext();
+ }
+
+ private AutomaticBrightnessController setupController(Sensor lightSensor) {
+ AutomaticBrightnessController controller = new AutomaticBrightnessController(
+ new AutomaticBrightnessController.Injector() {
+ @Override
+ public Handler getBackgroundThreadHandler() {
+ return mNoopHandler;
+ }
+ },
+ () -> { }, mContext.getMainLooper(), mSensorManager, lightSensor,
+ mBrightnessMappingStrategy, LIGHT_SENSOR_WARMUP_TIME, BRIGHTNESS_MIN,
+ BRIGHTNESS_MAX, DOZE_SCALE_FACTOR, LIGHT_SENSOR_RATE, INITIAL_LIGHT_SENSOR_RATE,
+ BRIGHTENING_LIGHT_DEBOUNCE_CONFIG, DARKENING_LIGHT_DEBOUNCE_CONFIG,
+ RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG, mAmbientBrightnessThresholds,
+ mScreenBrightnessThresholds, SHORT_TERM_MODEL_TIMEOUT, mPackageManager);
+ controller.setLoggingEnabled(true);
+
+ // Configure the brightness controller and grab an instance of the sensor listener,
+ // through which we can deliver fake (for test) sensor values.
+ controller.configure(true /* enable */, null /* configuration */,
+ 0 /* brightness */, false /* userChangedBrightness */, 0 /* adjustment */,
+ false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
+
+ return controller;
+ }
+
+ @Test
+ public void testNoHysteresisAtMinBrightness() throws Exception {
+ Sensor lightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor");
+ AutomaticBrightnessController controller = setupController(lightSensor);
+
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(lightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+
+ // Set up system to return 5 as a brightness value
+ float lux1 = 100.0f;
+ float normalizedBrightness1 = 0.02f;
+ when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux1))
+ .thenReturn(lux1);
+ when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux1))
+ .thenReturn(lux1);
+ when(mBrightnessMappingStrategy.getBrightness(eq(lux1), eq(null), anyInt()))
+ .thenReturn(normalizedBrightness1);
+
+ // This is the important bit: When the new brightness is set, make sure the new
+ // brightening threshold is beyond the maximum brightness value...so that we can test that
+ // our threshold clamping works.
+ when(mScreenBrightnessThresholds.getBrighteningThreshold(5)).thenReturn(1.0f);
+
+ // Send new sensor value and verify
+ listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, (int) lux1));
+ assertEquals(5, controller.getAutomaticScreenBrightness());
+
+
+ // Set up system to return 255 as a brightness value
+ float lux2 = 10.0f;
+ float normalizedBrightness2 = 0.0f;
+ when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux2))
+ .thenReturn(lux2);
+ when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux2))
+ .thenReturn(lux2);
+ when(mBrightnessMappingStrategy.getBrightness(anyFloat(), eq(null), anyInt()))
+ .thenReturn(normalizedBrightness2);
+
+ // Send new sensor value and verify
+ listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, (int) lux2));
+ assertEquals(1, controller.getAutomaticScreenBrightness());
+ }
+
+ @Test
+ public void testNoHysteresisAtMaxBrightness() throws Exception {
+ Sensor lightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor");
+ AutomaticBrightnessController controller = setupController(lightSensor);
+
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(lightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+
+ // Set up system to return 250 as a brightness value
+ float lux1 = 100.0f;
+ float normalizedBrightness1 = 0.98f;
+ when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux1))
+ .thenReturn(lux1);
+ when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux1))
+ .thenReturn(lux1);
+ when(mBrightnessMappingStrategy.getBrightness(eq(lux1), eq(null), anyInt()))
+ .thenReturn(normalizedBrightness1);
+
+ // This is the important bit: When the new brightness is set, make sure the new
+ // brightening threshold is beyond the maximum brightness value...so that we can test that
+ // our threshold clamping works.
+ when(mScreenBrightnessThresholds.getBrighteningThreshold(250)).thenReturn(260.0f);
+
+ // Send new sensor value and verify
+ listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, (int) lux1));
+ assertEquals(250, controller.getAutomaticScreenBrightness());
+
+
+ // Set up system to return 255 as a brightness value
+ float lux2 = 110.0f;
+ float normalizedBrightness2 = 1.0f;
+ when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux2))
+ .thenReturn(lux2);
+ when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux2))
+ .thenReturn(lux2);
+ when(mBrightnessMappingStrategy.getBrightness(anyFloat(), eq(null), anyInt()))
+ .thenReturn(normalizedBrightness2);
+
+ // Send new sensor value and verify
+ listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, (int) lux2));
+ assertEquals(255, controller.getAutomaticScreenBrightness());
+ }
+}
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/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 4742a73..8d5939a 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -11,7 +11,7 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
*/
package com.android.server.display;
diff --git a/services/tests/servicestests/src/com/android/server/display/TestUtils.java b/services/tests/servicestests/src/com/android/server/display/TestUtils.java
new file mode 100644
index 0000000..859dfe3
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/TestUtils.java
@@ -0,0 +1,60 @@
+/*
+ * 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.display;
+
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.os.SystemClock;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+public final class TestUtils {
+
+ public static SensorEvent createSensorEvent(Sensor sensor, int lux) throws Exception {
+ final Constructor<SensorEvent> constructor =
+ SensorEvent.class.getDeclaredConstructor(int.class);
+ constructor.setAccessible(true);
+ final SensorEvent event = constructor.newInstance(1);
+ event.sensor = sensor;
+ event.values[0] = lux;
+ event.timestamp = SystemClock.elapsedRealtimeNanos();
+ return event;
+ }
+
+
+ public static void setSensorType(Sensor sensor, int type, String strType) throws Exception {
+ Method setter = Sensor.class.getDeclaredMethod("setType", Integer.TYPE);
+ setter.setAccessible(true);
+ setter.invoke(sensor, type);
+ if (strType != null) {
+ Field f = sensor.getClass().getDeclaredField("mStringType");
+ f.setAccessible(true);
+ f.set(sensor, strType);
+ }
+ }
+
+ public static Sensor createSensor(int type, String strType) throws Exception {
+ Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor();
+ constr.setAccessible(true);
+ Sensor sensor = constr.newInstance();
+ setSensorType(sensor, type, strType);
+ return sensor;
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java b/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
index 6b0798b..e25c1c6 100644
--- a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
@@ -16,29 +16,19 @@
package com.android.server.display.whitebalance;
-import com.android.internal.R;
-import com.google.common.collect.ImmutableList;
-
import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.doAnswer;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.eq;
-import org.mockito.stubbing.Answer;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
import android.content.ContextWrapper;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Handler;
import android.os.Looper;
@@ -46,15 +36,21 @@
import androidx.test.InstrumentationRegistry;
+import com.android.internal.R;
+import com.android.server.display.TestUtils;
+import com.android.server.display.whitebalance.AmbientFilter;
+
+import com.google.common.collect.ImmutableList;
+
import org.junit.Before;
-import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
import java.util.List;
@RunWith(JUnit4.class)
@@ -80,8 +76,8 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mLightSensor = createSensor(Sensor.TYPE_LIGHT, null);
- mAmbientColorSensor = createSensor(AMBIENT_COLOR_TYPE, AMBIENT_COLOR_TYPE_STR);
+ mLightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, null);
+ mAmbientColorSensor = TestUtils.createSensor(AMBIENT_COLOR_TYPE, AMBIENT_COLOR_TYPE_STR);
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
mResourcesSpy = spy(mContextSpy.getResources());
when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
@@ -460,25 +456,6 @@
}
}
- private void setSensorType(Sensor sensor, int type, String strType) throws Exception {
- Method setter = Sensor.class.getDeclaredMethod("setType", Integer.TYPE);
- setter.setAccessible(true);
- setter.invoke(sensor, type);
- if (strType != null) {
- Field f = sensor.getClass().getDeclaredField("mStringType");
- f.setAccessible(true);
- f.set(sensor, strType);
- }
- }
-
- private Sensor createSensor(int type, String strType) throws Exception {
- Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor();
- constr.setAccessible(true);
- Sensor sensor = constr.newInstance();
- setSensorType(sensor, type, strType);
- return sensor;
- }
-
private TypedArray createTypedArray() throws Exception {
TypedArray mockArray = mock(TypedArray.class);
return mockArray;
diff --git a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientSensorTest.java b/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientSensorTest.java
index 6ff4f3b..3e3e535 100644
--- a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientSensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientSensorTest.java
@@ -28,15 +28,14 @@
import android.content.ContextWrapper;
import android.content.res.Resources;
import android.hardware.Sensor;
-import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Handler;
import android.os.Looper;
-import android.os.SystemClock;
import androidx.test.InstrumentationRegistry;
+import com.android.server.display.TestUtils;
import com.android.server.display.whitebalance.AmbientSensor.AmbientBrightnessSensor;
import com.android.server.display.whitebalance.AmbientSensor.AmbientColorTemperatureSensor;
@@ -50,9 +49,6 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -73,8 +69,8 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mLightSensor = createSensor(Sensor.TYPE_LIGHT, null);
- mAmbientColorSensor = createSensor(AMBIENT_COLOR_TYPE, AMBIENT_COLOR_TYPE_STR);
+ mLightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, null);
+ mAmbientColorSensor = TestUtils.createSensor(AMBIENT_COLOR_TYPE, AMBIENT_COLOR_TYPE_STR);
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
mResourcesSpy = spy(mContextSpy.getResources());
when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
@@ -96,7 +92,7 @@
// There should be no issues when we callback the listener, even if there is no callback
// set.
SensorEventListener listener = captor.getValue();
- listener.onSensorChanged(createSensorEvent(mLightSensor, 100));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 100));
}
@Test
@@ -122,7 +118,7 @@
verify(mSensorManagerMock).registerListener(captor.capture(), eq(mLightSensor),
anyInt(), eq(mHandler));
SensorEventListener listener = captor.getValue();
- listener.onSensorChanged(createSensorEvent(mLightSensor, luxValue));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, luxValue));
assertTrue(changeSignal.await(5, TimeUnit.SECONDS));
assertEquals(luxValue, luxReturned[0]);
}
@@ -155,39 +151,8 @@
verify(mSensorManagerMock).registerListener(captor.capture(), eq(mAmbientColorSensor),
anyInt(), eq(mHandler));
SensorEventListener listener = captor.getValue();
- listener.onSensorChanged(createSensorEvent(mAmbientColorSensor, colorTempValue));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mAmbientColorSensor, colorTempValue));
assertTrue(changeSignal.await(5, TimeUnit.SECONDS));
assertEquals(colorTempValue, colorTempReturned[0]);
}
-
- private SensorEvent createSensorEvent(Sensor sensor, int lux) throws Exception {
- final Constructor<SensorEvent> constructor =
- SensorEvent.class.getDeclaredConstructor(int.class);
- constructor.setAccessible(true);
- final SensorEvent event = constructor.newInstance(1);
- event.sensor = sensor;
- event.values[0] = lux;
- event.timestamp = SystemClock.elapsedRealtimeNanos();
- return event;
- }
-
-
- private void setSensorType(Sensor sensor, int type, String strType) throws Exception {
- Method setter = Sensor.class.getDeclaredMethod("setType", Integer.TYPE);
- setter.setAccessible(true);
- setter.invoke(sensor, type);
- if (strType != null) {
- Field f = sensor.getClass().getDeclaredField("mStringType");
- f.setAccessible(true);
- f.set(sensor, strType);
- }
- }
-
- private Sensor createSensor(int type, String strType) throws Exception {
- Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor();
- constr.setAccessible(true);
- Sensor sensor = constr.newInstance();
- setSensorType(sensor, type, strType);
- return sensor;
- }
}
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/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index 7c22350..fab6b7f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -52,11 +52,13 @@
import android.media.AudioAttributes;
import android.metrics.LogMaker;
import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.notification.Adjustment;
import android.service.notification.StatusBarNotification;
+import android.widget.RemoteViews;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -114,7 +116,9 @@
when(mMockContext.getResources()).thenReturn(getContext().getResources());
when(mMockContext.getPackageManager()).thenReturn(mPm);
- when(mMockContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
+ ApplicationInfo appInfo = new ApplicationInfo();
+ appInfo.targetSdkVersion = Build.VERSION_CODES.O;
+ when(mMockContext.getApplicationInfo()).thenReturn(appInfo);
}
private StatusBarNotification getNotification(String pkg, boolean noisy, boolean defaultSound,
@@ -168,6 +172,28 @@
return new StatusBarNotification(pkg, pkg, id1, tag1, uid, uid, n, mUser, null, uid);
}
+ private StatusBarNotification getStyledNotification(boolean customContent, boolean customBig,
+ boolean customHeadsUp, Notification.Style style) {
+ final Builder builder = new Builder(mMockContext)
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon);
+ if (style != null) {
+ builder.setStyle(style);
+ }
+ if (customContent) {
+ builder.setCustomContentView(mock(RemoteViews.class));
+ }
+ if (customBig) {
+ builder.setCustomBigContentView(mock(RemoteViews.class));
+ }
+ if (customHeadsUp) {
+ builder.setCustomHeadsUpContentView(mock(RemoteViews.class));
+ }
+
+ Notification n = builder.build();
+ return new StatusBarNotification(pkg, pkg, id1, tag1, uid, uid, n, mUser, null, uid);
+ }
+
//
// Tests
//
@@ -999,4 +1025,74 @@
assertEquals(IMPORTANCE_LOW, record.getImportance());
}
+
+ @Test
+ public void testHasUndecoratedRemoteViews_NoRemoteViews() {
+ StatusBarNotification sbn = getStyledNotification(false, false, false, null);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+ assertFalse("false positive detection", record.hasUndecoratedRemoteView());
+ }
+
+ @Test
+ public void testHasUndecoratedRemoteViews_NoRemoteViewsWithStyle() {
+ StatusBarNotification sbn = getStyledNotification(false, false, false,
+ new Notification.BigPictureStyle());
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+ assertFalse("false positive detection", record.hasUndecoratedRemoteView());
+ }
+
+ @Test
+ public void testHasUndecoratedRemoteViews_UndecoratedContent() {
+ StatusBarNotification sbn = getStyledNotification(true, false, false, null);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+ assertTrue("false negative detection", record.hasUndecoratedRemoteView());
+ }
+
+
+ @Test
+ public void testHasUndecoratedRemoteViews_UndecoratedBig() {
+ StatusBarNotification sbn = getStyledNotification(false, true, false, null);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+ assertTrue("false negative detection", record.hasUndecoratedRemoteView());
+ }
+
+
+ @Test
+ public void testHasUndecoratedRemoteViews_UndecoratedHeadsup() {
+ StatusBarNotification sbn = getStyledNotification(false, false, true, null);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+ assertTrue("false negative detection", record.hasUndecoratedRemoteView());
+ }
+
+ @Test
+ public void testHasUndecoratedRemoteViews_DecoratedRemoteViews() {
+ StatusBarNotification sbn = getStyledNotification(true, true, true,
+ new Notification.DecoratedCustomViewStyle());
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+ assertFalse("false positive detection", record.hasUndecoratedRemoteView());
+ }
+
+ @Test
+ public void testHasUndecoratedRemoteViews_DecoratedMediaRemoteViews() {
+ StatusBarNotification sbn = getStyledNotification(true, true, true,
+ new Notification.DecoratedMediaCustomViewStyle());
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+ assertFalse("false positive detection", record.hasUndecoratedRemoteView());
+ }
+
+ @Test
+ public void testHasUndecoratedRemoteViews_UndecoratedWrongStyle() {
+ StatusBarNotification sbn = getStyledNotification(true, true, true,
+ new Notification.BigPictureStyle());
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+ assertTrue("false negative detection", record.hasUndecoratedRemoteView());
+ }
}
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/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 0110e94..bb80e5e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -20,9 +20,11 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -124,6 +126,7 @@
mDisplayContent = spy(mDisplayContent);
mWindow = createDropTargetWindow("Drag test window", 0);
doReturn(mWindow).when(mDisplayContent).getTouchableWinAtPointLocked(0, 0);
+ when(mWm.mInputManager.transferTouchFocus(any(), any())).thenReturn(true);
synchronized (mWm.mGlobalLock) {
mWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow);
@@ -177,6 +180,7 @@
.setFormat(PixelFormat.TRANSLUCENT)
.build();
+ assertTrue(mWm.mInputManager.transferTouchFocus(null, null));
mToken = mTarget.performDrag(
new SurfaceSession(), 0, 0, mWindow.mClient, flag, surface, 0, 0, 0, 0, 0,
data);
diff --git a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
index dec88f0..af262b7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
+++ b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
@@ -97,11 +97,6 @@
}
@Override
- public SurfaceControl.Transaction transferTouchFocus(IBinder fromToken, IBinder toToken) {
- return this;
- }
-
- @Override
public SurfaceControl.Transaction setGeometry(SurfaceControl sc, Rect sourceCrop,
Rect destFrame, @Surface.Rotation int orientation) {
return this;
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
index 714d2f2..eb351b6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
@@ -18,6 +18,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
@@ -57,6 +58,10 @@
assertNotNull(mWm.mTaskPositioningController);
mTarget = mWm.mTaskPositioningController;
+ when(mWm.mInputManager.transferTouchFocus(
+ any(InputChannel.class),
+ any(InputChannel.class))).thenReturn(true);
+
mWindow = createWindow(null, TYPE_BASE_APPLICATION, "window");
mWindow.mInputChannel = new InputChannel();
synchronized (mWm.mGlobalLock) {
diff --git a/services/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/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 11c0e4a..9fc1949 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -136,17 +136,16 @@
}
// During system reboot, add a DEVICE_SHUTDOWN event to the end of event list, the timestamp
- // is last time UsageStatsDatabase is persisted to disk.
+ // is last time UsageStatsDatabase is persisted to disk or the last event's time whichever
+ // is higher (because the file system timestamp is round down to integral seconds).
// Also add a DEVICE_STARTUP event with current system timestamp.
final IntervalStats currentDailyStats = mCurrentStats[INTERVAL_DAILY];
if (currentDailyStats != null) {
- // File system timestamp only has precision of 1 second, add 1000ms to make up
- // for the loss of round up.
- final Event shutdownEvent =
- new Event(DEVICE_SHUTDOWN, currentDailyStats.lastTimeSaved + 1000);
+ final Event shutdownEvent = new Event(DEVICE_SHUTDOWN,
+ Math.max(currentDailyStats.lastTimeSaved, currentDailyStats.endTime));
shutdownEvent.mPackage = Event.DEVICE_EVENT_PACKAGE_NAME;
currentDailyStats.addEvent(shutdownEvent);
- final Event startupEvent = new Event(DEVICE_STARTUP, currentTimeMillis);
+ final Event startupEvent = new Event(DEVICE_STARTUP, System.currentTimeMillis());
startupEvent.mPackage = Event.DEVICE_EVENT_PACKAGE_NAME;
currentDailyStats.addEvent(startupEvent);
}
diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java
index 00c7548..8122374 100644
--- a/services/usb/java/com/android/server/usb/UsbHostManager.java
+++ b/services/usb/java/com/android/server/usb/UsbHostManager.java
@@ -486,7 +486,7 @@
/* Opens the specified USB device */
public ParcelFileDescriptor openDevice(String deviceAddress, UsbUserSettingsManager settings,
- String packageName, int uid) {
+ String packageName, int pid, int uid) {
synchronized (mLock) {
if (isBlackListed(deviceAddress)) {
throw new SecurityException("USB device is on a restricted bus");
@@ -498,7 +498,7 @@
"device " + deviceAddress + " does not exist or is restricted");
}
- settings.checkPermission(device, packageName, uid);
+ settings.checkPermission(device, packageName, pid, uid);
return nativeOpenDevice(deviceAddress);
}
}
diff --git a/services/usb/java/com/android/server/usb/UsbSerialReader.java b/services/usb/java/com/android/server/usb/UsbSerialReader.java
index 8ca77f0..077d6b9 100644
--- a/services/usb/java/com/android/server/usb/UsbSerialReader.java
+++ b/services/usb/java/com/android/server/usb/UsbSerialReader.java
@@ -93,7 +93,7 @@
UserHandle.getUserId(uid));
if (mDevice instanceof UsbDevice) {
- settings.checkPermission((UsbDevice) mDevice, packageName, uid);
+ settings.checkPermission((UsbDevice) mDevice, packageName, pid, uid);
} else {
settings.checkPermission((UsbAccessory) mDevice, uid);
}
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index 4be68b8..13275f3 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -249,6 +249,7 @@
if (mHostManager != null) {
if (deviceName != null) {
int uid = Binder.getCallingUid();
+ int pid = Binder.getCallingPid();
int user = UserHandle.getUserId(uid);
long ident = clearCallingIdentity();
@@ -256,7 +257,7 @@
synchronized (mLock) {
if (mUserManager.isSameProfileGroup(user, mCurrentUserId)) {
fd = mHostManager.openDevice(deviceName, getSettingsForUser(user),
- packageName, uid);
+ packageName, pid, uid);
} else {
Slog.w(TAG, "Cannot open " + deviceName + " for user " + user
+ " as user is not active.");
@@ -350,11 +351,12 @@
@Override
public boolean hasDevicePermission(UsbDevice device, String packageName) {
final int uid = Binder.getCallingUid();
+ final int pid = Binder.getCallingPid();
final int userId = UserHandle.getUserId(uid);
final long token = Binder.clearCallingIdentity();
try {
- return getSettingsForUser(userId).hasPermission(device, packageName, uid);
+ return getSettingsForUser(userId).hasPermission(device, packageName, pid, uid);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -376,11 +378,12 @@
@Override
public void requestDevicePermission(UsbDevice device, String packageName, PendingIntent pi) {
final int uid = Binder.getCallingUid();
+ final int pid = Binder.getCallingPid();
final int userId = UserHandle.getUserId(uid);
final long token = Binder.clearCallingIdentity();
try {
- getSettingsForUser(userId).requestPermission(device, packageName, pi, uid);
+ getSettingsForUser(userId).requestPermission(device, packageName, pi, pid, uid);
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java b/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java
index 84add88..e1bfb8a 100644
--- a/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java
+++ b/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java
@@ -127,11 +127,12 @@
* Check for camera permission of the calling process.
*
* @param packageName Package name of the caller.
+ * @param pid Linux pid of the calling process.
* @param uid Linux uid of the calling process.
*
* @return True in case camera permission is available, False otherwise.
*/
- private boolean isCameraPermissionGranted(String packageName, int uid) {
+ private boolean isCameraPermissionGranted(String packageName, int pid, int uid) {
int targetSdkVersion = android.os.Build.VERSION_CODES.P;
try {
ApplicationInfo aInfo = mPackageManager.getApplicationInfo(packageName, 0);
@@ -147,7 +148,8 @@
}
if (targetSdkVersion >= android.os.Build.VERSION_CODES.P) {
- int allowed = mUserContext.checkCallingPermission(android.Manifest.permission.CAMERA);
+ int allowed = mUserContext.checkPermission(android.Manifest.permission.CAMERA, pid,
+ uid);
if (android.content.pm.PackageManager.PERMISSION_DENIED == allowed) {
Slog.i(TAG, "Camera permission required for USB video class devices");
return false;
@@ -157,9 +159,9 @@
return true;
}
- public boolean hasPermission(UsbDevice device, String packageName, int uid) {
+ public boolean hasPermission(UsbDevice device, String packageName, int pid, int uid) {
if (isCameraDevicePresent(device)) {
- if (!isCameraPermissionGranted(packageName, uid)) {
+ if (!isCameraPermissionGranted(packageName, pid, uid)) {
return false;
}
}
@@ -171,8 +173,8 @@
return mUsbPermissionManager.hasPermission(accessory, uid);
}
- public void checkPermission(UsbDevice device, String packageName, int uid) {
- if (!hasPermission(device, packageName, uid)) {
+ public void checkPermission(UsbDevice device, String packageName, int pid, int uid) {
+ if (!hasPermission(device, packageName, pid, uid)) {
throw new SecurityException("User has not given " + uid + "/" + packageName
+ " permission to access device " + device.getDeviceName());
}
@@ -206,11 +208,12 @@
accessory, canBeDefault, packageName, uid, mUserContext, pi);
}
- public void requestPermission(UsbDevice device, String packageName, PendingIntent pi, int uid) {
+ public void requestPermission(UsbDevice device, String packageName, PendingIntent pi, int pid,
+ int uid) {
Intent intent = new Intent();
// respond immediately if permission has already been granted
- if (hasPermission(device, packageName, uid)) {
+ if (hasPermission(device, packageName, pid, uid)) {
intent.putExtra(UsbManager.EXTRA_DEVICE, device);
intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true);
try {
@@ -221,7 +224,7 @@
return;
}
if (isCameraDevicePresent(device)) {
- if (!isCameraPermissionGranted(packageName, uid)) {
+ if (!isCameraPermissionGranted(packageName, pid, uid)) {
intent.putExtra(UsbManager.EXTRA_DEVICE, device);
intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false);
try {
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 2f37c2a..444c538 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -3018,6 +3018,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";
+
+ /**
* Controls time in milli seconds until DcTracker reevaluates 5G connection state.
* @hide
*/
@@ -3848,6 +3896,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);
/* Default value is 1 hour. */
sDefaults.putLong(KEY_5G_WATCHDOG_TIME_MS_LONG, 3600000);
sDefaults.putAll(Gps.getDefaults());
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index cfb8fb4..4046f49 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -154,7 +154,7 @@
*/
@ChangeId
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
- private static final long CALLBACK_ON_MORE_ERROR_CODE_CHANGE = 130595455L;
+ public static final long CALLBACK_ON_MORE_ERROR_CODE_CHANGE = 130595455L;
/**
* The key to use when placing the result of {@link #requestModemActivityInfo(ResultReceiver)}
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/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index c42201f..be22c9e 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -269,7 +269,8 @@
// Then fail APP_A below the threshold
for (int i = 0; i < watchdog.getTriggerFailureCount() - 1; i++) {
- watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+ watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
+ PackageWatchdog.FAILURE_REASON_UNKNOWN);
}
// Run handler so package failures are dispatched to observers
@@ -296,7 +297,8 @@
// Then fail APP_C (not observed) above the threshold
for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
- watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_C, VERSION_CODE)));
+ watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_C, VERSION_CODE)),
+ PackageWatchdog.FAILURE_REASON_UNKNOWN);
}
// Run handler so package failures are dispatched to observers
@@ -331,7 +333,8 @@
// Then fail APP_A (different version) above the threshold
for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
watchdog.onPackageFailure(Arrays.asList(
- new VersionedPackage(APP_A, differentVersionCode)));
+ new VersionedPackage(APP_A, differentVersionCode)),
+ PackageWatchdog.FAILURE_REASON_UNKNOWN);
}
// Run handler so package failures are dispatched to observers
@@ -372,7 +375,8 @@
watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
new VersionedPackage(APP_B, VERSION_CODE),
new VersionedPackage(APP_C, VERSION_CODE),
- new VersionedPackage(APP_D, VERSION_CODE)));
+ new VersionedPackage(APP_D, VERSION_CODE)),
+ PackageWatchdog.FAILURE_REASON_UNKNOWN);
}
// Run handler so package failures are dispatched to observers
@@ -422,7 +426,8 @@
// Then fail APP_A above the threshold
for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
- watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+ watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
+ PackageWatchdog.FAILURE_REASON_UNKNOWN);
}
// Run handler so package failures are dispatched to observers
mTestLooper.dispatchAll();
@@ -439,7 +444,8 @@
// Then fail APP_A again above the threshold
for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
- watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+ watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
+ PackageWatchdog.FAILURE_REASON_UNKNOWN);
}
// Run handler so package failures are dispatched to observers
mTestLooper.dispatchAll();
@@ -456,7 +462,8 @@
// Then fail APP_A again above the threshold
for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
- watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+ watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
+ PackageWatchdog.FAILURE_REASON_UNKNOWN);
}
// Run handler so package failures are dispatched to observers
mTestLooper.dispatchAll();
@@ -473,7 +480,8 @@
// Then fail APP_A again above the threshold
for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
- watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+ watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
+ PackageWatchdog.FAILURE_REASON_UNKNOWN);
}
// Run handler so package failures are dispatched to observers
mTestLooper.dispatchAll();
@@ -500,7 +508,8 @@
// Then fail APP_A above the threshold
for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
- watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+ watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
+ PackageWatchdog.FAILURE_REASON_UNKNOWN);
}
// Run handler so package failures are dispatched to observers
@@ -746,6 +755,44 @@
assertEquals(APP_A, observer.mFailedPackages.get(0));
}
+ /** Test that observers execute correctly for different failure reasons */
+ @Test
+ public void testFailureReasons() {
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
+ TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
+ TestObserver observer3 = new TestObserver(OBSERVER_NAME_3);
+ TestObserver observer4 = new TestObserver(OBSERVER_NAME_4);
+
+ watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.startObservingHealth(observer2, Arrays.asList(APP_B), SHORT_DURATION);
+ watchdog.startObservingHealth(observer3, Arrays.asList(APP_C), SHORT_DURATION);
+ watchdog.startObservingHealth(observer4, Arrays.asList(APP_D), SHORT_DURATION);
+
+ for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
+ watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
+ PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
+ watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)),
+ PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
+ watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_C, VERSION_CODE)),
+ PackageWatchdog.FAILURE_REASON_APP_CRASH);
+ watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_D, VERSION_CODE)),
+ PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING);
+ }
+
+ // Run handler so requests are dispatched to the controller
+ mTestLooper.dispatchAll();
+
+ assertTrue(observer1.getLastFailureReason()
+ == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
+ assertTrue(observer2.getLastFailureReason()
+ == PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
+ assertTrue(observer3.getLastFailureReason()
+ == PackageWatchdog.FAILURE_REASON_APP_CRASH);
+ assertTrue(observer4.getLastFailureReason()
+ == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING);
+ }
+
private void adoptShellPermissions(String... permissions) {
InstrumentationRegistry
.getInstrumentation()
@@ -801,6 +848,7 @@
private static class TestObserver implements PackageHealthObserver {
private final String mName;
private int mImpact;
+ private int mLastFailureReason;
final List<String> mFailedPackages = new ArrayList<>();
TestObserver(String name) {
@@ -817,14 +865,19 @@
return mImpact;
}
- public boolean execute(VersionedPackage versionedPackage) {
+ public boolean execute(VersionedPackage versionedPackage, int failureReason) {
mFailedPackages.add(versionedPackage.getPackageName());
+ mLastFailureReason = failureReason;
return true;
}
public String getName() {
return mName;
}
+
+ public int getLastFailureReason() {
+ return mLastFailureReason;
+ }
}
private static class TestController extends ExplicitHealthCheckController {
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/p2p/WifiP2pConfig.java b/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java
index c3cfb02..80776fe 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java
@@ -28,6 +28,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.nio.charset.StandardCharsets;
import java.util.regex.PatternSyntaxException;
/**
@@ -228,6 +229,10 @@
private static final MacAddress MAC_ANY_ADDRESS =
MacAddress.fromString("02:00:00:00:00:00");
+ /**
+ * Maximum number of bytes allowed for a SSID.
+ */
+ private static final int MAX_SSID_BYTES = 32;
private MacAddress mDeviceAddress = MAC_ANY_ADDRESS;
private String mNetworkName = "";
@@ -279,6 +284,10 @@
throw new IllegalArgumentException(
"network name must be non-empty.");
}
+ if (networkName.getBytes(StandardCharsets.UTF_8).length > MAX_SSID_BYTES) {
+ throw new IllegalArgumentException(
+ "network name exceeds " + MAX_SSID_BYTES + " bytes.");
+ }
try {
if (!networkName.matches("^DIRECT-[a-zA-Z0-9]{2}.*")) {
throw new IllegalArgumentException(
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>
diff --git a/wifi/tests/src/android/net/wifi/p2p/WifiP2pConfigTest.java b/wifi/tests/src/android/net/wifi/p2p/WifiP2pConfigTest.java
index 41f109a..6199325 100644
--- a/wifi/tests/src/android/net/wifi/p2p/WifiP2pConfigTest.java
+++ b/wifi/tests/src/android/net/wifi/p2p/WifiP2pConfigTest.java
@@ -53,6 +53,13 @@
fail("Unexpected IllegalArgumentException");
}
+ // sunny case with maximum bytes for the network name
+ try {
+ b.setNetworkName("DIRECT-abcdefghijklmnopqrstuvwxy");
+ } catch (IllegalArgumentException e) {
+ fail("Unexpected IllegalArgumentException");
+ }
+
// less than 9 characters.
try {
b.setNetworkName("DIRECT-z");
@@ -77,6 +84,12 @@
b.setNetworkName("direct-a?");
fail("expected IllegalArgumentException");
} catch (IllegalArgumentException e) { }
+
+ // over maximum bytes
+ try {
+ b.setNetworkName("DIRECT-abcdefghijklmnopqrstuvwxyz");
+ fail("expected IllegalArgumentException");
+ } catch (IllegalArgumentException e) { }
}
/**