Merge "TextView: Support multiple textAssist menu items."
diff --git a/api/current.txt b/api/current.txt
index 8b3c740..55f8751 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -40445,6 +40445,8 @@
field public static final int ENCODING_7BIT = 1; // 0x1
field public static final int ENCODING_8BIT = 2; // 0x2
field public static final int ENCODING_UNKNOWN = 0; // 0x0
+ field public static final java.lang.String FORMAT_3GPP = "3gpp";
+ field public static final java.lang.String FORMAT_3GPP2 = "3gpp2";
field public static final int MAX_USER_DATA_BYTES = 140; // 0x8c
field public static final int MAX_USER_DATA_BYTES_WITH_HEADER = 134; // 0x86
field public static final int MAX_USER_DATA_SEPTETS = 160; // 0xa0
diff --git a/api/system-current.txt b/api/system-current.txt
index ee23b6d..c6a6fdd 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -43965,6 +43965,8 @@
field public static final int ENCODING_7BIT = 1; // 0x1
field public static final int ENCODING_8BIT = 2; // 0x2
field public static final int ENCODING_UNKNOWN = 0; // 0x0
+ field public static final java.lang.String FORMAT_3GPP = "3gpp";
+ field public static final java.lang.String FORMAT_3GPP2 = "3gpp2";
field public static final int MAX_USER_DATA_BYTES = 140; // 0x8c
field public static final int MAX_USER_DATA_BYTES_WITH_HEADER = 134; // 0x86
field public static final int MAX_USER_DATA_SEPTETS = 160; // 0xa0
diff --git a/api/system-removed.txt b/api/system-removed.txt
index 639877f..e9afa70 100644
--- a/api/system-removed.txt
+++ b/api/system-removed.txt
@@ -330,7 +330,6 @@
method public deprecated java.util.List<android.net.wifi.BatchedScanResult> getBatchedScanResults();
method public android.net.wifi.WifiConnectionStatistics getConnectionStatistics();
method public deprecated boolean isBatchedScanSupported();
- method public deprecated boolean setWifiApEnabled(android.net.wifi.WifiConfiguration, boolean);
method public deprecated boolean startLocationRestrictedScan(android.os.WorkSource);
}
diff --git a/api/test-current.txt b/api/test-current.txt
index 9a241ad..5e55bc3 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -32046,17 +32046,18 @@
public static final class StrictMode.ViolationInfo implements android.os.Parcelable {
ctor public StrictMode.ViolationInfo();
ctor public StrictMode.ViolationInfo(java.lang.Throwable, int);
- ctor public StrictMode.ViolationInfo(java.lang.String, java.lang.Throwable, int);
+ ctor public deprecated StrictMode.ViolationInfo(java.lang.String, java.lang.Throwable, int);
ctor public StrictMode.ViolationInfo(android.os.Parcel);
ctor public StrictMode.ViolationInfo(android.os.Parcel, boolean);
method public int describeContents();
method public void dump(android.util.Printer, java.lang.String);
+ method public java.lang.String getMessagePrefix();
+ method public java.lang.String getStackTrace();
+ method public java.lang.String getViolationDetails();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.os.StrictMode.ViolationInfo> CREATOR;
field public java.lang.String broadcastIntentAction;
- field public final android.app.ApplicationErrorReport.CrashInfo crashInfo;
field public int durationMillis;
- field public final java.lang.String message;
field public int numAnimationsRunning;
field public long numInstances;
field public final int policy;
@@ -40840,6 +40841,8 @@
field public static final int ENCODING_7BIT = 1; // 0x1
field public static final int ENCODING_8BIT = 2; // 0x2
field public static final int ENCODING_UNKNOWN = 0; // 0x0
+ field public static final java.lang.String FORMAT_3GPP = "3gpp";
+ field public static final java.lang.String FORMAT_3GPP2 = "3gpp2";
field public static final int MAX_USER_DATA_BYTES = 140; // 0x8c
field public static final int MAX_USER_DATA_BYTES_WITH_HEADER = 134; // 0x86
field public static final int MAX_USER_DATA_SEPTETS = 160; // 0xa0
diff --git a/cmds/incidentd/tests/Reporter_test.cpp b/cmds/incidentd/tests/Reporter_test.cpp
index 5d074bc..8d99fc7 100644
--- a/cmds/incidentd/tests/Reporter_test.cpp
+++ b/cmds/incidentd/tests/Reporter_test.cpp
@@ -32,8 +32,6 @@
using namespace std;
using ::testing::StrEq;
using ::testing::Test;
-using ::testing::internal::CaptureStdout;
-using ::testing::internal::GetCapturedStdout;
class TestListener : public IIncidentReportStatusListener
{
@@ -139,20 +137,24 @@
}
TEST_F(ReporterTest, RunReportWithHeaders) {
+ TemporaryFile tf;
IncidentReportArgs args1, args2;
args1.addSection(1);
args2.addSection(2);
std::vector<int8_t> header {'a', 'b', 'c', 'd', 'e'};
args2.addHeader(header);
- sp<ReportRequest> r1 = new ReportRequest(args1, l, STDOUT_FILENO);
- sp<ReportRequest> r2 = new ReportRequest(args2, l, STDOUT_FILENO);
+ sp<ReportRequest> r1 = new ReportRequest(args1, l, tf.fd);
+ sp<ReportRequest> r2 = new ReportRequest(args2, l, tf.fd);
reporter->batch.add(r1);
reporter->batch.add(r2);
- CaptureStdout();
ASSERT_EQ(Reporter::REPORT_FINISHED, reporter->runReport());
- EXPECT_THAT(GetCapturedStdout(), StrEq("\n\x5" "abcde"));
+
+ string result;
+ ReadFileToString(tf.path, &result);
+ EXPECT_THAT(result, StrEq("\n\x5" "abcde"));
+
EXPECT_EQ(l->startInvoked, 2);
EXPECT_EQ(l->finishInvoked, 2);
EXPECT_TRUE(l->startSections.empty());
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index 8946aed..dd9a965 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -38,6 +38,7 @@
src/matchers/SimpleLogMatchingTracker.cpp \
src/metrics/CountAnomalyTracker.cpp \
src/metrics/MetricProducer.cpp \
+ src/metrics/EventMetricProducer.cpp \
src/metrics/CountMetricProducer.cpp \
src/metrics/DurationMetricProducer.cpp \
src/metrics/MetricsManager.cpp \
@@ -65,7 +66,8 @@
libselinux \
libutils \
libservices \
- libandroidfw
+ libandroidfw \
+ libprotoutil
# =========
# statsd
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.cpp b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
index bde3846..aff4768 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.cpp
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define DEBUG true // STOPSHIP if true
+#define DEBUG false // STOPSHIP if true
#include "Log.h"
#include "SimpleConditionTracker.h"
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index 9ca7d62..c16971a 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -120,7 +120,7 @@
int WAKE_LOCK_TAG_ID = 11;
int WAKE_LOCK_UID_KEY_ID = 1;
- int WAKE_LOCK_STATE_KEY = 2;
+ int WAKE_LOCK_STATE_KEY = 3;
int WAKE_LOCK_ACQUIRE_VALUE = 1;
int WAKE_LOCK_RELEASE_VALUE = 0;
@@ -167,7 +167,7 @@
keyMatcher->set_key(UID_PROCESS_STATE_UID_KEY);
metric->set_condition("SCREEN_IS_OFF");
- // Count wake lock, slice by uid, while SCREEN_IS_OFF and app in background
+ // Count wake lock, slice by uid, while SCREEN_IS_ON and app in background
metric = config.add_count_metric();
metric->set_metric_id(4);
metric->set_what("APP_GET_WL");
@@ -195,6 +195,11 @@
link->add_key_in_main()->set_key(WAKE_LOCK_UID_KEY_ID);
link->add_key_in_condition()->set_key(APP_USAGE_UID_KEY_ID);
+ // Add an EventMetric to log process state change events.
+ EventMetric* eventMetric = config.add_event_metric();
+ eventMetric->set_metric_id(6);
+ eventMetric->set_what("SCREEN_TURNED_ON");
+
// Event matchers............
LogEntryMatcher* eventMatcher = config.add_log_entry_matcher();
eventMatcher->set_name("SCREEN_TURNED_ON");
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index fb992c1..1a039f6 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -17,6 +17,7 @@
#include "logd/LogEvent.h"
#include <sstream>
+#include "stats_util.h"
namespace android {
namespace os {
@@ -24,6 +25,7 @@
using std::ostringstream;
using std::string;
+using android::util::ProtoOutputStream;
// We need to keep a copy of the android_log_event_list owned by this instance so that the char*
// for strings is not cleared before we can read them.
@@ -203,30 +205,24 @@
return result.str();
}
-void LogEvent::ToProto(EventMetricData* out) const {
- // TODO: Implement this when we have the ProtoOutputStream version.
-
- // set timestamp of the event.
- out->set_timestamp_nanos(mTimestampNs);
-
- // uint64_t token = proto->StartObject(EventMetricData.FIELD);
+void LogEvent::ToProto(ProtoOutputStream& proto) const {
+ long long atomToken = proto.start(TYPE_MESSAGE + mTagId);
const size_t N = mElements.size();
for (size_t i=0; i<N; i++) {
const int key = i + 1;
const android_log_list_element& elem = mElements[i];
if (elem.type == EVENT_TYPE_INT) {
- // proto->Write(key, elem.data.int32);
+ proto.write(TYPE_INT32 + key, elem.data.int32);
} else if (elem.type == EVENT_TYPE_LONG) {
- // proto->Write(key, elem.data.int64);
+ proto.write(TYPE_INT64 + key, (long long)elem.data.int64);
} else if (elem.type == EVENT_TYPE_FLOAT) {
- // proto->Write(key, elem.data.float32);
+ proto.write(TYPE_FLOAT + key, elem.data.float32);
} else if (elem.type == EVENT_TYPE_STRING) {
- // proto->Write(key, elem.data.string);
+ proto.write(TYPE_STRING + key, elem.data.string);
}
}
-
- //proto->EndObject(token);
+ proto.end(atomToken);
}
} // namespace statsd
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index 4102675..9ef20ea 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -18,9 +18,10 @@
#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
-#include <utils/Errors.h>
+#include <android/util/ProtoOutputStream.h>
#include <log/log_event_list.h>
#include <log/log_read.h>
+#include <utils/Errors.h>
#include <memory>
#include <string>
@@ -80,10 +81,9 @@
string ToString() const;
/**
- * Write this object as an EventMetricData proto object.
- * TODO: Use the streaming output generator to do this instead of this proto lite object?
+ * Write this object to a ProtoOutputStream.
*/
- void ToProto(EventMetricData* out) const;
+ void ToProto(android::util::ProtoOutputStream& out) const;
/*
* Get a KeyValuePair proto object.
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp
new file mode 100644
index 0000000..8b3f405
--- /dev/null
+++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define DEBUG true // STOPSHIP if true
+#include "Log.h"
+
+#include "EventMetricProducer.h"
+#include "stats_util.h"
+
+#include <cutils/log.h>
+#include <limits.h>
+#include <stdlib.h>
+
+using android::util::ProtoOutputStream;
+using std::map;
+using std::string;
+using std::unordered_map;
+using std::vector;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// for StatsLogReport
+const int FIELD_ID_METRIC_ID = 1;
+const int FIELD_ID_START_REPORT_NANOS = 2;
+const int FIELD_ID_END_REPORT_NANOS = 2;
+const int FIELD_ID_EVENT_METRICS = 4;
+// for EventMetricData
+const int FIELD_ID_TIMESTAMP_NANOS = 1;
+const int FIELD_ID_STATS_EVENTS = 2;
+// for CountMetricDataWrapper
+const int FIELD_ID_DATA = 1;
+
+EventMetricProducer::EventMetricProducer(const EventMetric& metric, const int conditionIndex,
+ const sp<ConditionWizard>& wizard)
+ // TODO: Pass in the start time from MetricsManager, instead of calling time() here.
+ : MetricProducer((time(nullptr) * NANO_SECONDS_IN_A_SECOND), conditionIndex, wizard),
+ mMetric(metric) {
+ if (metric.links().size() > 0) {
+ mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
+ metric.links().end());
+ mConditionSliced = true;
+ }
+
+ startNewProtoOutputStream(mStartTimeNs);
+
+ VLOG("metric %lld created. bucket size %lld start_time: %lld", metric.metric_id(),
+ (long long)mBucketSizeNs, (long long)mStartTimeNs);
+}
+
+EventMetricProducer::~EventMetricProducer() {
+ VLOG("~EventMetricProducer() called");
+}
+
+void EventMetricProducer::startNewProtoOutputStream(long long startTime) {
+ mProto = std::make_unique<ProtoOutputStream>();
+ // TODO: We need to auto-generate the field IDs for StatsLogReport, EventMetricData,
+ // and StatsEvent.
+ mProto->write(TYPE_INT32 + FIELD_ID_METRIC_ID, mMetric.metric_id());
+ mProto->write(TYPE_INT64 + FIELD_ID_START_REPORT_NANOS, startTime);
+ mProtoToken = mProto->start(TYPE_MESSAGE + FIELD_ID_EVENT_METRICS);
+}
+
+void EventMetricProducer::finish() {
+}
+
+void EventMetricProducer::onSlicedConditionMayChange() {
+}
+
+StatsLogReport EventMetricProducer::onDumpReport() {
+ long long endTime = time(nullptr) * NANO_SECONDS_IN_A_SECOND;
+ mProto->end(mProtoToken);
+ mProto->write(TYPE_INT64 + FIELD_ID_END_REPORT_NANOS, endTime);
+
+ size_t bufferSize = mProto->size();
+ VLOG("metric %lld dump report now... proto size: %zu ", mMetric.metric_id(), bufferSize);
+ std::unique_ptr<uint8_t[]> buffer(new uint8_t[bufferSize]);
+ size_t pos = 0;
+ auto it = mProto->data();
+ while (it.readBuffer() != NULL) {
+ size_t toRead = it.currentToRead();
+ std::memcpy(&buffer[pos], it.readBuffer(), toRead);
+ pos += toRead;
+ it.rp()->move(toRead);
+ }
+
+ startNewProtoOutputStream(endTime);
+
+ // TODO: Once we migrate all MetricProducers to use ProtoOutputStream, we should return this:
+ // return std::move(buffer);
+ return StatsLogReport();
+}
+
+void EventMetricProducer::onConditionChanged(const bool conditionMet) {
+ VLOG("Metric %lld onConditionChanged", mMetric.metric_id());
+ mCondition = conditionMet;
+}
+
+void EventMetricProducer::onMatchedLogEventInternal(
+ const size_t matcherIndex, const HashableDimensionKey& eventKey,
+ const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition,
+ const LogEvent& event) {
+
+ if (!condition) {
+ return;
+ }
+
+ long long wrapperToken = mProto->start(TYPE_MESSAGE + FIELD_ID_DATA);
+ mProto->write(TYPE_INT64 + FIELD_ID_TIMESTAMP_NANOS, (long long)event.GetTimestampNs());
+ long long eventToken = mProto->start(TYPE_MESSAGE + FIELD_ID_STATS_EVENTS);
+ event.ToProto(*mProto);
+ mProto->end(eventToken);
+ mProto->end(wrapperToken);
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.h b/cmds/statsd/src/metrics/EventMetricProducer.h
new file mode 100644
index 0000000..879175c
--- /dev/null
+++ b/cmds/statsd/src/metrics/EventMetricProducer.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef EVENT_METRIC_PRODUCER_H
+#define EVENT_METRIC_PRODUCER_H
+
+#include <unordered_map>
+
+#include <android/util/ProtoOutputStream.h>
+#include "../condition/ConditionTracker.h"
+#include "../matchers/matcher_util.h"
+#include "MetricProducer.h"
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "stats_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class EventMetricProducer : public MetricProducer {
+public:
+ // TODO: Pass in the start time from MetricsManager, it should be consistent for all metrics.
+ EventMetricProducer(const EventMetric& eventMetric, const int conditionIndex,
+ const sp<ConditionWizard>& wizard);
+
+ virtual ~EventMetricProducer();
+
+ void onMatchedLogEventInternal(const size_t matcherIndex, const HashableDimensionKey& eventKey,
+ const std::map<std::string, HashableDimensionKey>& conditionKey,
+ bool condition, const LogEvent& event) override;
+
+ void onConditionChanged(const bool conditionMet) override;
+
+ void finish() override;
+
+ StatsLogReport onDumpReport() override;
+
+ void onSlicedConditionMayChange() override;
+
+ // TODO: Implement this later.
+ virtual void notifyAppUpgrade(const string& apk, const int uid, const int version) override{};
+
+private:
+ const EventMetric mMetric;
+
+ std::unique_ptr<android::util::ProtoOutputStream> mProto;
+
+ long long mProtoToken;
+
+ void startNewProtoOutputStream(long long timestamp);
+};
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+#endif // EVENT_METRIC_PRODUCER_H
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 23071aa..e90f998 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -20,6 +20,7 @@
#include "../matchers/SimpleLogMatchingTracker.h"
#include "CountMetricProducer.h"
#include "DurationMetricProducer.h"
+#include "EventMetricProducer.h"
#include "stats_util.h"
using std::set;
@@ -31,13 +32,51 @@
namespace os {
namespace statsd {
-int getTrackerIndex(const string& name, const unordered_map<string, int>& logTrackerMap) {
- auto logTrackerIt = logTrackerMap.find(name);
+bool handleMetricWithLogTrackers(const string what, const int metricIndex,
+ const unordered_map<string, int>& logTrackerMap,
+ unordered_map<int, std::vector<int>>& trackerToMetricMap,
+ int& logTrackerIndex) {
+ auto logTrackerIt = logTrackerMap.find(what);
if (logTrackerIt == logTrackerMap.end()) {
- ALOGW("cannot find the LogEventMatcher %s in config", name.c_str());
- return MATCHER_NOT_FOUND;
+ ALOGW("cannot find the LogEntryMatcher %s in config", what.c_str());
+ return false;
}
- return logTrackerIt->second;
+ logTrackerIndex = logTrackerIt->second;
+ auto& metric_list = trackerToMetricMap[logTrackerIndex];
+ metric_list.push_back(metricIndex);
+ return true;
+}
+
+bool handleMetricWithConditions(
+ const string condition, const int metricIndex,
+ const unordered_map<string, int>& conditionTrackerMap,
+ const ::google::protobuf::RepeatedPtrField<::android::os::statsd::EventConditionLink>&
+ links,
+ vector<sp<ConditionTracker>>& allConditionTrackers, int& conditionIndex,
+ unordered_map<int, std::vector<int>>& conditionToMetricMap) {
+ auto condition_it = conditionTrackerMap.find(condition);
+ if (condition_it == conditionTrackerMap.end()) {
+ ALOGW("cannot find the Condition %s in the config", condition.c_str());
+ return false;
+ }
+
+ for (const auto& link : links) {
+ auto it = conditionTrackerMap.find(link.condition());
+ if (it == conditionTrackerMap.end()) {
+ ALOGW("cannot find the Condition %s in the config", link.condition().c_str());
+ return false;
+ }
+ allConditionTrackers[condition_it->second]->setSliced(true);
+ allConditionTrackers[it->second]->setSliced(true);
+ allConditionTrackers[it->second]->addDimensions(
+ vector<KeyMatcher>(link.key_in_condition().begin(), link.key_in_condition().end()));
+ }
+ conditionIndex = condition_it->second;
+
+ // will create new vector if not exist before.
+ auto& metricList = conditionToMetricMap[condition_it->second];
+ metricList.push_back(metricIndex);
+ return true;
}
bool initLogTrackers(const StatsdConfig& config, unordered_map<string, int>& logTrackerMap,
@@ -142,7 +181,8 @@
unordered_map<int, std::vector<int>>& conditionToMetricMap,
unordered_map<int, std::vector<int>>& trackerToMetricMap) {
sp<ConditionWizard> wizard = new ConditionWizard(allConditionTrackers);
- const int allMetricsCount = config.count_metric_size() + config.duration_metric_size();
+ const int allMetricsCount =
+ config.count_metric_size() + config.duration_metric_size() + config.event_metric_size();
allMetricProducers.reserve(allMetricsCount);
// Build MetricProducers for each metric defined in config.
@@ -155,100 +195,52 @@
}
int metricIndex = allMetricProducers.size();
-
- auto logTrackerIt = logTrackerMap.find(metric.what());
- if (logTrackerIt == logTrackerMap.end()) {
- ALOGW("cannot find the LogEntryMatcher %s in config", metric.what().c_str());
+ int trackerIndex;
+ if (!handleMetricWithLogTrackers(metric.what(), metricIndex, logTrackerMap,
+ trackerToMetricMap, trackerIndex)) {
return false;
}
- int logTrackerIndex = logTrackerIt->second;
- auto& metric_list = trackerToMetricMap[logTrackerIndex];
- metric_list.push_back(metricIndex);
- sp<MetricProducer> countProducer;
-
+ int conditionIndex = -1;
if (metric.has_condition()) {
- auto condition_it = conditionTrackerMap.find(metric.condition());
- if (condition_it == conditionTrackerMap.end()) {
- ALOGW("cannot find the Condition %s in the config", metric.condition().c_str());
- return false;
- }
-
- for (const auto& link : metric.links()) {
- auto it = conditionTrackerMap.find(link.condition());
- if (it == conditionTrackerMap.end()) {
- ALOGW("cannot find the Condition %s in the config", link.condition().c_str());
- return false;
- }
- allConditionTrackers[condition_it->second]->setSliced(true);
- allConditionTrackers[it->second]->setSliced(true);
- allConditionTrackers[it->second]->addDimensions(vector<KeyMatcher>(
- link.key_in_condition().begin(), link.key_in_condition().end()));
- }
-
- countProducer = new CountMetricProducer(metric, condition_it->second, wizard);
- // will create new vector if not exist before.
- auto& metricList = conditionToMetricMap[condition_it->second];
- metricList.push_back(metricIndex);
- } else {
- countProducer = new CountMetricProducer(metric, -1 /*no condition*/, wizard);
+ handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap,
+ metric.links(), allConditionTrackers, conditionIndex,
+ conditionToMetricMap);
}
+
+ sp<MetricProducer> countProducer = new CountMetricProducer(metric, conditionIndex, wizard);
allMetricProducers.push_back(countProducer);
}
for (int i = 0; i < config.duration_metric_size(); i++) {
int metricIndex = allMetricProducers.size();
- const DurationMetric metric = config.duration_metric(i);
- if (!metric.has_start()) {
- ALOGW("cannot find start in DurationMetric %lld", metric.metric_id());
+ const DurationMetric& metric = config.duration_metric(i);
+ int trackerIndices[3] = {-1, -1, -1};
+ if (!metric.has_start() ||
+ !handleMetricWithLogTrackers(metric.start(), metricIndex, logTrackerMap,
+ trackerToMetricMap, trackerIndices[0])) {
+ ALOGE("Duration metrics must specify a valid the start event matcher");
return false;
}
- int trackerIndices[] = {-1, -1, -1};
- trackerIndices[0] = getTrackerIndex(metric.start(), logTrackerMap);
-
- if (metric.has_stop()) {
- trackerIndices[1] = getTrackerIndex(metric.stop(), logTrackerMap);
+ if (metric.has_stop() &&
+ !handleMetricWithLogTrackers(metric.stop(), metricIndex, logTrackerMap,
+ trackerToMetricMap, trackerIndices[1])) {
+ return false;
}
- if (metric.has_stop_all()) {
- trackerIndices[2] = getTrackerIndex(metric.stop_all(), logTrackerMap);
- }
-
- for (const int& index : trackerIndices) {
- if (index == MATCHER_NOT_FOUND) {
- return false;
- }
- if (index >= 0) {
- auto& metric_list = trackerToMetricMap[index];
- metric_list.push_back(metricIndex);
- }
+ if (metric.has_stop_all() &&
+ !handleMetricWithLogTrackers(metric.stop_all(), metricIndex, logTrackerMap,
+ trackerToMetricMap, trackerIndices[2])) {
+ return false;
}
int conditionIndex = -1;
if (metric.has_predicate()) {
- auto condition_it = conditionTrackerMap.find(metric.predicate());
- if (condition_it == conditionTrackerMap.end()) {
- ALOGW("cannot find the Condition %s in the config", metric.predicate().c_str());
- return false;
- }
- conditionIndex = condition_it->second;
-
- for (const auto& link : metric.links()) {
- auto it = conditionTrackerMap.find(link.condition());
- if (it == conditionTrackerMap.end()) {
- ALOGW("cannot find the Condition %s in the config", link.condition().c_str());
- return false;
- }
- allConditionTrackers[condition_it->second]->setSliced(true);
- allConditionTrackers[it->second]->setSliced(true);
- allConditionTrackers[it->second]->addDimensions(vector<KeyMatcher>(
- link.key_in_condition().begin(), link.key_in_condition().end()));
- }
-
- auto& metricList = conditionToMetricMap[conditionIndex];
- metricList.push_back(metricIndex);
+ handleMetricWithConditions(metric.predicate(), metricIndex, conditionTrackerMap,
+ metric.links(), allConditionTrackers, conditionIndex,
+ conditionToMetricMap);
}
sp<MetricProducer> durationMetric =
@@ -257,6 +249,32 @@
allMetricProducers.push_back(durationMetric);
}
+
+ for (int i = 0; i < config.event_metric_size(); i++) {
+ int metricIndex = allMetricProducers.size();
+ const EventMetric& metric = config.event_metric(i);
+ if (!metric.has_metric_id() || !metric.has_what()) {
+ ALOGW("cannot find the metric id or what in config");
+ return false;
+ }
+ int trackerIndex;
+ if (!handleMetricWithLogTrackers(metric.what(), metricIndex, logTrackerMap,
+ trackerToMetricMap, trackerIndex)) {
+ return false;
+ }
+
+ int conditionIndex = -1;
+ if (metric.has_condition()) {
+ handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap,
+ metric.links(), allConditionTrackers, conditionIndex,
+ conditionToMetricMap);
+ }
+
+ sp<MetricProducer> eventMetric = new EventMetricProducer(metric, conditionIndex, wizard);
+
+ allMetricProducers.push_back(eventMetric);
+ }
+
return true;
}
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/metrics_manager_util.h
index f23df9f..6722eb3 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.h
+++ b/cmds/statsd/src/metrics/metrics_manager_util.h
@@ -22,7 +22,6 @@
#include "../condition/ConditionTracker.h"
#include "../matchers/LogMatchingTracker.h"
-#include "CountMetricProducer.h"
namespace android {
namespace os {
@@ -91,8 +90,6 @@
std::unordered_map<int, std::vector<int>>& trackerToMetricMap,
std::unordered_map<int, std::vector<int>>& trackerToConditionMap);
-int getTrackerIndex(const std::string& name, const std::unordered_map<string, int>& logTrackerMap);
-
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/stats_util.h b/cmds/statsd/src/stats_util.h
index 575588b..39c1d59 100644
--- a/cmds/statsd/src/stats_util.h
+++ b/cmds/statsd/src/stats_util.h
@@ -30,6 +30,14 @@
#define MATCHER_NOT_FOUND -2
#define NANO_SECONDS_IN_A_SECOND (1000 * 1000 * 1000)
+// TODO: Remove the following constants once they are exposed in ProtOutputStream.h
+const uint64_t FIELD_TYPE_SHIFT = 32;
+const uint64_t TYPE_MESSAGE = 11ULL << FIELD_TYPE_SHIFT;
+const uint64_t TYPE_INT64 = 3ULL << FIELD_TYPE_SHIFT;
+const uint64_t TYPE_INT32 = 5ULL << FIELD_TYPE_SHIFT;
+const uint64_t TYPE_FLOAT = 2ULL << FIELD_TYPE_SHIFT;
+const uint64_t TYPE_STRING = 9ULL << FIELD_TYPE_SHIFT;
+
typedef std::string HashableDimensionKey;
EventMetricData parse(log_msg msg);
diff --git a/core/java/android/app/slice/SliceProvider.java b/core/java/android/app/slice/SliceProvider.java
index df87b45..33825b4 100644
--- a/core/java/android/app/slice/SliceProvider.java
+++ b/core/java/android/app/slice/SliceProvider.java
@@ -156,27 +156,34 @@
}
private Slice handleBindSlice(Uri sliceUri) {
- Slice[] output = new Slice[1];
- CountDownLatch latch = new CountDownLatch(1);
- Handler mainHandler = new Handler(Looper.getMainLooper());
- mainHandler.post(() -> {
- ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
- try {
- StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
- .detectAll()
- .penaltyDeath()
- .build());
- output[0] = onBindSlice(sliceUri);
- } finally {
- StrictMode.setThreadPolicy(oldPolicy);
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ return onBindSliceStrict(sliceUri);
+ } else {
+ CountDownLatch latch = new CountDownLatch(1);
+ Slice[] output = new Slice[1];
+ Handler.getMain().post(() -> {
+ output[0] = onBindSliceStrict(sliceUri);
latch.countDown();
+ });
+ try {
+ latch.await();
+ return output[0];
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
}
- });
+ }
+ }
+
+ private Slice onBindSliceStrict(Uri sliceUri) {
+ ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
try {
- latch.await();
- return output[0];
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
+ StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
+ .detectAll()
+ .penaltyDeath()
+ .build());
+ return onBindSlice(sliceUri);
+ } finally {
+ StrictMode.setThreadPolicy(oldPolicy);
}
}
}
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index d73f852..dcc6821 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -375,7 +375,7 @@
* {@code DownloadManager}, {@code MediaPlayer}) will refuse app's requests to use cleartext
* traffic. Third-party libraries are encouraged to honor this flag as well.
*
- * <p>NOTE: {@code WebView} does not honor this flag.
+ * <p>NOTE: {@code WebView} honors this flag for applications targeting API level 26 and up.
*
* <p>This flag is ignored on Android N and above if an Android Network Security Config is
* present.
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index 143c51d..7d58658 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -467,7 +467,4 @@
/** Updates the flags for the given permission. */
public abstract void updatePermissionFlagsTEMP(@NonNull String permName,
@NonNull String packageName, int flagMask, int flagValues, int userId);
- /** Returns a PermissionGroup. */
- public abstract @Nullable PackageParser.PermissionGroup getPermissionGroupTEMP(
- @NonNull String groupName);
}
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 826ec1eb..f52d94e 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -20,7 +20,6 @@
import android.annotation.TestApi;
import android.app.ActivityManager;
import android.app.ActivityThread;
-import android.app.ApplicationErrorReport;
import android.app.IActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -48,8 +47,10 @@
import java.io.StringWriter;
import java.net.InetAddress;
import java.net.UnknownHostException;
+import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Deque;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicInteger;
@@ -352,8 +353,8 @@
} else {
msg = "StrictMode policy violation:";
}
- if (info.crashInfo != null) {
- Log.d(TAG, msg + " " + info.crashInfo.stackTrace);
+ if (info.hasStackTrace()) {
+ Log.d(TAG, msg + " " + info.getStackTrace());
} else {
Log.d(TAG, msg + " missing stack trace!");
}
@@ -1247,28 +1248,6 @@
}
}
- /** Like parsePolicyFromMessage(), but returns the violation. */
- private static int parseViolationFromMessage(String message) {
- if (message == null) {
- return 0;
- }
- int violationIndex = message.indexOf("violation=");
- if (violationIndex == -1) {
- return 0;
- }
- int numberStartIndex = violationIndex + "violation=".length();
- int numberEndIndex = message.indexOf(' ', numberStartIndex);
- if (numberEndIndex == -1) {
- numberEndIndex = message.length();
- }
- String violationString = message.substring(numberStartIndex, numberEndIndex);
- try {
- return Integer.parseInt(violationString);
- } catch (NumberFormatException e) {
- return 0;
- }
- }
-
private static final ThreadLocal<ArrayList<ViolationInfo>> violationsBeingTimed =
new ThreadLocal<ArrayList<ViolationInfo>>() {
@Override
@@ -1516,7 +1495,7 @@
// to people who push/pop temporary policy in regions of code,
// hence the policy being passed around.
void handleViolation(final ViolationInfo info) {
- if (info == null || info.crashInfo == null || info.crashInfo.stackTrace == null) {
+ if (info == null || !info.hasStackTrace()) {
Log.wtf(TAG, "unexpected null stacktrace");
return;
}
@@ -1530,7 +1509,7 @@
gatheredViolations.set(violations);
}
for (ViolationInfo previous : violations) {
- if (info.crashInfo.stackTrace.equals(previous.crashInfo.stackTrace)) {
+ if (info.getStackTrace().equals(previous.getStackTrace())) {
// Duplicate. Don't log.
return;
}
@@ -1576,8 +1555,7 @@
}
if (violationMaskSubset != 0) {
- int violationBit = parseViolationFromMessage(info.crashInfo.exceptionMessage);
- violationMaskSubset |= violationBit;
+ violationMaskSubset |= info.getViolationBit();
final int savedPolicyMask = getThreadPolicyMask();
final boolean justDropBox = (info.policy & THREAD_PENALTY_MASK) == PENALTY_DROPBOX;
@@ -1622,8 +1600,7 @@
}
private static void executeDeathPenalty(ViolationInfo info) {
- int violationBit = parseViolationFromMessage(info.crashInfo.exceptionMessage);
- throw new StrictModeViolation(info.policy, violationBit, null);
+ throw new StrictModeViolation(info.policy, info.getViolationBit(), null);
}
/**
@@ -2027,21 +2004,14 @@
* read back all the encoded violations.
*/
/* package */ static void readAndHandleBinderCallViolations(Parcel p) {
- // Our own stack trace to append
- StringWriter sw = new StringWriter();
- sw.append("# via Binder call with stack:\n");
- PrintWriter pw = new FastPrintWriter(sw, false, 256);
- new LogStackTrace().printStackTrace(pw);
- pw.flush();
- String ourStack = sw.toString();
-
+ LogStackTrace localCallSite = new LogStackTrace();
final int policyMask = getThreadPolicyMask();
final boolean currentlyGathering = (policyMask & PENALTY_GATHER) != 0;
final int size = p.readInt();
for (int i = 0; i < size; i++) {
final ViolationInfo info = new ViolationInfo(p, !currentlyGathering);
- info.crashInfo.appendStackTrace(ourStack);
+ info.addLocalStack(localCallSite);
BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
if (policy instanceof AndroidBlockGuardPolicy) {
((AndroidBlockGuardPolicy) policy).handleViolationWithTimingAttempt(info);
@@ -2366,10 +2336,18 @@
*/
@TestApi
public static final class ViolationInfo implements Parcelable {
- public final String message;
+ /** Some VM violations provide additional information outside the throwable. */
+ @Nullable private final String mMessagePrefix;
- /** Stack and other stuff info. */
- @Nullable public final ApplicationErrorReport.CrashInfo crashInfo;
+ /** Stack and violation details. */
+ @Nullable private final Throwable mThrowable;
+
+ private final Deque<Throwable> mBinderStack = new ArrayDeque<>();
+
+ /** Memoized stack trace of full violation. */
+ @Nullable private String mStackTrace;
+ /** Memoized violation bit. */
+ private int mViolationBit;
/** The strict mode policy mask at the time of violation. */
public final int policy;
@@ -2404,19 +2382,25 @@
/** Create an uninitialized instance of ViolationInfo */
public ViolationInfo() {
- message = null;
- crashInfo = null;
+ mMessagePrefix = null;
+ mThrowable = null;
policy = 0;
}
+ /** Create an instance of ViolationInfo. */
public ViolationInfo(Throwable tr, int policy) {
this(null, tr, policy);
}
- /** Create an instance of ViolationInfo initialized from an exception. */
- public ViolationInfo(String message, Throwable tr, int policy) {
- this.message = message;
- crashInfo = new ApplicationErrorReport.CrashInfo(tr);
+ /**
+ * Create an instance of ViolationInfo initialized from an exception with a message prefix.
+ *
+ * @deprecated prefixes belong in the Throwable.
+ */
+ @Deprecated
+ public ViolationInfo(String messagePrefix, Throwable tr, int policy) {
+ this.mMessagePrefix = messagePrefix;
+ this.mThrowable = tr;
violationUptimeMillis = SystemClock.uptimeMillis();
this.policy = policy;
this.numAnimationsRunning = ValueAnimator.getCurrentAnimationsCount();
@@ -2446,11 +2430,101 @@
}
}
+ /** Equivalent output to {@link ApplicationErrorReport.CrashInfo#stackTrace}. */
+ public String getStackTrace() {
+ if (mThrowable != null && mStackTrace == null) {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new FastPrintWriter(sw, false, 256);
+ mThrowable.printStackTrace(pw);
+ for (Throwable t : mBinderStack) {
+ pw.append("# via Binder call with stack:\n");
+ t.printStackTrace(pw);
+ }
+ pw.flush();
+ pw.close();
+ mStackTrace = sw.toString();
+ }
+ return mStackTrace;
+ }
+
+ /**
+ * Optional message describing this violation.
+ *
+ * @hide
+ */
+ @TestApi
+ public String getViolationDetails() {
+ if (mThrowable != null) {
+ return mThrowable.getMessage();
+ } else {
+ return "";
+ }
+ }
+
+ /**
+ * A handful of VM violations provide extra information that should be presented before
+ * {@link #getViolationDetails()}.
+ *
+ * @hide
+ */
+ @TestApi
+ public String getMessagePrefix() {
+ return mMessagePrefix != null ? mMessagePrefix : "";
+ }
+
+ /** If this violation has a useful stack trace.
+ *
+ * @hide
+ */
+ public boolean hasStackTrace() {
+ return mThrowable != null;
+ }
+
+ /**
+ * Add a {@link Throwable} from the current process that caused the underlying violation.
+ *
+ * @hide
+ */
+ void addLocalStack(Throwable t) {
+ mBinderStack.addFirst(t);
+ }
+
+ /**
+ * Retrieve the type of StrictMode violation.
+ *
+ * @hide
+ */
+ int getViolationBit() {
+ if (mThrowable == null || mThrowable.getMessage() == null) {
+ return 0;
+ }
+ if (mViolationBit != 0) {
+ return mViolationBit;
+ }
+ String message = mThrowable.getMessage();
+ int violationIndex = message.indexOf("violation=");
+ if (violationIndex == -1) {
+ return 0;
+ }
+ int numberStartIndex = violationIndex + "violation=".length();
+ int numberEndIndex = message.indexOf(' ', numberStartIndex);
+ if (numberEndIndex == -1) {
+ numberEndIndex = message.length();
+ }
+ String violationString = message.substring(numberStartIndex, numberEndIndex);
+ try {
+ mViolationBit = Integer.parseInt(violationString);
+ return mViolationBit;
+ } catch (NumberFormatException e) {
+ return 0;
+ }
+ }
+
@Override
public int hashCode() {
int result = 17;
- if (crashInfo != null) {
- result = 37 * result + crashInfo.stackTrace.hashCode();
+ if (mThrowable != null) {
+ result = 37 * result + mThrowable.hashCode();
}
if (numAnimationsRunning != 0) {
result *= 37;
@@ -2478,11 +2552,11 @@
* should be removed.
*/
public ViolationInfo(Parcel in, boolean unsetGatheringBit) {
- message = in.readString();
- if (in.readInt() != 0) {
- crashInfo = new ApplicationErrorReport.CrashInfo(in);
- } else {
- crashInfo = null;
+ mMessagePrefix = in.readString();
+ mThrowable = (Throwable) in.readSerializable();
+ int binderStackSize = in.readInt();
+ for (int i = 0; i < binderStackSize; i++) {
+ mBinderStack.add((Throwable) in.readSerializable());
}
int rawPolicy = in.readInt();
if (unsetGatheringBit) {
@@ -2502,12 +2576,11 @@
/** Save a ViolationInfo instance to a parcel. */
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(message);
- if (crashInfo != null) {
- dest.writeInt(1);
- crashInfo.writeToParcel(dest, flags);
- } else {
- dest.writeInt(0);
+ dest.writeString(mMessagePrefix);
+ dest.writeSerializable(mThrowable);
+ dest.writeInt(mBinderStack.size());
+ for (Throwable t : mBinderStack) {
+ dest.writeSerializable(t);
}
int start = dest.dataPosition();
dest.writeInt(policy);
@@ -2542,8 +2615,8 @@
/** Dump a ViolationInfo instance to a Printer. */
public void dump(Printer pw, String prefix) {
- if (crashInfo != null) {
- crashInfo.dump(pw, prefix);
+ if (mThrowable != null) {
+ pw.println(prefix + "stackTrace: " + getStackTrace());
}
pw.println(prefix + "policy: " + policy);
if (durationMillis != -1) {
diff --git a/core/java/android/security/NetworkSecurityPolicy.java b/core/java/android/security/NetworkSecurityPolicy.java
index 812c956..0c4eeda 100644
--- a/core/java/android/security/NetworkSecurityPolicy.java
+++ b/core/java/android/security/NetworkSecurityPolicy.java
@@ -16,7 +16,6 @@
package android.security;
-import android.annotation.TestApi;
import android.content.Context;
import android.content.pm.PackageManager;
import android.security.net.config.ApplicationConfig;
@@ -63,7 +62,8 @@
* traffic from applications is handled by higher-level network stacks/components which can
* honor this aspect of the policy.
*
- * <p>NOTE: {@link android.webkit.WebView} does not honor this flag.
+ * <p>NOTE: {@link android.webkit.WebView} honors this flag for applications targeting API level
+ * 26 and up.
*/
public boolean isCleartextTrafficPermitted() {
return libcore.net.NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted();
diff --git a/core/java/android/security/net/config/NetworkSecurityConfig.java b/core/java/android/security/net/config/NetworkSecurityConfig.java
index b9e5505..52f48ef 100644
--- a/core/java/android/security/net/config/NetworkSecurityConfig.java
+++ b/core/java/android/security/net/config/NetworkSecurityConfig.java
@@ -164,7 +164,8 @@
* <p>
* The default configuration has the following properties:
* <ol>
- * <li>Cleartext traffic is permitted for non-ephemeral apps.</li>
+ * <li>If the application targets API level 27 (Android O MR1) or lower then cleartext traffic
+ * is allowed by default.</li>
* <li>Cleartext traffic is not permitted for ephemeral apps.</li>
* <li>HSTS is not enforced.</li>
* <li>No certificate pinning is used.</li>
@@ -183,7 +184,8 @@
// System certificate store, does not bypass static pins.
.addCertificatesEntryRef(
new CertificatesEntryRef(SystemCertificateSource.getInstance(), false));
- final boolean cleartextTrafficPermitted = info.targetSandboxVersion < 2;
+ final boolean cleartextTrafficPermitted = info.targetSdkVersion < Build.VERSION_CODES.P
+ && info.targetSandboxVersion < 2;
builder.setCleartextTrafficPermitted(cleartextTrafficPermitted);
// Applications targeting N and above must opt in into trusting the user added certificate
// store.
diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java
index 8c3b8a2..26d2141 100644
--- a/core/java/android/view/textclassifier/TextClassification.java
+++ b/core/java/android/view/textclassifier/TextClassification.java
@@ -33,6 +33,52 @@
/**
* Information for generating a widget to handle classified text.
+ *
+ * <p>A TextClassification object contains icons, labels, onClickListeners and intents that may
+ * be used to build a widget that can be used to act on classified text.
+ *
+ * <p>e.g. building a view that, when clicked, shares the classified text with the preferred app:
+ *
+ * <pre>{@code
+ * // Called preferably outside the UiThread.
+ * TextClassification classification = textClassifier.classifyText(allText, 10, 25, null);
+ *
+ * // Called on the UiThread.
+ * Button button = new Button(context);
+ * button.setCompoundDrawablesWithIntrinsicBounds(classification.getIcon(), null, null, null);
+ * button.setText(classification.getLabel());
+ * button.setOnClickListener(classification.getOnClickListener());
+ * }</pre>
+ *
+ * <p>e.g. starting an action mode with menu items that can handle the classified text:
+ *
+ * <pre>{@code
+ * // Called preferably outside the UiThread.
+ * final TextClassification classification = textClassifier.classifyText(allText, 10, 25, null);
+ *
+ * // Called on the UiThread.
+ * view.startActionMode(new ActionMode.Callback() {
+ *
+ * public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ * for (int i = 0; i < classification.getActionCount(); i++) {
+ * if (thisAppHasPermissionToInvokeIntent(classification.getIntent(i))) {
+ * menu.add(Menu.NONE, i, 20, classification.getLabel(i))
+ * .setIcon(classification.getIcon(i))
+ * .setIntent(classification.getIntent(i));
+ * }
+ * }
+ * return true;
+ * }
+ *
+ * public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ * context.startActivity(item.getIntent());
+ * return true;
+ * }
+ *
+ * ...
+ * });
+ * }</pre>
+ *
*/
public final class TextClassification {
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java
index c3601d9..46dbd0e 100644
--- a/core/java/android/view/textclassifier/TextClassifier.java
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -29,8 +29,8 @@
/**
* Interface for providing text classification related features.
*
- * <p>Unless otherwise stated, methods of this interface are blocking operations and you should
- * avoid calling them on the UI thread.
+ * <p>Unless otherwise stated, methods of this interface are blocking operations.
+ * Avoid calling them on the UI thread.
*/
public interface TextClassifier {
@@ -75,8 +75,8 @@
};
/**
- * Returns suggested text selection indices, recognized types and their associated confidence
- * scores. The selections are ordered from highest to lowest scoring.
+ * Returns suggested text selection start and end indices, recognized entity types, and their
+ * associated confidence scores. The entity types are ordered from highest to lowest scoring.
*
* @param text text providing context for the selected text (which is specified
* by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex)
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 95cb454..48e427f 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -25,7 +25,6 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
-import android.os.Build;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StrictMode;
@@ -445,38 +444,17 @@
}
}
- private static int prepareWebViewInSystemServer(String[] nativeLibraryPaths) {
- if (DEBUG) Log.v(LOGTAG, "creating relro files");
- int numRelros = 0;
-
- // We must always trigger createRelRo regardless of the value of nativeLibraryPaths. Any
- // unexpected values will be handled there to ensure that we trigger notifying any process
- // waiting on relro creation.
- if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
- if (DEBUG) Log.v(LOGTAG, "Create 32 bit relro");
- WebViewLibraryLoader.createRelroFile(false /* is64Bit */, nativeLibraryPaths[0]);
- numRelros++;
- }
-
- if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
- if (DEBUG) Log.v(LOGTAG, "Create 64 bit relro");
- WebViewLibraryLoader.createRelroFile(true /* is64Bit */, nativeLibraryPaths[1]);
- numRelros++;
- }
- return numRelros;
- }
-
/**
* @hide
*/
public static int onWebViewProviderChanged(PackageInfo packageInfo) {
- String[] nativeLibs = null;
+ int startedRelroProcesses = 0;
ApplicationInfo originalAppInfo = new ApplicationInfo(packageInfo.applicationInfo);
try {
fixupStubApplicationInfo(packageInfo.applicationInfo,
AppGlobals.getInitialApplication().getPackageManager());
- nativeLibs = WebViewLibraryLoader.updateWebViewZygoteVmSize(packageInfo);
+ startedRelroProcesses = WebViewLibraryLoader.prepareNativeLibraries(packageInfo);
} catch (Throwable t) {
// Log and discard errors at this stage as we must not crash the system server.
Log.e(LOGTAG, "error preparing webview native library", t);
@@ -484,7 +462,7 @@
WebViewZygote.onWebViewProviderChanged(packageInfo, originalAppInfo);
- return prepareWebViewInSystemServer(nativeLibs);
+ return startedRelroProcesses;
}
private static String WEBVIEW_UPDATE_SERVICE_NAME = "webviewupdate";
diff --git a/core/java/android/webkit/WebViewLibraryLoader.java b/core/java/android/webkit/WebViewLibraryLoader.java
index fa1a390..175f35f 100644
--- a/core/java/android/webkit/WebViewLibraryLoader.java
+++ b/core/java/android/webkit/WebViewLibraryLoader.java
@@ -127,11 +127,34 @@
}
}
+ static int prepareNativeLibraries(PackageInfo webviewPackageInfo)
+ throws WebViewFactory.MissingWebViewPackageException {
+ String[] nativeLibs = updateWebViewZygoteVmSize(webviewPackageInfo);
+ if (DEBUG) Log.v(LOGTAG, "creating relro files");
+ int numRelros = 0;
+
+ // We must always trigger createRelRo regardless of the value of nativeLibraryPaths. Any
+ // unexpected values will be handled there to ensure that we trigger notifying any process
+ // waiting on relro creation.
+ if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
+ if (DEBUG) Log.v(LOGTAG, "Create 32 bit relro");
+ createRelroFile(false /* is64Bit */, nativeLibs[0]);
+ numRelros++;
+ }
+
+ if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
+ if (DEBUG) Log.v(LOGTAG, "Create 64 bit relro");
+ createRelroFile(true /* is64Bit */, nativeLibs[1]);
+ numRelros++;
+ }
+ return numRelros;
+ }
+
/**
*
* @return the native WebView libraries in the new WebView APK.
*/
- static String[] updateWebViewZygoteVmSize(PackageInfo packageInfo)
+ private static String[] updateWebViewZygoteVmSize(PackageInfo packageInfo)
throws WebViewFactory.MissingWebViewPackageException {
// Find the native libraries of the new WebView package, to change the size of the
// memory region in the Zygote reserved for the library.
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index f63207a..e6da69d 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -166,7 +166,6 @@
private static final int MENU_ITEM_ORDER_SECONDARY_ASSIST_ACTIONS_START = 50;
private static final int MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START = 100;
- private static final float MAGNIFIER_ZOOM = 1.25f;
@IntDef({MagnifierHandleTrigger.SELECTION_START,
MagnifierHandleTrigger.SELECTION_END,
MagnifierHandleTrigger.INSERTION})
@@ -4621,7 +4620,7 @@
+ mTextView.getTotalPaddingTop() - mTextView.getScrollY();
suspendBlink();
- mMagnifier.show(xPosInView, yPosInView, MAGNIFIER_ZOOM);
+ mMagnifier.show(xPosInView, yPosInView);
}
protected final void dismissMagnifier() {
diff --git a/core/java/com/android/internal/widget/Magnifier.java b/core/java/com/android/internal/widget/Magnifier.java
index 6d54d7b..f9e98ea 100644
--- a/core/java/com/android/internal/widget/Magnifier.java
+++ b/core/java/com/android/internal/widget/Magnifier.java
@@ -63,7 +63,7 @@
// the copy is finished.
private final Handler mPixelCopyHandler = Handler.getMain();
// Current magnification scale.
- private float mScale;
+ private final float mZoomScale;
// Timer used to schedule the copy task.
private Timer mTimer;
@@ -76,11 +76,12 @@
public Magnifier(@NonNull View view) {
mView = Preconditions.checkNotNull(view);
final Context context = mView.getContext();
+ final float elevation = context.getResources().getDimension(R.dimen.magnifier_elevation);
final View content = LayoutInflater.from(context).inflate(R.layout.magnifier, null);
content.findViewById(R.id.magnifier_inner).setClipToOutline(true);
mWindowWidth = context.getResources().getDimensionPixelSize(R.dimen.magnifier_width);
mWindowHeight = context.getResources().getDimensionPixelSize(R.dimen.magnifier_height);
- final float elevation = context.getResources().getDimension(R.dimen.magnifier_elevation);
+ mZoomScale = context.getResources().getFloat(R.dimen.magnifier_zoom_scale);
mWindow = new PopupWindow(context);
mWindow.setContentView(content);
@@ -90,7 +91,9 @@
mWindow.setTouchable(false);
mWindow.setBackgroundDrawable(null);
- mBitmap = Bitmap.createBitmap(mWindowWidth, mWindowHeight, Bitmap.Config.ARGB_8888);
+ final int bitmapWidth = (int) (mWindowWidth / mZoomScale);
+ final int bitmapHeight = (int) (mWindowHeight / mZoomScale);
+ mBitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
getImageView().setImageBitmap(mBitmap);
}
@@ -101,20 +104,8 @@
* to the view. The lower end is clamped to 0
* @param yPosInView vertical coordinate of the center point of the magnifier source
* relative to the view. The lower end is clamped to 0
- * @param scale the scale at which the magnifier zooms on the source content. The
- * lower end is clamped to 1 and the higher end to 4
*/
- public void show(@FloatRange(from=0) float xPosInView,
- @FloatRange(from=0) float yPosInView,
- @FloatRange(from=1, to=4) float scale) {
- if (scale > 4) {
- scale = 4;
- }
-
- if (scale < 1) {
- scale = 1;
- }
-
+ public void show(@FloatRange(from=0) float xPosInView, @FloatRange(from=0) float yPosInView) {
if (xPosInView < 0) {
xPosInView = 0;
}
@@ -123,10 +114,6 @@
yPosInView = 0;
}
- if (mScale != scale) {
- resizeBitmap(scale);
- }
- mScale = scale;
configureCoordinates(xPosInView, yPosInView);
if (mTimer == null) {
@@ -164,6 +151,7 @@
/**
* @return the height of the magnifier window.
*/
+ @NonNull
public int getHeight() {
return mWindowHeight;
}
@@ -171,15 +159,17 @@
/**
* @return the width of the magnifier window.
*/
+ @NonNull
public int getWidth() {
return mWindowWidth;
}
- private void resizeBitmap(float scale) {
- final int bitmapWidth = (int) (mWindowWidth / scale);
- final int bitmapHeight = (int) (mWindowHeight / scale);
- mBitmap.reconfigure(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
- getImageView().setImageBitmap(mBitmap);
+ /**
+ * @return the zoom scale of the magnifier.
+ */
+ @NonNull
+ public float getZoomScale() {
+ return mZoomScale;
}
private void configureCoordinates(float xPosInView, float yPosInView) {
diff --git a/core/res/res/layout/magnifier.xml b/core/res/res/layout/magnifier.xml
index d6cd8b4..f3344c7 100644
--- a/core/res/res/layout/magnifier.xml
+++ b/core/res/res/layout/magnifier.xml
@@ -22,10 +22,11 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/magnifier_inner"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="@android:dimen/magnifier_width"
+ android:layout_height="@android:dimen/magnifier_height"
+ android:elevation="@android:dimen/magnifier_elevation"
android:background="?android:attr/floatingToolbarPopupBackgroundDrawable"
- android:elevation="@android:dimen/magnifier_elevation">
+ android:scaleType="fitXY">
<ImageView
android:id="@+id/magnifier_image"
android:layout_width="match_parent"
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 08e2233..dc75ba6 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -525,6 +525,7 @@
<dimen name="magnifier_height">48dp</dimen>
<dimen name="magnifier_elevation">2dp</dimen>
<dimen name="magnifier_offset">42dp</dimen>
+ <item type="dimen" format="float" name="magnifier_zoom_scale">1.25</item>
<dimen name="chooser_grid_padding">0dp</dimen>
<!-- Spacing around the background change frome service to non-service -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 1627a0e..896de53 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2484,6 +2484,7 @@
<java-symbol type="dimen" name="magnifier_width" />
<java-symbol type="dimen" name="magnifier_height" />
<java-symbol type="dimen" name="magnifier_elevation" />
+ <java-symbol type="dimen" name="magnifier_zoom_scale" />
<java-symbol type="dimen" name="magnifier_offset" />
<java-symbol type="string" name="date_picker_prev_month_button" />
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java
index fbaf0f3..0806fa0 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java
@@ -20,7 +20,6 @@
import android.test.InstrumentationTestRunner;
import android.test.InstrumentationTestSuite;
-import com.android.connectivitymanagertest.stress.WifiApStress;
import com.android.connectivitymanagertest.stress.WifiStressTest;
import junit.framework.TestSuite;
@@ -35,7 +34,7 @@
*/
public class ConnectivityManagerStressTestRunner extends InstrumentationTestRunner {
- private int mSoftApIterations = 100;
+ private int mSoftApIterations = 0;
private int mScanIterations = 100;
private int mReconnectIterations = 100;
// sleep time before restart wifi, default is set to 2 minutes
@@ -47,7 +46,6 @@
@Override
public TestSuite getAllTests() {
TestSuite suite = new InstrumentationTestSuite(this);
- suite.addTestSuite(WifiApStress.class);
suite.addTestSuite(WifiStressTest.class);
return suite;
}
@@ -60,13 +58,6 @@
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
- String valueStr = icicle.getString("softap_iterations");
- if (valueStr != null) {
- int iteration = Integer.parseInt(valueStr);
- if (iteration > 0) {
- mSoftApIterations = iteration;
- }
- }
String scanIterationStr = icicle.getString("scan_iterations");
if (scanIterationStr != null) {
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java
index 64fed7f..3706e4b 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java
@@ -129,12 +129,6 @@
// Get an instance of WifiManager
mWifiManager =(WifiManager)mContext.getSystemService(Context.WIFI_SERVICE);
- if (mWifiManager.isWifiApEnabled()) {
- // if soft AP is enabled, disable it
- mWifiManager.setWifiApEnabled(null, false);
- logv("Disable soft ap");
- }
-
// register a connectivity receiver for CONNECTIVITY_ACTION;
mConnectivityReceiver = new ConnectivityReceiver();
mContext.registerReceiver(mConnectivityReceiver,
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerUnitTestRunner.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerUnitTestRunner.java
index 0e57a00..746cb84 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerUnitTestRunner.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerUnitTestRunner.java
@@ -19,7 +19,6 @@
import android.test.InstrumentationTestRunner;
import android.test.InstrumentationTestSuite;
import com.android.connectivitymanagertest.unit.WifiClientTest;
-import com.android.connectivitymanagertest.unit.WifiSoftAPTest;
import junit.framework.TestSuite;
@@ -35,7 +34,6 @@
public TestSuite getAllTests() {
TestSuite suite = new InstrumentationTestSuite(this);
suite.addTestSuite(WifiClientTest.class);
- suite.addTestSuite(WifiSoftAPTest.class);
return suite;
}
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiApStress.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiApStress.java
deleted file mode 100644
index de934b9..0000000
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiApStress.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2010, 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.connectivitymanagertest.stress;
-
-
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiConfiguration.AuthAlgorithm;
-import android.net.wifi.WifiConfiguration.KeyMgmt;
-import android.net.wifi.WifiManager;
-import android.os.Environment;
-import android.test.suitebuilder.annotation.LargeTest;
-
-import com.android.connectivitymanagertest.ConnectivityManagerStressTestRunner;
-import com.android.connectivitymanagertest.ConnectivityManagerTestBase;
-
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileWriter;
-
-/**
- * Stress test setting up device as wifi hotspot
- */
-public class WifiApStress extends ConnectivityManagerTestBase {
- private static String NETWORK_ID = "AndroidAPTest";
- private static String PASSWD = "androidwifi";
- private final static String OUTPUT_FILE = "WifiStressTestOutput.txt";
- private int mTotalIterations;
- private BufferedWriter mOutputWriter = null;
- private int mLastIteration = 0;
- private boolean mWifiOnlyFlag;
-
- public WifiApStress() {
- super(WifiApStress.class.getSimpleName());
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- ConnectivityManagerStressTestRunner mRunner =
- (ConnectivityManagerStressTestRunner)getInstrumentation();
- mTotalIterations = mRunner.getSoftApInterations();
- mWifiOnlyFlag = mRunner.isWifiOnly();
- turnScreenOn();
- }
-
- @Override
- protected void tearDown() throws Exception {
- // write the total number of iterations into output file
- mOutputWriter = new BufferedWriter(new FileWriter(new File(
- Environment.getExternalStorageDirectory(), OUTPUT_FILE)));
- mOutputWriter.write(String.format("iteration %d out of %d\n",
- mLastIteration + 1, mTotalIterations));
- mOutputWriter.flush();
- mOutputWriter.close();
- super.tearDown();
- }
-
- @LargeTest
- public void testWifiHotSpot() {
- if (mWifiOnlyFlag) {
- logv(getName() + " is excluded for wi-fi only test");
- return;
- }
- WifiConfiguration config = new WifiConfiguration();
- config.SSID = NETWORK_ID;
- config.allowedKeyManagement.set(KeyMgmt.WPA_PSK);
- config.allowedAuthAlgorithms.set(AuthAlgorithm.OPEN);
- config.preSharedKey = PASSWD;
-
- // if wifiap enabled, disable it
- assertTrue("failed to disable wifi hotspot",
- mWifiManager.setWifiApEnabled(config, false));
- assertTrue("wifi hotspot not enabled", waitForWifiApState(
- WifiManager.WIFI_AP_STATE_DISABLED, 2 * LONG_TIMEOUT));
-
- // if Wifi is enabled, disable it
- if (mWifiManager.isWifiEnabled()) {
- assertTrue("failed to disable wifi", disableWifi());
- // wait for the wifi state to be DISABLED
- assertTrue("wifi state not disabled", waitForWifiState(
- WifiManager.WIFI_STATE_DISABLED, LONG_TIMEOUT));
- }
- int i;
- for (i = 0; i < mTotalIterations; i++) {
- logv("iteration: " + i);
- mLastIteration = i;
- // enable Wifi tethering
- assertTrue("failed to enable wifi hotspot",
- mWifiManager.setWifiApEnabled(config, true));
- // wait for wifi ap state to be ENABLED
- assertTrue("wifi hotspot not enabled", waitForWifiApState(
- WifiManager.WIFI_AP_STATE_ENABLED, 2 * LONG_TIMEOUT));
- // wait for wifi tethering result
- assertTrue("tether state not changed", waitForTetherStateChange(LONG_TIMEOUT));
- // allow the wifi tethering to be enabled for 10 seconds
- try {
- Thread.sleep(2 * SHORT_TIMEOUT);
- } catch (Exception e) {
- // ignore
- }
- assertTrue("no uplink data connection after Wi-Fi tethering", pingTest());
- // disable wifi hotspot
- assertTrue("failed to disable wifi hotspot",
- mWifiManager.setWifiApEnabled(config, false));
- assertTrue("wifi hotspot not enabled", waitForWifiApState(
- WifiManager.WIFI_AP_STATE_DISABLED, 2 * LONG_TIMEOUT));
- assertFalse("wifi hotspot still enabled", mWifiManager.isWifiApEnabled());
- }
- }
-
-}
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/unit/WifiSoftAPTest.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/unit/WifiSoftAPTest.java
deleted file mode 100644
index f202862..0000000
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/unit/WifiSoftAPTest.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2010 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.connectivitymanagertest.unit;
-
-import android.content.Context;
-import android.net.wifi.WifiManager;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiConfiguration.KeyMgmt;
-
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.AndroidTestCase;
-
-import android.util.Log;
-
-/**
- * Test Wifi soft AP configuration
- */
-public class WifiSoftAPTest extends AndroidTestCase {
-
- private WifiManager mWifiManager;
- private WifiConfiguration mWifiConfig = null;
- private final String TAG = "WifiSoftAPTest";
- private final int DURATION = 10000;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mWifiManager = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
- assertNotNull(mWifiManager);
- assertTrue(mWifiManager.setWifiApEnabled(null, true));
- mWifiConfig = mWifiManager.getWifiApConfiguration();
- if (mWifiConfig != null) {
- Log.v(TAG, "mWifiConfig is " + mWifiConfig.toString());
- } else {
- Log.v(TAG, "mWifiConfig is null.");
- }
- }
-
- @Override
- protected void tearDown() throws Exception {
- Log.v(TAG, "turn off wifi tethering");
- mWifiManager.setWifiApEnabled(null, false);
- super.tearDown();
- }
-
- // Test case 1: Test the soft AP SSID with letters
- @LargeTest
- public void testApSsidWithAlphabet() {
- WifiConfiguration config = new WifiConfiguration();
- config.SSID = "abcdefghijklmnopqrstuvwxyz";
- config.allowedKeyManagement.set(KeyMgmt.NONE);
- mWifiConfig = config;
- assertTrue(mWifiManager.setWifiApEnabled(mWifiConfig, true));
- try {
- Thread.sleep(DURATION);
- } catch (InterruptedException e) {
- Log.v(TAG, "exception " + e.getStackTrace());
- assertFalse(true);
- }
- assertNotNull(mWifiManager.getWifiApConfiguration());
- assertEquals("wifi AP state is not enabled", WifiManager.WIFI_AP_STATE_ENABLED,
- mWifiManager.getWifiApState());
- }
-}
diff --git a/libs/protoutil/include/android/util/ProtoOutputStream.h b/libs/protoutil/include/android/util/ProtoOutputStream.h
index 0f1cced..10be649 100644
--- a/libs/protoutil/include/android/util/ProtoOutputStream.h
+++ b/libs/protoutil/include/android/util/ProtoOutputStream.h
@@ -49,7 +49,7 @@
bool write(uint64_t fieldId, long long val);
bool write(uint64_t fieldId, bool val);
bool write(uint64_t fieldId, std::string val);
- bool write(uint64_t fieldId, const char* val);
+ bool write(uint64_t fieldId, const char* val, size_t size);
/**
* Starts a sub-message write session.
@@ -103,4 +103,4 @@
}
}
-#endif // ANDROID_UTIL_PROTOOUTPUT_STREAM_H
\ No newline at end of file
+#endif // ANDROID_UTIL_PROTOOUTPUT_STREAM_H
diff --git a/libs/protoutil/src/ProtoOutputStream.cpp b/libs/protoutil/src/ProtoOutputStream.cpp
index 15144ac..9dadf1c 100644
--- a/libs/protoutil/src/ProtoOutputStream.cpp
+++ b/libs/protoutil/src/ProtoOutputStream.cpp
@@ -225,14 +225,13 @@
}
bool
-ProtoOutputStream::write(uint64_t fieldId, const char* val)
+ProtoOutputStream::write(uint64_t fieldId, const char* val, size_t size)
{
if (mCompact) return false;
const uint32_t id = (uint32_t)fieldId;
- int size = 0;
- while (val[size] != '\0') size++;
switch (fieldId & FIELD_TYPE_MASK) {
case TYPE_STRING:
+ case TYPE_BYTES:
writeUtf8StringImpl(id, val, size);
return true;
default:
diff --git a/packages/SettingsLib/OWNERS b/packages/SettingsLib/OWNERS
new file mode 100644
index 0000000..4211c27
--- /dev/null
+++ b/packages/SettingsLib/OWNERS
@@ -0,0 +1,21 @@
+# People who can approve changes for submission
+asapperstein@google.com
+asargent@google.com
+dehboxturtle@google.com
+dhnishi@google.com
+dling@google.com
+dsandler@google.com
+evanlaird@google.com
+jackqdyulei@google.com
+jmonk@google.com
+mfritze@google.com
+nicoya@google.com
+rogerxue@google.com
+virgild@google.com
+zhfan@google.com
+
+# Emergency approvers in case the above are not available
+miket@google.com
+
+# Exempt resource files (because they are in a flat directory and too hard to manage via OWNERS)
+per-file *.xml=*
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java b/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java
index 817989a..f4c9bb3 100755
--- a/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java
@@ -415,8 +415,8 @@
: (mLevel == 100 ? 0.38f : 0.5f)));
mTextHeight = -mTextPaint.getFontMetrics().ascent;
pctText = String.valueOf(SINGLE_DIGIT_PERCENT ? (level / 10) : level);
- pctX = mWidth * 0.5f;
- pctY = (mHeight + mTextHeight) * 0.47f;
+ pctX = mWidth * 0.5f + left;
+ pctY = (mHeight + mTextHeight) * 0.47f + top;
pctOpaque = levelTop > pctY;
if (!pctOpaque) {
mTextPath.reset();
@@ -439,8 +439,8 @@
if (!mCharging && !mPowerSaveEnabled) {
if (level <= mCriticalLevel) {
// draw the warning text
- final float x = mWidth * 0.5f;
- final float y = (mHeight + mWarningTextHeight) * 0.48f;
+ final float x = mWidth * 0.5f + left;
+ final float y = (mHeight + mWarningTextHeight) * 0.48f + top;
c.drawText(mWarningString, x, y, mWarningTextPaint);
} else if (pctOpaque) {
// draw the percentage text
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/OWNERS b/packages/SettingsLib/src/com/android/settingslib/inputmethod/OWNERS
new file mode 100644
index 0000000..a0e28ba
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/OWNERS
@@ -0,0 +1,5 @@
+# Default reviewers for this and subdirectories.
+takaoka@google.com
+yukawa@google.com
+
+# Emergency approvers in case the above are not available
\ No newline at end of file
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BatteryMeterDrawableBaseTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BatteryMeterDrawableBaseTest.java
new file mode 100644
index 0000000..3522b8a
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BatteryMeterDrawableBaseTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.graph;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+import com.android.settingslib.TestConfig;
+import com.android.settingslib.testutils.shadow.SettingsLibShadowResources;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
+ shadows = SettingsLibShadowResources.class)
+public class BatteryMeterDrawableBaseTest {
+ private static final int PADDING = 5;
+ private static final int HEIGHT = 80;
+ private static final int WIDTH = 40;
+ @Mock
+ private Canvas mCanvas;
+ private Context mContext;
+ private BatteryMeterDrawableBase mBatteryMeterDrawableBase;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = RuntimeEnvironment.application;
+ mBatteryMeterDrawableBase = new BatteryMeterDrawableBase(mContext, 0 /* frameColor */);
+ }
+
+ @Test
+ public void testDraw_hasPaddingAndBounds_drawWarningInCorrectPosition() {
+ mBatteryMeterDrawableBase.setPadding(PADDING, PADDING, PADDING, PADDING);
+ mBatteryMeterDrawableBase.setBounds(0, 0, WIDTH + 2 * PADDING, HEIGHT + 2 * PADDING);
+ mBatteryMeterDrawableBase.setBatteryLevel(3);
+
+ mBatteryMeterDrawableBase.draw(mCanvas);
+
+ // WIDTH * 0.5 + PADDING = 25
+ // (HEIGHT + TEXT_HEIGHT) * 0.48 + PADDING = 43.3999998
+ verify(mCanvas).drawText(eq("!"), eq(25f), eq(43.399998f), any(Paint.class));
+ }
+
+ @Test
+ public void testDraw_hasPaddingAndBounds_drawBatteryLevelInCorrectPosition() {
+ mBatteryMeterDrawableBase.setPadding(PADDING, PADDING, PADDING, PADDING);
+ mBatteryMeterDrawableBase.setBounds(0, 0, WIDTH + 2 * PADDING, HEIGHT + 2 * PADDING);
+ mBatteryMeterDrawableBase.setBatteryLevel(20);
+ mBatteryMeterDrawableBase.setShowPercent(true);
+
+ mBatteryMeterDrawableBase.draw(mCanvas);
+
+ // WIDTH * 0.5 + PADDING = 25
+ // (HEIGHT + TEXT_HEIGHT) * 0.47 + PADDING = 42.6
+ verify(mCanvas).drawText(eq("20"), eq(25f), eq(42.6f), any(Paint.class));
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index d24e51c..40ee838 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -382,13 +382,6 @@
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... args) {
- // Disable tethering if enabling Wifi
- final int wifiApState = mWifiManager.getWifiApState();
- if (enabled && ((wifiApState == WifiManager.WIFI_AP_STATE_ENABLING) ||
- (wifiApState == WifiManager.WIFI_AP_STATE_ENABLED))) {
- mWifiManager.setWifiApEnabled(null, false);
- }
-
mWifiManager.setWifiEnabled(enabled);
return null;
}
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index cf1d33c..a139ac4 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -754,7 +754,7 @@
* and re-binding, during which the system could *technically* hand that port out to someone
* else.
*/
- private void bindToRandomPort(FileDescriptor sockFd) throws IOException {
+ private int bindToRandomPort(FileDescriptor sockFd) throws IOException {
for (int i = MAX_PORT_BIND_ATTEMPTS; i > 0; i--) {
try {
FileDescriptor probeSocket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
@@ -763,7 +763,7 @@
Os.close(probeSocket);
Log.v(TAG, "Binding to port " + port);
Os.bind(sockFd, INADDR_ANY, port);
- return;
+ return port;
} catch (ErrnoException e) {
// Someone miraculously claimed the port just after we closed probeSocket.
if (e.errno == OsConstants.EADDRINUSE) {
@@ -803,7 +803,7 @@
Log.v(TAG, "Binding to port " + port);
Os.bind(sockFd, INADDR_ANY, port);
} else {
- bindToRandomPort(sockFd);
+ port = bindToRandomPort(sockFd);
}
// This code is common to both the unspecified and specified port cases
Os.setsockoptInt(
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index cd25610..bfbce40 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -641,8 +641,8 @@
break;
}
case H_PARTITION_FORGET: {
- final String partGuid = (String) msg.obj;
- forgetPartition(partGuid);
+ final VolumeRecord rec = (VolumeRecord) msg.obj;
+ forgetPartition(rec.partGuid, rec.fsUuid);
break;
}
case H_RESET: {
@@ -1694,7 +1694,7 @@
synchronized (mLock) {
final VolumeRecord rec = mRecords.remove(fsUuid);
if (rec != null && !TextUtils.isEmpty(rec.partGuid)) {
- mHandler.obtainMessage(H_PARTITION_FORGET, rec.partGuid).sendToTarget();
+ mHandler.obtainMessage(H_PARTITION_FORGET, rec).sendToTarget();
}
mCallbacks.notifyVolumeForgotten(fsUuid);
@@ -1718,7 +1718,7 @@
final String fsUuid = mRecords.keyAt(i);
final VolumeRecord rec = mRecords.valueAt(i);
if (!TextUtils.isEmpty(rec.partGuid)) {
- mHandler.obtainMessage(H_PARTITION_FORGET, rec.partGuid).sendToTarget();
+ mHandler.obtainMessage(H_PARTITION_FORGET, rec).sendToTarget();
}
mCallbacks.notifyVolumeForgotten(fsUuid);
}
@@ -1733,9 +1733,9 @@
}
}
- private void forgetPartition(String partGuid) {
+ private void forgetPartition(String partGuid, String fsUuid) {
try {
- mVold.forgetPartition(partGuid);
+ mVold.forgetPartition(partGuid, fsUuid);
} catch (Exception e) {
Slog.wtf(TAG, e);
}
diff --git a/services/core/java/com/android/server/am/ActivityDisplay.java b/services/core/java/com/android/server/am/ActivityDisplay.java
index 089db87..29073cb 100644
--- a/services/core/java/com/android/server/am/ActivityDisplay.java
+++ b/services/core/java/com/android/server/am/ActivityDisplay.java
@@ -38,7 +38,9 @@
import static com.android.server.am.proto.ActivityDisplayProto.STACKS;
import static com.android.server.am.proto.ActivityDisplayProto.ID;
+import android.annotation.Nullable;
import android.app.ActivityManagerInternal;
+import android.app.ActivityOptions;
import android.app.WindowConfiguration;
import android.util.IntArray;
import android.util.Slog;
@@ -206,6 +208,18 @@
}
/**
+ * Returns an existing stack compatible with the input params or creates one
+ * if a compatible stack doesn't exist.
+ * @see #getOrCreateStack(int, int, boolean)
+ */
+ <T extends ActivityStack> T getOrCreateStack(@Nullable ActivityRecord r,
+ @Nullable ActivityOptions options, @Nullable TaskRecord candidateTask, int activityType,
+ boolean onTop) {
+ final int windowingMode = resolveWindowingMode(r, options, candidateTask, activityType);
+ return getOrCreateStack(windowingMode, activityType, onTop);
+ }
+
+ /**
* Creates a stack matching the input windowing mode and activity type on this display.
* @param windowingMode The windowing mode the stack should be created in. If
* {@link WindowConfiguration#WINDOWING_MODE_UNDEFINED} then the stack will
@@ -235,7 +249,7 @@
}
final ActivityManagerService service = mSupervisor.mService;
- if (!mSupervisor.isWindowingModeSupported(windowingMode, service.mSupportsMultiWindow,
+ if (!isWindowingModeSupported(windowingMode, service.mSupportsMultiWindow,
service.mSupportsSplitScreenMultiWindow, service.mSupportsFreeformWindowManagement,
service.mSupportsPictureInPicture, activityType)) {
throw new IllegalArgumentException("Can't create stack for unsupported windowingMode="
@@ -252,33 +266,27 @@
}
}
- windowingMode = updateWindowingModeForSplitScreenIfNeeded(windowingMode, activityType);
-
final int stackId = mSupervisor.getNextStackId();
-
- final T stack = createStackUnchecked(windowingMode, activityType, stackId, onTop);
-
- if (mDisplayId == DEFAULT_DISPLAY && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
- // Make sure recents stack exist when creating a dock stack as it normally need to be on
- // the other side of the docked stack and we make visibility decisions based on that.
- // TODO: Not sure if this is needed after we change to calculate visibility based on
- // stack z-order vs. id.
- getOrCreateStack(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_RECENTS, onTop);
- }
-
- return stack;
+ return createStackUnchecked(windowingMode, activityType, stackId, onTop);
}
@VisibleForTesting
<T extends ActivityStack> T createStackUnchecked(int windowingMode, int activityType,
int stackId, boolean onTop) {
- switch (windowingMode) {
- case WINDOWING_MODE_PINNED:
- return (T) new PinnedActivityStack(this, stackId, mSupervisor, onTop);
- default:
- return (T) new ActivityStack(
- this, stackId, mSupervisor, windowingMode, activityType, onTop);
+ if (windowingMode == WINDOWING_MODE_PINNED) {
+ return (T) new PinnedActivityStack(this, stackId, mSupervisor, onTop);
}
+ final T stack = (T) new ActivityStack(
+ this, stackId, mSupervisor, windowingMode, activityType, onTop);
+
+ if (mDisplayId == DEFAULT_DISPLAY && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+ // Make sure recents stack exist when creating a dock stack as it normally needs to be
+ // on the other side of the docked stack and we make visibility decisions based on that.
+ // TODO: Not sure if this is needed after we change to calculate visibility based on
+ // stack z-order vs. id.
+ getOrCreateStack(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_RECENTS, onTop);
+ }
+ return stack;
}
/**
@@ -372,6 +380,105 @@
}
}
+ /**
+ * Returns true if the {@param windowingMode} is supported based on other parameters passed in.
+ * @param windowingMode The windowing mode we are checking support for.
+ * @param supportsMultiWindow If we should consider support for multi-window mode in general.
+ * @param supportsSplitScreen If we should consider support for split-screen multi-window.
+ * @param supportsFreeform If we should consider support for freeform multi-window.
+ * @param supportsPip If we should consider support for picture-in-picture mutli-window.
+ * @param activityType The activity type under consideration.
+ * @return true if the windowing mode is supported.
+ */
+ private boolean isWindowingModeSupported(int windowingMode, boolean supportsMultiWindow,
+ boolean supportsSplitScreen, boolean supportsFreeform, boolean supportsPip,
+ int activityType) {
+
+ if (windowingMode == WINDOWING_MODE_UNDEFINED
+ || windowingMode == WINDOWING_MODE_FULLSCREEN) {
+ return true;
+ }
+ if (!supportsMultiWindow) {
+ return false;
+ }
+
+ if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+ || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
+ return supportsSplitScreen && WindowConfiguration.supportSplitScreenWindowingMode(
+ windowingMode, activityType);
+ }
+
+ if (!supportsFreeform && windowingMode == WINDOWING_MODE_FREEFORM) {
+ return false;
+ }
+
+ if (!supportsPip && windowingMode == WINDOWING_MODE_PINNED) {
+ return false;
+ }
+ return true;
+ }
+
+ int resolveWindowingMode(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
+ @Nullable TaskRecord task, int activityType) {
+
+ // First preference if the windowing mode in the activity options if set.
+ int windowingMode = (options != null)
+ ? options.getLaunchWindowingMode() : WINDOWING_MODE_UNDEFINED;
+
+ // If windowing mode is unset, then next preference is the candidate task, then the
+ // activity record.
+ if (windowingMode == WINDOWING_MODE_UNDEFINED) {
+ if (task != null) {
+ windowingMode = task.getWindowingMode();
+ }
+ if (windowingMode == WINDOWING_MODE_UNDEFINED && r != null) {
+ windowingMode = r.getWindowingMode();
+ }
+ if (windowingMode == WINDOWING_MODE_UNDEFINED) {
+ // Use the display's windowing mode.
+ windowingMode = getWindowingMode();
+ }
+ }
+
+ // Make sure the windowing mode we are trying to use makes sense for what is supported.
+ final ActivityManagerService service = mSupervisor.mService;
+ boolean supportsMultiWindow = service.mSupportsMultiWindow;
+ boolean supportsSplitScreen = service.mSupportsSplitScreenMultiWindow;
+ boolean supportsFreeform = service.mSupportsFreeformWindowManagement;
+ boolean supportsPip = service.mSupportsPictureInPicture;
+ if (supportsMultiWindow) {
+ if (task != null) {
+ supportsMultiWindow = task.isResizeable();
+ supportsSplitScreen = task.supportsSplitScreenWindowingMode();
+ // TODO: Do we need to check for freeform and Pip support here?
+ } else if (r != null) {
+ supportsMultiWindow = r.isResizeable();
+ supportsSplitScreen = r.supportsSplitScreenWindowingMode();
+ supportsFreeform = r.supportsFreeform();
+ supportsPip = r.supportsPictureInPicture();
+ }
+ }
+
+ final boolean inSplitScreenMode = hasSplitScreenPrimaryStack();
+ if (!inSplitScreenMode
+ && windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) {
+ // Switch to fullscreen windowing mode if we are not in split-screen mode and we are
+ // trying to launch in split-screen secondary.
+ windowingMode = WINDOWING_MODE_FULLSCREEN;
+ } else if (inSplitScreenMode && windowingMode == WINDOWING_MODE_FULLSCREEN
+ && supportsSplitScreen) {
+ windowingMode = WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+ }
+
+ if (windowingMode != WINDOWING_MODE_UNDEFINED
+ && isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen,
+ supportsFreeform, supportsPip, activityType)) {
+ return windowingMode;
+ }
+ // Return the display's windowing mode
+ return getWindowingMode();
+ }
+
/** Returns the top visible stack activity type that isn't in the exclude windowing mode. */
int getTopVisibleStackActivityType(int excludeWindowingMode) {
for (int i = mStacks.size() - 1; i >= 0; --i) {
@@ -408,6 +515,14 @@
}
}
+ /** We are in the process of exiting split-screen mode. */
+ void onExitingSplitScreenMode() {
+ // Remove reference to the primary-split-screen stack so it no longer has any effect on the
+ // display. For example, we want to be able to create fullscreen stack for standard activity
+ // types when exiting split-screen mode.
+ mSplitScreenPrimaryStack = null;
+ }
+
ActivityStack getSplitScreenPrimaryStack() {
return mSplitScreenPrimaryStack;
}
@@ -424,21 +539,6 @@
return mPinnedStack != null;
}
- int updateWindowingModeForSplitScreenIfNeeded(int windowingMode, int activityType) {
- final boolean inSplitScreenMode = hasSplitScreenPrimaryStack();
- if (!inSplitScreenMode
- && windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) {
- // Switch to fullscreen windowing mode if we are not in split-screen mode and we are
- // trying to launch in split-screen secondary.
- return WINDOWING_MODE_FULLSCREEN;
- } else if (inSplitScreenMode && windowingMode == WINDOWING_MODE_FULLSCREEN
- && WindowConfiguration.supportSplitScreenWindowingMode(
- windowingMode, activityType)) {
- return WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
- }
- return windowingMode;
- }
-
@Override
public String toString() {
return "ActivityDisplay={" + mDisplayId + " numStacks=" + mStacks.size() + "}";
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index e1e53b3..3fd1f4a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -31,6 +31,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS;
@@ -10318,7 +10319,8 @@
}
// TODO(multi-display): Have the caller pass in the windowing mode and activity type.
final ActivityStack stack = display.createStack(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /*onTop*/);
+ WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD,
+ ON_TOP);
return (stack != null) ? stack.mStackId : INVALID_STACK_ID;
}
}
@@ -14239,12 +14241,12 @@
}
}
sb.append("\n");
- if (info.crashInfo != null && info.crashInfo.stackTrace != null) {
- sb.append(info.crashInfo.stackTrace);
+ if (info.hasStackTrace()) {
+ sb.append(info.getStackTrace());
sb.append("\n");
}
- if (info.message != null) {
- sb.append(info.message);
+ if (info.getViolationDetails() != null) {
+ sb.append(info.getViolationDetails());
sb.append("\n");
}
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 2c72a4d..b885eab 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -165,6 +165,7 @@
import android.view.IApplicationToken;
import android.view.WindowManager.LayoutParams;
+import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ResolverActivity;
import com.android.internal.content.ReferrerIntent;
@@ -232,7 +233,9 @@
final String processName; // process where this component wants to run
final String taskAffinity; // as per ActivityInfo.taskAffinity
final boolean stateNotNeeded; // As per ActivityInfo.flags
- boolean fullscreen; // covers the full screen?
+ boolean fullscreen; // The activity is opaque and fills the entire space of this task.
+ // TODO: See if it possible to combine this with the fullscreen field.
+ final boolean hasWallpaper; // Has a wallpaper window as a background.
final boolean noDisplay; // activity is not displayed?
private final boolean componentSpecified; // did caller specify an explicit component?
final boolean rootVoiceInteraction; // was this the root activity of a voice interaction?
@@ -883,9 +886,14 @@
Entry ent = AttributeCache.instance().get(packageName,
realTheme, com.android.internal.R.styleable.Window, userId);
- fullscreen = ent != null && !ActivityInfo.isTranslucentOrFloating(ent.array);
- noDisplay = ent != null && ent.array.getBoolean(
- com.android.internal.R.styleable.Window_windowNoDisplay, false);
+ if (ent != null) {
+ fullscreen = !ActivityInfo.isTranslucentOrFloating(ent.array);
+ hasWallpaper = ent.array.getBoolean(R.styleable.Window_windowShowWallpaper, false);
+ noDisplay = ent.array.getBoolean(R.styleable.Window_windowNoDisplay, false);
+ } else {
+ hasWallpaper = false;
+ noDisplay = false;
+ }
setActivityType(_componentSpecified, _launchedFromUid, _intent, options, sourceRecord);
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 941c371..f05243b 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -489,22 +489,19 @@
activityType = ACTIVITY_TYPE_STANDARD;
}
final ActivityDisplay display = getDisplay();
- if (display != null) {
- if (activityType == ACTIVITY_TYPE_STANDARD
+ if (display != null && activityType == ACTIVITY_TYPE_STANDARD
&& windowingMode == WINDOWING_MODE_UNDEFINED) {
- // Standard activity types will mostly take on the windowing mode of the display if
- // one isn't specified, so look-up a compatible stack based on the display's
- // windowing mode.
- windowingMode = display.getWindowingMode();
- }
- windowingMode =
- display.updateWindowingModeForSplitScreenIfNeeded(windowingMode, activityType);
+ // Standard activity types will mostly take on the windowing mode of the display if one
+ // isn't specified, so look-up a compatible stack based on the display's windowing mode.
+ windowingMode = display.getWindowingMode();
}
return super.isCompatible(windowingMode, activityType);
}
/** Adds the stack to specified display and calls WindowManager to do the same. */
void reparent(ActivityDisplay activityDisplay, boolean onTop) {
+ // TODO: We should probably resolve the windowing mode for the stack on the new display here
+ // so that it end up in a compatible mode in the new display. e.g. split-screen secondary.
removeFromDisplay();
mTmpRect2.setEmpty();
postAddToDisplay(activityDisplay, mTmpRect2.isEmpty() ? null : mTmpRect2, onTop);
@@ -1496,25 +1493,19 @@
}
}
- /** Returns true if the stack contains a fullscreen task. */
- private boolean hasFullscreenTask() {
- for (int i = mTaskHistory.size() - 1; i >= 0; --i) {
- final TaskRecord task = mTaskHistory.get(i);
- if (task.mFullscreen) {
- return true;
- }
- }
- return false;
- }
-
/**
* Returns true if the stack is translucent and can have other contents visible behind it if
* needed. A stack is considered translucent if it don't contain a visible or
* starting (about to be visible) activity that is fullscreen (opaque).
* @param starting The currently starting activity or null if there is none.
+ * TODO: Can be removed once we are no longer using returnToType for back functionality
* @param stackBehind The stack directly behind this one.
*/
- private boolean isStackTranslucent(ActivityRecord starting, ActivityStack stackBehind) {
+ @VisibleForTesting
+ boolean isStackTranslucent(ActivityRecord starting, ActivityStack stackBehind) {
+ if (!isAttached() || mForceHidden) {
+ return true;
+ }
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
final TaskRecord task = mTaskHistory.get(taskNdx);
final ArrayList<ActivityRecord> activities = task.mActivities;
@@ -1533,7 +1524,7 @@
continue;
}
- if (r.fullscreen) {
+ if (r.fullscreen || r.hasWallpaper) {
// Stack isn't translucent if it has at least one fullscreen activity
// that is visible.
return false;
@@ -1572,127 +1563,66 @@
if (!isAttached() || mForceHidden) {
return false;
}
-
- final ActivityDisplay display = getDisplay();
- if (isTopStackOnDisplay() || mStackSupervisor.isFocusedStack(this)) {
+ if (mStackSupervisor.isFocusedStack(this)) {
return true;
}
- final int stackIndex = display.getIndexOf(this);
-
- // Check position and visibility of this stack relative to the front stack on its display.
- final ActivityStack topStack = getDisplay().getTopStack();
- final int windowingMode = getWindowingMode();
- final int activityType = getActivityType();
-
- if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
- // If the assistant stack is focused and translucent, then the docked stack is always
- // visible
- if (topStack.isActivityTypeAssistant()) {
- return topStack.isStackTranslucent(starting, this);
- }
- return true;
- }
-
- // Set home stack to invisible when it is below but not immediately below the docked stack
- // A case would be if recents stack exists but has no tasks and is below the docked stack
- // and home stack is below recents
- if (activityType == ACTIVITY_TYPE_HOME) {
- final ActivityStack splitScreenStack = display.getSplitScreenPrimaryStack();
- int dockedStackIndex = display.getIndexOf(splitScreenStack);
- if (dockedStackIndex > stackIndex && stackIndex != dockedStackIndex - 1) {
- return false;
- }
- }
-
- // Find the first stack behind front stack that actually got something visible.
- int stackBehindTopIndex = display.getIndexOf(topStack) - 1;
- while (stackBehindTopIndex >= 0 &&
- display.getChildAt(stackBehindTopIndex).topRunningActivityLocked() == null) {
- stackBehindTopIndex--;
- }
- final ActivityStack stackBehindTop = (stackBehindTopIndex >= 0)
- ? display.getChildAt(stackBehindTopIndex) : null;
- int stackBehindTopWindowingMode = WINDOWING_MODE_UNDEFINED;
- int stackBehindTopActivityType = ACTIVITY_TYPE_UNDEFINED;
- if (stackBehindTop != null) {
- stackBehindTopWindowingMode = stackBehindTop.getWindowingMode();
- stackBehindTopActivityType = stackBehindTop.getActivityType();
- }
-
- final boolean alwaysOnTop = topStack.getWindowConfiguration().isAlwaysOnTop();
- if (topStack.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY || alwaysOnTop) {
- if (this == stackBehindTop) {
- // Stacks directly behind the docked or pinned stack are always visible.
- return true;
- } else if (alwaysOnTop && stackIndex == stackBehindTopIndex - 1) {
- // Otherwise, this stack can also be visible if it is directly behind a docked stack
- // or translucent assistant stack behind an always-on-top top-most stack
- if (stackBehindTopWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
- return true;
- } else if (stackBehindTopActivityType == ACTIVITY_TYPE_ASSISTANT) {
- return stackBehindTop.isStackTranslucent(starting, this);
- }
- }
- }
-
- if (topStack.isBackdropToTranslucentActivity()
- && topStack.isStackTranslucent(starting, stackBehindTop)) {
- // Stacks behind the fullscreen or assistant stack with a translucent activity are
- // always visible so they can act as a backdrop to the translucent activity.
- // For example, dialog activities
- if (stackIndex == stackBehindTopIndex) {
- return true;
- }
- if (stackBehindTopIndex >= 0) {
- if ((stackBehindTopWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
- || stackBehindTopWindowingMode == WINDOWING_MODE_PINNED)
- && stackIndex == (stackBehindTopIndex - 1)) {
- // The stack behind the docked or pinned stack is also visible so we can have a
- // complete backdrop to the translucent activity when the docked stack is up.
- return true;
- }
- }
- }
-
- if (isOnHomeDisplay()) {
- // Visibility of any stack on default display should have been determined by the
- // conditions above.
+ final ActivityRecord top = topRunningActivityLocked();
+ if (top == null && isInStackLocked(starting) == null && !isTopStackOnDisplay()) {
+ // Shouldn't be visible if you don't have any running activities, not starting one, and
+ // not the top stack on display.
return false;
}
- final int stackCount = display.getChildCount();
- for (int i = stackIndex + 1; i < stackCount; i++) {
- final ActivityStack stack = display.getChildAt(i);
-
- if (!stack.mFullscreen && !stack.hasFullscreenTask()) {
- continue;
- }
-
- if (!stack.isDynamicStacksVisibleBehindAllowed()) {
- // These stacks can't have any dynamic stacks visible behind them.
- return false;
- }
-
- if (!stack.isStackTranslucent(starting, null /* stackBehind */)) {
- return false;
- }
- }
-
- return true;
- }
-
- private boolean isBackdropToTranslucentActivity() {
- if (isActivityTypeAssistant()) {
- return true;
- }
+ final ActivityDisplay display = getDisplay();
+ boolean gotOpaqueSplitScreenPrimary = false;
+ boolean gotOpaqueSplitScreenSecondary = false;
final int windowingMode = getWindowingMode();
- return windowingMode == WINDOWING_MODE_FULLSCREEN
- || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
- }
+ for (int i = display.getChildCount() - 1; i >= 0; --i) {
+ final ActivityStack other = display.getChildAt(i);
+ if (other == this) {
+ // Should be visible if there is no other stack occluding it.
+ return true;
+ }
- private boolean isDynamicStacksVisibleBehindAllowed() {
- return isActivityTypeAssistant() || getWindowingMode() == WINDOWING_MODE_PINNED;
+ final int otherWindowingMode = other.getWindowingMode();
+ // TODO: Can be removed once we are no longer using returnToType for back functionality
+ final ActivityStack stackBehind = i > 0 ? display.getChildAt(i - 1) : null;
+
+ if (otherWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+ if (other.isStackTranslucent(starting, stackBehind)) {
+ // Can be visible behind a translucent fullscreen stack.
+ continue;
+ }
+ return false;
+ } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+ && !gotOpaqueSplitScreenPrimary) {
+ gotOpaqueSplitScreenPrimary =
+ !other.isStackTranslucent(starting, stackBehind);
+ if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+ && gotOpaqueSplitScreenPrimary) {
+ // Can not be visible behind another opaque stack in split-screen-primary mode.
+ return false;
+ }
+ } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
+ && !gotOpaqueSplitScreenSecondary) {
+ gotOpaqueSplitScreenSecondary =
+ !other.isStackTranslucent(starting, stackBehind);
+ if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
+ && gotOpaqueSplitScreenSecondary) {
+ // Can not be visible behind another opaque stack in split-screen-secondary mode.
+ return false;
+ }
+ }
+ if (gotOpaqueSplitScreenPrimary && gotOpaqueSplitScreenSecondary) {
+ // Can not be visible if we are in split-screen windowing mode and both halves of
+ // the screen are opaque.
+ return false;
+ }
+ }
+
+ // Well, nothing is stopping you from being visible...
+ return true;
}
final int rankTaskLayers(int baseLayer) {
@@ -5263,7 +5193,8 @@
public String toString() {
return "ActivityStack{" + Integer.toHexString(System.identityHashCode(this))
+ " stackId=" + mStackId + " type=" + activityTypeToString(getActivityType())
- + " mode=" + windowingModeToString(getWindowingMode()) + ", "
+ + " mode=" + windowingModeToString(getWindowingMode())
+ + " visible=" + shouldBeVisible(null /* starting */) + ", "
+ mTaskHistory.size() + " tasks}";
}
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index c15b5e2..7a4a0d4 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -110,7 +110,6 @@
import android.app.ProfilerInfo;
import android.app.ResultInfo;
import android.app.WaitResult;
-import android.app.WindowConfiguration;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -223,13 +222,6 @@
// at the top of its container (e.g. stack).
static final boolean ON_TOP = true;
- // Used to indicate that an objects (e.g. task) removal from its container
- // (e.g. stack) is due to it moving to another container.
- static final boolean MOVING = true;
-
- // Force the focus to change to the stack we are moving a task to..
- static final boolean FORCE_FOCUS = true;
-
// Don't execute any calls to resume.
static final boolean DEFER_RESUME = true;
@@ -405,6 +397,7 @@
* object each time.
*/
private final Rect tempRect = new Rect();
+ private final ActivityOptions mTmpOptions = ActivityOptions.makeBasic();
// The default minimal size that will be used if the activity doesn't specify its minimal size.
// It will be calculated when the default display gets added.
@@ -2186,89 +2179,6 @@
return null;
}
- /**
- * Returns true if the {@param windowingMode} is supported based on other parameters passed in.
- * @param windowingMode The windowing mode we are checking support for.
- * @param supportsMultiWindow If we should consider support for multi-window mode in general.
- * @param supportsSplitScreen If we should consider support for split-screen multi-window.
- * @param supportsFreeform If we should consider support for freeform multi-window.
- * @param supportsPip If we should consider support for picture-in-picture mutli-window.
- * @param activityType The activity type under consideration.
- * @return true if the windowing mode is supported.
- */
- boolean isWindowingModeSupported(int windowingMode, boolean supportsMultiWindow,
- boolean supportsSplitScreen, boolean supportsFreeform, boolean supportsPip,
- int activityType) {
-
- if (windowingMode == WINDOWING_MODE_UNDEFINED
- || windowingMode == WINDOWING_MODE_FULLSCREEN) {
- return true;
- }
- if (!supportsMultiWindow) {
- return false;
- }
-
- if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
- || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
- return supportsSplitScreen && WindowConfiguration.supportSplitScreenWindowingMode(
- windowingMode, activityType);
- }
-
- if (!supportsFreeform && windowingMode == WINDOWING_MODE_FREEFORM) {
- return false;
- }
-
- if (!supportsPip && windowingMode == WINDOWING_MODE_PINNED) {
- return false;
- }
- return true;
- }
-
- private int resolveWindowingMode(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
- @Nullable TaskRecord task, int activityType) {
-
- // First preference if the windowing mode in the activity options if set.
- int windowingMode = (options != null)
- ? options.getLaunchWindowingMode() : WINDOWING_MODE_UNDEFINED;
-
- // If windowing mode is unset, then next preference is the candidate task, then the
- // activity record.
- if (windowingMode == WINDOWING_MODE_UNDEFINED) {
- if (task != null) {
- windowingMode = task.getWindowingMode();
- }
- if (windowingMode == WINDOWING_MODE_UNDEFINED && r != null) {
- windowingMode = r.getWindowingMode();
- }
- }
-
- // Make sure the windowing mode we are trying to use makes sense for what is supported.
- boolean supportsMultiWindow = mService.mSupportsMultiWindow;
- boolean supportsSplitScreen = mService.mSupportsSplitScreenMultiWindow;
- boolean supportsFreeform = mService.mSupportsFreeformWindowManagement;
- boolean supportsPip = mService.mSupportsPictureInPicture;
- if (supportsMultiWindow) {
- if (task != null) {
- supportsMultiWindow = task.isResizeable();
- supportsSplitScreen = task.supportsSplitScreenWindowingMode();
- // TODO: Do we need to check for freeform and Pip support here?
- } else if (r != null) {
- supportsMultiWindow = r.isResizeable();
- supportsSplitScreen = r.supportsSplitScreenWindowingMode();
- supportsFreeform = r.supportsFreeform();
- supportsPip = r.supportsPictureInPicture();
- }
- }
-
- if (windowingMode != WINDOWING_MODE_UNDEFINED
- && isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen,
- supportsFreeform, supportsPip, activityType)) {
- return windowingMode;
- }
- // Return root/systems windowing mode
- return getWindowingMode();
- }
-
int resolveActivityType(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
@Nullable TaskRecord task) {
// Preference is given to the activity type for the activity then the task since the type
@@ -2329,7 +2239,6 @@
}
final int activityType = resolveActivityType(r, options, candidateTask);
- int windowingMode = resolveWindowingMode(r, options, candidateTask, activityType);
T stack = null;
// Next preference for stack goes to the display Id set in the activity options or the
@@ -2347,7 +2256,7 @@
}
final ActivityDisplay display = getActivityDisplayOrCreateLocked(displayId);
if (display != null) {
- stack = display.getOrCreateStack(windowingMode, activityType, onTop);
+ stack = display.getOrCreateStack(r, options, candidateTask, activityType, onTop);
if (stack != null) {
return stack;
}
@@ -2365,10 +2274,14 @@
stack = r.getStack();
}
if (stack != null) {
- if (stack.isCompatible(windowingMode, activityType)) {
- return stack;
- }
display = stack.getDisplay();
+ if (display != null) {
+ final int windowingMode =
+ display.resolveWindowingMode(r, options, candidateTask, activityType);
+ if (stack.isCompatible(windowingMode, activityType)) {
+ return stack;
+ }
+ }
}
if (display == null
@@ -2379,7 +2292,7 @@
display = getDefaultDisplay();
}
- return display.getOrCreateStack(windowingMode, activityType, onTop);
+ return display.getOrCreateStack(r, options, candidateTask, activityType, onTop);
}
/**
@@ -2596,6 +2509,8 @@
final ActivityDisplay toDisplay = getActivityDisplay(toDisplayId);
if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+ // Tell the display we are exiting split-screen mode.
+ toDisplay.onExitingSplitScreenMode();
// We are moving all tasks from the docked stack to the fullscreen stack,
// which is dismissing the docked stack, so resize all other stacks to
// fullscreen here already so we don't end up with resize trashing.
@@ -2625,35 +2540,34 @@
final ArrayList<TaskRecord> tasks = fromStack.getAllTasks();
if (!tasks.isEmpty()) {
+ mTmpOptions.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN);
final int size = tasks.size();
- final ActivityStack fullscreenStack = toDisplay.getOrCreateStack(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, onTop);
+ for (int i = 0; i < size; ++i) {
+ final TaskRecord task = tasks.get(i);
+ final ActivityStack toStack = toDisplay.getOrCreateStack(
+ null, mTmpOptions, task, task.getActivityType(), onTop);
- if (onTop) {
- final int returnToType =
- toDisplay.getTopVisibleStackActivityType(WINDOWING_MODE_PINNED);
- for (int i = 0; i < size; i++) {
- final TaskRecord task = tasks.get(i);
+ if (onTop) {
+ final int returnToType =
+ toDisplay.getTopVisibleStackActivityType(WINDOWING_MODE_PINNED);
final boolean isTopTask = i == (size - 1);
if (inPinnedWindowingMode) {
- // Update the return-to to reflect where the pinned stack task was moved
- // from so that we retain the stack that was previously visible if the
- // pinned stack is recreated. See moveActivityToPinnedStackLocked().
+ // Update the return-to to reflect where the pinned stack task was
+ // moved from so that we retain the stack that was previously
+ // visible if the pinned stack is recreated.
+ // See moveActivityToPinnedStackLocked().
task.setTaskToReturnTo(returnToType);
}
// Defer resume until all the tasks have been moved to the fullscreen stack
- task.reparent(fullscreenStack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT,
+ task.reparent(toStack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT,
isTopTask /* animate */, DEFER_RESUME,
schedulePictureInPictureModeChange,
"moveTasksToFullscreenStack - onTop");
- }
- } else {
- for (int i = 0; i < size; i++) {
- final TaskRecord task = tasks.get(i);
+ } else {
// Position the tasks in the fullscreen stack in order at the bottom of the
// stack. Also defer resume until all the tasks have been moved to the
// fullscreen stack.
- task.reparent(fullscreenStack, i /* position */,
+ task.reparent(toStack, ON_TOP,
REPARENT_LEAVE_STACK_IN_PLACE, !ANIMATE, DEFER_RESUME,
schedulePictureInPictureModeChange,
"moveTasksToFullscreenStack - NOT_onTop");
@@ -3080,23 +2994,16 @@
+ " task=" + task);
}
- // We don't allow moving a unresizeable task to the docked stack since the docked stack is
- // used for split-screen mode and will cause things like the docked divider to show up. We
- // instead leave the task in its current stack or move it to the fullscreen stack if it
- // isn't currently in a stack.
+ // Leave the task in its current stack or a fullscreen stack if it isn't resizeable and the
+ // preferred stack is in multi-window mode.
if (inMultiWindowMode && !task.isResizeable()) {
- Slog.w(TAG, "Can not move unresizeable task=" + task + " to docked stack."
- + " Moving to stackId=" + stackId + " instead.");
- // Temporarily disable resizeablility of the task as we don't want it to be resized if,
- // for example, a docked stack is created which will lead to the stack we are moving
- // from being resized and and its resizeable tasks being resized.
- try {
- task.mTemporarilyUnresizable = true;
- stack = stack.getDisplay().createStack(
- WINDOWING_MODE_FULLSCREEN, stack.getActivityType(), toTop);
- } finally {
- task.mTemporarilyUnresizable = false;
+ Slog.w(TAG, "Can not move unresizeable task=" + task + " to multi-window stack=" + stack
+ + " Moving to a fullscreen stack instead.");
+ if (prevStack != null) {
+ return prevStack;
}
+ stack = stack.getDisplay().createStack(
+ WINDOWING_MODE_FULLSCREEN, stack.getActivityType(), toTop);
}
return stack;
}
@@ -4287,9 +4194,9 @@
final boolean isSecondaryDisplayPreferred =
(preferredDisplayId != DEFAULT_DISPLAY && preferredDisplayId != INVALID_DISPLAY);
final boolean inSplitScreenMode = actualStack != null
- && actualStack.inSplitScreenWindowingMode();
+ && actualStack.getDisplay().hasSplitScreenPrimaryStack();
if (((!inSplitScreenMode && preferredWindowingMode != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
- && !isSecondaryDisplayPreferred) || task.isActivityTypeHome()) {
+ && !isSecondaryDisplayPreferred) || !task.isActivityTypeStandardOrUndefined()) {
return;
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index c62cc38..aa82d00 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -1198,11 +1198,6 @@
+ " (uid " + r.callingUid + ")");
skip = true;
}
- if (!skip) {
- r.manifestCount++;
- } else {
- r.manifestSkipCount++;
- }
if (r.curApp != null && r.curApp.crashing) {
// If the target process is crashing, just skip it.
Slog.w(TAG, "Skipping deliver ordered [" + mQueueName + "] " + r
@@ -1283,6 +1278,16 @@
}
}
+ if (!skip && !Intent.ACTION_SHUTDOWN.equals(r.intent.getAction())
+ && !mService.mUserController
+ .isUserRunning(UserHandle.getUserId(info.activityInfo.applicationInfo.uid),
+ 0 /* flags */)) {
+ skip = true;
+ Slog.w(TAG,
+ "Skipping delivery to " + info.activityInfo.packageName + " / "
+ + info.activityInfo.applicationInfo.uid + " : user is not running");
+ }
+
if (skip) {
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
"Skipping delivery of ordered [" + mQueueName + "] "
@@ -1291,9 +1296,11 @@
r.receiver = null;
r.curFilter = null;
r.state = BroadcastRecord.IDLE;
+ r.manifestSkipCount++;
scheduleBroadcastsLocked();
return;
}
+ r.manifestCount++;
r.delivery[recIdx] = BroadcastRecord.DELIVERY_DELIVERED;
r.state = BroadcastRecord.APP_RECEIVE;
@@ -1302,7 +1309,7 @@
if (DEBUG_MU && r.callingUid > UserHandle.PER_USER_RANGE) {
Slog.v(TAG_MU, "Updated broadcast record activity info for secondary user, "
+ info.activityInfo + ", callingUid = " + r.callingUid + ", uid = "
- + info.activityInfo.applicationInfo.uid);
+ + receiverUid);
}
if (brOptions != null && brOptions.getTemporaryAppWhitelistDuration() > 0) {
@@ -1365,7 +1372,7 @@
// and mark the broadcast record as ready for the next.
Slog.w(TAG, "Unable to launch app "
+ info.activityInfo.applicationInfo.packageName + "/"
- + info.activityInfo.applicationInfo.uid + " for broadcast "
+ + receiverUid + " for broadcast "
+ r.intent + ": process is bad");
logBroadcastReceiverDiscardLocked(r);
finishReceiverLocked(r, r.resultCode, r.resultData,
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index c451235..4080c27 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -231,9 +231,6 @@
private boolean mSupportsPictureInPicture; // Whether or not this task and its activities
// support PiP. Based on the {@link ActivityInfo#FLAG_SUPPORTS_PICTURE_IN_PICTURE} flag
// of the root activity.
- boolean mTemporarilyUnresizable; // Separate flag from mResizeMode used to suppress resize
- // changes on a temporary basis.
-
/** Can't be put in lockTask mode. */
final static int LOCK_TASK_AUTH_DONT_LOCK = 0;
/** Can enter app pinning with user approval. Can never start over existing lockTask task. */
@@ -1457,7 +1454,7 @@
private boolean isResizeable(boolean checkSupportsPip) {
return (mService.mForceResizableActivities || ActivityInfo.isResizeableMode(mResizeMode)
- || (checkSupportsPip && mSupportsPictureInPicture)) && !mTemporarilyUnresizable;
+ || (checkSupportsPip && mSupportsPictureInPicture));
}
boolean isResizeable() {
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index e41c17d..4cf35bc 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -669,9 +669,11 @@
for (String item : configValues) {
if (DEBUG) Log.d(TAG, "GpsParamsResource: " + item);
// We need to support "KEY =", but not "=VALUE".
- String[] split = item.split("=");
- if (split.length == 2) {
- properties.setProperty(split[0].trim().toUpperCase(), split[1]);
+ int index = item.indexOf("=");
+ if (index > 0 && index + 1 < item.length()) {
+ String key = item.substring(0, index);
+ String value = item.substring(index + 1);
+ properties.setProperty(key.trim().toUpperCase(), value);
} else {
Log.w(TAG, "malformed contents: " + item);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 391deb7..7fb2ec4 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -753,9 +753,6 @@
PackageManagerInternal.ExternalSourcesPolicy mExternalSourcesPolicy;
- // System configuration read by SystemConfig.
- final int[] mGlobalGids;
- final SparseArray<ArraySet<String>> mSystemPermissions;
@GuardedBy("mAvailableFeatures")
final ArrayMap<String, FeatureInfo> mAvailableFeatures;
@@ -938,10 +935,6 @@
final ArrayMap<ComponentName, PackageParser.Instrumentation> mInstrumentation =
new ArrayMap<ComponentName, PackageParser.Instrumentation>();
- // Mapping from permission names to info about them.
- final ArrayMap<String, PackageParser.PermissionGroup> mPermissionGroups =
- new ArrayMap<String, PackageParser.PermissionGroup>();
-
// Packages whose data we have transfered into another package, thus
// should no longer exist.
final ArraySet<String> mTransferedPackages = new ArraySet<String>();
@@ -2434,8 +2427,6 @@
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "get system config");
SystemConfig systemConfig = SystemConfig.getInstance();
- mGlobalGids = systemConfig.getGlobalGids();
- mSystemPermissions = systemConfig.getSystemPermissions();
mAvailableFeatures = systemConfig.getAvailableFeatures();
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
@@ -4228,44 +4219,22 @@
@Override
public @Nullable ParceledListSlice<PermissionInfo> queryPermissionsByGroup(String groupName,
int flags) {
- // TODO Move this to PermissionManager when mPermissionGroups is moved there
- synchronized (mPackages) {
- if (groupName != null && !mPermissionGroups.containsKey(groupName)) {
- // This is thrown as NameNotFoundException
- return null;
- }
- }
- return new ParceledListSlice<>(
- mPermissionManager.getPermissionInfoByGroup(groupName, flags, getCallingUid()));
+ final List<PermissionInfo> permissionList =
+ mPermissionManager.getPermissionInfoByGroup(groupName, flags, getCallingUid());
+ return (permissionList == null) ? null : new ParceledListSlice<>(permissionList);
}
@Override
- public PermissionGroupInfo getPermissionGroupInfo(String name, int flags) {
- if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
- return null;
- }
- // reader
- synchronized (mPackages) {
- return PackageParser.generatePermissionGroupInfo(
- mPermissionGroups.get(name), flags);
- }
+ public PermissionGroupInfo getPermissionGroupInfo(String groupName, int flags) {
+ return mPermissionManager.getPermissionGroupInfo(groupName, flags, getCallingUid());
}
@Override
public @NonNull ParceledListSlice<PermissionGroupInfo> getAllPermissionGroups(int flags) {
- if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
- return ParceledListSlice.emptyList();
- }
- // reader
- synchronized (mPackages) {
- final int N = mPermissionGroups.size();
- ArrayList<PermissionGroupInfo> out
- = new ArrayList<PermissionGroupInfo>(N);
- for (PackageParser.PermissionGroup pg : mPermissionGroups.values()) {
- out.add(PackageParser.generatePermissionGroupInfo(pg, flags));
- }
- return new ParceledListSlice<>(out);
- }
+ final List<PermissionGroupInfo> permissionList =
+ mPermissionManager.getAllPermissionGroups(flags, getCallingUid());
+ return (permissionList == null)
+ ? ParceledListSlice.emptyList() : new ParceledListSlice<>(permissionList);
}
private ApplicationInfo generateApplicationInfoFromSettingsLPw(String packageName, int flags,
@@ -5138,59 +5107,7 @@
@Override
public int checkUidPermission(String permName, int uid) {
- final int callingUid = Binder.getCallingUid();
- final int callingUserId = UserHandle.getUserId(callingUid);
- final boolean isCallerInstantApp = getInstantAppPackageName(callingUid) != null;
- final boolean isUidInstantApp = getInstantAppPackageName(uid) != null;
- final int userId = UserHandle.getUserId(uid);
- if (!sUserManager.exists(userId)) {
- return PackageManager.PERMISSION_DENIED;
- }
-
- synchronized (mPackages) {
- Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
- if (obj != null) {
- if (obj instanceof SharedUserSetting) {
- if (isCallerInstantApp) {
- return PackageManager.PERMISSION_DENIED;
- }
- } else if (obj instanceof PackageSetting) {
- final PackageSetting ps = (PackageSetting) obj;
- if (filterAppAccessLPr(ps, callingUid, callingUserId)) {
- return PackageManager.PERMISSION_DENIED;
- }
- }
- final SettingBase settingBase = (SettingBase) obj;
- final PermissionsState permissionsState = settingBase.getPermissionsState();
- if (permissionsState.hasPermission(permName, userId)) {
- if (isUidInstantApp) {
- if (mSettings.mPermissions.isPermissionInstant(permName)) {
- return PackageManager.PERMISSION_GRANTED;
- }
- } else {
- return PackageManager.PERMISSION_GRANTED;
- }
- }
- // Special case: ACCESS_FINE_LOCATION permission includes ACCESS_COARSE_LOCATION
- if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && permissionsState
- .hasPermission(Manifest.permission.ACCESS_FINE_LOCATION, userId)) {
- return PackageManager.PERMISSION_GRANTED;
- }
- } else {
- ArraySet<String> perms = mSystemPermissions.get(uid);
- if (perms != null) {
- if (perms.contains(permName)) {
- return PackageManager.PERMISSION_GRANTED;
- }
- if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && perms
- .contains(Manifest.permission.ACCESS_FINE_LOCATION)) {
- return PackageManager.PERMISSION_GRANTED;
- }
- }
- }
- }
-
- return PackageManager.PERMISSION_DENIED;
+ return mPermissionManager.checkUidPermission(permName, uid, getCallingUid());
}
@Override
@@ -11156,54 +11073,15 @@
if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Activities: " + r);
}
- N = pkg.permissionGroups.size();
- r = null;
- for (i=0; i<N; i++) {
- PackageParser.PermissionGroup pg = pkg.permissionGroups.get(i);
- PackageParser.PermissionGroup cur = mPermissionGroups.get(pg.info.name);
- final String curPackageName = cur == null ? null : cur.info.packageName;
- // Dont allow ephemeral apps to define new permission groups.
- if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) {
- Slog.w(TAG, "Permission group " + pg.info.name + " from package "
- + pg.info.packageName
- + " ignored: instant apps cannot define new permission groups.");
- continue;
- }
- final boolean isPackageUpdate = pg.info.packageName.equals(curPackageName);
- if (cur == null || isPackageUpdate) {
- mPermissionGroups.put(pg.info.name, pg);
- if (chatty) {
- if (r == null) {
- r = new StringBuilder(256);
- } else {
- r.append(' ');
- }
- if (isPackageUpdate) {
- r.append("UPD:");
- }
- r.append(pg.info.name);
- }
- } else {
- Slog.w(TAG, "Permission group " + pg.info.name + " from package "
- + pg.info.packageName + " ignored: original from "
- + cur.info.packageName);
- if (chatty) {
- if (r == null) {
- r = new StringBuilder(256);
- } else {
- r.append(' ');
- }
- r.append("DUP:");
- r.append(pg.info.name);
- }
- }
- }
- if (r != null) {
- if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Permission Groups: " + r);
+ // Don't allow ephemeral applications to define new permissions groups.
+ if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) {
+ Slog.w(TAG, "Permission groups from package " + pkg.packageName
+ + " ignored: instant apps cannot define new permission groups.");
+ } else {
+ mPermissionManager.addAllPermissionGroups(pkg, chatty);
}
-
- // Dont allow ephemeral apps to define new permissions.
+ // Don't allow ephemeral applications to define new permissions.
if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) {
Slog.w(TAG, "Permissions from package " + pkg.packageName
+ " ignored: instant apps cannot define new permissions.");
@@ -12107,7 +11985,7 @@
}
}
- permissionsState.setGlobalGids(mGlobalGids);
+ permissionsState.setGlobalGids(mPermissionManager.getGlobalGidsTEMP());
final int N = pkg.requestedPermissions.size();
for (int i=0; i<N; i++) {
@@ -23606,13 +23484,6 @@
}
@Override
- public PackageParser.PermissionGroup getPermissionGroupTEMP(String groupName) {
- synchronized (mPackages) {
- return mPermissionGroups.get(groupName);
- }
- }
-
- @Override
public boolean isInstantApp(String packageName, int userId) {
return PackageManagerService.this.isInstantApp(packageName, userId);
}
diff --git a/services/core/java/com/android/server/pm/UserDataPreparer.java b/services/core/java/com/android/server/pm/UserDataPreparer.java
index b8b00af..bfe09b8 100644
--- a/services/core/java/com/android/server/pm/UserDataPreparer.java
+++ b/services/core/java/com/android/server/pm/UserDataPreparer.java
@@ -132,11 +132,9 @@
if ((flags & StorageManager.FLAG_STORAGE_DE) != 0) {
FileUtils.deleteContentsAndDir(getUserSystemDirectory(userId));
FileUtils.deleteContentsAndDir(getDataSystemDeDirectory(userId));
- FileUtils.deleteContentsAndDir(getDataMiscDeDirectory(userId));
}
if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) {
FileUtils.deleteContentsAndDir(getDataSystemCeDirectory(userId));
- FileUtils.deleteContentsAndDir(getDataMiscCeDirectory(userId));
}
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerInternal.java
index 8aac52a..9605fcc 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerInternal.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.PackageParser;
+import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManager.PermissionInfoFlags;
@@ -89,6 +90,7 @@
* the permission settings.
*/
public abstract void addAllPermissions(@NonNull PackageParser.Package pkg, boolean chatty);
+ public abstract void addAllPermissionGroups(@NonNull PackageParser.Package pkg, boolean chatty);
public abstract void removeAllPermissions(@NonNull PackageParser.Package pkg, boolean chatty);
public abstract boolean addDynamicPermission(@NonNull PermissionInfo info, boolean async,
int callingUid, @Nullable PermissionCallback callback);
@@ -105,6 +107,16 @@
public abstract int getPermissionFlags(@NonNull String permName,
@NonNull String packageName, int callingUid, int userId);
/**
+ * Retrieve all of the information we know about a particular group of permissions.
+ */
+ public abstract @Nullable PermissionGroupInfo getPermissionGroupInfo(
+ @NonNull String groupName, int flags, int callingUid);
+ /**
+ * Retrieve all of the known permission groups in the system.
+ */
+ public abstract @Nullable List<PermissionGroupInfo> getAllPermissionGroups(int flags,
+ int callingUid);
+ /**
* Retrieve all of the information we know about a particular permission.
*/
public abstract @Nullable PermissionInfo getPermissionInfo(@NonNull String permName,
@@ -132,6 +144,7 @@
public abstract int checkPermission(@NonNull String permName, @NonNull String packageName,
int callingUid, int userId);
+ public abstract int checkUidPermission(String permName, int uid, int callingUid);
/**
* Enforces the request is from the system or an app that has INTERACT_ACROSS_USERS
@@ -147,8 +160,6 @@
public abstract @NonNull DefaultPermissionGrantPolicy getDefaultPermissionGrantPolicy();
/** HACK HACK methods to allow for partial migration of data to the PermissionManager class */
- public abstract Iterator<BasePermission> getPermissionIteratorTEMP();
public abstract @Nullable BasePermission getPermissionTEMP(@NonNull String permName);
- public abstract void putPermissionTEMP(@NonNull String permName,
- @NonNull BasePermission permission);
+ public abstract @Nullable int[] getGlobalGidsTEMP();
}
\ No newline at end of file
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 d2d857c..a94a00a 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -18,6 +18,7 @@
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
import android.Manifest;
import android.annotation.NonNull;
@@ -27,7 +28,7 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.PackageParser;
-import android.content.pm.ParceledListSlice;
+import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.content.pm.PackageParser.Package;
import android.os.Binder;
@@ -43,6 +44,7 @@
import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
+import android.util.SparseArray;
import com.android.internal.R;
import com.android.internal.logging.MetricsLogger;
@@ -58,6 +60,7 @@
import com.android.server.pm.PackageSetting;
import com.android.server.pm.ProcessLoggingHandler;
import com.android.server.pm.SharedUserSetting;
+import com.android.server.pm.UserManagerService;
import com.android.server.pm.permission.DefaultPermissionGrantPolicy.DefaultPermissionGrantedCallback;
import com.android.server.pm.permission.PermissionManagerInternal.PermissionCallback;
import com.android.server.pm.permission.PermissionsState.PermissionState;
@@ -122,6 +125,10 @@
/** Default permission policy to provide proper behaviour out-of-the-box */
private final DefaultPermissionGrantPolicy mDefaultPermissionGrantPolicy;
+ // System configuration read by SystemConfig.
+ private final SparseArray<ArraySet<String>> mSystemPermissions;
+ private final int[] mGlobalGids;
+
/** Internal storage for permissions and related settings */
private final PermissionSettings mSettings;
@@ -146,6 +153,9 @@
mDefaultPermissionGrantPolicy = new DefaultPermissionGrantPolicy(
context, mHandlerThread.getLooper(), defaultGrantCallback, this);
+ SystemConfig systemConfig = SystemConfig.getInstance();
+ mSystemPermissions = systemConfig.getSystemPermissions();
+ mGlobalGids = systemConfig.getGlobalGids();
// propagate permission configuration
final ArrayMap<String, SystemConfig.PermissionEntry> permConfig =
@@ -230,14 +240,94 @@
return PackageManager.PERMISSION_DENIED;
}
- private PermissionInfo getPermissionInfo(String name, String packageName, int flags,
+ private int checkUidPermission(String permName, int uid, int callingUid) {
+ final int callingUserId = UserHandle.getUserId(callingUid);
+ final boolean isCallerInstantApp =
+ mPackageManagerInt.getInstantAppPackageName(callingUid) != null;
+ final boolean isUidInstantApp =
+ mPackageManagerInt.getInstantAppPackageName(uid) != null;
+ final int userId = UserHandle.getUserId(uid);
+ if (!mUserManagerInt.exists(userId)) {
+ return PackageManager.PERMISSION_DENIED;
+ }
+
+ final String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
+ if (packages != null && packages.length > 0) {
+ final PackageParser.Package pkg = mPackageManagerInt.getPackage(packages[0]);
+ if (pkg.mSharedUserId != null) {
+ if (isCallerInstantApp) {
+ return PackageManager.PERMISSION_DENIED;
+ }
+ } else {
+ if (mPackageManagerInt.filterAppAccess(pkg, callingUid, callingUserId)) {
+ return PackageManager.PERMISSION_DENIED;
+ }
+ }
+ final PermissionsState permissionsState =
+ ((PackageSetting) pkg.mExtras).getPermissionsState();
+ if (permissionsState.hasPermission(permName, userId)) {
+ if (isUidInstantApp) {
+ if (mSettings.isPermissionInstant(permName)) {
+ return PackageManager.PERMISSION_GRANTED;
+ }
+ } else {
+ return PackageManager.PERMISSION_GRANTED;
+ }
+ }
+ // Special case: ACCESS_FINE_LOCATION permission includes ACCESS_COARSE_LOCATION
+ if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && permissionsState
+ .hasPermission(Manifest.permission.ACCESS_FINE_LOCATION, userId)) {
+ return PackageManager.PERMISSION_GRANTED;
+ }
+ } else {
+ ArraySet<String> perms = mSystemPermissions.get(uid);
+ if (perms != null) {
+ if (perms.contains(permName)) {
+ return PackageManager.PERMISSION_GRANTED;
+ }
+ if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && perms
+ .contains(Manifest.permission.ACCESS_FINE_LOCATION)) {
+ return PackageManager.PERMISSION_GRANTED;
+ }
+ }
+ }
+ return PackageManager.PERMISSION_DENIED;
+ }
+
+ private PermissionGroupInfo getPermissionGroupInfo(String groupName, int flags,
+ int callingUid) {
+ if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
+ return null;
+ }
+ synchronized (mLock) {
+ return PackageParser.generatePermissionGroupInfo(
+ mSettings.mPermissionGroups.get(groupName), flags);
+ }
+ }
+
+ private List<PermissionGroupInfo> getAllPermissionGroups(int flags, int callingUid) {
+ if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
+ return null;
+ }
+ synchronized (mLock) {
+ final int N = mSettings.mPermissionGroups.size();
+ final ArrayList<PermissionGroupInfo> out
+ = new ArrayList<PermissionGroupInfo>(N);
+ for (PackageParser.PermissionGroup pg : mSettings.mPermissionGroups.values()) {
+ out.add(PackageParser.generatePermissionGroupInfo(pg, flags));
+ }
+ return out;
+ }
+ }
+
+ private PermissionInfo getPermissionInfo(String permName, String packageName, int flags,
int callingUid) {
if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
return null;
}
// reader
synchronized (mLock) {
- final BasePermission bp = mSettings.getPermissionLocked(name);
+ final BasePermission bp = mSettings.getPermissionLocked(permName);
if (bp == null) {
return null;
}
@@ -252,14 +342,10 @@
if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
return null;
}
- // reader
synchronized (mLock) {
- // TODO Uncomment when mPermissionGroups moves to this class
-// if (groupName != null && !mPermissionGroups.containsKey(groupName)) {
-// // This is thrown as NameNotFoundException
-// return null;
-// }
-
+ if (groupName != null && !mSettings.mPermissionGroups.containsKey(groupName)) {
+ return null;
+ }
final ArrayList<PermissionInfo> out = new ArrayList<PermissionInfo>(10);
for (BasePermission bp : mSettings.mPermissions.values()) {
final PermissionInfo pi = bp.generatePermissionInfo(groupName, flags);
@@ -314,21 +400,21 @@
// Assume by default that we did not install this permission into the system.
p.info.flags &= ~PermissionInfo.FLAG_INSTALLED;
- // Now that permission groups have a special meaning, we ignore permission
- // groups for legacy apps to prevent unexpected behavior. In particular,
- // permissions for one app being granted to someone just because they happen
- // to be in a group defined by another app (before this had no implications).
- if (pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
- p.group = mPackageManagerInt.getPermissionGroupTEMP(p.info.group);
- // Warn for a permission in an unknown group.
- if (PackageManagerService.DEBUG_PERMISSIONS
- && p.info.group != null && p.group == null) {
- Slog.i(TAG, "Permission " + p.info.name + " from package "
- + p.info.packageName + " in an unknown group " + p.info.group);
- }
- }
-
synchronized (PermissionManagerService.this.mLock) {
+ // Now that permission groups have a special meaning, we ignore permission
+ // groups for legacy apps to prevent unexpected behavior. In particular,
+ // permissions for one app being granted to someone just because they happen
+ // to be in a group defined by another app (before this had no implications).
+ if (pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
+ p.group = mSettings.mPermissionGroups.get(p.info.group);
+ // Warn for a permission in an unknown group.
+ if (PackageManagerService.DEBUG_PERMISSIONS
+ && p.info.group != null && p.group == null) {
+ Slog.i(TAG, "Permission " + p.info.name + " from package "
+ + p.info.packageName + " in an unknown group " + p.info.group);
+ }
+ }
+
if (p.tree) {
final BasePermission bp = BasePermission.createOrUpdate(
mSettings.getPermissionTreeLocked(p.info.name), p, pkg,
@@ -344,6 +430,48 @@
}
}
+ private void addAllPermissionGroups(PackageParser.Package pkg, boolean chatty) {
+ final int N = pkg.permissionGroups.size();
+ StringBuilder r = null;
+ for (int i=0; i<N; i++) {
+ final PackageParser.PermissionGroup pg = pkg.permissionGroups.get(i);
+ final PackageParser.PermissionGroup cur = mSettings.mPermissionGroups.get(pg.info.name);
+ final String curPackageName = (cur == null) ? null : cur.info.packageName;
+ final boolean isPackageUpdate = pg.info.packageName.equals(curPackageName);
+ if (cur == null || isPackageUpdate) {
+ mSettings.mPermissionGroups.put(pg.info.name, pg);
+ if (chatty && PackageManagerService.DEBUG_PACKAGE_SCANNING) {
+ if (r == null) {
+ r = new StringBuilder(256);
+ } else {
+ r.append(' ');
+ }
+ if (isPackageUpdate) {
+ r.append("UPD:");
+ }
+ r.append(pg.info.name);
+ }
+ } else {
+ Slog.w(TAG, "Permission group " + pg.info.name + " from package "
+ + pg.info.packageName + " ignored: original from "
+ + cur.info.packageName);
+ if (chatty && PackageManagerService.DEBUG_PACKAGE_SCANNING) {
+ if (r == null) {
+ r = new StringBuilder(256);
+ } else {
+ r.append(' ');
+ }
+ r.append("DUP:");
+ r.append(pg.info.name);
+ }
+ }
+ }
+ if (r != null && PackageManagerService.DEBUG_PACKAGE_SCANNING) {
+ Log.d(TAG, " Permission Groups: " + r);
+ }
+
+ }
+
private void removeAllPermissions(PackageParser.Package pkg, boolean chatty) {
synchronized (mLock) {
int N = pkg.permissions.size();
@@ -1158,6 +1286,10 @@
PermissionManagerService.this.addAllPermissions(pkg, chatty);
}
@Override
+ public void addAllPermissionGroups(Package pkg, boolean chatty) {
+ PermissionManagerService.this.addAllPermissionGroups(pkg, chatty);
+ }
+ @Override
public void removeAllPermissions(Package pkg, boolean chatty) {
PermissionManagerService.this.removeAllPermissions(pkg, chatty);
}
@@ -1252,6 +1384,20 @@
permName, packageName, callingUid, userId);
}
@Override
+ public int checkUidPermission(String permName, int uid, int callingUid) {
+ return PermissionManagerService.this.checkUidPermission(permName, uid, callingUid);
+ }
+ @Override
+ public PermissionGroupInfo getPermissionGroupInfo(String groupName, int flags,
+ int callingUid) {
+ return PermissionManagerService.this.getPermissionGroupInfo(
+ groupName, flags, callingUid);
+ }
+ @Override
+ public List<PermissionGroupInfo> getAllPermissionGroups(int flags, int callingUid) {
+ return PermissionManagerService.this.getAllPermissionGroups(flags, callingUid);
+ }
+ @Override
public PermissionInfo getPermissionInfo(String permName, String packageName, int flags,
int callingUid) {
return PermissionManagerService.this.getPermissionInfo(
@@ -1277,15 +1423,9 @@
}
}
@Override
- public void putPermissionTEMP(String permName, BasePermission permission) {
+ public int[] getGlobalGidsTEMP() {
synchronized (PermissionManagerService.this.mLock) {
- mSettings.putPermissionLocked(permName, (BasePermission) permission);
- }
- }
- @Override
- public Iterator<BasePermission> getPermissionIteratorTEMP() {
- synchronized (PermissionManagerService.this.mLock) {
- return mSettings.getAllPermissionsLocked().iterator();
+ return mGlobalGids;
}
}
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionSettings.java b/services/core/java/com/android/server/pm/permission/PermissionSettings.java
index 7d125c9..0ed94a1 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionSettings.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionSettings.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.pm.PackageParser;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
@@ -64,6 +65,14 @@
new ArrayMap<String, BasePermission>();
/**
+ * All permisson groups know to the system. The mapping is from permission group
+ * name to permission group object.
+ */
+ @GuardedBy("mLock")
+ final ArrayMap<String, PackageParser.PermissionGroup> mPermissionGroups =
+ new ArrayMap<String, PackageParser.PermissionGroup>();
+
+ /**
* Set of packages that request a particular app op. The mapping is from permission
* name to package names.
*/
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 3fdafc7..21dffff 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -22,6 +22,10 @@
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import android.animation.Animator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.annotation.Nullable;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Context;
@@ -29,7 +33,9 @@
import android.graphics.Point;
import android.hardware.input.InputManager;
import android.os.Build;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
@@ -44,16 +50,12 @@
import android.view.InputDevice;
import android.view.PointerIcon;
import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
import android.view.View;
import android.view.WindowManager;
-import android.view.animation.AlphaAnimation;
-import android.view.animation.Animation;
-import android.view.animation.AnimationSet;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
-import android.view.animation.ScaleAnimation;
import android.view.animation.Transformation;
-import android.view.animation.TranslateAnimation;
import com.android.server.input.InputApplicationHandle;
import com.android.server.input.InputWindowHandle;
@@ -78,8 +80,20 @@
View.DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION |
View.DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION;
+ // Property names for animations
+ private static final String ANIMATED_PROPERTY_X = "x";
+ private static final String ANIMATED_PROPERTY_Y = "y";
+ private static final String ANIMATED_PROPERTY_ALPHA = "alpha";
+ private static final String ANIMATED_PROPERTY_SCALE = "scale";
+
+ // Messages for Handler.
+ private static final int MSG_ANIMATION_END = 0;
+
final WindowManagerService mService;
IBinder mToken;
+ /**
+ * Do not use the variable from the out of animation thread while mAnimator is not null.
+ */
SurfaceControl mSurfaceControl;
int mFlags;
IBinder mLocalWin;
@@ -101,10 +115,10 @@
boolean mDragInProgress;
DisplayContent mDisplayContent;
- private Animation mAnimation;
- final Transformation mTransformation = new Transformation();
+ @Nullable private ValueAnimator mAnimator;
private final Interpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f);
private Point mDisplaySize = new Point();
+ private final Handler mHandler;
DragState(WindowManagerService service, IBinder token, SurfaceControl surface,
int flags, IBinder localWin) {
@@ -114,9 +128,14 @@
mFlags = flags;
mLocalWin = localWin;
mNotifiedWindows = new ArrayList<WindowState>();
+ mHandler = new DragStateHandler(service.mH.getLooper());
}
void reset() {
+ if (mAnimator != null) {
+ Slog.wtf(TAG_WM,
+ "Unexpectedly destroying mSurfaceControl while animation is running");
+ }
if (mSurfaceControl != null) {
mSurfaceControl.destroy();
}
@@ -388,11 +407,11 @@
}
void endDragLw() {
- if (mAnimation != null) {
+ if (mAnimator != null) {
return;
}
if (!mDragResult) {
- mAnimation = createReturnAnimationLocked();
+ mAnimator = createReturnAnimationLocked();
mService.scheduleAnimationLocked();
return; // Will call cleanUpDragLw when the animation is done.
}
@@ -400,11 +419,22 @@
}
void cancelDragLw() {
- if (mAnimation != null) {
+ if (mAnimator != null) {
return;
}
- mAnimation = createCancelAnimationLocked();
- mService.scheduleAnimationLocked();
+ if (!mDragInProgress) {
+ // This can happen if an app invokes Session#cancelDragAndDrop before
+ // Session#performDrag. Reset the drag state:
+ // 1. without sending the end broadcast because the start broadcast has not been sent,
+ // and
+ // 2. without playing the cancel animation because H.DRAG_START_TIMEOUT may be sent to
+ // WindowManagerService, which will cause DragState#reset() while playing the
+ // cancel animation.
+ reset();
+ mService.mDragState = null;
+ return;
+ }
+ mAnimator = createCancelAnimationLocked();
}
private void cleanUpDragLw() {
@@ -422,7 +452,7 @@
}
void notifyMoveLw(float x, float y) {
- if (mAnimation != null) {
+ if (mAnimator != null) {
return;
}
mCurrentX = x;
@@ -491,7 +521,7 @@
// dispatch the global drag-ended message, 'false' if we need to wait for a
// result from the recipient.
boolean notifyDropLw(float x, float y) {
- if (mAnimation != null) {
+ if (mAnimator != null) {
return false;
}
mCurrentX = x;
@@ -560,56 +590,52 @@
dragAndDropPermissions, result);
}
- boolean stepAnimationLocked(long currentTimeMs) {
- if (mAnimation == null) {
- return false;
- }
+ private ValueAnimator createReturnAnimationLocked() {
+ final ValueAnimator animator = ValueAnimator.ofPropertyValuesHolder(
+ PropertyValuesHolder.ofFloat(
+ ANIMATED_PROPERTY_X, mCurrentX - mThumbOffsetX,
+ mOriginalX - mThumbOffsetX),
+ PropertyValuesHolder.ofFloat(
+ ANIMATED_PROPERTY_Y, mCurrentY - mThumbOffsetY,
+ mOriginalY - mThumbOffsetY),
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, 1, 1),
+ PropertyValuesHolder.ofFloat(
+ ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, mOriginalAlpha / 2));
- mTransformation.clear();
- if (!mAnimation.getTransformation(currentTimeMs, mTransformation)) {
- cleanUpDragLw();
- return false;
- }
-
- mTransformation.getMatrix().postTranslate(
- mCurrentX - mThumbOffsetX, mCurrentY - mThumbOffsetY);
- final float tmpFloats[] = mService.mTmpFloats;
- mTransformation.getMatrix().getValues(tmpFloats);
- mSurfaceControl.setPosition(tmpFloats[Matrix.MTRANS_X], tmpFloats[Matrix.MTRANS_Y]);
- mSurfaceControl.setAlpha(mTransformation.getAlpha());
- mSurfaceControl.setMatrix(tmpFloats[Matrix.MSCALE_X], tmpFloats[Matrix.MSKEW_Y],
- tmpFloats[Matrix.MSKEW_X], tmpFloats[Matrix.MSCALE_Y]);
- return true;
- }
-
- private Animation createReturnAnimationLocked() {
- final AnimationSet set = new AnimationSet(false);
final float translateX = mOriginalX - mCurrentX;
final float translateY = mOriginalY - mCurrentY;
- set.addAnimation(new TranslateAnimation( 0, translateX, 0, translateY));
- set.addAnimation(new AlphaAnimation(mOriginalAlpha, mOriginalAlpha / 2));
// Adjust the duration to the travel distance.
final double travelDistance = Math.sqrt(translateX * translateX + translateY * translateY);
final double displayDiagonal =
Math.sqrt(mDisplaySize.x * mDisplaySize.x + mDisplaySize.y * mDisplaySize.y);
final long duration = MIN_ANIMATION_DURATION_MS + (long) (travelDistance / displayDiagonal
* (MAX_ANIMATION_DURATION_MS - MIN_ANIMATION_DURATION_MS));
- set.setDuration(duration);
- set.setInterpolator(mCubicEaseOutInterpolator);
- set.initialize(0, 0, 0, 0);
- set.start(); // Will start on the first call to getTransformation.
- return set;
+ final AnimationListener listener = new AnimationListener();
+ animator.setDuration(duration);
+ animator.setInterpolator(mCubicEaseOutInterpolator);
+ animator.addListener(listener);
+ animator.addUpdateListener(listener);
+
+ mService.mAnimationHandler.post(() -> animator.start());
+ return animator;
}
- private Animation createCancelAnimationLocked() {
- final AnimationSet set = new AnimationSet(false);
- set.addAnimation(new ScaleAnimation(1, 0, 1, 0, mThumbOffsetX, mThumbOffsetY));
- set.addAnimation(new AlphaAnimation(mOriginalAlpha, 0));
- set.setDuration(MIN_ANIMATION_DURATION_MS);
- set.setInterpolator(mCubicEaseOutInterpolator);
- set.initialize(0, 0, 0, 0);
- set.start(); // Will start on the first call to getTransformation.
- return set;
+ private ValueAnimator createCancelAnimationLocked() {
+ final ValueAnimator animator = ValueAnimator.ofPropertyValuesHolder(
+ PropertyValuesHolder.ofFloat(
+ ANIMATED_PROPERTY_X, mCurrentX - mThumbOffsetX, mCurrentX),
+ PropertyValuesHolder.ofFloat(
+ ANIMATED_PROPERTY_Y, mCurrentY - mThumbOffsetY, mCurrentY),
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, 1, 0),
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, 0));
+ final AnimationListener listener = new AnimationListener();
+ animator.setDuration(MIN_ANIMATION_DURATION_MS);
+ animator.setInterpolator(mCubicEaseOutInterpolator);
+ animator.addListener(listener);
+ animator.addUpdateListener(listener);
+
+ mService.mAnimationHandler.post(() -> animator.start());
+ return animator;
}
private boolean isFromSource(int source) {
@@ -622,4 +648,68 @@
InputManager.getInstance().setPointerIconType(PointerIcon.TYPE_GRABBING);
}
}
+
+ private class DragStateHandler extends Handler {
+ DragStateHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_ANIMATION_END:
+ synchronized (mService.mWindowMap) {
+ if (mService.mDragState != DragState.this) {
+ Slog.wtf(TAG_WM, "mDragState is updated unexpectedly while " +
+ "playing animation");
+ return;
+ }
+ if (mAnimator == null) {
+ Slog.wtf(TAG_WM, "Unexpected null mAnimator");
+ return;
+ }
+ mAnimator = null;
+ cleanUpDragLw();
+ }
+ break;
+ }
+ }
+ }
+
+ private class AnimationListener
+ implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ try (final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) {
+ transaction.setPosition(
+ mSurfaceControl,
+ (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_X),
+ (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_Y));
+ transaction.setAlpha(
+ mSurfaceControl,
+ (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_ALPHA));
+ transaction.setMatrix(
+ mSurfaceControl,
+ (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_SCALE), 0,
+ 0, (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_SCALE));
+ transaction.apply();
+ }
+ }
+
+ @Override
+ public void onAnimationStart(Animator animator) {}
+
+ @Override
+ public void onAnimationCancel(Animator animator) {}
+
+ @Override
+ public void onAnimationRepeat(Animator animator) {}
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ // Updating mDragState requires the WM lock so continues it on the out of
+ // AnimationThread.
+ mHandler.sendEmptyMessage(MSG_ANIMATION_END);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 4dd147e..2bb0235 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -68,9 +68,7 @@
* This class represents an active client session. There is generally one
* Session object per process that is interacting with the window manager.
*/
-// Needs to be public and not final so we can mock during tests...sucks I know :(
-public class Session extends IWindowSession.Stub
- implements IBinder.DeathRecipient {
+class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
final WindowManagerService mService;
final IWindowSessionCallback mCallback;
final IInputMethodClient mClient;
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index c01ee31..e409a68 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -223,10 +223,6 @@
}
}
- if (mService.mDragState != null) {
- mAnimating |= mService.mDragState.stepAnimationLocked(mCurrentTime);
- }
-
if (!mAnimating) {
cancelAnimation();
}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
index 679be1d..9e4a9e9 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
@@ -57,7 +57,7 @@
ComponentName.unflattenFromString("com.foo/.BarActivity2");
private ActivityManagerService mService;
- private ActivityStack mStack;
+ private TestActivityStack mStack;
private TaskRecord mTask;
private ActivityRecord mActivity;
@@ -76,13 +76,13 @@
@Test
public void testStackCleanupOnClearingTask() throws Exception {
mActivity.setTask(null);
- assertEquals(getActivityRemovedFromStackCount(), 1);
+ assertEquals(mStack.onActivityRemovedFromStackInvocationCount(), 1);
}
@Test
public void testStackCleanupOnActivityRemoval() throws Exception {
mTask.removeActivity(mActivity);
- assertEquals(getActivityRemovedFromStackCount(), 1);
+ assertEquals(mStack.onActivityRemovedFromStackInvocationCount(), 1);
}
@Test
@@ -97,7 +97,7 @@
final TaskRecord newTask =
createTask(mService.mStackSupervisor, testActivityComponent, mStack);
mActivity.reparent(newTask, 0, null /*reason*/);
- assertEquals(getActivityRemovedFromStackCount(), 0);
+ assertEquals(mStack.onActivityRemovedFromStackInvocationCount(), 0);
}
@Test
@@ -129,15 +129,6 @@
assertEquals(expectedActivityBounds, mActivity.getBounds());
}
- private int getActivityRemovedFromStackCount() {
- if (mStack instanceof ActivityStackReporter) {
- return ((ActivityStackReporter) mStack).onActivityRemovedFromStackInvocationCount();
- }
-
- return -1;
- }
-
-
@Test
public void testCanBeLaunchedOnDisplay() throws Exception {
testSupportsLaunchingResizeable(false /*taskPresent*/, true /*taskResizeable*/,
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
index 4ee1f47..e17e51b 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
@@ -16,13 +16,19 @@
package com.android.server.am;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_DESTROYING;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
@@ -45,7 +51,6 @@
@Presubmit
@RunWith(AndroidJUnit4.class)
public class ActivityStackTests extends ActivityTestsBase {
- private static final int TEST_STACK_ID = 100;
private static final ComponentName testActivityComponent =
ComponentName.unflattenFromString("com.foo/.BarActivity");
private static final ComponentName testOverlayComponent =
@@ -127,4 +132,122 @@
assertEquals(mTask.getTopActivity(true /* includeOverlays */), taskOverlay);
assertNotNull(result.r);
}
+
+ @Test
+ public void testShouldBeVisible_Fullscreen() throws Exception {
+ final ActivityDisplay display = mService.mStackSupervisor.getDefaultDisplay();
+ final TestActivityStack homeStack = createStackForShouldBeVisibleTest(display,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
+ final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(display,
+ WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+
+ assertTrue(homeStack.shouldBeVisible(null /* starting */));
+ assertTrue(pinnedStack.shouldBeVisible(null /* starting */));
+
+ final TestActivityStack fullscreenStack = createStackForShouldBeVisibleTest(display,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ // Home stack shouldn't be visible behind an opaque fullscreen stack, but pinned stack
+ // should be visible since it is always on-top.
+ fullscreenStack.setIsTranslucent(false);
+ assertFalse(homeStack.shouldBeVisible(null /* starting */));
+ assertTrue(pinnedStack.shouldBeVisible(null /* starting */));
+ assertTrue(fullscreenStack.shouldBeVisible(null /* starting */));
+
+ // Home stack should be visible behind a translucent fullscreen stack.
+ fullscreenStack.setIsTranslucent(true);
+ assertTrue(homeStack.shouldBeVisible(null /* starting */));
+ assertTrue(pinnedStack.shouldBeVisible(null /* starting */));
+ }
+
+ @Test
+ public void testShouldBeVisible_SplitScreen() throws Exception {
+ final ActivityDisplay display = mService.mStackSupervisor.getDefaultDisplay();
+ final TestActivityStack homeStack = createStackForShouldBeVisibleTest(display,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
+ final TestActivityStack splitScreenPrimary = createStackForShouldBeVisibleTest(display,
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ final TestActivityStack splitScreenSecondary = createStackForShouldBeVisibleTest(display,
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+
+ // Home stack shouldn't be visible if both halves of split-screen are opaque.
+ splitScreenPrimary.setIsTranslucent(false);
+ splitScreenSecondary.setIsTranslucent(false);
+ assertFalse(homeStack.shouldBeVisible(null /* starting */));
+ assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
+ assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
+
+ // Home stack should be visible if one of the halves of split-screen is translucent.
+ splitScreenPrimary.setIsTranslucent(true);
+ assertTrue(homeStack.shouldBeVisible(null /* starting */));
+ assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
+ assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
+
+ final TestActivityStack splitScreenSecondary2 = createStackForShouldBeVisibleTest(display,
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ // First split-screen secondary shouldn't be visible behind another opaque split-split
+ // secondary.
+ splitScreenSecondary2.setIsTranslucent(false);
+ assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */));
+ assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */));
+
+ // First split-screen secondary should be visible behind another translucent split-split
+ // secondary.
+ splitScreenSecondary2.setIsTranslucent(true);
+ assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
+ assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */));
+
+ final TestActivityStack assistantStack = createStackForShouldBeVisibleTest(display,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */);
+
+ // Split-screen stacks shouldn't be visible behind an opaque fullscreen stack.
+ assistantStack.setIsTranslucent(false);
+ assertTrue(assistantStack.shouldBeVisible(null /* starting */));
+ assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */));
+ assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */));
+ assertFalse(splitScreenSecondary2.shouldBeVisible(null /* starting */));
+
+ // Split-screen stacks should be visible behind a translucent fullscreen stack.
+ assistantStack.setIsTranslucent(true);
+ assertTrue(assistantStack.shouldBeVisible(null /* starting */));
+ assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
+ assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
+ assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */));
+ }
+
+ @Test
+ public void testShouldBeVisible_Finishing() throws Exception {
+ final ActivityDisplay display = mService.mStackSupervisor.getDefaultDisplay();
+ final TestActivityStack homeStack = createStackForShouldBeVisibleTest(display,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
+ final TestActivityStack translucentStack = createStackForShouldBeVisibleTest(display,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ translucentStack.setIsTranslucent(true);
+
+ assertTrue(homeStack.shouldBeVisible(null /* starting */));
+ assertTrue(translucentStack.shouldBeVisible(null /* starting */));
+
+ final ActivityRecord topRunningHomeActivity = homeStack.topRunningActivityLocked();
+ topRunningHomeActivity.finishing = true;
+ final ActivityRecord topRunningTranslucentActivity =
+ translucentStack.topRunningActivityLocked();
+ topRunningTranslucentActivity.finishing = true;
+
+ // Home shouldn't be visible since its activity is marked as finishing and it isn't the top
+ // of the stack list.
+ assertFalse(homeStack.shouldBeVisible(null /* starting */));
+ // Home should be visible if we are starting an activity within it.
+ assertTrue(homeStack.shouldBeVisible(topRunningHomeActivity /* starting */));
+ // The translucent stack should be visible since it is the top of the stack list even though
+ // it has its activity marked as finishing.
+ assertTrue(translucentStack.shouldBeVisible(null /* starting */));
+ }
+
+ private <T extends ActivityStack> T createStackForShouldBeVisibleTest(
+ ActivityDisplay display, int windowingMode, int activityType, boolean onTop) {
+ final T stack = display.createStack(windowingMode, activityType, onTop);
+ // Create a task and activity in the stack so that it has a top running activity.
+ final TaskRecord task = createTask(mSupervisor, testActivityComponent, stack);
+ final ActivityRecord r = createActivity(mService, testActivityComponent, task, 0);
+ return stack;
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
index d36f9d3..f5cdf21 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
@@ -276,20 +276,21 @@
return service;
}
- protected interface ActivityStackReporter {
- int onActivityRemovedFromStackInvocationCount();
- }
-
/**
* Overrided of {@link ActivityStack} that tracks test metrics, such as the number of times a
* method is called. Note that its functionality depends on the implementations of the
* construction arguments.
*/
protected static class TestActivityStack<T extends StackWindowController>
- extends ActivityStack<T> implements ActivityStackReporter {
+ extends ActivityStack<T> {
private int mOnActivityRemovedFromStackCount = 0;
private T mContainerController;
+ static final int IS_TRANSLUCENT_UNSET = 0;
+ static final int IS_TRANSLUCENT_FALSE = 1;
+ static final int IS_TRANSLUCENT_TRUE = 2;
+ private int mIsTranslucent = IS_TRANSLUCENT_UNSET;
+
TestActivityStack(ActivityDisplay display, int stackId, ActivityStackSupervisor supervisor,
int windowingMode, int activityType, boolean onTop) {
super(display, stackId, supervisor, windowingMode, activityType, onTop);
@@ -302,8 +303,7 @@
}
// Returns the number of times {@link #onActivityRemovedFromStack} has been called
- @Override
- public int onActivityRemovedFromStackInvocationCount() {
+ int onActivityRemovedFromStackInvocationCount() {
return mOnActivityRemovedFromStackCount;
}
@@ -317,5 +317,22 @@
T getWindowContainerController() {
return mContainerController;
}
+
+ void setIsTranslucent(boolean isTranslucent) {
+ mIsTranslucent = isTranslucent ? IS_TRANSLUCENT_TRUE : IS_TRANSLUCENT_FALSE;
+ }
+
+ @Override
+ boolean isStackTranslucent(ActivityRecord starting, ActivityStack stackBehind) {
+ switch (mIsTranslucent) {
+ case IS_TRANSLUCENT_TRUE:
+ return true;
+ case IS_TRANSLUCENT_FALSE:
+ return false;
+ case IS_TRANSLUCENT_UNSET:
+ default:
+ return super.isStackTranslucent(starting, stackBehind);
+ }
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserDataPreparerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserDataPreparerTest.java
index 7a676e25..bb35beb 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserDataPreparerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserDataPreparerTest.java
@@ -154,9 +154,6 @@
File systemDeDir = mUserDataPreparer.getDataSystemDeDirectory(TEST_USER_ID);
systemDeDir.mkdirs();
writeFile(new File(systemDeDir, "file"), "-----" );
- File miscDeDir = mUserDataPreparer.getDataMiscDeDirectory(TEST_USER_ID);
- miscDeDir.mkdirs();
- writeFile(new File(miscDeDir, "file"), "-----" );
mUserDataPreparer.destroyUserData(TEST_USER_ID, StorageManager.FLAG_STORAGE_DE);
@@ -168,8 +165,6 @@
assertEquals(Collections.emptyList(), Arrays.asList(FileUtils.listFilesOrEmpty(systemDir)));
assertEquals(Collections.emptyList(), Arrays.asList(FileUtils.listFilesOrEmpty(
systemDeDir)));
- assertEquals(Collections.emptyList(), Arrays.asList(FileUtils.listFilesOrEmpty(
- miscDeDir)));
}
@Test
@@ -177,9 +172,6 @@
File systemCeDir = mUserDataPreparer.getDataSystemCeDirectory(TEST_USER_ID);
systemCeDir.mkdirs();
writeFile(new File(systemCeDir, "file"), "-----" );
- File miscCeDir = mUserDataPreparer.getDataMiscCeDirectory(TEST_USER_ID);
- miscCeDir.mkdirs();
- writeFile(new File(miscCeDir, "file"), "-----" );
mUserDataPreparer.destroyUserData(TEST_USER_ID, StorageManager.FLAG_STORAGE_CE);
@@ -190,8 +182,6 @@
assertEquals(Collections.emptyList(), Arrays.asList(FileUtils.listFilesOrEmpty(
systemCeDir)));
- assertEquals(Collections.emptyList(), Arrays.asList(FileUtils.listFilesOrEmpty(
- miscCeDir)));
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index b8e8946..0980f7e 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -63,7 +63,7 @@
class WindowTestsBase {
static WindowManagerService sWm = null;
private static final IWindow sIWindow = new TestIWindow();
- private static final Session sMockSession = mock(Session.class);
+ private static Session sMockSession;
// The default display is removed in {@link #setUp} and then we iterate over all displays to
// make sure we don't collide with any existing display. If we run into no other display, the
// added display should be treated as default. This cannot be the default display
@@ -93,6 +93,7 @@
// Allows to mock package local classes and methods
System.setProperty("dexmaker.share_classloader", "true");
MockitoAnnotations.initMocks(this);
+ sMockSession = mock(Session.class);
}
final Context context = InstrumentationRegistry.getTargetContext();
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 6029995..98195ad 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -390,20 +390,23 @@
* Inject an SMS PDU into the android application framework.
*
* <p>Requires permission: {@link android.Manifest.permission#MODIFY_PHONE_STATE} or carrier
- * privileges. @see android.telephony.TelephonyManager#hasCarrierPrivileges
+ * privileges per {@link android.telephony.TelephonyManager#hasCarrierPrivileges}.
*
* @param pdu is the byte array of pdu to be injected into android application framework
- * @param format is the format of SMS pdu (3gpp or 3gpp2)
+ * @param format is the format of SMS pdu ({@link SmsMessage#FORMAT_3GPP} or
+ * {@link SmsMessage#FORMAT_3GPP2})
* @param receivedIntent if not NULL this <code>PendingIntent</code> is
* broadcast when the message is successfully received by the
* android application framework, or failed. This intent is broadcasted at
* the same time an SMS received from radio is acknowledged back.
- * The result code will be <code>RESULT_SMS_HANDLED</code> for success, or
- * <code>RESULT_SMS_GENERIC_ERROR</code> for error.
+ * The result code will be {@link android.provider.Telephony.Sms.Intents#RESULT_SMS_HANDLED}
+ * for success, or {@link android.provider.Telephony.Sms.Intents#RESULT_SMS_GENERIC_ERROR} for
+ * error.
*
- * @throws IllegalArgumentException if format is not one of 3gpp and 3gpp2.
+ * @throws IllegalArgumentException if the format is invalid.
*/
- public void injectSmsPdu(byte[] pdu, String format, PendingIntent receivedIntent) {
+ public void injectSmsPdu(
+ byte[] pdu, @SmsMessage.Format String format, PendingIntent receivedIntent) {
if (!format.equals(SmsMessage.FORMAT_3GPP) && !format.equals(SmsMessage.FORMAT_3GPP2)) {
// Format must be either 3gpp or 3gpp2.
throw new IllegalArgumentException(
diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java
index dcdda86..df41233 100644
--- a/telephony/java/android/telephony/SmsMessage.java
+++ b/telephony/java/android/telephony/SmsMessage.java
@@ -16,24 +16,25 @@
package android.telephony;
-import android.os.Binder;
-import android.os.Parcel;
+import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA;
+
+import android.annotation.StringDef;
import android.content.res.Resources;
+import android.os.Binder;
import android.text.TextUtils;
import com.android.internal.telephony.GsmAlphabet;
import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
+import com.android.internal.telephony.Sms7BitEncodingTranslator;
import com.android.internal.telephony.SmsConstants;
import com.android.internal.telephony.SmsMessageBase;
import com.android.internal.telephony.SmsMessageBase.SubmitPduBase;
-import com.android.internal.telephony.Sms7BitEncodingTranslator;
-import java.lang.Math;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
-import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA;
-
/**
* A Short Message Service message.
@@ -81,15 +82,18 @@
*/
public static final int MAX_USER_DATA_SEPTETS_WITH_HEADER = 153;
+ /** @hide */
+ @StringDef({FORMAT_3GPP, FORMAT_3GPP2})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Format {}
+
/**
* Indicates a 3GPP format SMS message.
- * @hide pending API council approval
*/
public static final String FORMAT_3GPP = "3gpp";
/**
* Indicates a 3GPP2 format SMS message.
- * @hide pending API council approval
*/
public static final String FORMAT_3GPP2 = "3gpp2";
diff --git a/tests/net/java/com/android/server/IpSecServiceTest.java b/tests/net/java/com/android/server/IpSecServiceTest.java
index 83ee361..7b07038 100644
--- a/tests/net/java/com/android/server/IpSecServiceTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceTest.java
@@ -21,6 +21,7 @@
import static android.system.OsConstants.IPPROTO_UDP;
import static android.system.OsConstants.SOCK_DGRAM;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
@@ -174,6 +175,7 @@
mIpSecService.openUdpEncapsulationSocket(0, new Binder());
assertNotNull(udpEncapResp);
assertEquals(IpSecManager.Status.OK, udpEncapResp.status);
+ assertNotEquals(0, udpEncapResp.port);
mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId);
udpEncapResp.fileDescriptor.close();
}
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 0c02ec7..551e4df 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -128,8 +128,6 @@
void releaseMulticastLock();
- void setWifiApEnabled(in WifiConfiguration wifiConfig, boolean enable);
-
void updateInterfaceIpState(String ifaceName, int mode);
boolean startSoftAp(in WifiConfiguration wifiConfig);
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 649b0ce..b72df07 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1877,32 +1877,6 @@
}
/**
- * This call is deprecated and removed. It is no longer used to
- * start WiFi Tethering. Please use {@link ConnectivityManager#startTethering(int, boolean,
- * ConnectivityManager#OnStartTetheringCallback)} if
- * the caller has proper permissions. Callers can also use the LocalOnlyHotspot feature for a
- * hotspot capable of communicating with co-located devices {@link
- * WifiManager#startLocalOnlyHotspot(LocalOnlyHotspotCallback)}.
- *
- * @param wifiConfig SSID, security and channel details as
- * part of WifiConfiguration
- * @return {@code false}
- *
- * @hide
- * @deprecated This API is nolonger supported.
- * @removed
- */
- @SystemApi
- @Deprecated
- @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
- public boolean setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
- String packageName = mContext.getOpPackageName();
-
- Log.w(TAG, packageName + " attempted call to setWifiApEnabled: enabled = " + enabled);
- return false;
- }
-
- /**
* Call allowing ConnectivityService to update WifiService with interface mode changes.
*
* The possible modes include: {@link IFACE_IP_MODE_TETHERED},
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index b235ccc7..ee6f12b 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -777,14 +777,4 @@
mWifiManager.unregisterLocalOnlyHotspotObserver();
verify(mWifiService).stopWatchLocalOnlyHotspot();
}
-
- /**
- * Verify that calls to setWifiApEnabled return false.
- */
- @Test
- public void testSetWifiApEnabledReturnsFalse() throws Exception {
- assertFalse(mWifiManager.setWifiApEnabled(null, true));
- assertFalse(mWifiManager.setWifiApEnabled(null, false));
- verify(mWifiService, never()).setWifiApEnabled(any(), anyBoolean());
- }
}