Merge "Improve handling of devices without a WebView."
diff --git a/Android.mk b/Android.mk
index aa7caa5..7d31fdc 100644
--- a/Android.mk
+++ b/Android.mk
@@ -552,6 +552,8 @@
wifi/java/android/net/wifi/aware/IWifiAwareManager.aidl \
wifi/java/android/net/wifi/aware/IWifiAwareDiscoverySessionCallback.aidl \
wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl \
+ wifi/java/android/net/wifi/rtt/IRttCallback.aidl \
+ wifi/java/android/net/wifi/rtt/IWifiRttManager.aidl \
wifi/java/android/net/wifi/IWifiScanner.aidl \
wifi/java/android/net/wifi/IRttManager.aidl \
packages/services/PacProcessor/com/android/net/IProxyService.aidl \
@@ -721,6 +723,8 @@
frameworks/base/wifi/java/android/net/wifi/p2p/WifiP2pGroup.aidl \
frameworks/base/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.aidl \
frameworks/base/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.aidl \
+ frameworks/base/wifi/java/android/net/wifi/rtt/RangingRequest.aidl \
+ frameworks/base/wifi/java/android/net/wifi/rtt/RangingResult.aidl \
frameworks/base/wifi/java/android/net/wifi/WpsInfo.aidl \
frameworks/base/wifi/java/android/net/wifi/ScanResult.aidl \
frameworks/base/wifi/java/android/net/wifi/PasspointManagementObjectDefinition.aidl \
diff --git a/api/current.txt b/api/current.txt
index 25cac5c..6fae882 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -40765,6 +40765,7 @@
public class ServiceInfo {
method public java.util.List<java.util.Locale> getLocales();
method public java.lang.CharSequence getNameForLocale(java.util.Locale);
+ method public java.util.Set<java.util.Locale> getNamedContentLocales();
method public java.lang.String getServiceClassName();
method public java.lang.String getServiceId();
method public java.util.Date getSessionEndTime();
@@ -52394,6 +52395,8 @@
field public static final int OP_CONST_CLASS = 28; // 0x1c
field public static final int OP_CONST_CLASS_JUMBO = 255; // 0xff
field public static final int OP_CONST_HIGH16 = 21; // 0x15
+ field public static final int OP_CONST_METHOD_HANDLE = 254; // 0xfe
+ field public static final int OP_CONST_METHOD_TYPE = 255; // 0xff
field public static final int OP_CONST_STRING = 26; // 0x1a
field public static final int OP_CONST_STRING_JUMBO = 27; // 0x1b
field public static final int OP_CONST_WIDE = 24; // 0x18
diff --git a/api/system-current.txt b/api/system-current.txt
index 82cdbaf..8c1169a 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -44371,6 +44371,7 @@
public class ServiceInfo {
method public java.util.List<java.util.Locale> getLocales();
method public java.lang.CharSequence getNameForLocale(java.util.Locale);
+ method public java.util.Set<java.util.Locale> getNamedContentLocales();
method public java.lang.String getServiceClassName();
method public java.lang.String getServiceId();
method public java.util.Date getSessionEndTime();
@@ -56457,6 +56458,8 @@
field public static final int OP_CONST_CLASS = 28; // 0x1c
field public static final int OP_CONST_CLASS_JUMBO = 255; // 0xff
field public static final int OP_CONST_HIGH16 = 21; // 0x15
+ field public static final int OP_CONST_METHOD_HANDLE = 254; // 0xfe
+ field public static final int OP_CONST_METHOD_TYPE = 255; // 0xff
field public static final int OP_CONST_STRING = 26; // 0x1a
field public static final int OP_CONST_STRING_JUMBO = 27; // 0x1b
field public static final int OP_CONST_WIDE = 24; // 0x18
diff --git a/api/test-current.txt b/api/test-current.txt
index 34b663be..65918c5 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -41115,6 +41115,7 @@
public class ServiceInfo {
method public java.util.List<java.util.Locale> getLocales();
method public java.lang.CharSequence getNameForLocale(java.util.Locale);
+ method public java.util.Set<java.util.Locale> getNamedContentLocales();
method public java.lang.String getServiceClassName();
method public java.lang.String getServiceId();
method public java.util.Date getSessionEndTime();
@@ -52965,6 +52966,8 @@
field public static final int OP_CONST_CLASS = 28; // 0x1c
field public static final int OP_CONST_CLASS_JUMBO = 255; // 0xff
field public static final int OP_CONST_HIGH16 = 21; // 0x15
+ field public static final int OP_CONST_METHOD_HANDLE = 254; // 0xfe
+ field public static final int OP_CONST_METHOD_TYPE = 255; // 0xff
field public static final int OP_CONST_STRING = 26; // 0x1a
field public static final int OP_CONST_STRING_JUMBO = 27; // 0x1b
field public static final int OP_CONST_WIDE = 24; // 0x18
diff --git a/cmds/bootanimation/Android.mk b/cmds/bootanimation/Android.mk
index 7e78d6c..b16188e 100644
--- a/cmds/bootanimation/Android.mk
+++ b/cmds/bootanimation/Android.mk
@@ -31,12 +31,13 @@
LOCAL_SRC_FILES += \
iot/iotbootanimation_main.cpp \
- iot/BootAction.cpp
+ iot/BootAction.cpp \
+ iot/BootParameters.cpp \
LOCAL_SHARED_LIBRARIES += \
libandroidthings \
libbase \
- libbinder
+ libbinder \
LOCAL_STATIC_LIBRARIES += cpufeatures
diff --git a/cmds/bootanimation/iot/BootAction.cpp b/cmds/bootanimation/iot/BootAction.cpp
index 665b4d9..fa79744 100644
--- a/cmds/bootanimation/iot/BootAction.cpp
+++ b/cmds/bootanimation/iot/BootAction.cpp
@@ -19,96 +19,20 @@
#define LOG_TAG "BootAction"
#include <dlfcn.h>
-#include <fcntl.h>
-#include <map>
-
-#include <android-base/file.h>
-#include <android-base/strings.h>
-#include <base/json/json_parser.h>
-#include <base/json/json_value_converter.h>
-#include <cpu-features.h>
#include <pio/peripheral_manager_client.h>
#include <utils/Log.h>
-using android::base::ReadFileToString;
-using android::base::RemoveFileIfExists;
-using android::base::Split;
-using android::base::Join;
-using android::base::StartsWith;
-using android::base::EndsWith;
-using base::JSONReader;
-using base::Value;
-
namespace android {
-// Brightness and volume are stored as integer strings in next_boot.json.
-// They are divided by this constant to produce the actual float values in
-// range [0.0, 1.0]. This constant must match its counterpart in
-// DeviceManager.
-constexpr const float kFloatScaleFactor = 1000.0f;
-
-constexpr const char* kNextBootFile = "/data/misc/bootanimation/next_boot.json";
-constexpr const char* kLastBootFile = "/data/misc/bootanimation/last_boot.json";
-
-bool loadParameters(BootAction::SavedBootParameters* parameters)
-{
- std::string contents;
- if (!ReadFileToString(kLastBootFile, &contents)) {
- if (errno != ENOENT)
- ALOGE("Unable to read from %s: %s", kLastBootFile, strerror(errno));
-
- return false;
- }
-
- std::unique_ptr<Value> json = JSONReader::Read(contents);
- if (json.get() == nullptr) return false;
-
- JSONValueConverter<BootAction::SavedBootParameters> converter;
- if (!converter.Convert(*(json.get()), parameters)) return false;
-
- return true;
-}
-
-void BootAction::SavedBootParameters::RegisterJSONConverter(
- JSONValueConverter<SavedBootParameters> *converter) {
- converter->RegisterIntField("brightness", &SavedBootParameters::brightness);
- converter->RegisterIntField("volume", &SavedBootParameters::volume);
- converter->RegisterRepeatedString("param_names",
- &SavedBootParameters::param_names);
- converter->RegisterRepeatedString("param_values",
- &SavedBootParameters::param_values);
-}
-
BootAction::~BootAction() {
if (mLibHandle != nullptr) {
dlclose(mLibHandle);
}
}
-void BootAction::swapBootConfigs() {
- // rename() will fail if next_boot.json doesn't exist, so delete
- // last_boot.json manually first.
- std::string err;
- if (!RemoveFileIfExists(kLastBootFile, &err))
- ALOGE("Unable to delete last boot file: %s", err.c_str());
-
- if (rename(kNextBootFile, kLastBootFile) && errno != ENOENT)
- ALOGE("Unable to swap boot files: %s", strerror(errno));
-
- int fd = open(kNextBootFile, O_CREAT, DEFFILEMODE);
- if (fd == -1) {
- ALOGE("Unable to create next boot file: %s", strerror(errno));
- } else {
- // Make next_boot.json writible to everyone so DeviceManagementService
- // can save parameters there.
- if (fchmod(fd, DEFFILEMODE))
- ALOGE("Unable to set next boot file permissions: %s", strerror(errno));
- close(fd);
- }
-}
-
-bool BootAction::init(const std::string& libraryPath) {
+bool BootAction::init(const std::string& libraryPath,
+ const std::vector<ABootActionParameter>& parameters) {
APeripheralManagerClient* client = nullptr;
ALOGD("Connecting to peripheralmanager");
// Wait for peripheral manager to come up.
@@ -122,27 +46,6 @@
ALOGD("Peripheralmanager is up.");
APeripheralManagerClient_delete(client);
- float brightness = -1.0f;
- float volume = -1.0f;
- std::vector<ABootActionParameter> parameters;
- SavedBootParameters saved_parameters;
-
- if (loadParameters(&saved_parameters)) {
- // TODO(b/65462981): Do something with brightness and volume?
- brightness = saved_parameters.brightness / kFloatScaleFactor;
- volume = saved_parameters.volume / kFloatScaleFactor;
-
- if (saved_parameters.param_names.size() == saved_parameters.param_values.size()) {
- for (size_t i = 0; i < saved_parameters.param_names.size(); i++) {
- parameters.push_back({
- .key = saved_parameters.param_names[i]->c_str(),
- .value = saved_parameters.param_values[i]->c_str()
- });
- }
- } else {
- ALOGW("Parameter names and values size mismatch");
- }
- }
ALOGI("Loading boot action %s", libraryPath.c_str());
mLibHandle = dlopen(libraryPath.c_str(), RTLD_NOW);
diff --git a/cmds/bootanimation/iot/BootAction.h b/cmds/bootanimation/iot/BootAction.h
index 3cd43be..5e2495f 100644
--- a/cmds/bootanimation/iot/BootAction.h
+++ b/cmds/bootanimation/iot/BootAction.h
@@ -17,38 +17,21 @@
#ifndef _BOOTANIMATION_BOOTACTION_H
#define _BOOTANIMATION_BOOTACTION_H
-#include <map>
#include <string>
+#include <vector>
-#include <base/json/json_value_converter.h>
#include <boot_action/boot_action.h> // libandroidthings native API.
#include <utils/RefBase.h>
-using base::JSONValueConverter;
-
namespace android {
class BootAction : public RefBase {
public:
- struct SavedBootParameters {
- int brightness;
- int volume;
- ScopedVector<std::string> param_names;
- ScopedVector<std::string> param_values;
- static void RegisterJSONConverter(
- JSONValueConverter<SavedBootParameters>* converter);
- };
-
~BootAction();
- // Rename next_boot.json to last_boot.json so that we don't repeat
- // parameters if there is a crash before the framework comes up.
- // TODO(b/65462981): Is this what we want to do? Should we swap in the
- // framework instead?
- static void swapBootConfigs();
-
// libraryPath is a fully qualified path to the target .so library.
- bool init(const std::string& libraryPath);
+ bool init(const std::string& libraryPath,
+ const std::vector<ABootActionParameter>& parameters);
// The animation is going to start playing partNumber for the playCount'th
// time, update the action as needed.
diff --git a/cmds/bootanimation/iot/BootParameters.cpp b/cmds/bootanimation/iot/BootParameters.cpp
new file mode 100644
index 0000000..da6ad0d
--- /dev/null
+++ b/cmds/bootanimation/iot/BootParameters.cpp
@@ -0,0 +1,124 @@
+/*
+ * 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.
+ */
+
+#include "BootParameters.h"
+
+#define LOG_TAG "BootParameters"
+
+#include <fcntl.h>
+
+#include <string>
+
+#include <android-base/file.h>
+#include <base/json/json_parser.h>
+#include <base/json/json_reader.h>
+#include <base/json/json_value_converter.h>
+#include <utils/Log.h>
+
+using android::base::RemoveFileIfExists;
+using android::base::ReadFileToString;
+using base::JSONReader;
+using base::JSONValueConverter;
+using base::Value;
+
+namespace android {
+
+namespace {
+
+// Brightness and volume are stored as integer strings in next_boot.json.
+// They are divided by this constant to produce the actual float values in
+// range [0.0, 1.0]. This constant must match its counterpart in
+// DeviceManager.
+constexpr const float kFloatScaleFactor = 1000.0f;
+
+constexpr const char* kNextBootFile = "/data/misc/bootanimation/next_boot.json";
+constexpr const char* kLastBootFile = "/data/misc/bootanimation/last_boot.json";
+
+void swapBootConfigs() {
+ // rename() will fail if next_boot.json doesn't exist, so delete
+ // last_boot.json manually first.
+ std::string err;
+ if (!RemoveFileIfExists(kLastBootFile, &err))
+ ALOGE("Unable to delete last boot file: %s", err.c_str());
+
+ if (rename(kNextBootFile, kLastBootFile) && errno != ENOENT)
+ ALOGE("Unable to swap boot files: %s", strerror(errno));
+
+ int fd = open(kNextBootFile, O_CREAT, DEFFILEMODE);
+ if (fd == -1) {
+ ALOGE("Unable to create next boot file: %s", strerror(errno));
+ } else {
+ // Make next_boot.json writable to everyone so DeviceManagementService
+ // can save saved_parameters there.
+ if (fchmod(fd, DEFFILEMODE))
+ ALOGE("Unable to set next boot file permissions: %s", strerror(errno));
+ close(fd);
+ }
+}
+
+} // namespace
+
+BootParameters::SavedBootParameters::SavedBootParameters()
+ : brightness(-kFloatScaleFactor), volume(-kFloatScaleFactor) {}
+
+void BootParameters::SavedBootParameters::RegisterJSONConverter(
+ JSONValueConverter<SavedBootParameters>* converter) {
+ converter->RegisterIntField("brightness", &SavedBootParameters::brightness);
+ converter->RegisterIntField("volume", &SavedBootParameters::volume);
+ converter->RegisterRepeatedString("param_names",
+ &SavedBootParameters::param_names);
+ converter->RegisterRepeatedString("param_values",
+ &SavedBootParameters::param_values);
+}
+
+BootParameters::BootParameters() {
+ swapBootConfigs();
+ loadParameters();
+}
+
+void BootParameters::loadParameters() {
+ std::string contents;
+ if (!ReadFileToString(kLastBootFile, &contents)) {
+ if (errno != ENOENT)
+ ALOGE("Unable to read from %s: %s", kLastBootFile, strerror(errno));
+
+ return;
+ }
+
+ std::unique_ptr<Value> json = JSONReader::Read(contents);
+ if (json.get() == nullptr) {
+ return;
+ }
+
+ JSONValueConverter<SavedBootParameters> converter;
+ if (converter.Convert(*(json.get()), &mRawParameters)) {
+ mBrightness = mRawParameters.brightness / kFloatScaleFactor;
+ mVolume = mRawParameters.volume / kFloatScaleFactor;
+
+ if (mRawParameters.param_names.size() == mRawParameters.param_values.size()) {
+ for (size_t i = 0; i < mRawParameters.param_names.size(); i++) {
+ mParameters.push_back({
+ .key = mRawParameters.param_names[i]->c_str(),
+ .value = mRawParameters.param_values[i]->c_str()
+ });
+ }
+ } else {
+ ALOGW("Parameter names and values size mismatch");
+ }
+ }
+}
+
+} // namespace android
diff --git a/cmds/bootanimation/iot/BootParameters.h b/cmds/bootanimation/iot/BootParameters.h
new file mode 100644
index 0000000..ff3b018
--- /dev/null
+++ b/cmds/bootanimation/iot/BootParameters.h
@@ -0,0 +1,73 @@
+/*
+ * 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 _BOOTANIMATION_BOOT_PARAMETERS_H_
+#define _BOOTANIMATION_BOOT_PARAMETERS_H_
+
+#include <list>
+#include <vector>
+
+#include <base/json/json_value_converter.h>
+#include <boot_action/boot_action.h> // libandroidthings native API.
+
+namespace android {
+
+// Provides access to the parameters set by DeviceManager.reboot().
+class BootParameters {
+public:
+ // Constructor loads the parameters for this boot and swaps the param files
+ // to clear the parameters for next boot.
+ BootParameters();
+
+ // Returns true if volume/brightness were explicitly set on reboot.
+ bool hasVolume() const { return mVolume >= 0; }
+ bool hasBrightness() const { return mBrightness >= 0; }
+
+ // Returns volume/brightness in [0,1], or -1 if unset.
+ float getVolume() const { return mVolume; }
+ float getBrightness() const { return mBrightness; }
+
+ // Returns the additional boot parameters that were set on reboot.
+ const std::vector<ABootActionParameter>& getParameters() const { return mParameters; }
+
+private:
+ // Raw boot saved_parameters loaded from .json.
+ struct SavedBootParameters {
+ int brightness;
+ int volume;
+ ScopedVector<std::string> param_names;
+ ScopedVector<std::string> param_values;
+
+ SavedBootParameters();
+ static void RegisterJSONConverter(
+ ::base::JSONValueConverter<SavedBootParameters>* converter);
+ };
+
+ void loadParameters();
+
+ float mVolume = -1.f;
+ float mBrightness = -1.f;
+ std::vector<ABootActionParameter> mParameters;
+
+ // ABootActionParameter is just a raw pointer so we need to keep the
+ // original strings around to avoid losing them.
+ SavedBootParameters mRawParameters;
+};
+
+} // namespace android
+
+
+#endif // _BOOTANIMATION_BOOT_PARAMETERS_H_
diff --git a/cmds/bootanimation/iot/iotbootanimation_main.cpp b/cmds/bootanimation/iot/iotbootanimation_main.cpp
index 441a140..742f9c24 100644
--- a/cmds/bootanimation/iot/iotbootanimation_main.cpp
+++ b/cmds/bootanimation/iot/iotbootanimation_main.cpp
@@ -28,6 +28,7 @@
#include "BootAction.h"
#include "BootAnimationUtil.h"
+#include "BootParameters.h"
using namespace android;
using android::base::ReadFileToString;
@@ -37,7 +38,11 @@
namespace {
-class BootActionAnimationCallbacks : public android::BootAnimation::Callbacks {public:
+class BootActionAnimationCallbacks : public android::BootAnimation::Callbacks {
+public:
+ BootActionAnimationCallbacks(std::unique_ptr<BootParameters> bootParameters)
+ : mBootParameters(std::move(bootParameters)) {}
+
void init(const Vector<Animation::Part>&) override {
std::string library_path("/oem/lib/");
@@ -51,7 +56,7 @@
library_path += property;
mBootAction = new BootAction();
- if (!mBootAction->init(library_path)) {
+ if (!mBootAction->init(library_path, mBootParameters->getParameters())) {
mBootAction = NULL;
}
};
@@ -86,6 +91,7 @@
};
private:
+ std::unique_ptr<BootParameters> mBootParameters;
sp<BootAction> mBootAction = nullptr;
};
@@ -94,9 +100,8 @@
int main() {
setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_DISPLAY);
- // TODO(b/65462981): Should we set brightness/volume here in case the boot
- // animation is disabled?
- BootAction::swapBootConfigs();
+ // Clear our params for next boot no matter what.
+ std::unique_ptr<BootParameters> bootParameters(new BootParameters());
if (bootAnimationDisabled()) {
ALOGI("boot animation disabled");
@@ -108,7 +113,8 @@
sp<ProcessState> proc(ProcessState::self());
ProcessState::self()->startThreadPool();
- sp<BootAnimation> boot = new BootAnimation(new BootActionAnimationCallbacks());
+ sp<BootAnimation> boot = new BootAnimation(
+ new BootActionAnimationCallbacks(std::move(bootParameters)));
IPCThreadState::self()->joinThreadPool();
return 0;
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index e2e1a7f..56f9512 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -44,6 +44,7 @@
../../core/java/android/os/IStatsManager.aidl \
src/StatsService.cpp \
src/AnomalyMonitor.cpp \
+ src/StatsPuller.cpp \
src/LogEntryPrinter.cpp \
src/LogReader.cpp \
src/main.cpp \
diff --git a/cmds/statsd/src/StatsPuller.cpp b/cmds/statsd/src/StatsPuller.cpp
new file mode 100644
index 0000000..94e8361
--- /dev/null
+++ b/cmds/statsd/src/StatsPuller.cpp
@@ -0,0 +1,60 @@
+/*
+ * 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 LOG_TAG "StatsPuller"
+#define DEBUG true
+
+#include "StatsPuller.h"
+#include "StatsService.h"
+#include <android/os/IStatsCompanionService.h>
+#include <cutils/log.h>
+
+using namespace android;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+String16 StatsPuller::pull(int pullCode) {
+ if (DEBUG) ALOGD("Initiating pulling %d", pullCode);
+
+ switch (pullCode) {
+ // All stats_companion_service cases go here with fallthroughs
+ case PULL_CODE_KERNEL_WAKELOCKS: {
+ // TODO: Consider caching the statsCompanion service
+ sp <IStatsCompanionService>
+ statsCompanion = StatsService::getStatsCompanionService();
+ String16 returned_value("");
+ Status status = statsCompanion->pullData(pullCode, &returned_value);
+ if (DEBUG) ALOGD("Finished pulling the data");
+ if (!status.isOk()) {
+ ALOGW("error pulling data of type %d", pullCode);
+ }
+ return returned_value;
+ }
+
+ // case OTHER_TYPES: etc.
+
+ default: {
+ ALOGE("invalid pull code %d", pullCode);
+ return String16("");
+ }
+ }
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/StatsPuller.h b/cmds/statsd/src/StatsPuller.h
new file mode 100644
index 0000000..05343b5
--- /dev/null
+++ b/cmds/statsd/src/StatsPuller.h
@@ -0,0 +1,43 @@
+/*
+ * 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 STATSD_STATSPULLER_H
+#define STATSD_STATSPULLER_H
+
+#include <utils/String16.h>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class StatsPuller {
+public:
+ // Enums of pulled data types (pullCodes)
+ // These values must be kept in sync with com/android/server/stats/StatsCompanionService.java.
+ // TODO: pull the constant from stats_events.proto instead
+ const static int PULL_CODE_KERNEL_WAKELOCKS = 20;
+
+ StatsPuller();
+ ~StatsPuller();
+
+ static String16 pull(int pullCode);
+};
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+
+#endif //STATSD_STATSPULLER_H
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index a28f085..ae7d66b 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -177,6 +177,9 @@
if (DEBUG) ALOGD("StatsService::informPollAlarmFired succeeded");
// TODO: determine what services to poll and poll (or ask StatsCompanionService to poll) them.
+ String16 output = StatsPuller::pull(StatsPuller::PULL_CODE_KERNEL_WAKELOCKS);
+ // TODO: do something useful with the output instead of writing a string to screen.
+ ALOGD("%s", String8(output).string());
return Status::ok();
}
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index eb6aa49..a16b115 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -19,6 +19,7 @@
#include "AnomalyMonitor.h"
#include "StatsLogProcessor.h"
+#include "StatsPuller.h"
#include <android/os/BnStatsManager.h>
#include <android/os/IStatsCompanionService.h>
@@ -66,19 +67,21 @@
/** Inform statsCompanion that statsd is ready. */
virtual void sayHiToStatsCompanion();
-private:
- sp<StatsLogProcessor> m_processor; // Reference to the processor for updating configs.
+ // TODO: Move this to a more logical file/class
+ // TODO: Should be private. Temporarily public for testing purposes only.
+ const sp<AnomalyMonitor> mAnomalyMonitor;
- const sp<AnomalyMonitor> mAnomalyMonitor; // TODO: Move this to a more logical file/class
+ /** Fetches and returns the StatsCompanionService. */
+ static sp<IStatsCompanionService> getStatsCompanionService();
+
+ private:
+ sp<StatsLogProcessor> m_processor; // Reference to the processor for updating configs.
status_t doPrintStatsLog(FILE* out, const Vector<String8>& args);
void printCmdHelp(FILE* out);
status_t doLoadConfig(FILE* in);
-
- /** Fetches the StatsCompanionService. */
- sp<IStatsCompanionService> getStatsCompanionService();
};
// --- StatsdDeathRecipient ---
diff --git a/cmds/statsd/src/stats_events.proto b/cmds/statsd/src/stats_events.proto
index dffc68e..1e17895 100644
--- a/cmds/statsd/src/stats_events.proto
+++ b/cmds/statsd/src/stats_events.proto
@@ -23,13 +23,19 @@
option java_outer_classname = "StatsEventProto";
message StatsEvent {
- oneof log_entry_event {
+ oneof event {
+ // Screen state change.
ScreenStateChange screen_state_change = 2;
+ // Process state change.
ProcessStateChange process_state_change = 1112;
}
}
+// Logs changes in screen state. This event is logged in
+// frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java
message ScreenStateChange {
+ // Screen state enums follow the values defined in below file.
+ // frameworks/base/core/java/android/view/Display.java
enum State {
STATE_UNKNOWN = 0;
STATE_OFF = 1;
@@ -38,20 +44,20 @@
STATE_DOZE_SUSPEND = 4;
STATE_VR = 5;
}
+ // New screen state.
optional State display_state = 1;
}
+// Logs changes in process state. This event is logged in
+// frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java
message ProcessStateChange {
+ // Type of process event.
enum State {
START = 1;
CRASH = 2;
}
optional State state = 1;
+ // UID associated with the package.
optional int32 uid = 2;
-
- optional string package_name = 1002;
-
- optional int32 package_version = 3;
- optional string package_version_string = 4;
}
diff --git a/core/java/android/app/SharedElementCallback.java b/core/java/android/app/SharedElementCallback.java
index 0d14a8d..80fb805 100644
--- a/core/java/android/app/SharedElementCallback.java
+++ b/core/java/android/app/SharedElementCallback.java
@@ -27,6 +27,7 @@
import android.os.Parcelable;
import android.transition.TransitionUtils;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
@@ -202,7 +203,8 @@
} else {
mTempMatrix.set(viewToGlobalMatrix);
}
- return TransitionUtils.createViewBitmap(sharedElement, mTempMatrix, screenBounds);
+ ViewGroup parent = (ViewGroup) sharedElement.getParent();
+ return TransitionUtils.createViewBitmap(sharedElement, mTempMatrix, screenBounds, parent);
}
/**
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index ab70f0e7..50f1f36 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -81,10 +81,10 @@
import android.net.IpSecManager;
import android.net.NetworkPolicyManager;
import android.net.NetworkScoreManager;
-import android.net.nsd.INsdManager;
-import android.net.nsd.NsdManager;
import android.net.lowpan.ILowpanManager;
import android.net.lowpan.LowpanManager;
+import android.net.nsd.INsdManager;
+import android.net.nsd.NsdManager;
import android.net.wifi.IRttManager;
import android.net.wifi.IWifiManager;
import android.net.wifi.IWifiScanner;
@@ -95,6 +95,8 @@
import android.net.wifi.aware.WifiAwareManager;
import android.net.wifi.p2p.IWifiP2pManager;
import android.net.wifi.p2p.WifiP2pManager;
+import android.net.wifi.rtt.IWifiRttManager;
+import android.net.wifi.rtt.WifiRttManager;
import android.nfc.NfcManager;
import android.os.BatteryManager;
import android.os.BatteryStats;
@@ -603,6 +605,16 @@
ConnectivityThread.getInstanceLooper());
}});
+ registerService(Context.WIFI_RTT2_SERVICE, WifiRttManager.class,
+ new CachedServiceFetcher<WifiRttManager>() {
+ @Override
+ public WifiRttManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ IBinder b = ServiceManager.getServiceOrThrow(Context.WIFI_RTT2_SERVICE);
+ IWifiRttManager service = IWifiRttManager.Stub.asInterface(b);
+ return new WifiRttManager(ctx.getOuterContext(), service);
+ }});
+
registerService(Context.ETHERNET_SERVICE, EthernetManager.class,
new CachedServiceFetcher<EthernetManager>() {
@Override
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 693921d..cd9c6a3 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3467,6 +3467,19 @@
/**
* Use with {@link #getSystemService} to retrieve a {@link
+ * android.net.wifi.rtt.WifiRttManager} for ranging devices with wifi
+ *
+ * Note: this is a replacement for WIFI_RTT_SERVICE above. It will
+ * be renamed once final implementation in place.
+ *
+ * @see #getSystemService
+ * @see android.net.wifi.rtt.WifiRttManager
+ * @hide
+ */
+ public static final String WIFI_RTT2_SERVICE = "rttmanager2";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a {@link
* android.net.lowpan.LowpanManager} for handling management of
* LoWPAN access.
*
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index d3a3560..6b9c753 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -1763,21 +1763,43 @@
return 0;
}
+
/**
* Return a string representation, intended for logging. Some fields will be retracted.
*/
@Override
public String toString() {
- return toStringInner(/* secure =*/ true, /* includeInternalData =*/ false);
+ return toStringInner(/* secure =*/ true, /* includeInternalData =*/ false,
+ /*indent=*/ null);
}
/** @hide */
public String toInsecureString() {
- return toStringInner(/* secure =*/ false, /* includeInternalData =*/ true);
+ return toStringInner(/* secure =*/ false, /* includeInternalData =*/ true,
+ /*indent=*/ null);
}
- private String toStringInner(boolean secure, boolean includeInternalData) {
+ /** @hide */
+ public String toDumpString(String indent) {
+ return toStringInner(/* secure =*/ false, /* includeInternalData =*/ true, indent);
+ }
+
+ private void addIndentOrComma(StringBuilder sb, String indent) {
+ if (indent != null) {
+ sb.append("\n ");
+ sb.append(indent);
+ } else {
+ sb.append(", ");
+ }
+ }
+
+ private String toStringInner(boolean secure, boolean includeInternalData, String indent) {
final StringBuilder sb = new StringBuilder();
+
+ if (indent != null) {
+ sb.append(indent);
+ }
+
sb.append("ShortcutInfo {");
sb.append("id=");
@@ -1787,47 +1809,51 @@
sb.append(Integer.toHexString(mFlags));
sb.append(" [");
if (!isEnabled()) {
- sb.append("X");
+ sb.append("Dis");
}
if (isImmutable()) {
sb.append("Im");
}
if (isManifestShortcut()) {
- sb.append("M");
+ sb.append("Man");
}
if (isDynamic()) {
- sb.append("D");
+ sb.append("Dyn");
}
if (isPinned()) {
- sb.append("P");
+ sb.append("Pin");
}
if (hasIconFile()) {
- sb.append("If");
+ sb.append("Ic-f");
}
if (isIconPendingSave()) {
- sb.append("^");
+ sb.append("Pens");
}
if (hasIconResource()) {
- sb.append("Ir");
+ sb.append("Ic-r");
}
if (hasKeyFieldsOnly()) {
- sb.append("K");
+ sb.append("Key");
}
if (hasStringResourcesResolved()) {
- sb.append("Sr");
+ sb.append("Str");
}
if (isReturnedByServer()) {
- sb.append("V");
+ sb.append("Rets");
}
sb.append("]");
- sb.append(", packageName=");
+ addIndentOrComma(sb, indent);
+
+ sb.append("packageName=");
sb.append(mPackageName);
sb.append(", activity=");
sb.append(mActivity);
- sb.append(", shortLabel=");
+ addIndentOrComma(sb, indent);
+
+ sb.append("shortLabel=");
sb.append(secure ? "***" : mTitle);
sb.append(", resId=");
sb.append(mTitleResId);
@@ -1835,7 +1861,9 @@
sb.append(mTitleResName);
sb.append("]");
- sb.append(", longLabel=");
+ addIndentOrComma(sb, indent);
+
+ sb.append("longLabel=");
sb.append(secure ? "***" : mText);
sb.append(", resId=");
sb.append(mTextResId);
@@ -1843,7 +1871,9 @@
sb.append(mTextResName);
sb.append("]");
- sb.append(", disabledMessage=");
+ addIndentOrComma(sb, indent);
+
+ sb.append("disabledMessage=");
sb.append(secure ? "***" : mDisabledMessage);
sb.append(", resId=");
sb.append(mDisabledMessageResId);
@@ -1851,19 +1881,27 @@
sb.append(mDisabledMessageResName);
sb.append("]");
- sb.append(", categories=");
+ addIndentOrComma(sb, indent);
+
+ sb.append("categories=");
sb.append(mCategories);
- sb.append(", icon=");
+ addIndentOrComma(sb, indent);
+
+ sb.append("icon=");
sb.append(mIcon);
- sb.append(", rank=");
+ addIndentOrComma(sb, indent);
+
+ sb.append("rank=");
sb.append(mRank);
sb.append(", timestamp=");
sb.append(mLastChangedTimestamp);
- sb.append(", intents=");
+ addIndentOrComma(sb, indent);
+
+ sb.append("intents=");
if (mIntents == null) {
sb.append("null");
} else {
@@ -1885,12 +1923,15 @@
}
}
- sb.append(", extras=");
+ addIndentOrComma(sb, indent);
+
+ sb.append("extras=");
sb.append(mExtras);
if (includeInternalData) {
+ addIndentOrComma(sb, indent);
- sb.append(", iconRes=");
+ sb.append("iconRes=");
sb.append(mIconResId);
sb.append("[");
sb.append(mIconResName);
diff --git a/core/java/android/os/IStatsCompanionService.aidl b/core/java/android/os/IStatsCompanionService.aidl
index a83d313..d8f9567 100644
--- a/core/java/android/os/IStatsCompanionService.aidl
+++ b/core/java/android/os/IStatsCompanionService.aidl
@@ -20,11 +20,11 @@
* Binder interface to communicate with the Java-based statistics service helper.
* {@hide}
*/
-oneway interface IStatsCompanionService {
+interface IStatsCompanionService {
/**
* Tell statscompanion that stastd is up and running.
*/
- void statsdReady();
+ oneway void statsdReady();
/**
* Register an alarm for anomaly detection to fire at the given timestamp (ms since epoch).
@@ -32,10 +32,10 @@
* Uses AlarmManager.set API, so if the timestamp is in the past, alarm fires immediately, and
* alarm is inexact.
*/
- void setAnomalyAlarm(long timestampMs);
+ oneway void setAnomalyAlarm(long timestampMs);
/** Cancel any anomaly detection alarm. */
- void cancelAnomalyAlarm();
+ oneway void cancelAnomalyAlarm();
/**
* Register a repeating alarm for polling to fire at the given timestamp and every
@@ -44,8 +44,11 @@
* Uses AlarmManager.setRepeating API, so if the timestamp is in past, alarm fires immediately,
* and alarm is inexact.
*/
- void setPollingAlarms(long timestampMs, long intervalMs);
+ oneway void setPollingAlarms(long timestampMs, long intervalMs);
/** Cancel any repeating polling alarm. */
- void cancelPollingAlarms();
+ oneway void cancelPollingAlarms();
+
+ /** Pull the specified data. Results will be sent to statsd when complete. */
+ String pullData(int pullCode);
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 3c3e466..eca2fcd 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10894,6 +10894,26 @@
*/
public static final String ENABLE_DELETION_HELPER_NO_THRESHOLD_TOGGLE =
"enable_deletion_helper_no_threshold_toggle";
+
+ /**
+ * The list of snooze options for notifications
+ * This is encoded as a key=value list, separated by commas. Ex:
+ *
+ * "default=60,options_array=15:30:60:120"
+ *
+ * The following keys are supported:
+ *
+ * <pre>
+ * default (int)
+ * options_array (string)
+ * </pre>
+ *
+ * All delays in integer minutes. Array order is respected.
+ * Options will be used in order up to the maximum allowed by the UI.
+ * @hide
+ */
+ public static final String NOTIFICATION_SNOOZE_OPTIONS =
+ "notification_snooze_options";
}
/**
diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java
index 2cfec30..6e69c8f 100644
--- a/core/java/android/text/MeasuredText.java
+++ b/core/java/android/text/MeasuredText.java
@@ -90,10 +90,6 @@
}
}
- void setPos(int pos) {
- mPos = pos - mTextStart;
- }
-
/**
* Analyzes text for bidirectional runs. Allocates working buffers.
*/
diff --git a/core/java/android/transition/TransitionUtils.java b/core/java/android/transition/TransitionUtils.java
index 3306a50..084b79d 100644
--- a/core/java/android/transition/TransitionUtils.java
+++ b/core/java/android/transition/TransitionUtils.java
@@ -101,7 +101,7 @@
ImageView copy = new ImageView(view.getContext());
copy.setScaleType(ImageView.ScaleType.CENTER_CROP);
- Bitmap bitmap = createViewBitmap(view, matrix, bounds);
+ Bitmap bitmap = createViewBitmap(view, matrix, bounds, sceneRoot);
if (bitmap != null) {
copy.setImageBitmap(bitmap);
}
@@ -156,11 +156,18 @@
* returning.
* @param bounds The bounds of the bitmap in the destination coordinate system (where the
* view should be presented. Typically, this is matrix.mapRect(viewBounds);
+ * @param sceneRoot A ViewGroup that is attached to the window to temporarily contain the view
+ * if it isn't attached to the window.
* @return A bitmap of the given view or null if bounds has no width or height.
*/
- public static Bitmap createViewBitmap(View view, Matrix matrix, RectF bounds) {
- if (!view.isAttachedToWindow()) {
- return null;
+ public static Bitmap createViewBitmap(View view, Matrix matrix, RectF bounds,
+ ViewGroup sceneRoot) {
+ final boolean addToOverlay = !view.isAttachedToWindow();
+ if (addToOverlay) {
+ if (sceneRoot == null || !sceneRoot.isAttachedToWindow()) {
+ return null;
+ }
+ sceneRoot.getOverlay().add(view);
}
Bitmap bitmap = null;
int bitmapWidth = Math.round(bounds.width());
@@ -181,6 +188,9 @@
node.end(canvas);
bitmap = ThreadedRenderer.createHardwareBitmap(node, bitmapWidth, bitmapHeight);
}
+ if (addToOverlay) {
+ sceneRoot.getOverlay().remove(view);
+ }
return bitmap;
}
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index eb639bd..6e0ba341 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -100,7 +100,7 @@
private static final boolean DEBUG = false;
private static final int QUERY_TARGET_SERVICE_LIMIT = 5;
- private static final int WATCHDOG_TIMEOUT_MILLIS = 5000;
+ private static final int WATCHDOG_TIMEOUT_MILLIS = 2000;
private Bundle mReplacementExtras;
private IntentSender mChosenComponentSender;
@@ -1585,8 +1585,8 @@
} catch (RemoteException e) {
Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e);
mChooserActivity.unbindService(this);
- destroy();
mChooserActivity.mServiceConnections.remove(this);
+ destroy();
}
}
}
@@ -1602,7 +1602,6 @@
}
mChooserActivity.unbindService(this);
- destroy();
mChooserActivity.mServiceConnections.remove(this);
if (mChooserActivity.mServiceConnections.isEmpty()) {
mChooserActivity.mChooserHandler.removeMessages(
@@ -1610,6 +1609,7 @@
mChooserActivity.sendVoiceChoicesIfNeeded();
}
mConnectedComponent = null;
+ destroy();
}
}
diff --git a/core/res/res/drawable/ic_ab_back_material_settings.xml b/core/res/res/drawable/ic_ab_back_material_settings.xml
new file mode 100644
index 0000000..7325a41
--- /dev/null
+++ b/core/res/res/drawable/ic_ab_back_material_settings.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:autoMirrored="true"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@color/white"
+ android:pathData="M20,11H7.62l4.88,-4.88a0.996,0.996 0,1 0,-1.41 -1.41l-6.94,6.94c-0.2,0.2 -0.2,0.51 0,0.71l6.94,6.94a0.996,0.996 0,1 0,1.41 -1.41L7.62,13H20c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1z"/>
+</vector>
diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml
index 9dafa7a..9bea3ee 100644
--- a/core/res/res/values/themes_material.xml
+++ b/core/res/res/values/themes_material.xml
@@ -1336,6 +1336,7 @@
<!-- Default theme for Settings and activities launched from Settings. -->
<style name="Theme.Material.Settings" parent="Theme.Material.Light.LightStatusBar">
+ <item name="homeAsUpIndicator">@drawable/ic_ab_back_material_settings</item>
<item name="colorPrimary">@color/primary_material_settings_light</item>
<item name="colorPrimaryDark">@color/primary_dark_material_settings_light</item>
<item name="colorSecondary">@color/secondary_material_settings_light</item>
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 8395638..d9bcc57d 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -277,6 +277,7 @@
Settings.Global.NEW_CONTACT_AGGREGATOR,
Settings.Global.NITZ_UPDATE_DIFF,
Settings.Global.NITZ_UPDATE_SPACING,
+ Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
Settings.Global.NSD_ON,
Settings.Global.NTP_SERVER,
Settings.Global.NTP_TIMEOUT,
diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index e3527e3..43fd270 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -658,6 +658,14 @@
* float confidence = floatDepthBuffer.get();
* </pre>
*
+ * For camera devices that support the
+ * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT DEPTH_OUTPUT}
+ * capability, DEPTH_POINT_CLOUD coordinates have units of meters, and the coordinate system is
+ * defined by the camera's pose transforms:
+ * {@link android.hardware.camera2.CameraCharacteristics#LENS_POSE_TRANSLATION} and
+ * {@link android.hardware.camera2.CameraCharacteristics#LENS_POSE_ROTATION}. That means the origin is
+ * the optical center of the camera device, and the positive Z axis points along the camera's optical axis,
+ * toward the scene.
*/
public static final int DEPTH_POINT_CLOUD = 0x101;
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 186b265..dab7632a 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -4119,7 +4119,15 @@
Log.w(TAG, "updateAudioPortCache: listAudioPatches failed");
return status;
}
- } while (patchGeneration[0] != portGeneration[0]);
+ // Loop until patch generation is the same as port generation unless audio ports
+ // and audio patches are not null.
+ } while (patchGeneration[0] != portGeneration[0]
+ && (ports == null || patches == null));
+ // If the patch generation doesn't equal port generation, return ERROR here in case
+ // of mismatch between audio ports and audio patches.
+ if (patchGeneration[0] != portGeneration[0]) {
+ return ERROR;
+ }
for (int i = 0; i < newPatches.size(); i++) {
for (int j = 0; j < newPatches.get(i).sources().length; j++) {
diff --git a/media/java/android/media/AudioPortEventHandler.java b/media/java/android/media/AudioPortEventHandler.java
index c152245..ac3904a 100644
--- a/media/java/android/media/AudioPortEventHandler.java
+++ b/media/java/android/media/AudioPortEventHandler.java
@@ -17,6 +17,7 @@
package android.media;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import java.util.ArrayList;
@@ -30,6 +31,7 @@
class AudioPortEventHandler {
private Handler mHandler;
+ private HandlerThread mHandlerThread;
private final ArrayList<AudioManager.OnAudioPortUpdateListener> mListeners =
new ArrayList<AudioManager.OnAudioPortUpdateListener>();
@@ -40,6 +42,8 @@
private static final int AUDIOPORT_EVENT_SERVICE_DIED = 3;
private static final int AUDIOPORT_EVENT_NEW_LISTENER = 4;
+ private static final long RESCHEDULE_MESSAGE_DELAY_MS = 100;
+
/**
* Accessed by native methods: JNI Callback context.
*/
@@ -51,11 +55,12 @@
if (mHandler != null) {
return;
}
- // find the looper for our new event handler
- Looper looper = Looper.getMainLooper();
+ // create a new thread for our new event handler
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
- if (looper != null) {
- mHandler = new Handler(looper) {
+ if (mHandlerThread.getLooper() != null) {
+ mHandler = new Handler(mHandlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
ArrayList<AudioManager.OnAudioPortUpdateListener> listeners;
@@ -86,6 +91,12 @@
if (msg.what != AUDIOPORT_EVENT_SERVICE_DIED) {
int status = AudioManager.updateAudioPortCache(ports, patches, null);
if (status != AudioManager.SUCCESS) {
+ // Since audio ports and audio patches are not null, the return
+ // value could be ERROR due to inconsistency between port generation
+ // and patch generation. In this case, we need to reschedule the
+ // message to make sure the native callback is done.
+ sendMessageDelayed(obtainMessage(msg.what, msg.obj),
+ RESCHEDULE_MESSAGE_DELAY_MS);
return;
}
}
@@ -132,6 +143,9 @@
@Override
protected void finalize() {
native_finalize();
+ if (mHandlerThread.isAlive()) {
+ mHandlerThread.quit();
+ }
}
private native void native_finalize();
@@ -168,6 +182,10 @@
Handler handler = eventHandler.handler();
if (handler != null) {
Message m = handler.obtainMessage(what, arg1, arg2, obj);
+ if (what != AUDIOPORT_EVENT_NEW_LISTENER) {
+ // Except AUDIOPORT_EVENT_NEW_LISTENER, we can only respect the last message.
+ handler.removeMessages(what);
+ }
handler.sendMessage(m);
}
}
diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
index 80fd5c0..17b2326 100755
--- a/media/java/android/mtp/MtpDatabase.java
+++ b/media/java/android/mtp/MtpDatabase.java
@@ -845,6 +845,33 @@
return MtpConstants.RESPONSE_OK;
}
+ private int moveObject(int handle, int newParent, String newPath) {
+ String[] whereArgs = new String[] { Integer.toString(handle) };
+
+ // do not allow renaming any of the special subdirectories
+ if (isStorageSubDirectory(newPath)) {
+ return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED;
+ }
+
+ // update database
+ ContentValues values = new ContentValues();
+ values.put(Files.FileColumns.DATA, newPath);
+ values.put(Files.FileColumns.PARENT, newParent);
+ int updated = 0;
+ try {
+ // note - we are relying on a special case in MediaProvider.update() to update
+ // the paths for all children in the case where this is a directory.
+ updated = mMediaProvider.update(mObjectsUri, values, ID_WHERE, whereArgs);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in mMediaProvider.update", e);
+ }
+ if (updated == 0) {
+ Log.e(TAG, "Unable to update path for " + handle + " to " + newPath);
+ return MtpConstants.RESPONSE_GENERAL_ERROR;
+ }
+ return MtpConstants.RESPONSE_OK;
+ }
+
private int setObjectProperty(int handle, int property,
long intValue, String stringValue) {
switch (property) {
diff --git a/media/jni/android_mtp_MtpDatabase.cpp b/media/jni/android_mtp_MtpDatabase.cpp
index 5b874cd..7225f10 100644
--- a/media/jni/android_mtp_MtpDatabase.cpp
+++ b/media/jni/android_mtp_MtpDatabase.cpp
@@ -68,6 +68,7 @@
static jmethodID method_getObjectInfo;
static jmethodID method_getObjectFilePath;
static jmethodID method_deleteFile;
+static jmethodID method_moveObject;
static jmethodID method_getObjectReferences;
static jmethodID method_setObjectReferences;
static jmethodID method_sessionStarted;
@@ -178,6 +179,9 @@
virtual MtpProperty* getDevicePropertyDesc(MtpDeviceProperty property);
+ virtual MtpResponseCode moveObject(MtpObjectHandle handle, MtpObjectHandle newParent,
+ MtpString& newPath);
+
virtual void sessionStarted();
virtual void sessionEnded();
@@ -995,6 +999,18 @@
return result;
}
+MtpResponseCode MyMtpDatabase::moveObject(MtpObjectHandle handle, MtpObjectHandle newParent,
+ MtpString &newPath) {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ jstring stringValue = env->NewStringUTF((const char *) newPath);
+ MtpResponseCode result = env->CallIntMethod(mDatabase, method_moveObject,
+ (jint)handle, (jint)newParent, stringValue);
+
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+ env->DeleteLocalRef(stringValue);
+ return result;
+}
+
struct PropertyTableEntry {
MtpObjectProperty property;
int type;
@@ -1360,6 +1376,11 @@
ALOGE("Can't find deleteFile");
return -1;
}
+ method_moveObject = env->GetMethodID(clazz, "moveObject", "(IILjava/lang/String;)I");
+ if (method_moveObject == NULL) {
+ ALOGE("Can't find moveObject");
+ return -1;
+ }
method_getObjectReferences = env->GetMethodID(clazz, "getObjectReferences", "(I)[I");
if (method_getObjectReferences == NULL) {
ALOGE("Can't find getObjectReferences");
diff --git a/packages/SettingsLib/src/com/android/settingslib/wrapper/PackageManagerWrapper.java b/packages/SettingsLib/src/com/android/settingslib/wrapper/PackageManagerWrapper.java
index 4ca5bef..b1f3f3c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wrapper/PackageManagerWrapper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wrapper/PackageManagerWrapper.java
@@ -215,6 +215,13 @@
}
/**
+ * Calls {@code PackageManager.setComponentEnabledSetting}
+ */
+ public void setComponentEnabledSetting(ComponentName componentName, int newState, int flags) {
+ mPm.setComponentEnabledSetting(componentName, newState, flags);
+ }
+
+ /**
* Calls {@code PackageManager.getApplicationInfo}
*/
public ApplicationInfo getApplicationInfo(String packageName, int flags)
@@ -228,5 +235,12 @@
public CharSequence getApplicationLabel(ApplicationInfo info) {
return mPm.getApplicationLabel(info);
}
+
+ /**
+ * Calls {@code PackageManager.queryBroadcastReceivers}
+ */
+ public List<ResolveInfo> queryBroadcastReceivers(Intent intent, int flags) {
+ return mPm.queryBroadcastReceivers(intent, flags);
+ }
}
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 6da67df..1be0645 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -193,4 +193,7 @@
<!-- Default for Settings.Secure.BACKUP_MANAGER_CONSTANTS -->
<string name="def_backup_manager_constants"></string>
+
+ <!-- Default setting for Settings.Global.MOBILE_DATA_ALWAYS_ON -->
+ <bool name="def_mobile_data_always_on">true</bool>
</resources>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index a463db6..36f9b84 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -2896,7 +2896,7 @@
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 149;
+ private static final int SETTINGS_VERSION = 150;
private final int mUserId;
@@ -3470,9 +3470,25 @@
true, SettingsState.SYSTEM_PACKAGE_NAME);
}
}
-
currentVersion = 149;
}
+
+ if (currentVersion == 149) {
+ // Version 150: Set a default value for mobile data always on
+ final SettingsState globalSettings = getGlobalSettingsLocked();
+ final Setting currentSetting = globalSettings.getSettingLocked(
+ Settings.Global.MOBILE_DATA_ALWAYS_ON);
+ if (currentSetting.isNull()) {
+ globalSettings.insertSettingLocked(
+ Settings.Global.MOBILE_DATA_ALWAYS_ON,
+ getContext().getResources().getBoolean(
+ R.bool.def_mobile_data_always_on) ? "1" : "0",
+ null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+
+ currentVersion = 150;
+ }
+
// vXXX: Add new settings above this point.
if (currentVersion != newVersion) {
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index b758e7f5..eab42da 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -96,6 +96,9 @@
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<uses-permission android:name="android.permission.CREATE_USERS" />
<uses-permission android:name="android.permission.MANAGE_DEVICE_ADMINS" />
+ <uses-permission android:name="android.permission.ACCESS_LOWPAN_STATE"/>
+ <uses-permission android:name="android.permission.CHANGE_LOWPAN_STATE"/>
+ <uses-permission android:name="android.permission.READ_LOWPAN_CREDENTIAL"/>
<uses-permission android:name="android.permission.BLUETOOTH_STACK" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.RETRIEVE_WINDOW_TOKEN" />
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 87f6306..8a1e0b9 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -422,4 +422,14 @@
increase the rate of unintentional unlocks. -->
<bool name="config_lockscreenAntiFalsingClassifierEnabled">true</bool>
+ <!-- Snooze: default notificaiton snooze time. -->
+ <integer name="config_notification_snooze_time_default">60</integer>
+
+ <!-- Snooze: List of snooze values in integer minutes. -->
+ <integer-array name="config_notification_snooze_times">
+ <item>15</item>
+ <item>30</item>
+ <item>60</item>
+ <item>120</item>
+ </integer-array>
</resources>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 2148c80..e5f8029 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -82,10 +82,10 @@
<!-- Accessibility actions for the notification menu -->
<item type="id" name="action_snooze_undo"/>
- <item type="id" name="action_snooze_15_min"/>
- <item type="id" name="action_snooze_30_min"/>
- <item type="id" name="action_snooze_1_hour"/>
- <item type="id" name="action_snooze_2_hours"/>
+ <item type="id" name="action_snooze_shorter"/>
+ <item type="id" name="action_snooze_short"/>
+ <item type="id" name="action_snooze_long"/>
+ <item type="id" name="action_snooze_longer"/>
<item type="id" name="action_snooze_assistant_suggestion_1"/>
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
index c45ca54..54e9ed9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
@@ -16,8 +16,10 @@
*/
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
@@ -29,11 +31,13 @@
import android.content.res.Resources;
import android.graphics.Typeface;
import android.os.Bundle;
+import android.provider.Settings;
import android.service.notification.SnoozeCriterion;
import android.service.notification.StatusBarNotification;
import android.text.SpannableString;
import android.text.style.StyleSpan;
import android.util.AttributeSet;
+import android.util.KeyValueListParser;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -51,11 +55,14 @@
public class NotificationSnooze extends LinearLayout
implements NotificationGuts.GutsContent, View.OnClickListener {
+ private static final String TAG = "NotificationSnooze";
/**
* If this changes more number increases, more assistant action resId's should be defined for
* accessibility purposes, see {@link #setSnoozeOptions(List)}
*/
private static final int MAX_ASSISTANT_SUGGESTIONS = 1;
+ private static final String KEY_DEFAULT_SNOOZE = "default";
+ private static final String KEY_OPTIONS = "options_array";
private NotificationGuts mGutsContainer;
private NotificationSwipeActionHelper mSnoozeListener;
private StatusBarNotification mSbn;
@@ -72,9 +79,29 @@
private boolean mSnoozing;
private boolean mExpanded;
private AnimatorSet mExpandAnimation;
+ private KeyValueListParser mParser;
+
+ private final static int[] sAccessibilityActions = {
+ R.id.action_snooze_shorter,
+ R.id.action_snooze_short,
+ R.id.action_snooze_long,
+ R.id.action_snooze_longer,
+ };
public NotificationSnooze(Context context, AttributeSet attrs) {
super(context, attrs);
+ mParser = new KeyValueListParser(',');
+ }
+
+ @VisibleForTesting
+ SnoozeOption getDefaultOption()
+ {
+ return mDefaultOption;
+ }
+
+ @VisibleForTesting
+ void setKeyValueListParser(KeyValueListParser parser) {
+ mParser = parser;
}
@Override
@@ -172,17 +199,49 @@
mSbn = sbn;
}
- private ArrayList<SnoozeOption> getDefaultSnoozeOptions() {
+ @VisibleForTesting
+ ArrayList<SnoozeOption> getDefaultSnoozeOptions() {
+ final Resources resources = getContext().getResources();
ArrayList<SnoozeOption> options = new ArrayList<>();
+ try {
+ final String config = Settings.Global.getString(getContext().getContentResolver(),
+ Settings.Global.NOTIFICATION_SNOOZE_OPTIONS);
+ mParser.setString(config);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Bad snooze constants");
+ }
- options.add(createOption(15 /* minutes */, R.id.action_snooze_15_min));
- options.add(createOption(30 /* minutes */, R.id.action_snooze_30_min));
- mDefaultOption = createOption(60 /* minutes */, R.id.action_snooze_1_hour);
- options.add(mDefaultOption);
- options.add(createOption(60 * 2 /* minutes */, R.id.action_snooze_2_hours));
+ final int defaultSnooze = mParser.getInt(KEY_DEFAULT_SNOOZE,
+ resources.getInteger(R.integer.config_notification_snooze_time_default));
+ final int[] snoozeTimes = parseIntArray(KEY_OPTIONS,
+ resources.getIntArray(R.array.config_notification_snooze_times));
+
+ for (int i = 0; i < snoozeTimes.length && i < sAccessibilityActions.length; i++) {
+ int snoozeTime = snoozeTimes[i];
+ SnoozeOption option = createOption(snoozeTime, sAccessibilityActions[i]);
+ if (i == 0 || snoozeTime == defaultSnooze) {
+ mDefaultOption = option;
+ }
+ options.add(option);
+ }
return options;
}
+ @VisibleForTesting
+ int[] parseIntArray(final String key, final int[] defaultArray) {
+ final String value = mParser.getString(key, null);
+ if (value != null) {
+ try {
+ return Arrays.stream(value.split(":")).map(String::trim).mapToInt(
+ Integer::parseInt).toArray();
+ } catch (NumberFormatException e) {
+ return defaultArray;
+ }
+ } else {
+ return defaultArray;
+ }
+ }
+
private SnoozeOption createOption(int minutes, int accessibilityActionId) {
Resources res = getResources();
boolean showInHours = minutes >= 60;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationSnoozeTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationSnoozeTest.java
new file mode 100644
index 0000000..6b31c96
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationSnoozeTest.java
@@ -0,0 +1,172 @@
+/*
+ * 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.systemui.statusbar;
+
+import android.provider.Settings;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableResources;
+import android.testing.UiThreadTest;
+import android.util.KeyValueListParser;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Matchers.isNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@UiThreadTest
+public class NotificationSnoozeTest extends SysuiTestCase {
+ private static final int RES_DEFAULT = 2;
+ private static final int[] RES_OPTIONS = {1, 2, 3};
+ private NotificationSnooze mNotificationSnooze;
+ private KeyValueListParser mMockParser;
+
+ @Before
+ public void setUp() throws Exception {
+ Settings.Global.putString(mContext.getContentResolver(),
+ Settings.Global.NOTIFICATION_SNOOZE_OPTIONS, null);
+ TestableResources resources = mContext.getOrCreateTestableResources();
+ resources.addOverride(R.integer.config_notification_snooze_time_default, RES_DEFAULT);
+ resources.addOverride(R.array.config_notification_snooze_times, RES_OPTIONS);
+ mNotificationSnooze = new NotificationSnooze(mContext, null);
+ mMockParser = mock(KeyValueListParser.class);
+ }
+
+ @Test
+ public void testParseIntArrayNull() throws Exception {
+ when(mMockParser.getString(anyString(), isNull())).thenReturn(null);
+ mNotificationSnooze.setKeyValueListParser(mMockParser);
+
+ int[] result = mNotificationSnooze.parseIntArray("foo", RES_OPTIONS);
+ assertEquals(RES_OPTIONS, result);
+ }
+
+ @Test
+ public void testParseIntArrayLeadingSep() throws Exception {
+ when(mMockParser.getString(anyString(), isNull())).thenReturn(":4:5:6");
+ mNotificationSnooze.setKeyValueListParser(mMockParser);
+
+ int[] result = mNotificationSnooze.parseIntArray("foo", RES_OPTIONS);
+ assertEquals(RES_OPTIONS, result);
+ }
+
+ @Test
+ public void testParseIntArrayEmptyItem() throws Exception {
+ when(mMockParser.getString(anyString(), isNull())).thenReturn("4::6");
+ mNotificationSnooze.setKeyValueListParser(mMockParser);
+
+ int[] result = mNotificationSnooze.parseIntArray("foo", RES_OPTIONS);
+ assertEquals(RES_OPTIONS, result);
+ }
+
+ @Test
+ public void testParseIntArrayTrailingSep() throws Exception {
+ when(mMockParser.getString(anyString(), isNull())).thenReturn("4:5:6:");
+ mNotificationSnooze.setKeyValueListParser(mMockParser);
+
+ int[] result = mNotificationSnooze.parseIntArray("foo", RES_OPTIONS);
+ assertEquals(3, result.length);
+ assertEquals(4, result[0]); // respect order
+ assertEquals(5, result[1]);
+ assertEquals(6, result[2]);
+ }
+
+ @Test
+ public void testParseIntArrayGoodData() throws Exception {
+ when(mMockParser.getString(anyString(), isNull())).thenReturn("4:5:6");
+ mNotificationSnooze.setKeyValueListParser(mMockParser);
+
+ int[] result = mNotificationSnooze.parseIntArray("foo", RES_OPTIONS);
+ assertEquals(3, result.length);
+ assertEquals(4, result[0]); // respect order
+ assertEquals(5, result[1]);
+ assertEquals(6, result[2]);
+ }
+
+ @Test
+ public void testGetOptionsWithNoConfig() throws Exception {
+ ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions();
+ assertEquals(3, result.size());
+ assertEquals(1, result.get(0).getMinutesToSnoozeFor()); // respect order
+ assertEquals(2, result.get(1).getMinutesToSnoozeFor());
+ assertEquals(3, result.get(2).getMinutesToSnoozeFor());
+ assertEquals(2, mNotificationSnooze.getDefaultOption().getMinutesToSnoozeFor());
+ }
+
+ @Test
+ public void testGetOptionsWithInvalidConfig() throws Exception {
+ Settings.Global.putString(mContext.getContentResolver(),
+ Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
+ "this is garbage");
+ ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions();
+ assertEquals(3, result.size());
+ assertEquals(1, result.get(0).getMinutesToSnoozeFor()); // respect order
+ assertEquals(2, result.get(1).getMinutesToSnoozeFor());
+ assertEquals(3, result.get(2).getMinutesToSnoozeFor());
+ assertEquals(2, mNotificationSnooze.getDefaultOption().getMinutesToSnoozeFor());
+ }
+
+ @Test
+ public void testGetOptionsWithValidDefault() throws Exception {
+ Settings.Global.putString(mContext.getContentResolver(),
+ Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
+ "default=10,options_array=4:5:6:7");
+ ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions();
+ assertNotNull(mNotificationSnooze.getDefaultOption()); // pick one
+ }
+
+ @Test
+ public void testGetOptionsWithValidConfig() throws Exception {
+ Settings.Global.putString(mContext.getContentResolver(),
+ Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
+ "default=6,options_array=4:5:6:7");
+ ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions();
+ assertEquals(4, result.size());
+ assertEquals(4, result.get(0).getMinutesToSnoozeFor()); // respect order
+ assertEquals(5, result.get(1).getMinutesToSnoozeFor());
+ assertEquals(6, result.get(2).getMinutesToSnoozeFor());
+ assertEquals(7, result.get(3).getMinutesToSnoozeFor());
+ assertEquals(6, mNotificationSnooze.getDefaultOption().getMinutesToSnoozeFor());
+ }
+
+ @Test
+ public void testGetOptionsWithLongConfig() throws Exception {
+ Settings.Global.putString(mContext.getContentResolver(),
+ Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
+ "default=6,options_array=4:5:6:7:8:9:10:11:12:13:14:15:16:17");
+ ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions();
+ assertTrue(result.size() > 3);
+ assertEquals(4, result.get(0).getMinutesToSnoozeFor()); // respect order
+ assertEquals(5, result.get(1).getMinutesToSnoozeFor());
+ assertEquals(6, result.get(2).getMinutesToSnoozeFor());
+ }
+}
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 787d363..bed8e3f 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -3851,7 +3851,7 @@
// Tag of a field for the length of the filter text
FIELD_AUTOFILL_FILTERTEXT_LEN = 911;
- // An autofill authentification succeeded
+ // An autofill authentication succeeded
// Package: Package of app that was autofilled
AUTOFILL_AUTHENTICATED = 912;
@@ -4462,19 +4462,19 @@
// OS: O MR
FIELD_AUTOFILL_PREVIOUS_LENGTH = 1125;
- // An autofill dataset authentification succeeded
+ // An autofill dataset authentication succeeded
// Package: Package of app that was autofilled
// OS: O MR
// Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
AUTOFILL_DATASET_AUTHENTICATED = 1126;
- // An autofill service provided an invalid dataset authentification
+ // An autofill service provided an invalid dataset authentication
// Package: Package of app that was autofilled
// OS: O MR
// Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
AUTOFILL_INVALID_DATASET_AUTHENTICATION = 1127;
- // An autofill service provided an invalid authentification extra
+ // An autofill service provided an invalid authentication extra
// Package: Package of app that was autofilled
// OS: O MR
// Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
@@ -4636,6 +4636,11 @@
// OS: P
DIALOG_ENABLE_ADB = 1161;
+ // OPEN: Settings > Developer options > Revoke USB debugging authorizations > Info dialog
+ // CATEGORY: SETTINGS
+ // OS: P
+ DIALOG_CLEAR_ADB_KEYS = 1162;
+
// Add new aosp constants above this line.
// END OF AOSP CONSTANTS
}
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 2f31fc3..f4dbb5a 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -1987,7 +1987,7 @@
if (uri == null) {
return;
}
- String pkgName = uri.getSchemeSpecificPart();
+ final String pkgName = uri.getSchemeSpecificPart();
if (pkgName != null) {
pkgList = new String[] { pkgName };
}
@@ -1995,7 +1995,7 @@
// At package-changed we only care about looking at new transport states
if (changed) {
- String[] components =
+ final String[] components =
intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
if (MORE_DEBUG) {
@@ -2005,7 +2005,8 @@
}
}
- mTransportManager.onPackageChanged(pkgName, components);
+ mBackupHandler.post(
+ () -> mTransportManager.onPackageChanged(pkgName, components));
return; // nothing more to do in the PACKAGE_CHANGED case
}
@@ -2037,7 +2038,7 @@
}
// If they're full-backup candidates, add them there instead
final long now = System.currentTimeMillis();
- for (String packageName : pkgList) {
+ for (final String packageName : pkgList) {
try {
PackageInfo app = mPackageManager.getPackageInfo(packageName, 0);
if (appGetsFullBackup(app)
@@ -2054,7 +2055,8 @@
writeFullBackupScheduleAsync();
}
- mTransportManager.onPackageAdded(packageName);
+ mBackupHandler.post(
+ () -> mTransportManager.onPackageAdded(packageName));
} catch (NameNotFoundException e) {
// doesn't really exist; ignore it
@@ -2078,8 +2080,9 @@
removePackageParticipantsLocked(pkgList, uid);
}
}
- for (String pkgName : pkgList) {
- mTransportManager.onPackageRemoved(pkgName);
+ for (final String pkgName : pkgList) {
+ mBackupHandler.post(
+ () -> mTransportManager.onPackageRemoved(pkgName));
}
}
}
diff --git a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
index 8b4cc7f..14a008e 100644
--- a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
@@ -1188,7 +1188,7 @@
if (uri == null) {
return;
}
- String pkgName = uri.getSchemeSpecificPart();
+ final String pkgName = uri.getSchemeSpecificPart();
if (pkgName != null) {
pkgList = new String[]{pkgName};
}
@@ -1196,7 +1196,7 @@
// At package-changed we only care about looking at new transport states
if (changed) {
- String[] components =
+ final String[] components =
intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
if (MORE_DEBUG) {
@@ -1206,7 +1206,8 @@
}
}
- mTransportManager.onPackageChanged(pkgName, components);
+ mBackupHandler.post(
+ () -> mTransportManager.onPackageChanged(pkgName, components));
return; // nothing more to do in the PACKAGE_CHANGED case
}
@@ -1238,7 +1239,7 @@
}
// If they're full-backup candidates, add them there instead
final long now = System.currentTimeMillis();
- for (String packageName : pkgList) {
+ for (final String packageName : pkgList) {
try {
PackageInfo app = mPackageManager.getPackageInfo(packageName, 0);
if (AppBackupUtils.appGetsFullBackup(app)
@@ -1256,7 +1257,8 @@
writeFullBackupScheduleAsync();
}
- mTransportManager.onPackageAdded(packageName);
+ mBackupHandler.post(
+ () -> mTransportManager.onPackageAdded(packageName));
} catch (NameNotFoundException e) {
// doesn't really exist; ignore it
@@ -1280,8 +1282,9 @@
removePackageParticipantsLocked(pkgList, uid);
}
}
- for (String pkgName : pkgList) {
- mTransportManager.onPackageRemoved(pkgName);
+ for (final String pkgName : pkgList) {
+ mBackupHandler.post(
+ () -> mTransportManager.onPackageRemoved(pkgName));
}
}
}
diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java
index 9aae384..7a0173f 100644
--- a/services/backup/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/java/com/android/server/backup/TransportManager.java
@@ -341,9 +341,9 @@
private class TransportConnection implements ServiceConnection {
// Hold mTransportsLock to access these fields so as to provide a consistent view of them.
- private IBackupTransport mBinder;
+ private volatile IBackupTransport mBinder;
private final List<TransportReadyCallback> mListeners = new ArrayList<>();
- private String mTransportName;
+ private volatile String mTransportName;
private final ComponentName mTransportComponent;
@@ -426,25 +426,24 @@
+ rebindTimeout + "ms");
}
+ // Intentionally not synchronized -- the variable is volatile and changes to its value
+ // are inside synchronized blocks, providing a memory sync barrier; and this method
+ // does not touch any other state protected by that lock.
private IBackupTransport getBinder() {
- synchronized (mTransportLock) {
- return mBinder;
- }
+ return mBinder;
}
+ // Intentionally not synchronized; same as getBinder()
private String getName() {
- synchronized (mTransportLock) {
- return mTransportName;
- }
+ return mTransportName;
}
+ // Intentionally not synchronized; same as getBinder()
private void bindIfUnbound() {
- synchronized (mTransportLock) {
- if (mBinder == null) {
- Slog.d(TAG,
- "Rebinding to transport " + mTransportComponent.flattenToShortString());
- bindToTransport(mTransportComponent, this);
- }
+ if (mBinder == null) {
+ Slog.d(TAG,
+ "Rebinding to transport " + mTransportComponent.flattenToShortString());
+ bindToTransport(mTransportComponent, this);
}
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0bc20a2e..c17583f 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -4158,7 +4158,8 @@
newDevice, AudioSystem.getOutputDeviceName(newDevice)));
}
synchronized (mConnectedDevices) {
- if ((newDevice & DEVICE_MEDIA_UNMUTED_ON_PLUG) != 0
+ if (mNm.getZenMode() != Settings.Global.ZEN_MODE_NO_INTERRUPTIONS
+ && (newDevice & DEVICE_MEDIA_UNMUTED_ON_PLUG) != 0
&& mStreamStates[AudioSystem.STREAM_MUSIC].mIsMuted
&& mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(newDevice) != 0
&& (newDevice & AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC)) != 0)
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index ac80794..78aa2f9 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -795,18 +795,22 @@
* @param uid Uid to check against for removal of a job.
*
*/
- public void cancelJobsForUid(int uid, String reason) {
+ public boolean cancelJobsForUid(int uid, String reason) {
if (uid == Process.SYSTEM_UID) {
Slog.wtfStack(TAG, "Can't cancel all jobs for system uid");
- return;
+ return false;
}
+
+ boolean jobsCanceled = false;
synchronized (mLock) {
final List<JobStatus> jobsForUid = mJobs.getJobsByUid(uid);
for (int i=0; i<jobsForUid.size(); i++) {
JobStatus toRemove = jobsForUid.get(i);
cancelJobImplLocked(toRemove, null, reason);
+ jobsCanceled = true;
}
}
+ return jobsCanceled;
}
/**
@@ -816,13 +820,14 @@
* @param uid Uid of the calling client.
* @param jobId Id of the job, provided at schedule-time.
*/
- public void cancelJob(int uid, int jobId) {
+ public boolean cancelJob(int uid, int jobId) {
JobStatus toCancel;
synchronized (mLock) {
toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
if (toCancel != null) {
cancelJobImplLocked(toCancel, null, "cancel() called by app");
}
+ return (toCancel != null);
}
}
@@ -2147,6 +2152,39 @@
return 0;
}
+ // Shell command infrastructure: cancel a scheduled job
+ int executeCancelCommand(PrintWriter pw, String pkgName, int userId,
+ boolean hasJobId, int jobId) {
+ if (DEBUG) {
+ Slog.v(TAG, "executeCancelCommand(): " + pkgName + "/" + userId + " " + jobId);
+ }
+
+ int pkgUid = -1;
+ try {
+ IPackageManager pm = AppGlobals.getPackageManager();
+ pkgUid = pm.getPackageUid(pkgName, 0, userId);
+ } catch (RemoteException e) { /* can't happen */ }
+
+ if (pkgUid < 0) {
+ pw.println("Package " + pkgName + " not found.");
+ return JobSchedulerShellCommand.CMD_ERR_NO_PACKAGE;
+ }
+
+ if (!hasJobId) {
+ pw.println("Canceling all jobs for " + pkgName + " in user " + userId);
+ if (!cancelJobsForUid(pkgUid, "cancel shell command for package")) {
+ pw.println("No matching jobs found.");
+ }
+ } else {
+ pw.println("Canceling job " + pkgName + "/#" + jobId + " in user " + userId);
+ if (!cancelJob(pkgUid, jobId)) {
+ pw.println("No matching job found.");
+ }
+ }
+
+ return 0;
+ }
+
void setMonitorBattery(boolean enabled) {
synchronized (mLock) {
if (mBatteryController != null) {
diff --git a/services/core/java/com/android/server/job/JobSchedulerShellCommand.java b/services/core/java/com/android/server/job/JobSchedulerShellCommand.java
index a53c088..d630aab 100644
--- a/services/core/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/services/core/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -48,6 +48,8 @@
return runJob(pw);
case "timeout":
return timeout(pw);
+ case "cancel":
+ return cancelJob(pw);
case "monitor-battery":
return monitorBattery(pw);
case "get-battery-seq":
@@ -205,6 +207,42 @@
}
}
+ private int cancelJob(PrintWriter pw) throws Exception {
+ checkPermission("cancel jobs");
+
+ int userId = UserHandle.USER_SYSTEM;
+
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-u":
+ case "--user":
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ break;
+
+ default:
+ pw.println("Error: unknown option '" + opt + "'");
+ return -1;
+ }
+ }
+
+ if (userId < 0) {
+ pw.println("Error: must specify a concrete user ID");
+ return -1;
+ }
+
+ final String pkgName = getNextArg();
+ final String jobIdStr = getNextArg();
+ final int jobId = jobIdStr != null ? Integer.parseInt(jobIdStr) : -1;
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return mInternal.executeCancelCommand(pw, pkgName, userId, jobIdStr != null, jobId);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
private int monitorBattery(PrintWriter pw) throws Exception {
checkPermission("change battery monitoring");
String opt = getNextArgRequired();
@@ -315,6 +353,12 @@
pw.println(" Options:");
pw.println(" -u or --user: specify which user's job is to be run; the default is");
pw.println(" all users");
+ pw.println(" cancel [-u | --user USER_ID] PACKAGE [JOB_ID]");
+ pw.println(" Cancel a scheduled job. If a job ID is not supplied, all jobs scheduled");
+ pw.println(" by that package will be canceled. USE WITH CAUTION.");
+ pw.println(" Options:");
+ pw.println(" -u or --user: specify which user's job is to be run; the default is");
+ pw.println(" the primary or system user");
pw.println(" monitor-battery [on|off]");
pw.println(" Control monitoring of all battery changes. Off by default. Turning");
pw.println(" on makes get-battery-seq useful.");
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 6f70f4c..9309f18 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -1186,9 +1186,7 @@
final int size = shortcuts.size();
for (int i = 0; i < size; i++) {
final ShortcutInfo si = shortcuts.valueAt(i);
- pw.print(prefix);
- pw.print(" ");
- pw.println(si.toInsecureString());
+ pw.println(si.toDumpString(prefix + " "));
if (si.getBitmapPath() != null) {
final long len = new File(si.getBitmapPath()).length();
pw.print(prefix);
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index 1a62662..05fd248 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -30,8 +30,12 @@
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.KernelWakelockReader;
+import com.android.internal.os.KernelWakelockStats;
import com.android.server.SystemService;
+import java.util.Map;
+
/**
* Helper service for statsd (the native stats management service in cmds/statsd/).
* Used for registering and receiving alarms on behalf of statsd.
@@ -157,7 +161,44 @@
}
}
- @Override
+ // These values must be kept in sync with cmd/statsd/StatsPuller.h.
+ // TODO: pull the constant from stats_events.proto instead
+ private static final int PULL_CODE_KERNEL_WAKELOCKS = 20;
+
+ private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
+ private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
+
+ @Override // Binder call
+ public String pullData(int pullCode) {
+ enforceCallingPermission();
+ if (DEBUG) Slog.d(TAG, "Fetching " + pullCode);
+
+ StringBuilder s = new StringBuilder(); // TODO: use and return a Parcel instead of a string
+ switch (pullCode) {
+ case PULL_CODE_KERNEL_WAKELOCKS:
+ final KernelWakelockStats wakelockStats =
+ mKernelWakelockReader.readKernelWakelockStats(mTmpWakelockStats);
+
+ for (Map.Entry<String, KernelWakelockStats.Entry> ent : wakelockStats.entrySet()) {
+ String name = ent.getKey();
+ KernelWakelockStats.Entry kws = ent.getValue();
+ s.append("Wakelock ")
+ .append(name)
+ .append(", time=")
+ .append(kws.mTotalTime)
+ .append(", count=")
+ .append(kws.mCount)
+ .append('\n');
+ }
+ break;
+ default:
+ Slog.w(TAG, "No such pollable data as " + pullCode);
+ return null;
+ }
+ return s.toString();
+ }
+
+ @Override // Binder call
public void statsdReady() {
enforceCallingPermission();
if (DEBUG) Slog.d(TAG, "learned that statsdReady");
diff --git a/services/core/java/com/android/server/vr/Vr2dDisplay.java b/services/core/java/com/android/server/vr/Vr2dDisplay.java
index 8f50a39..5721415 100644
--- a/services/core/java/com/android/server/vr/Vr2dDisplay.java
+++ b/services/core/java/com/android/server/vr/Vr2dDisplay.java
@@ -294,6 +294,8 @@
int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH;
flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT;
+ flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
+ flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
mVirtualDisplay = mDisplayManager.createVirtualDisplay(null /* projection */,
DISPLAY_NAME, mVirtualDisplayWidth, mVirtualDisplayHeight, mVirtualDisplayDpi,
null /* surface */, flags, null /* callback */, null /* handler */,
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 4308ff9..65278837 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -923,7 +923,9 @@
void resetAnimationBackgroundAnimator() {
mAnimationBackgroundAnimator = null;
- mAnimationBackgroundSurface.hide();
+ if (mAnimationBackgroundSurface != null) {
+ mAnimationBackgroundSurface.hide();
+ }
}
void setAnimationBackground(WindowStateAnimator winAnimator, int color) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 1c06662..1c0e0cd 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1092,6 +1092,7 @@
if (!disableRtt) {
traceBeginAndSlog("StartWifiRtt");
mSystemServiceManager.startService("com.android.server.wifi.RttService");
+ mSystemServiceManager.startService("com.android.server.wifi.rtt.RttService");
traceEnd();
}
diff --git a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
index 61415b5..fe27537 100644
--- a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
+++ b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
@@ -165,6 +165,12 @@
Log.w(LOG_TAG, "Download result did not include a result code. Ignoring.");
return false;
}
+ // We do not need to verify below extras if the result is not success.
+ if (MbmsDownloadSession.RESULT_SUCCESSFUL !=
+ intent.getIntExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT,
+ MbmsDownloadSession.RESULT_CANCELLED)) {
+ return true;
+ }
if (!intent.hasExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST)) {
Log.w(LOG_TAG, "Download result did not include the associated request. Ignoring.");
return false;
diff --git a/telephony/java/android/telephony/mbms/ServiceInfo.java b/telephony/java/android/telephony/mbms/ServiceInfo.java
index 9a01ed0..8529f52 100644
--- a/telephony/java/android/telephony/mbms/ServiceInfo.java
+++ b/telephony/java/android/telephony/mbms/ServiceInfo.java
@@ -23,6 +23,7 @@
import android.text.TextUtils;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
@@ -62,12 +63,6 @@
throw new RuntimeException("bad locales length " + newLocales.size());
}
- for (Locale l : newLocales) {
- if (!newNames.containsKey(l)) {
- throw new IllegalArgumentException("A name must be provided for each locale");
- }
- }
-
names = new HashMap(newNames.size());
names.putAll(newNames);
className = newClassName;
@@ -127,7 +122,7 @@
* Get the user-displayable name for this cell-broadcast service corresponding to the
* provided {@link Locale}.
* @param locale The {@link Locale} in which you want the name of the service. This must be a
- * value from the list returned by {@link #getLocales()} -- an
+ * value from the set returned by {@link #getNamedContentLocales()} -- an
* {@link java.util.NoSuchElementException} may be thrown otherwise.
* @return The {@link CharSequence} providing the name of the service in the given
* {@link Locale}
@@ -140,6 +135,17 @@
}
/**
+ * Return an unmodifiable set of the current {@link Locale}s that have a user-displayable name
+ * associated with them. The user-displayable name associated with any {@link Locale} in this
+ * set can be retrieved with {@link #getNameForLocale(Locale)}.
+ * @return An unmodifiable set of {@link Locale} objects corresponding to a user-displayable
+ * content name in that locale.
+ */
+ public @NonNull Set<Locale> getNamedContentLocales() {
+ return Collections.unmodifiableSet(names.keySet());
+ }
+
+ /**
* The class name for this service - used to categorize and filter
*/
public String getServiceClassName() {
diff --git a/telephony/java/android/telephony/mbms/vendor/VendorUtils.java b/telephony/java/android/telephony/mbms/vendor/VendorUtils.java
index 8fb27b2..a43f122 100644
--- a/telephony/java/android/telephony/mbms/vendor/VendorUtils.java
+++ b/telephony/java/android/telephony/mbms/vendor/VendorUtils.java
@@ -38,8 +38,9 @@
/**
* The MBMS middleware should send this when a download of single file has completed or
- * failed. Mandatory extras are
+ * failed. The only mandatory extra is
* {@link MbmsDownloadSession#EXTRA_MBMS_DOWNLOAD_RESULT}
+ * and the following are required when the download has completed:
* {@link MbmsDownloadSession#EXTRA_MBMS_FILE_INFO}
* {@link MbmsDownloadSession#EXTRA_MBMS_DOWNLOAD_REQUEST}
* {@link #EXTRA_TEMP_LIST}
diff --git a/tools/aapt/Images.cpp b/tools/aapt/Images.cpp
index 5f586a1..627a231 100644
--- a/tools/aapt/Images.cpp
+++ b/tools/aapt/Images.cpp
@@ -1246,7 +1246,7 @@
if (kIsDebug) {
printf("Adding 9-patch info...\n");
}
- strcpy((char*)unknowns[p_index].name, "npTc");
+ memcpy((char*)unknowns[p_index].name, "npTc", 5);
unknowns[p_index].data = (png_byte*)imageInfo.serialize9patch();
unknowns[p_index].size = imageInfo.info9Patch.serializedSize();
// TODO: remove the check below when everything works
@@ -1254,7 +1254,7 @@
// automatically generated 9 patch outline data
int chunk_size = sizeof(png_uint_32) * 6;
- strcpy((char*)unknowns[o_index].name, "npOl");
+ memcpy((char*)unknowns[o_index].name, "npOl", 5);
unknowns[o_index].data = (png_byte*) calloc(chunk_size, 1);
png_byte outputData[chunk_size];
memcpy(&outputData, &imageInfo.outlineInsetsLeft, 4 * sizeof(png_uint_32));
@@ -1266,7 +1266,7 @@
// optional optical inset / layout bounds data
if (imageInfo.haveLayoutBounds) {
int chunk_size = sizeof(png_uint_32) * 4;
- strcpy((char*)unknowns[b_index].name, "npLb");
+ memcpy((char*)unknowns[b_index].name, "npLb", 5);
unknowns[b_index].data = (png_byte*) calloc(chunk_size, 1);
memcpy(unknowns[b_index].data, &imageInfo.layoutBoundsLeft, chunk_size);
unknowns[b_index].size = chunk_size;
diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp
index b80780e..6f2b865 100644
--- a/tools/aapt2/LoadedApk.cpp
+++ b/tools/aapt2/LoadedApk.cpp
@@ -20,11 +20,15 @@
#include "ValueVisitor.h"
#include "flatten/Archive.h"
#include "flatten/TableFlattener.h"
+#include "flatten/XmlFlattener.h"
#include "io/BigBufferInputStream.h"
#include "io/Util.h"
+#include "xml/XmlDom.h"
namespace aapt {
+using xml::XmlResource;
+
std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(IAaptContext* context,
const android::StringPiece& path) {
Source source(path);
@@ -52,6 +56,7 @@
if (!parser.Parse()) {
return {};
}
+
return util::make_unique<LoadedApk>(source, std::move(apk), std::move(table));
}
@@ -63,7 +68,7 @@
bool LoadedApk::WriteToArchive(IAaptContext* context, ResourceTable* split_table,
const TableFlattenerOptions& options, FilterChain* filters,
- IArchiveWriter* writer) {
+ IArchiveWriter* writer, XmlResource* manifest) {
std::set<std::string> referenced_resources;
// List the files being referenced in the resource table.
for (auto& pkg : split_table->packages) {
@@ -119,6 +124,20 @@
return false;
}
+ } else if (manifest != nullptr && path == "AndroidManifest.xml") {
+ BigBuffer buffer(8192);
+ XmlFlattener xml_flattener(&buffer, {});
+ if (!xml_flattener.Consume(context, manifest)) {
+ context->GetDiagnostics()->Error(DiagMessage(path) << "flattening failed");
+ return false;
+ }
+
+ uint32_t compression_flags = file->WasCompressed() ? ArchiveEntry::kCompress : 0u;
+ io::BigBufferInputStream manifest_buffer_in(&buffer);
+ if (!io::CopyInputStreamToArchive(context, &manifest_buffer_in, path, compression_flags,
+ writer)) {
+ return false;
+ }
} else {
uint32_t compression_flags = file->WasCompressed() ? ArchiveEntry::kCompress : 0u;
if (!io::CopyFileToArchive(context, file, path, compression_flags, writer)) {
@@ -129,4 +148,26 @@
return true;
}
+std::unique_ptr<xml::XmlResource> LoadedApk::InflateManifest(IAaptContext* context) {
+ IDiagnostics* diag = context->GetDiagnostics();
+
+ io::IFile* manifest_file = GetFileCollection()->FindFile("AndroidManifest.xml");
+ if (manifest_file == nullptr) {
+ diag->Error(DiagMessage(source_) << "no AndroidManifest.xml found");
+ return {};
+ }
+
+ std::unique_ptr<io::IData> manifest_data = manifest_file->OpenAsData();
+ if (manifest_data == nullptr) {
+ diag->Error(DiagMessage(manifest_file->GetSource()) << "could not open AndroidManifest.xml");
+ return {};
+ }
+
+ std::unique_ptr<xml::XmlResource> manifest =
+ xml::Inflate(manifest_data->data(), manifest_data->size(), diag, manifest_file->GetSource());
+ if (manifest == nullptr) {
+ diag->Error(DiagMessage() << "failed to read binary AndroidManifest.xml");
+ }
+ return manifest;
+}
} // namespace aapt
diff --git a/tools/aapt2/LoadedApk.h b/tools/aapt2/LoadedApk.h
index dacd0c2..d779b7e 100644
--- a/tools/aapt2/LoadedApk.h
+++ b/tools/aapt2/LoadedApk.h
@@ -25,17 +25,17 @@
#include "flatten/TableFlattener.h"
#include "io/ZipArchive.h"
#include "unflatten/BinaryResourceParser.h"
+#include "xml/XmlDom.h"
namespace aapt {
/** Info about an APK loaded in memory. */
class LoadedApk {
public:
- LoadedApk(
- const Source& source,
- std::unique_ptr<io::IFileCollection> apk,
- std::unique_ptr<ResourceTable> table)
- : source_(source), apk_(std::move(apk)), table_(std::move(table)) {}
+ LoadedApk(const Source& source, std::unique_ptr<io::IFileCollection> apk,
+ std::unique_ptr<ResourceTable> table)
+ : source_(source), apk_(std::move(apk)), table_(std::move(table)) {
+ }
io::IFileCollection* GetFileCollection() { return apk_.get(); }
@@ -51,13 +51,20 @@
IArchiveWriter* writer);
/**
- * Writes the APK on disk at the given path, while also removing the resource
- * files that are not referenced in the resource table. The provided filter
- * chain is applied to each entry in the APK file.
+ * Writes the APK on disk at the given path, while also removing the resource files that are not
+ * referenced in the resource table. The provided filter chain is applied to each entry in the APK
+ * file.
+ *
+ * If the manifest is also provided, it will be written to the new APK file, otherwise the
+ * original manifest will be written. The manifest is only required if the contents of the new APK
+ * have been modified in a way that require the AndroidManifest.xml to also be modified.
*/
virtual bool WriteToArchive(IAaptContext* context, ResourceTable* split_table,
const TableFlattenerOptions& options, FilterChain* filters,
- IArchiveWriter* writer);
+ IArchiveWriter* writer, xml::XmlResource* manifest = nullptr);
+
+ /** Inflates the AndroidManifest.xml file from the APK. */
+ std::unique_ptr<xml::XmlResource> InflateManifest(IAaptContext* context);
static std::unique_ptr<LoadedApk> LoadApkFromPath(IAaptContext* context,
const android::StringPiece& path);
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index 704faee..56b61d0 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -283,24 +283,8 @@
bool ExtractAppDataFromManifest(OptimizeContext* context, LoadedApk* apk,
OptimizeOptions* out_options) {
- io::IFile* manifest_file = apk->GetFileCollection()->FindFile("AndroidManifest.xml");
- if (manifest_file == nullptr) {
- context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
- << "missing AndroidManifest.xml");
- return false;
- }
-
- std::unique_ptr<io::IData> data = manifest_file->OpenAsData();
- if (data == nullptr) {
- context->GetDiagnostics()->Error(DiagMessage(manifest_file->GetSource())
- << "failed to open file");
- return false;
- }
-
- std::unique_ptr<xml::XmlResource> manifest = xml::Inflate(
- data->data(), data->size(), context->GetDiagnostics(), manifest_file->GetSource());
+ std::unique_ptr<xml::XmlResource> manifest = apk->InflateManifest(context);
if (manifest == nullptr) {
- context->GetDiagnostics()->Error(DiagMessage() << "failed to read binary AndroidManifest.xml");
return false;
}
diff --git a/tools/aapt2/compile/Png.cpp b/tools/aapt2/compile/Png.cpp
index 6d6147d..33122dc 100644
--- a/tools/aapt2/compile/Png.cpp
+++ b/tools/aapt2/compile/Png.cpp
@@ -538,7 +538,7 @@
if (kDebug) {
diag->Note(DiagMessage() << "adding 9-patch info..");
}
- strcpy((char*)unknowns[pIndex].name, "npTc");
+ memcpy((char*)unknowns[pIndex].name, "npTc", 5);
unknowns[pIndex].data = (png_byte*)info->serialize9Patch();
unknowns[pIndex].size = info->info9Patch.serializedSize();
// TODO: remove the check below when everything works
@@ -546,7 +546,7 @@
// automatically generated 9 patch outline data
int chunkSize = sizeof(png_uint_32) * 6;
- strcpy((char*)unknowns[oIndex].name, "npOl");
+ memcpy((char*)unknowns[oIndex].name, "npOl", 5);
unknowns[oIndex].data = (png_byte*)calloc(chunkSize, 1);
png_byte outputData[chunkSize];
memcpy(&outputData, &info->outlineInsetsLeft, 4 * sizeof(png_uint_32));
@@ -558,7 +558,7 @@
// optional optical inset / layout bounds data
if (info->haveLayoutBounds) {
int chunkSize = sizeof(png_uint_32) * 4;
- strcpy((char*)unknowns[bIndex].name, "npLb");
+ memcpy((char*)unknowns[bIndex].name, "npLb", 5);
unknowns[bIndex].data = (png_byte*)calloc(chunkSize, 1);
memcpy(unknowns[bIndex].data, &info->layoutBoundsLeft, chunkSize);
unknowns[bIndex].size = chunkSize;
diff --git a/tools/aapt2/configuration/ConfigurationParser.cpp b/tools/aapt2/configuration/ConfigurationParser.cpp
index 9d6d328..a79a577 100644
--- a/tools/aapt2/configuration/ConfigurationParser.cpp
+++ b/tools/aapt2/configuration/ConfigurationParser.cpp
@@ -27,6 +27,7 @@
#include "ConfigDescription.h"
#include "Diagnostics.h"
+#include "ResourceUtils.h"
#include "io/File.h"
#include "io/FileSystem.h"
#include "io/StringInputStream.h"
@@ -329,15 +330,32 @@
// TODO: Validate all references in the configuration are valid. It should be safe to assume from
// this point on that any references from one section to another will be present.
+ // TODO: Automatically arrange artifacts so that they match Play Store multi-APK requirements.
+ // see: https://developer.android.com/google/play/publishing/multiple-apks.html
+ //
+ // For now, make sure the version codes are unique.
+ std::vector<Artifact>& artifacts = config.artifacts;
+ std::sort(artifacts.begin(), artifacts.end());
+ if (std::adjacent_find(artifacts.begin(), artifacts.end()) != artifacts.end()) {
+ diag_->Error(DiagMessage() << "Configuration has duplicate versions");
+ return {};
+ }
+
return {config};
}
ConfigurationParser::ActionHandler ConfigurationParser::artifact_handler_ =
[](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool {
+ // This will be incremented later so the first version will always be different to the base APK.
+ int current_version = (config->artifacts.empty()) ? 0 : config->artifacts.back().version;
+
Artifact artifact{};
+ Maybe<int> version;
for (const auto& attr : root_element->attributes) {
if (attr.name == "name") {
artifact.name = attr.value;
+ } else if (attr.name == "version") {
+ version = std::stoi(attr.value);
} else if (attr.name == "abi-group") {
artifact.abi_group = {attr.value};
} else if (attr.name == "screen-density-group") {
@@ -355,6 +373,9 @@
<< attr.value);
}
}
+
+ artifact.version = (version) ? version.value() : current_version + 1;
+
config->artifacts.push_back(artifact);
return true;
};
@@ -499,11 +520,11 @@
AndroidSdk entry;
for (const auto& attr : child->attributes) {
if (attr.name == "minSdkVersion") {
- entry.min_sdk_version = {attr.value};
+ entry.min_sdk_version = ResourceUtils::ParseSdkVersion(attr.value);
} else if (attr.name == "targetSdkVersion") {
- entry.target_sdk_version = {attr.value};
+ entry.target_sdk_version = ResourceUtils::ParseSdkVersion(attr.value);
} else if (attr.name == "maxSdkVersion") {
- entry.max_sdk_version = {attr.value};
+ entry.max_sdk_version = ResourceUtils::ParseSdkVersion(attr.value);
} else {
diag->Warn(DiagMessage() << "Unknown attribute: " << attr.name << " = " << attr.value);
}
diff --git a/tools/aapt2/configuration/ConfigurationParser.h b/tools/aapt2/configuration/ConfigurationParser.h
index 9bc9081..c5d3284 100644
--- a/tools/aapt2/configuration/ConfigurationParser.h
+++ b/tools/aapt2/configuration/ConfigurationParser.h
@@ -17,6 +17,7 @@
#ifndef AAPT2_CONFIGURATION_H
#define AAPT2_CONFIGURATION_H
+#include <set>
#include <string>
#include <unordered_map>
#include <vector>
@@ -41,6 +42,12 @@
struct Artifact {
/** Name to use for output of processing foo.apk -> foo.<name>.apk. */
Maybe<std::string> name;
+ /**
+ * Value to add to the base Android manifest versionCode. If it is not present in the
+ * configuration file, it is set to the previous artifact + 1. If the first artifact does not have
+ * a value, artifacts are a 1 based index.
+ */
+ int version;
/** If present, uses the ABI group with this name. */
Maybe<std::string> abi_group;
/** If present, uses the screen density group with this name. */
@@ -60,6 +67,15 @@
/** Convert an artifact name template into a name string based on configuration contents. */
Maybe<std::string> Name(const android::StringPiece& apk_name, IDiagnostics* diag) const;
+
+ bool operator<(const Artifact& rhs) const {
+ // TODO(safarmer): Order by play store multi-APK requirements.
+ return version < rhs.version;
+ }
+
+ bool operator==(const Artifact& rhs) const {
+ return version == rhs.version;
+ }
};
/** Enumeration of currently supported ABIs. */
@@ -103,14 +119,14 @@
};
struct AndroidSdk {
- Maybe<std::string> min_sdk_version;
- Maybe<std::string> target_sdk_version;
- Maybe<std::string> max_sdk_version;
+ Maybe<int> min_sdk_version;
+ Maybe<int> target_sdk_version;
+ Maybe<int> max_sdk_version;
Maybe<AndroidManifest> manifest;
- static AndroidSdk ForMinSdk(std::string min_sdk) {
+ static AndroidSdk ForMinSdk(int min_sdk) {
AndroidSdk sdk;
- sdk.min_sdk_version = {std::move(min_sdk)};
+ sdk.min_sdk_version = min_sdk;
return sdk;
}
diff --git a/tools/aapt2/configuration/ConfigurationParser_test.cpp b/tools/aapt2/configuration/ConfigurationParser_test.cpp
index 7ffb3d5..3654901 100644
--- a/tools/aapt2/configuration/ConfigurationParser_test.cpp
+++ b/tools/aapt2/configuration/ConfigurationParser_test.cpp
@@ -24,6 +24,15 @@
#include "xml/XmlDom.h"
namespace aapt {
+
+namespace configuration {
+void PrintTo(const AndroidSdk& sdk, std::ostream* os) {
+ *os << "SDK: min=" << sdk.min_sdk_version.value_or_default(-1)
+ << ", target=" << sdk.target_sdk_version.value_or_default(-1)
+ << ", max=" << sdk.max_sdk_version.value_or_default(-1);
+}
+} // namespace configuration
+
namespace {
using ::android::ResTable_config;
@@ -76,9 +85,9 @@
</locale-group>
<android-sdk-group label="v19">
<android-sdk
- minSdkVersion="v19"
- targetSdkVersion="v24"
- maxSdkVersion="v25">
+ minSdkVersion="19"
+ targetSdkVersion="24"
+ maxSdkVersion="25">
<manifest>
<!--- manifest additions here XSLT? TODO -->
</manifest>
@@ -156,7 +165,7 @@
EXPECT_EQ(1ul, value.android_sdk_groups.size());
EXPECT_TRUE(value.android_sdk_groups["v19"].min_sdk_version);
- EXPECT_EQ("v19", value.android_sdk_groups["v19"].min_sdk_version.value());
+ EXPECT_EQ(19, value.android_sdk_groups["v19"].min_sdk_version.value());
EXPECT_EQ(1ul, value.gl_texture_groups.size());
EXPECT_EQ(1ul, value.gl_texture_groups["dxt1"].size());
@@ -174,55 +183,117 @@
}
TEST_F(ConfigurationParserTest, ArtifactAction) {
- static constexpr const char* xml = R"xml(
+ PostProcessingConfiguration config;
+ {
+ const auto doc = test::BuildXmlDom(R"xml(
+ <artifact
+ abi-group="arm"
+ screen-density-group="large"
+ locale-group="europe"
+ android-sdk-group="v19"
+ gl-texture-group="dxt1"
+ device-feature-group="low-latency"/>)xml");
+
+ ASSERT_TRUE(artifact_handler_(&config, NodeCast<Element>(doc->root.get()), &diag_));
+
+ EXPECT_EQ(1ul, config.artifacts.size());
+
+ auto& artifact = config.artifacts.back();
+ EXPECT_FALSE(artifact.name); // TODO: make this fail.
+ EXPECT_EQ(1, artifact.version);
+ EXPECT_EQ("arm", artifact.abi_group.value());
+ EXPECT_EQ("large", artifact.screen_density_group.value());
+ EXPECT_EQ("europe", artifact.locale_group.value());
+ EXPECT_EQ("v19", artifact.android_sdk_group.value());
+ EXPECT_EQ("dxt1", artifact.gl_texture_group.value());
+ EXPECT_EQ("low-latency", artifact.device_feature_group.value());
+ }
+
+ {
+ // Perform a second action to ensure we get 2 artifacts.
+ const auto doc = test::BuildXmlDom(R"xml(
+ <artifact
+ abi-group="other"
+ screen-density-group="large"
+ locale-group="europe"
+ android-sdk-group="v19"
+ gl-texture-group="dxt1"
+ device-feature-group="low-latency"/>)xml");
+
+ ASSERT_TRUE(artifact_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_));
+ EXPECT_EQ(2ul, config.artifacts.size());
+ EXPECT_EQ(2, config.artifacts.back().version);
+ }
+
+ {
+ // Perform a third action with a set version code.
+ const auto doc = test::BuildXmlDom(R"xml(
<artifact
- abi-group="arm"
+ version="5"
+ abi-group="other"
screen-density-group="large"
locale-group="europe"
android-sdk-group="v19"
gl-texture-group="dxt1"
- device-feature-group="low-latency"/>)xml";
+ device-feature-group="low-latency"/>)xml");
- auto doc = test::BuildXmlDom(xml);
+ ASSERT_TRUE(artifact_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_));
+ EXPECT_EQ(3ul, config.artifacts.size());
+ EXPECT_EQ(5, config.artifacts.back().version);
+ }
- PostProcessingConfiguration config;
- bool ok = artifact_handler_(&config, NodeCast<Element>(doc->root.get()), &diag_);
- ASSERT_TRUE(ok);
-
- EXPECT_EQ(1ul, config.artifacts.size());
-
- auto& artifact = config.artifacts.front();
- EXPECT_FALSE(artifact.name); // TODO: make this fail.
- EXPECT_EQ("arm", artifact.abi_group.value());
- EXPECT_EQ("large", artifact.screen_density_group.value());
- EXPECT_EQ("europe", artifact.locale_group.value());
- EXPECT_EQ("v19", artifact.android_sdk_group.value());
- EXPECT_EQ("dxt1", artifact.gl_texture_group.value());
- EXPECT_EQ("low-latency", artifact.device_feature_group.value());
-
- // Perform a second action to ensure we get 2 artifacts.
- static constexpr const char* second = R"xml(
+ {
+ // Perform a fourth action to ensure the version code still increments.
+ const auto doc = test::BuildXmlDom(R"xml(
<artifact
abi-group="other"
screen-density-group="large"
locale-group="europe"
android-sdk-group="v19"
gl-texture-group="dxt1"
- device-feature-group="low-latency"/>)xml";
- doc = test::BuildXmlDom(second);
+ device-feature-group="low-latency"/>)xml");
- ok = artifact_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
- ASSERT_TRUE(ok);
- EXPECT_EQ(2ul, config.artifacts.size());
+ ASSERT_TRUE(artifact_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_));
+ EXPECT_EQ(4ul, config.artifacts.size());
+ EXPECT_EQ(6, config.artifacts.back().version);
+ }
+}
+
+TEST_F(ConfigurationParserTest, DuplicateArtifactVersion) {
+ static constexpr const char* configuration = R"xml(<?xml version="1.0" encoding="utf-8" ?>
+ <pst-process xmlns="http://schemas.android.com/tools/aapt">>
+ <artifacts>
+ <artifact-format>
+ ${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release
+ </artifact-format>
+ <artifact
+ name="art1"
+ abi-group="arm"
+ screen-density-group="large"
+ locale-group="europe"
+ android-sdk-group="v19"
+ gl-texture-group="dxt1"
+ device-feature-group="low-latency"/>
+ <artifact
+ name="art2"
+ version = "1"
+ abi-group="other"
+ screen-density-group="alldpi"
+ locale-group="north-america"
+ android-sdk-group="v19"
+ gl-texture-group="dxt1"
+ device-feature-group="low-latency"/>
+ </artifacts>
+ </post-process>)xml";
+ auto result = ConfigurationParser::ForContents(configuration).Parse();
+ ASSERT_FALSE(result);
}
TEST_F(ConfigurationParserTest, ArtifactFormatAction) {
- static constexpr const char* xml = R"xml(
+ const auto doc = test::BuildXmlDom(R"xml(
<artifact-format>
${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release
- </artifact-format>)xml";
-
- auto doc = test::BuildXmlDom(xml);
+ </artifact-format>)xml");
PostProcessingConfiguration config;
bool ok = artifact_format_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
@@ -321,9 +392,9 @@
static constexpr const char* xml = R"xml(
<android-sdk-group label="v19">
<android-sdk
- minSdkVersion="v19"
- targetSdkVersion="v24"
- maxSdkVersion="v25">
+ minSdkVersion="19"
+ targetSdkVersion="24"
+ maxSdkVersion="25">
<manifest>
<!--- manifest additions here XSLT? TODO -->
</manifest>
@@ -342,14 +413,43 @@
auto& out = config.android_sdk_groups["v19"];
AndroidSdk sdk;
- sdk.min_sdk_version = std::string("v19");
- sdk.target_sdk_version = std::string("v24");
- sdk.max_sdk_version = std::string("v25");
+ sdk.min_sdk_version = 19;
+ sdk.target_sdk_version = 24;
+ sdk.max_sdk_version = 25;
sdk.manifest = AndroidManifest();
ASSERT_EQ(sdk, out);
}
+TEST_F(ConfigurationParserTest, AndroidSdkGroupAction_NonNumeric) {
+ static constexpr const char* xml = R"xml(
+ <android-sdk-group label="O">
+ <android-sdk
+ minSdkVersion="M"
+ targetSdkVersion="O"
+ maxSdkVersion="O">
+ </android-sdk>
+ </android-sdk-group>)xml";
+
+ auto doc = test::BuildXmlDom(xml);
+
+ PostProcessingConfiguration config;
+ bool ok = android_sdk_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ ASSERT_TRUE(ok);
+
+ ASSERT_EQ(1ul, config.android_sdk_groups.size());
+ ASSERT_EQ(1u, config.android_sdk_groups.count("O"));
+
+ auto& out = config.android_sdk_groups["O"];
+
+ AndroidSdk sdk;
+ sdk.min_sdk_version = {}; // Only the latest development version is supported.
+ sdk.target_sdk_version = 26;
+ sdk.max_sdk_version = 26;
+
+ ASSERT_EQ(sdk, out);
+}
+
TEST_F(ConfigurationParserTest, GlTextureGroupAction) {
static constexpr const char* xml = R"xml(
<gl-texture-group label="dxt1">
diff --git a/tools/aapt2/configuration/aapt2.xsd b/tools/aapt2/configuration/aapt2.xsd
index 47bf99e..134153a 100644
--- a/tools/aapt2/configuration/aapt2.xsd
+++ b/tools/aapt2/configuration/aapt2.xsd
@@ -39,6 +39,7 @@
<!-- Groups output artifacts together by dimension labels. -->
<xsd:complexType name="artifact">
<xsd:attribute name="name" type="xsd:string"/>
+ <xsd:attribute name="version" type="xsd:integer"/>
<xsd:attribute name="abi-group" type="xsd:string"/>
<xsd:attribute name="android-sdk-group" type="xsd:string"/>
<xsd:attribute name="device-feature-group" type="xsd:string"/>
diff --git a/tools/aapt2/optimize/MultiApkGenerator.cpp b/tools/aapt2/optimize/MultiApkGenerator.cpp
index 5ff8908..8e4b82c 100644
--- a/tools/aapt2/optimize/MultiApkGenerator.cpp
+++ b/tools/aapt2/optimize/MultiApkGenerator.cpp
@@ -22,22 +22,50 @@
#include "androidfw/StringPiece.h"
#include "LoadedApk.h"
+#include "ResourceUtils.h"
+#include "ValueVisitor.h"
#include "configuration/ConfigurationParser.h"
#include "filter/AbiFilter.h"
#include "filter/Filter.h"
#include "flatten/Archive.h"
+#include "flatten/XmlFlattener.h"
#include "optimize/VersionCollapser.h"
#include "process/IResourceTableConsumer.h"
#include "split/TableSplitter.h"
#include "util/Files.h"
+#include "xml/XmlDom.h"
+#include "xml/XmlUtil.h"
namespace aapt {
using ::aapt::configuration::AndroidSdk;
using ::aapt::configuration::Artifact;
using ::aapt::configuration::PostProcessingConfiguration;
+using ::aapt::xml::kSchemaAndroid;
+using ::aapt::xml::XmlResource;
using ::android::StringPiece;
+namespace {
+
+Maybe<AndroidSdk> GetAndroidSdk(const Artifact& artifact, const PostProcessingConfiguration& config,
+ IDiagnostics* diag) {
+ if (!artifact.android_sdk_group) {
+ return {};
+ }
+
+ const std::string& group_name = artifact.android_sdk_group.value();
+ auto group = config.android_sdk_groups.find(group_name);
+ // TODO: Remove validation when configuration parser ensures referential integrity.
+ if (group == config.android_sdk_groups.end()) {
+ diag->Error(DiagMessage() << "could not find referenced group '" << group_name << "'");
+ return {};
+ }
+
+ return group->second;
+}
+
+} // namespace
+
/**
* Context wrapper that allows the min Android SDK value to be overridden.
*/
@@ -127,6 +155,13 @@
return false;
}
+ std::unique_ptr<XmlResource> manifest;
+ if (!UpdateManifest(artifact, config, &manifest, diag)) {
+ diag->Error(DiagMessage() << "could not update AndroidManifest.xml for "
+ << artifact_name.value());
+ return false;
+ }
+
std::string out = options.out_dir;
if (!file::mkdirs(out)) {
context_->GetDiagnostics()->Warn(DiagMessage() << "could not create out dir: " << out);
@@ -145,7 +180,7 @@
}
if (!apk_->WriteToArchive(context_, table.get(), options.table_flattener_options, &filters,
- writer.get())) {
+ writer.get(), manifest.get())) {
return false;
}
}
@@ -208,37 +243,15 @@
splits.config_filter = &axis_filter;
}
- if (artifact.android_sdk_group) {
- const std::string& group_name = artifact.android_sdk_group.value();
- auto group = config.android_sdk_groups.find(group_name);
- // TODO: Remove validation when configuration parser ensures referential integrity.
- if (group == config.android_sdk_groups.end()) {
- context_->GetDiagnostics()->Error(DiagMessage() << "could not find referenced group '"
- << group_name << "'");
- return {};
- }
-
- const AndroidSdk& sdk = group->second;
- if (!sdk.min_sdk_version) {
- context_->GetDiagnostics()->Error(DiagMessage()
- << "skipping SDK version. No min SDK: " << group_name);
- return {};
- }
-
- ConfigDescription c;
- const std::string& version = sdk.min_sdk_version.value();
- if (!ConfigDescription::Parse(version, &c)) {
- context_->GetDiagnostics()->Error(DiagMessage() << "could not parse min SDK: " << version);
- return {};
- }
-
- wrappedContext.SetMinSdkVersion(c.sdkVersion);
+ Maybe<AndroidSdk> sdk = GetAndroidSdk(artifact, config, context_->GetDiagnostics());
+ if (sdk && sdk.value().min_sdk_version) {
+ wrappedContext.SetMinSdkVersion(sdk.value().min_sdk_version.value());
}
std::unique_ptr<ResourceTable> table = old_table.Clone();
VersionCollapser collapser;
- if (!collapser.Consume(context_, table.get())) {
+ if (!collapser.Consume(&wrappedContext, table.get())) {
context_->GetDiagnostics()->Error(DiagMessage() << "Failed to strip versioned resources");
return {};
}
@@ -248,4 +261,95 @@
return table;
}
+bool MultiApkGenerator::UpdateManifest(const Artifact& artifact,
+ const PostProcessingConfiguration& config,
+ std::unique_ptr<XmlResource>* updated_manifest,
+ IDiagnostics* diag) {
+ *updated_manifest = apk_->InflateManifest(context_);
+ XmlResource* manifest = updated_manifest->get();
+ if (manifest == nullptr) {
+ return false;
+ }
+
+ // Make sure the first element is <manifest> with package attribute.
+ xml::Element* manifest_el = manifest->root.get();
+ if (manifest_el == nullptr) {
+ return false;
+ }
+
+ if (!manifest_el->namespace_uri.empty() || manifest_el->name != "manifest") {
+ diag->Error(DiagMessage(manifest->file.source) << "root tag must be <manifest>");
+ return false;
+ }
+
+ // Update the versionCode attribute.
+ xml::Attribute* versionCode = manifest_el->FindAttribute(kSchemaAndroid, "versionCode");
+ if (versionCode == nullptr) {
+ diag->Error(DiagMessage(manifest->file.source) << "manifest must have a versionCode attribute");
+ return false;
+ }
+
+ auto* compiled_version = ValueCast<BinaryPrimitive>(versionCode->compiled_value.get());
+ if (compiled_version == nullptr) {
+ diag->Error(DiagMessage(manifest->file.source) << "versionCode is invalid");
+ return false;
+ }
+
+ int new_version = compiled_version->value.data + artifact.version;
+ versionCode->compiled_value = ResourceUtils::TryParseInt(std::to_string(new_version));
+
+ // Check to see if the minSdkVersion needs to be updated.
+ Maybe<AndroidSdk> maybe_sdk = GetAndroidSdk(artifact, config, diag);
+ if (maybe_sdk) {
+ // TODO(safarmer): Handle the rest of the Android SDK.
+ const AndroidSdk& android_sdk = maybe_sdk.value();
+
+ if (xml::Element* uses_sdk_el = manifest_el->FindChild({}, "uses-sdk")) {
+ if (xml::Attribute* min_sdk_attr =
+ uses_sdk_el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion")) {
+ // Populate with a pre-compiles attribute to we don't need to relink etc.
+ const std::string& min_sdk_str = std::to_string(android_sdk.min_sdk_version.value());
+ min_sdk_attr->compiled_value = ResourceUtils::TryParseInt(min_sdk_str);
+ } else {
+ // There was no minSdkVersion. This is strange since at this point we should have been
+ // through the manifest fixer which sets the default minSdkVersion.
+ diag->Error(DiagMessage(manifest->file.source) << "missing minSdkVersion from <uses-sdk>");
+ return false;
+ }
+ } else {
+ // No uses-sdk present. This is strange since at this point we should have been
+ // through the manifest fixer which should have added it.
+ diag->Error(DiagMessage(manifest->file.source) << "missing <uses-sdk> from <manifest>");
+ return false;
+ }
+ }
+
+ if (artifact.screen_density_group) {
+ auto densities = config.screen_density_groups.find(artifact.screen_density_group.value());
+ CHECK(densities != config.screen_density_groups.end()) << "Missing density group";
+
+ xml::Element* screens_el = manifest_el->FindChild({}, "compatible-screens");
+ if (!screens_el) {
+ // create a new element.
+ std::unique_ptr<xml::Element> new_screens_el = util::make_unique<xml::Element>();
+ new_screens_el->name = "compatible-screens";
+ screens_el = new_screens_el.get();
+ manifest_el->InsertChild(0, std::move(new_screens_el));
+ } else {
+ // clear out the old element.
+ screens_el->GetChildElements().clear();
+ }
+
+ for (const auto& density : densities->second) {
+ std::unique_ptr<xml::Element> screen_el = util::make_unique<xml::Element>();
+ screen_el->name = "screen";
+ const char* density_str = density.toString().string();
+ screen_el->attributes.push_back(xml::Attribute{kSchemaAndroid, "screenDensity", density_str});
+ screens_el->AppendChild(std::move(screen_el));
+ }
+ }
+
+ return true;
+}
+
} // namespace aapt
diff --git a/tools/aapt2/optimize/MultiApkGenerator.h b/tools/aapt2/optimize/MultiApkGenerator.h
index b064400..e6546ee 100644
--- a/tools/aapt2/optimize/MultiApkGenerator.h
+++ b/tools/aapt2/optimize/MultiApkGenerator.h
@@ -54,6 +54,10 @@
return context_->GetDiagnostics();
}
+ bool UpdateManifest(const configuration::Artifact& artifact,
+ const configuration::PostProcessingConfiguration& config,
+ std::unique_ptr<xml::XmlResource>* updated_manifest, IDiagnostics* diag);
+
LoadedApk* apk_;
IAaptContext* context_;
};
diff --git a/tools/aapt2/optimize/MultiApkGenerator_test.cpp b/tools/aapt2/optimize/MultiApkGenerator_test.cpp
index 23f573c..e8e6adc 100644
--- a/tools/aapt2/optimize/MultiApkGenerator_test.cpp
+++ b/tools/aapt2/optimize/MultiApkGenerator_test.cpp
@@ -50,14 +50,6 @@
using ::testing::Test;
using ::testing::_;
-/** Subclass the LoadedApk class so that we can mock the WriteToArchive method. */
-class MockApk : public LoadedApk {
- public:
- MockApk(std::unique_ptr<ResourceTable> table) : LoadedApk({"test.apk"}, {}, std::move(table)){};
- MOCK_METHOD5(WriteToArchive, bool(IAaptContext*, ResourceTable*, const TableFlattenerOptions&,
- FilterChain*, IArchiveWriter*));
-};
-
/**
* Subclass the MultiApkGenerator class so that we can access the protected FilterTable method to
* directly test table filter.
@@ -111,54 +103,10 @@
ConfigDescription v21_ = ParseConfigOrDie("v21");
};
-TEST_F(MultiApkGeneratorTest, FromBaseApk) {
- std::unique_ptr<ResourceTable> table = BuildTable();
-
- MockApk apk{std::move(table)};
-
- EXPECT_CALL(apk, WriteToArchive(_, _, _, _, _)).Times(0);
-
- test::Context ctx;
- PostProcessingConfiguration empty_config;
- TableFlattenerOptions table_flattener_options;
-
- MultiApkGenerator generator{&apk, &ctx};
- EXPECT_TRUE(generator.FromBaseApk({"out", empty_config, table_flattener_options}));
-
- Artifact x64 = test::ArtifactBuilder()
- .SetName("${basename}.x64.apk")
- .SetAbiGroup("x64")
- .SetLocaleGroup("en")
- .SetDensityGroup("xhdpi")
- .Build();
-
- Artifact intel = test::ArtifactBuilder()
- .SetName("${basename}.intel.apk")
- .SetAbiGroup("intel")
- .SetLocaleGroup("europe")
- .SetDensityGroup("large")
- .Build();
-
- auto config = test::PostProcessingConfigurationBuilder()
- .SetLocaleGroup("en", {"en"})
- .SetLocaleGroup("europe", {"en", "fr", "de", "es"})
- .SetAbiGroup("x64", {Abi::kX86_64})
- .SetAbiGroup("intel", {Abi::kX86_64, Abi::kX86})
- .SetDensityGroup("xhdpi", {"xhdpi"})
- .SetDensityGroup("large", {"xhdpi", "xxhdpi", "xxxhdpi"})
- .AddArtifact(x64)
- .AddArtifact(intel)
- .Build();
-
- // Called once for each artifact.
- EXPECT_CALL(apk, WriteToArchive(Eq(&ctx), _, _, _, _)).Times(2).WillRepeatedly(Return(true));
- EXPECT_TRUE(generator.FromBaseApk({"out", config, table_flattener_options}));
-}
-
TEST_F(MultiApkGeneratorTest, VersionFilterNewerVersion) {
std::unique_ptr<ResourceTable> table = BuildTable();
- MockApk apk{std::move(table)};
+ LoadedApk apk = {{"test.apk"}, {}, std::move(table)};
std::unique_ptr<IAaptContext> ctx = test::ContextBuilder().SetMinSdkVersion(19).Build();
PostProcessingConfiguration empty_config;
TableFlattenerOptions table_flattener_options;
@@ -174,7 +122,7 @@
.SetLocaleGroup("en", {"en"})
.SetAbiGroup("x64", {Abi::kX86_64})
.SetDensityGroup("xhdpi", {"xhdpi"})
- .SetAndroidSdk("v23", AndroidSdk::ForMinSdk("v23"))
+ .SetAndroidSdk("v23", AndroidSdk::ForMinSdk(23))
.AddArtifact(x64)
.Build();
@@ -199,7 +147,7 @@
TEST_F(MultiApkGeneratorTest, VersionFilterOlderVersion) {
std::unique_ptr<ResourceTable> table = BuildTable();
- MockApk apk{std::move(table)};
+ LoadedApk apk = {{"test.apk"}, {}, std::move(table)};
std::unique_ptr<IAaptContext> ctx = test::ContextBuilder().SetMinSdkVersion(1).Build();
PostProcessingConfiguration empty_config;
TableFlattenerOptions table_flattener_options;
@@ -215,7 +163,7 @@
.SetLocaleGroup("en", {"en"})
.SetAbiGroup("x64", {Abi::kX86_64})
.SetDensityGroup("xhdpi", {"xhdpi"})
- .SetAndroidSdk("v4", AndroidSdk::ForMinSdk("v4"))
+ .SetAndroidSdk("v4", AndroidSdk::ForMinSdk(4))
.AddArtifact(x64)
.Build();
@@ -238,7 +186,7 @@
TEST_F(MultiApkGeneratorTest, VersionFilterNoVersion) {
std::unique_ptr<ResourceTable> table = BuildTable();
- MockApk apk{std::move(table)};
+ LoadedApk apk = {{"test.apk"}, {}, std::move(table)};
std::unique_ptr<IAaptContext> ctx = test::ContextBuilder().SetMinSdkVersion(1).Build();
PostProcessingConfiguration empty_config;
TableFlattenerOptions table_flattener_options;
diff --git a/wifi/java/android/net/wifi/rtt/IRttCallback.aidl b/wifi/java/android/net/wifi/rtt/IRttCallback.aidl
new file mode 100644
index 0000000..fb1636f
--- /dev/null
+++ b/wifi/java/android/net/wifi/rtt/IRttCallback.aidl
@@ -0,0 +1,32 @@
+/*
+ * 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 android.net.wifi.rtt;
+
+import android.net.wifi.rtt.RangingResult;
+
+/**
+ * Interface for RTT result callback.
+ *
+ * @hide
+ */
+oneway interface IRttCallback
+{
+ /**
+ * Service to manager callback providing RTT status and results.
+ */
+ void onRangingResults(int status, in List<RangingResult> results);
+}
diff --git a/wifi/java/android/net/wifi/rtt/IWifiRttManager.aidl b/wifi/java/android/net/wifi/rtt/IWifiRttManager.aidl
new file mode 100644
index 0000000..ad92e04
--- /dev/null
+++ b/wifi/java/android/net/wifi/rtt/IWifiRttManager.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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 android.net.wifi.rtt;
+
+import android.net.wifi.rtt.IRttCallback;
+import android.net.wifi.rtt.RangingRequest;
+
+/**
+ * @hide
+ */
+interface IWifiRttManager
+{
+ void startRanging(in IBinder binder, in String callingPackage, in RangingRequest request,
+ in IRttCallback callback);
+}
diff --git a/wifi/java/android/net/wifi/rtt/RangingRequest.aidl b/wifi/java/android/net/wifi/rtt/RangingRequest.aidl
new file mode 100644
index 0000000..8053c94
--- /dev/null
+++ b/wifi/java/android/net/wifi/rtt/RangingRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.net.wifi.rtt;
+
+parcelable RangingRequest;
diff --git a/wifi/java/android/net/wifi/rtt/RangingRequest.java b/wifi/java/android/net/wifi/rtt/RangingRequest.java
new file mode 100644
index 0000000..997b680
--- /dev/null
+++ b/wifi/java/android/net/wifi/rtt/RangingRequest.java
@@ -0,0 +1,237 @@
+/*
+ * 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 android.net.wifi.rtt;
+
+import android.net.wifi.ScanResult;
+import android.os.Handler;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringJoiner;
+
+/**
+ * Defines the ranging request to other devices. The ranging request is built using
+ * {@link RangingRequest.Builder}.
+ * A ranging request is executed using
+ * {@link WifiRttManager#startRanging(RangingRequest, RangingResultCallback, Handler)}.
+ * <p>
+ * The ranging request is a batch request - specifying a set of devices (specified using
+ * {@link RangingRequest.Builder#addAp(ScanResult)} and
+ * {@link RangingRequest.Builder#addAps(List)}).
+ *
+ * @hide RTT_API
+ */
+public final class RangingRequest implements Parcelable {
+ private static final int MAX_PEERS = 10;
+
+ /**
+ * Returns the maximum number of peers to range which can be specified in a single {@code
+ * RangingRequest}. The limit applies no matter how the peers are added to the request, e.g.
+ * through {@link RangingRequest.Builder#addAp(ScanResult)} or
+ * {@link RangingRequest.Builder#addAps(List)}.
+ *
+ * @return Maximum number of peers.
+ */
+ public static int getMaxPeers() {
+ return MAX_PEERS;
+ }
+
+ /** @hide */
+ public final List<RttPeer> mRttPeers;
+
+ /** @hide */
+ private RangingRequest(List<RttPeer> rttPeers) {
+ mRttPeers = rttPeers;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeList(mRttPeers);
+ }
+
+ public static final Creator<RangingRequest> CREATOR = new Creator<RangingRequest>() {
+ @Override
+ public RangingRequest[] newArray(int size) {
+ return new RangingRequest[size];
+ }
+
+ @Override
+ public RangingRequest createFromParcel(Parcel in) {
+ return new RangingRequest(in.readArrayList(null));
+ }
+ };
+
+ /** @hide */
+ @Override
+ public String toString() {
+ StringJoiner sj = new StringJoiner(", ", "RangingRequest: mRttPeers=[", ",");
+ for (RttPeer rp : mRttPeers) {
+ sj.add(rp.toString());
+ }
+ return sj.toString();
+ }
+
+ /** @hide */
+ public void enforceValidity() {
+ if (mRttPeers.size() > MAX_PEERS) {
+ throw new IllegalArgumentException(
+ "Ranging to too many peers requested. Use getMaxPeers() API to get limit.");
+ }
+ }
+
+ /**
+ * Builder class used to construct {@link RangingRequest} objects.
+ */
+ public static final class Builder {
+ private List<RttPeer> mRttPeers = new ArrayList<>();
+
+ /**
+ * Add the device specified by the {@link ScanResult} to the list of devices with
+ * which to measure range. The total number of results added to a request cannot exceed the
+ * limit specified by {@link #getMaxPeers()}.
+ *
+ * @param apInfo Information of an Access Point (AP) obtained in a Scan Result.
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder addAp(ScanResult apInfo) {
+ if (apInfo == null) {
+ throw new IllegalArgumentException("Null ScanResult!");
+ }
+ mRttPeers.add(new RttPeerAp(apInfo));
+ return this;
+ }
+
+ /**
+ * Add the devices specified by the {@link ScanResult}s to the list of devices with
+ * which to measure range. The total number of results added to a request cannot exceed the
+ * limit specified by {@link #getMaxPeers()}.
+ *
+ * @param apInfos Information of an Access Points (APs) obtained in a Scan Result.
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder addAps(List<ScanResult> apInfos) {
+ if (apInfos == null) {
+ throw new IllegalArgumentException("Null list of ScanResults!");
+ }
+ for (ScanResult scanResult : apInfos) {
+ addAp(scanResult);
+ }
+ return this;
+ }
+
+ /**
+ * Build {@link RangingRequest} given the current configurations made on the
+ * builder.
+ */
+ public RangingRequest build() {
+ return new RangingRequest(mRttPeers);
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof RangingRequest)) {
+ return false;
+ }
+
+ RangingRequest lhs = (RangingRequest) o;
+
+ return mRttPeers.size() == lhs.mRttPeers.size() && mRttPeers.containsAll(lhs.mRttPeers);
+ }
+
+ @Override
+ public int hashCode() {
+ return mRttPeers.hashCode();
+ }
+
+ /** @hide */
+ public interface RttPeer {
+ // empty (marker interface)
+ }
+
+ /** @hide */
+ public static class RttPeerAp implements RttPeer, Parcelable {
+ public final ScanResult scanResult;
+
+ public RttPeerAp(ScanResult scanResult) {
+ this.scanResult = scanResult;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ scanResult.writeToParcel(dest, flags);
+ }
+
+ public static final Creator<RttPeerAp> CREATOR = new Creator<RttPeerAp>() {
+ @Override
+ public RttPeerAp[] newArray(int size) {
+ return new RttPeerAp[size];
+ }
+
+ @Override
+ public RttPeerAp createFromParcel(Parcel in) {
+ return new RttPeerAp(ScanResult.CREATOR.createFromParcel(in));
+ }
+ };
+
+ @Override
+ public String toString() {
+ return new StringBuilder("RttPeerAp: scanResult=").append(
+ scanResult.toString()).toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof RttPeerAp)) {
+ return false;
+ }
+
+ RttPeerAp lhs = (RttPeerAp) o;
+
+ // Note: the only thing which matters for the request identity is the BSSID of the AP
+ return TextUtils.equals(scanResult.BSSID, lhs.scanResult.BSSID);
+ }
+
+ @Override
+ public int hashCode() {
+ return scanResult.hashCode();
+ }
+ }
+}
\ No newline at end of file
diff --git a/wifi/java/android/net/wifi/rtt/RangingResult.aidl b/wifi/java/android/net/wifi/rtt/RangingResult.aidl
new file mode 100644
index 0000000..ae295a6
--- /dev/null
+++ b/wifi/java/android/net/wifi/rtt/RangingResult.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.net.wifi.rtt;
+
+parcelable RangingResult;
diff --git a/wifi/java/android/net/wifi/rtt/RangingResult.java b/wifi/java/android/net/wifi/rtt/RangingResult.java
new file mode 100644
index 0000000..918803e
--- /dev/null
+++ b/wifi/java/android/net/wifi/rtt/RangingResult.java
@@ -0,0 +1,194 @@
+/*
+ * 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 android.net.wifi.rtt;
+
+import android.os.Handler;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import libcore.util.HexEncoding;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Ranging result for a request started by
+ * {@link WifiRttManager#startRanging(RangingRequest, RangingResultCallback, Handler)}. Results are
+ * returned in {@link RangingResultCallback#onRangingResults(List)}.
+ * <p>
+ * A ranging result is the distance measurement result for a single device specified in the
+ * {@link RangingRequest}.
+ *
+ * @hide RTT_API
+ */
+public final class RangingResult implements Parcelable {
+ private static final String TAG = "RangingResult";
+
+ private final int mStatus;
+ private final byte[] mMac;
+ private final int mDistanceCm;
+ private final int mDistanceStdDevCm;
+ private final int mRssi;
+ private final long mTimestamp;
+
+ /** @hide */
+ public RangingResult(int status, byte[] mac, int distanceCm, int distanceStdDevCm, int rssi,
+ long timestamp) {
+ mStatus = status;
+ mMac = mac;
+ mDistanceCm = distanceCm;
+ mDistanceStdDevCm = distanceStdDevCm;
+ mRssi = rssi;
+ mTimestamp = timestamp;
+ }
+
+ /**
+ * @return The status of ranging measurement: {@link RangingResultCallback#STATUS_SUCCESS} in
+ * case of success, and {@link RangingResultCallback#STATUS_FAIL} in case of failure.
+ */
+ public int getStatus() {
+ return mStatus;
+ }
+
+ /**
+ * @return The MAC address of the device whose range measurement was requested. Will correspond
+ * to the MAC address of the device in the {@link RangingRequest}.
+ * <p>
+ * Always valid (i.e. when {@link #getStatus()} is either SUCCESS or FAIL.
+ */
+ public byte[] getMacAddress() {
+ return mMac;
+ }
+
+ /**
+ * @return The distance (in cm) to the device specified by {@link #getMacAddress()}.
+ * <p>
+ * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}.
+ */
+ public int getDistanceCm() {
+ if (mStatus != RangingResultCallback.STATUS_SUCCESS) {
+ Log.e(TAG, "getDistanceCm(): invalid value retrieved");
+ }
+ return mDistanceCm;
+ }
+
+ /**
+ * @return The standard deviation of the measured distance (in cm) to the device specified by
+ * {@link #getMacAddress()}. The standard deviation is calculated over the measurements
+ * executed in a single RTT burst.
+ * <p>
+ * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}.
+ */
+ public int getDistanceStdDevCm() {
+ if (mStatus != RangingResultCallback.STATUS_SUCCESS) {
+ Log.e(TAG, "getDistanceStdDevCm(): invalid value retrieved");
+ }
+ return mDistanceStdDevCm;
+ }
+
+ /**
+ * @return The average RSSI (in units of -0.5dB) observed during the RTT measurement.
+ * <p>
+ * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}.
+ */
+ public int getRssi() {
+ if (mStatus != RangingResultCallback.STATUS_SUCCESS) {
+ // TODO: should this be an exception?
+ Log.e(TAG, "getRssi(): invalid value retrieved");
+ }
+ return mRssi;
+ }
+
+ /**
+ * @return The timestamp (in us) at which the ranging operation was performed
+ * <p>
+ * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}.
+ */
+ public long getRangingTimestamp() {
+ return mTimestamp;
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mStatus);
+ dest.writeByteArray(mMac);
+ dest.writeInt(mDistanceCm);
+ dest.writeInt(mDistanceStdDevCm);
+ dest.writeInt(mRssi);
+ dest.writeLong(mTimestamp);
+ }
+
+ /** @hide */
+ public static final Creator<RangingResult> CREATOR = new Creator<RangingResult>() {
+ @Override
+ public RangingResult[] newArray(int size) {
+ return new RangingResult[size];
+ }
+
+ @Override
+ public RangingResult createFromParcel(Parcel in) {
+ int status = in.readInt();
+ byte[] mac = in.createByteArray();
+ int distanceCm = in.readInt();
+ int distanceStdDevCm = in.readInt();
+ int rssi = in.readInt();
+ long timestamp = in.readLong();
+ return new RangingResult(status, mac, distanceCm, distanceStdDevCm, rssi, timestamp);
+ }
+ };
+
+ /** @hide */
+ @Override
+ public String toString() {
+ return new StringBuilder("RangingResult: [status=").append(mStatus).append(", mac=").append(
+ mMac == null ? "<null>" : HexEncoding.encodeToString(mMac)).append(
+ ", distanceCm=").append(mDistanceCm).append(", distanceStdDevCm=").append(
+ mDistanceStdDevCm).append(", rssi=").append(mRssi).append(", timestamp=").append(
+ mTimestamp).append("]").toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof RangingResult)) {
+ return false;
+ }
+
+ RangingResult lhs = (RangingResult) o;
+
+ return mStatus == lhs.mStatus && Arrays.equals(mMac, lhs.mMac)
+ && mDistanceCm == lhs.mDistanceCm && mDistanceStdDevCm == lhs.mDistanceStdDevCm
+ && mRssi == lhs.mRssi && mTimestamp == lhs.mTimestamp;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mStatus, mMac, mDistanceCm, mDistanceStdDevCm, mRssi, mTimestamp);
+ }
+}
\ No newline at end of file
diff --git a/wifi/java/android/net/wifi/rtt/RangingResultCallback.java b/wifi/java/android/net/wifi/rtt/RangingResultCallback.java
new file mode 100644
index 0000000..d7270ad
--- /dev/null
+++ b/wifi/java/android/net/wifi/rtt/RangingResultCallback.java
@@ -0,0 +1,56 @@
+/*
+ * 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 android.net.wifi.rtt;
+
+import android.os.Handler;
+
+import java.util.List;
+
+/**
+ * Base class for ranging result callbacks. Should be extended by applications and set when calling
+ * {@link WifiRttManager#startRanging(RangingRequest, RangingResultCallback, Handler)}. A single
+ * result from a range request will be called in this object.
+ *
+ * @hide RTT_API
+ */
+public abstract class RangingResultCallback {
+ /**
+ * Individual range request status, {@link RangingResult#getStatus()}. Indicates ranging
+ * operation was successful and distance value is valid.
+ */
+ public static final int STATUS_SUCCESS = 0;
+
+ /**
+ * Individual range request status, {@link RangingResult#getStatus()}. Indicates ranging
+ * operation failed and the distance value is invalid.
+ */
+ public static final int STATUS_FAIL = 1;
+
+ /**
+ * Called when a ranging operation failed in whole - i.e. no ranging operation to any of the
+ * devices specified in the request was attempted.
+ */
+ public abstract void onRangingFailure();
+
+ /**
+ * Called when a ranging operation was executed. The list of results corresponds to devices
+ * specified in the ranging request.
+ *
+ * @param results List of range measurements, one per requested device.
+ */
+ public abstract void onRangingResults(List<RangingResult> results);
+}
diff --git a/wifi/java/android/net/wifi/rtt/WifiRttManager.java b/wifi/java/android/net/wifi/rtt/WifiRttManager.java
new file mode 100644
index 0000000..a085de1
--- /dev/null
+++ b/wifi/java/android/net/wifi/rtt/WifiRttManager.java
@@ -0,0 +1,100 @@
+package android.net.wifi.rtt;
+
+import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.Manifest.permission.ACCESS_WIFI_STATE;
+import static android.Manifest.permission.CHANGE_WIFI_STATE;
+
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * This class provides the primary API for measuring distance (range) to other devices using the
+ * IEEE 802.11mc Wi-Fi Round Trip Time (RTT) technology.
+ * <p>
+ * The devices which can be ranged include:
+ * <li>Access Points (APs)
+ * <p>
+ * Ranging requests are triggered using
+ * {@link #startRanging(RangingRequest, RangingResultCallback, Handler)}. Results (in case of
+ * successful operation) are returned in the {@link RangingResultCallback#onRangingResults(List)}
+ * callback.
+ *
+ * @hide RTT_API
+ */
+@SystemService(Context.WIFI_RTT2_SERVICE)
+public class WifiRttManager {
+ private static final String TAG = "WifiRttManager";
+ private static final boolean VDBG = true;
+
+ private final Context mContext;
+ private final IWifiRttManager mService;
+
+ /** @hide */
+ public WifiRttManager(Context context, IWifiRttManager service) {
+ mContext = context;
+ mService = service;
+ }
+
+ /**
+ * Initiate a request to range to a set of devices specified in the {@link RangingRequest}.
+ * Results will be returned in the {@link RangingResultCallback} set of callbacks.
+ *
+ * @param request A request specifying a set of devices whose distance measurements are
+ * requested.
+ * @param callback A callback for the result of the ranging request.
+ * @param handler The Handler on whose thread to execute the callbacks of the {@code
+ * callback} object. If a null is provided then the application's main thread
+ * will be used.
+ */
+ @RequiresPermission(allOf = {ACCESS_COARSE_LOCATION, CHANGE_WIFI_STATE, ACCESS_WIFI_STATE})
+ public void startRanging(RangingRequest request, RangingResultCallback callback,
+ @Nullable Handler handler) {
+ if (VDBG) {
+ Log.v(TAG, "startRanging: request=" + request + ", callback=" + callback + ", handler="
+ + handler);
+ }
+
+ Looper looper = (handler == null) ? Looper.getMainLooper() : handler.getLooper();
+ Binder binder = new Binder();
+ try {
+ mService.startRanging(binder, mContext.getOpPackageName(), request,
+ new RttCallbackProxy(looper, callback));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private static class RttCallbackProxy extends IRttCallback.Stub {
+ private final Handler mHandler;
+ private final RangingResultCallback mCallback;
+
+ RttCallbackProxy(Looper looper, RangingResultCallback callback) {
+ mHandler = new Handler(looper);
+ mCallback = callback;
+ }
+
+ @Override
+ public void onRangingResults(int status, List<RangingResult> results) throws RemoteException {
+ if (VDBG) {
+ Log.v(TAG, "RttCallbackProxy: onRanginResults: status=" + status + ", results="
+ + results);
+ }
+ mHandler.post(() -> {
+ if (status == RangingResultCallback.STATUS_SUCCESS) {
+ mCallback.onRangingResults(results);
+ } else {
+ mCallback.onRangingFailure();
+ }
+ });
+ }
+ }
+}
diff --git a/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java b/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java
new file mode 100644
index 0000000..23c75ce
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java
@@ -0,0 +1,226 @@
+/*
+ * 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 android.net.wifi.rtt;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.wifi.ScanResult;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.test.TestLooper;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import libcore.util.HexEncoding;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit test harness for WifiRttManager class.
+ */
+@SmallTest
+public class WifiRttManagerTest {
+ private WifiRttManager mDut;
+ private TestLooper mMockLooper;
+ private Handler mMockLooperHandler;
+
+ private final String packageName = "some.package.name.for.rtt.app";
+
+ @Mock
+ public Context mockContext;
+
+ @Mock
+ public IWifiRttManager mockRttService;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mDut = new WifiRttManager(mockContext, mockRttService);
+ mMockLooper = new TestLooper();
+ mMockLooperHandler = new Handler(mMockLooper.getLooper());
+
+ when(mockContext.getOpPackageName()).thenReturn(packageName);
+ }
+
+ /**
+ * Validate ranging call flow with succesful results.
+ */
+ @Test
+ public void testRangeSuccess() throws Exception {
+ RangingRequest request = new RangingRequest.Builder().build();
+ List<RangingResult> results = new ArrayList<>();
+ results.add(new RangingResult(RangingResultCallback.STATUS_SUCCESS, null, 15, 5, 10, 666));
+ RangingResultCallback callbackMock = mock(RangingResultCallback.class);
+ ArgumentCaptor<IRttCallback> callbackCaptor = ArgumentCaptor.forClass(IRttCallback.class);
+
+ // verify ranging request passed to service
+ mDut.startRanging(request, callbackMock, mMockLooperHandler);
+ verify(mockRttService).startRanging(any(IBinder.class), eq(packageName), eq(request),
+ callbackCaptor.capture());
+
+ // service calls back with success
+ callbackCaptor.getValue().onRangingResults(RangingResultCallback.STATUS_SUCCESS, results);
+ mMockLooper.dispatchAll();
+ verify(callbackMock).onRangingResults(results);
+
+ verifyNoMoreInteractions(mockRttService, callbackMock);
+ }
+
+ /**
+ * Validate ranging call flow which failed.
+ */
+ @Test
+ public void testRangeFail() throws Exception {
+ RangingRequest request = new RangingRequest.Builder().build();
+ RangingResultCallback callbackMock = mock(RangingResultCallback.class);
+ ArgumentCaptor<IRttCallback> callbackCaptor = ArgumentCaptor.forClass(IRttCallback.class);
+
+ // verify ranging request passed to service
+ mDut.startRanging(request, callbackMock, mMockLooperHandler);
+ verify(mockRttService).startRanging(any(IBinder.class), eq(packageName), eq(request),
+ callbackCaptor.capture());
+
+ // service calls back with failure code
+ callbackCaptor.getValue().onRangingResults(RangingResultCallback.STATUS_FAIL, null);
+ mMockLooper.dispatchAll();
+ verify(callbackMock).onRangingFailure();
+
+ verifyNoMoreInteractions(mockRttService, callbackMock);
+ }
+
+ /**
+ * Validate that RangingRequest parcel works (produces same object on write/read).
+ */
+ @Test
+ public void testRangingRequestParcel() {
+ // Note: not validating parcel code of ScanResult (assumed to work)
+ ScanResult scanResult1 = new ScanResult();
+ scanResult1.BSSID = "00:01:02:03:04:05";
+ ScanResult scanResult2 = new ScanResult();
+ scanResult2.BSSID = "06:07:08:09:0A:0B";
+ ScanResult scanResult3 = new ScanResult();
+ scanResult3.BSSID = "AA:BB:CC:DD:EE:FF";
+ List<ScanResult> scanResults2and3 = new ArrayList<>(2);
+ scanResults2and3.add(scanResult2);
+ scanResults2and3.add(scanResult3);
+
+ RangingRequest.Builder builder = new RangingRequest.Builder();
+ builder.addAp(scanResult1);
+ builder.addAps(scanResults2and3);
+ RangingRequest request = builder.build();
+
+ Parcel parcelW = Parcel.obtain();
+ request.writeToParcel(parcelW, 0);
+ byte[] bytes = parcelW.marshall();
+ parcelW.recycle();
+
+ Parcel parcelR = Parcel.obtain();
+ parcelR.unmarshall(bytes, 0, bytes.length);
+ parcelR.setDataPosition(0);
+ RangingRequest rereadRequest = RangingRequest.CREATOR.createFromParcel(parcelR);
+
+ assertEquals(request, rereadRequest);
+ }
+
+ /**
+ * Validate that can request as many range operation as the upper limit on number of requests.
+ */
+ @Test
+ public void testRangingRequestAtLimit() {
+ ScanResult scanResult = new ScanResult();
+ List<ScanResult> scanResultList = new ArrayList<>();
+ for (int i = 0; i < RangingRequest.getMaxPeers() - 2; ++i) {
+ scanResultList.add(scanResult);
+ }
+
+ // create request
+ RangingRequest.Builder builder = new RangingRequest.Builder();
+ builder.addAp(scanResult);
+ builder.addAps(scanResultList);
+ builder.addAp(scanResult);
+ RangingRequest request = builder.build();
+
+ // verify request
+ request.enforceValidity();
+ }
+
+ /**
+ * Validate that limit on number of requests is applied.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testRangingRequestPastLimit() {
+ ScanResult scanResult = new ScanResult();
+ List<ScanResult> scanResultList = new ArrayList<>();
+ for (int i = 0; i < RangingRequest.getMaxPeers() - 1; ++i) {
+ scanResultList.add(scanResult);
+ }
+
+ // create request
+ RangingRequest.Builder builder = new RangingRequest.Builder();
+ builder.addAp(scanResult);
+ builder.addAps(scanResultList);
+ builder.addAp(scanResult);
+ RangingRequest request = builder.build();
+
+ // verify request
+ request.enforceValidity();
+ }
+
+ /**
+ * Validate that RangingResults parcel works (produces same object on write/read).
+ */
+ @Test
+ public void testRangingResultsParcel() {
+ // Note: not validating parcel code of ScanResult (assumed to work)
+ int status = RangingResultCallback.STATUS_SUCCESS;
+ final byte[] mac = HexEncoding.decode("000102030405".toCharArray(), false);
+ int distanceCm = 105;
+ int distanceStdDevCm = 10;
+ int rssi = 5;
+ long timestamp = System.currentTimeMillis();
+
+ RangingResult result = new RangingResult(status, mac, distanceCm, distanceStdDevCm, rssi,
+ timestamp);
+
+ Parcel parcelW = Parcel.obtain();
+ result.writeToParcel(parcelW, 0);
+ byte[] bytes = parcelW.marshall();
+ parcelW.recycle();
+
+ Parcel parcelR = Parcel.obtain();
+ parcelR.unmarshall(bytes, 0, bytes.length);
+ parcelR.setDataPosition(0);
+ RangingResult rereadResult = RangingResult.CREATOR.createFromParcel(parcelR);
+
+ assertEquals(result, rereadResult);
+ }
+}