diff options
94 files changed, 3306 insertions, 871 deletions
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg index ae42882f917a..de83f3e01074 100644 --- a/PREUPLOAD.cfg +++ b/PREUPLOAD.cfg @@ -15,3 +15,7 @@ api_lint_hook = ${REPO_ROOT}/frameworks/base/tools/apilint/apilint_sha.sh ${PREU strings_lint_hook = ${REPO_ROOT}/frameworks/base/tools/stringslint/stringslint_sha.sh ${PREUPLOAD_COMMIT} hidden_api_txt_hook = ${REPO_ROOT}/frameworks/base/tools/hiddenapi/checksorted_sha.sh ${PREUPLOAD_COMMIT} ${REPO_ROOT} + +ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py -f ${PREUPLOAD_FILES} + +owners_hook = ${REPO_ROOT}/frameworks/base/tools/aosp/aosp_sha.sh ${PREUPLOAD_COMMIT} "OWNERS$" diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp index c37d0cfdba93..33f3917f05ad 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.cpp +++ b/cmds/statsd/src/guardrail/StatsdStats.cpp @@ -50,7 +50,7 @@ const int FIELD_ID_ANOMALY_ALARM_STATS = 9; // const int FIELD_ID_PULLED_ATOM_STATS = 10; // The proto is written in stats_log_util.cpp const int FIELD_ID_LOGGER_ERROR_STATS = 11; const int FIELD_ID_PERIODIC_ALARM_STATS = 12; -const int FIELD_ID_LOG_LOSS_STATS = 14; +// const int FIELD_ID_LOG_LOSS_STATS = 14; const int FIELD_ID_SYSTEM_SERVER_RESTART = 15; const int FIELD_ID_ATOM_STATS_TAG = 1; @@ -180,12 +180,12 @@ void StatsdStats::noteConfigReset(const ConfigKey& key) { noteConfigResetInternalLocked(key); } -void StatsdStats::noteLogLost(int64_t timestampNs, int32_t count) { +void StatsdStats::noteLogLost(int32_t wallClockTimeSec, int32_t count) { lock_guard<std::mutex> lock(mLock); - if (mLogLossTimestampNs.size() == kMaxLoggerErrors) { - mLogLossTimestampNs.pop_front(); + if (mLogLossStats.size() == kMaxLoggerErrors) { + mLogLossStats.pop_front(); } - mLogLossTimestampNs.push_back(std::make_pair(timestampNs, count)); + mLogLossStats.push_back(std::make_pair(wallClockTimeSec, count)); } void StatsdStats::noteBroadcastSent(const ConfigKey& key) { @@ -365,15 +365,6 @@ void StatsdStats::noteSystemServerRestart(int32_t timeSec) { mSystemServerRestartSec.push_back(timeSec); } -void StatsdStats::noteLoggerError(int error) { - lock_guard<std::mutex> lock(mLock); - // grows strictly one at a time. so it won't > kMaxLoggerErrors - if (mLoggerErrors.size() == kMaxLoggerErrors) { - mLoggerErrors.pop_front(); - } - mLoggerErrors.push_back(std::make_pair(getWallClockSec(), error)); -} - void StatsdStats::reset() { lock_guard<std::mutex> lock(mLock); resetInternalLocked(); @@ -386,9 +377,8 @@ void StatsdStats::resetInternalLocked() { std::fill(mPushedAtomStats.begin(), mPushedAtomStats.end(), 0); mAnomalyAlarmRegisteredStats = 0; mPeriodicAlarmRegisteredStats = 0; - mLoggerErrors.clear(); mSystemServerRestartSec.clear(); - mLogLossTimestampNs.clear(); + mLogLossStats.clear(); for (auto& config : mConfigStats) { config.second->broadcast_sent_time_sec.clear(); config.second->data_drop_time_sec.clear(); @@ -515,21 +505,13 @@ void StatsdStats::dumpStats(FILE* out) const { mUidMapStats.bytes_used, mUidMapStats.changes, mUidMapStats.deleted_apps, mUidMapStats.dropped_changes); - for (const auto& error : mLoggerErrors) { - time_t error_time = error.first; - struct tm* error_tm = localtime(&error_time); - char buffer[80]; - strftime(buffer, sizeof(buffer), "%Y-%m-%d %I:%M%p\n", error_tm); - fprintf(out, "Logger error %d at %s\n", error.second, buffer); - } - for (const auto& restart : mSystemServerRestartSec) { fprintf(out, "System server restarts at %s(%lld)\n", buildTimeString(restart).c_str(), (long long)restart); } - for (const auto& loss : mLogLossTimestampNs) { - fprintf(out, "Log loss: %lld (elapsedRealtimeNs) - %d (count)\n", (long long)loss.first, + for (const auto& loss : mLogLossStats) { + fprintf(out, "Log loss: %lld (wall clock sec) - %d (count)\n", (long long)loss.first, loss.second); } } @@ -678,7 +660,10 @@ void StatsdStats::dumpStats(std::vector<uint8_t>* output, bool reset) { proto.write(FIELD_TYPE_INT32 | FIELD_ID_UID_MAP_DELETED_APPS, mUidMapStats.deleted_apps); proto.end(uidMapToken); - for (const auto& error : mLoggerErrors) { + for (const auto& error : mLogLossStats) { + // The logger error stats are not used anymore since we move away from logd. + // Temporarily use this field to log the log loss timestamp and count + // TODO(b/80538532) Add a dedicated field in stats_log for this. uint64_t token = proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_LOGGER_ERROR_STATS | FIELD_COUNT_REPEATED); proto.write(FIELD_TYPE_INT32 | FIELD_ID_LOGGER_STATS_TIME, error.first); @@ -686,11 +671,6 @@ void StatsdStats::dumpStats(std::vector<uint8_t>* output, bool reset) { proto.end(token); } - for (const auto& loss : mLogLossTimestampNs) { - proto.write(FIELD_TYPE_INT64 | FIELD_ID_LOG_LOSS_STATS | FIELD_COUNT_REPEATED, - (long long)loss.first); - } - for (const auto& restart : mSystemServerRestartSec) { proto.write(FIELD_TYPE_INT32 | FIELD_ID_SYSTEM_SERVER_RESTART | FIELD_COUNT_REPEATED, restart); diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h index daea027e68f9..b5156dadade6 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.h +++ b/cmds/statsd/src/guardrail/StatsdStats.h @@ -271,11 +271,6 @@ public: // Notify pull request for an atom served from cached data void notePullFromCache(int pullAtomId); - /** - * Records statsd met an error while reading from logd. - */ - void noteLoggerError(int error); - /* * Records when system server restarts. */ @@ -284,7 +279,7 @@ public: /** * Records statsd skipped an event. */ - void noteLogLost(int64_t timestamp, int32_t count); + void noteLogLost(int32_t wallClockTimeSec, int32_t count); /** * Reset the historical stats. Including all stats in icebox, and the tracked stats about @@ -338,11 +333,8 @@ private: // Maps PullAtomId to its stats. The size is capped by the puller atom counts. std::map<int, PulledAtomStats> mPulledAtomStats; - // Logd errors. Size capped by kMaxLoggerErrors. - std::list<const std::pair<int, int>> mLoggerErrors; - // Timestamps when we detect log loss, and the number of logs lost. - std::list<std::pair<int64_t, int32_t>> mLogLossTimestampNs; + std::list<std::pair<int32_t, int32_t>> mLogLossStats; std::list<int32_t> mSystemServerRestartSec; diff --git a/cmds/statsd/src/socket/StatsSocketListener.cpp b/cmds/statsd/src/socket/StatsSocketListener.cpp index 4041da7b84d2..9b0691b6092d 100755 --- a/cmds/statsd/src/socket/StatsSocketListener.cpp +++ b/cmds/statsd/src/socket/StatsSocketListener.cpp @@ -111,7 +111,8 @@ bool StatsSocketListener::onDataAvailable(SocketClient* cli) { android_log_event_int_t* int_event = reinterpret_cast<android_log_event_int_t*>(ptr); if (int_event->header.tag == kLibLogTag && int_event->payload.type == EVENT_TYPE_INT) { ALOGE("Found dropped events: %d", int_event->payload.data); - StatsdStats::getInstance().noteLogLost(getElapsedRealtimeNs(), int_event->payload.data); + StatsdStats::getInstance().noteLogLost((int32_t)getWallClockSec(), + int_event->payload.data); return true; } } diff --git a/config/boot-image-profile.txt b/config/boot-image-profile.txt index a78773692ef3..9ca0745c04f4 100644 --- a/config/boot-image-profile.txt +++ b/config/boot-image-profile.txt @@ -12204,11 +12204,11 @@ HPLorg/json/JSONStringer;->value(J)Lorg/json/JSONStringer; HPLorg/json/JSONStringer;->value(Z)Lorg/json/JSONStringer; HPLorg/json/JSONTokener;->syntaxError(Ljava/lang/String;)Lorg/json/JSONException; HPLorg/json/JSONTokener;->toString()Ljava/lang/String; -HPLorg/kxml2/io/KXmlSerializer;->getDepth()I -HPLorg/kxml2/io/KXmlSerializer;->getNamespace()Ljava/lang/String; -HPLorg/kxml2/io/KXmlSerializer;->getPrefix(Ljava/lang/String;ZZ)Ljava/lang/String; -HPLorg/kxml2/io/KXmlSerializer;->setPrefix(Ljava/lang/String;Ljava/lang/String;)V -HPLorg/kxml2/io/KXmlSerializer;->text(Ljava/lang/String;)Lorg/xmlpull/v1/XmlSerializer; +HPLcom/android/org/kxml2/io/KXmlSerializer;->getDepth()I +HPLcom/android/org/kxml2/io/KXmlSerializer;->getNamespace()Ljava/lang/String; +HPLcom/android/org/kxml2/io/KXmlSerializer;->getPrefix(Ljava/lang/String;ZZ)Ljava/lang/String; +HPLcom/android/org/kxml2/io/KXmlSerializer;->setPrefix(Ljava/lang/String;Ljava/lang/String;)V +HPLcom/android/org/kxml2/io/KXmlSerializer;->text(Ljava/lang/String;)Lorg/xmlpull/v1/XmlSerializer; HPLorg/w3c/dom/NamedNodeMap;->getLength()I HPLorg/w3c/dom/NamedNodeMap;->getNamedItem(Ljava/lang/String;)Lorg/w3c/dom/Node; HPLorg/w3c/dom/NamedNodeMap;->getNamedItemNS(Ljava/lang/String;Ljava/lang/String;)Lorg/w3c/dom/Node; @@ -51896,7 +51896,7 @@ HSPLorg/apache/harmony/xml/dom/NodeListImpl;->item(I)Lorg/w3c/dom/Node; HSPLorg/apache/harmony/xml/dom/TextImpl;->getNodeType()S HSPLorg/apache/harmony/xml/parsers/DocumentBuilderFactoryImpl;->newDocumentBuilder()Ljavax/xml/parsers/DocumentBuilder; HSPLorg/apache/harmony/xml/parsers/DocumentBuilderImpl;->appendText(Lorg/apache/harmony/xml/dom/DocumentImpl;Lorg/w3c/dom/Node;ILjava/lang/String;)V -HSPLorg/apache/harmony/xml/parsers/DocumentBuilderImpl;->parse(Lorg/kxml2/io/KXmlParser;Lorg/apache/harmony/xml/dom/DocumentImpl;Lorg/w3c/dom/Node;I)V +HSPLorg/apache/harmony/xml/parsers/DocumentBuilderImpl;->parse(Lcom/android/org/kxml2/io/KXmlParser;Lorg/apache/harmony/xml/dom/DocumentImpl;Lorg/w3c/dom/Node;I)V HSPLorg/apache/harmony/xml/parsers/DocumentBuilderImpl;->parse(Lorg/xml/sax/InputSource;)Lorg/w3c/dom/Document; HSPLorg/apache/harmony/xml/parsers/SAXParserFactoryImpl;->getFeature(Ljava/lang/String;)Z HSPLorg/apache/harmony/xml/parsers/SAXParserFactoryImpl;->isValidating()Z @@ -52176,64 +52176,64 @@ HSPLorg/json/JSONTokener;->readEscapeCharacter()C HSPLorg/json/JSONTokener;->readLiteral()Ljava/lang/Object; HSPLorg/json/JSONTokener;->readObject()Lorg/json/JSONObject; HSPLorg/json/JSONTokener;->skipToEndOfLine()V -HSPLorg/kxml2/io/KXmlParser$ValueContext;-><init>(Ljava/lang/String;I)V -HSPLorg/kxml2/io/KXmlParser;-><init>()V -HSPLorg/kxml2/io/KXmlParser;->adjustNsp()Z -HSPLorg/kxml2/io/KXmlParser;->close()V -HSPLorg/kxml2/io/KXmlParser;->fillBuffer(I)Z -HSPLorg/kxml2/io/KXmlParser;->getAttributeCount()I -HSPLorg/kxml2/io/KXmlParser;->getAttributeName(I)Ljava/lang/String; -HSPLorg/kxml2/io/KXmlParser;->getAttributeNamespace(I)Ljava/lang/String; -HSPLorg/kxml2/io/KXmlParser;->getAttributePrefix(I)Ljava/lang/String; -HSPLorg/kxml2/io/KXmlParser;->getAttributeType(I)Ljava/lang/String; -HSPLorg/kxml2/io/KXmlParser;->getAttributeValue(I)Ljava/lang/String; -HSPLorg/kxml2/io/KXmlParser;->getAttributeValue(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; -HSPLorg/kxml2/io/KXmlParser;->getColumnNumber()I -HSPLorg/kxml2/io/KXmlParser;->getDepth()I -HSPLorg/kxml2/io/KXmlParser;->getEventType()I -HSPLorg/kxml2/io/KXmlParser;->getFeature(Ljava/lang/String;)Z -HSPLorg/kxml2/io/KXmlParser;->getLineNumber()I -HSPLorg/kxml2/io/KXmlParser;->getName()Ljava/lang/String; -HSPLorg/kxml2/io/KXmlParser;->getNamespace()Ljava/lang/String; -HSPLorg/kxml2/io/KXmlParser;->getNamespace(Ljava/lang/String;)Ljava/lang/String; -HSPLorg/kxml2/io/KXmlParser;->getNamespaceCount(I)I -HSPLorg/kxml2/io/KXmlParser;->getPositionDescription()Ljava/lang/String; -HSPLorg/kxml2/io/KXmlParser;->getPrefix()Ljava/lang/String; -HSPLorg/kxml2/io/KXmlParser;->getText()Ljava/lang/String; -HSPLorg/kxml2/io/KXmlParser;->getTextCharacters([I)[C -HSPLorg/kxml2/io/KXmlParser;->next()I -HSPLorg/kxml2/io/KXmlParser;->next(Z)I -HSPLorg/kxml2/io/KXmlParser;->nextTag()I -HSPLorg/kxml2/io/KXmlParser;->nextText()Ljava/lang/String; -HSPLorg/kxml2/io/KXmlParser;->nextToken()I -HSPLorg/kxml2/io/KXmlParser;->parseStartTag(ZZ)V -HSPLorg/kxml2/io/KXmlParser;->peekType(Z)I -HSPLorg/kxml2/io/KXmlParser;->read(C)V -HSPLorg/kxml2/io/KXmlParser;->read([C)V -HSPLorg/kxml2/io/KXmlParser;->readComment(Z)Ljava/lang/String; -HSPLorg/kxml2/io/KXmlParser;->readEndTag()V -HSPLorg/kxml2/io/KXmlParser;->readEntity(Ljava/lang/StringBuilder;ZZLorg/kxml2/io/KXmlParser$ValueContext;)V -HSPLorg/kxml2/io/KXmlParser;->readName()Ljava/lang/String; -HSPLorg/kxml2/io/KXmlParser;->readUntil([CZ)Ljava/lang/String; -HSPLorg/kxml2/io/KXmlParser;->readValue(CZZLorg/kxml2/io/KXmlParser$ValueContext;)Ljava/lang/String; -HSPLorg/kxml2/io/KXmlParser;->readXmlDeclaration()V -HSPLorg/kxml2/io/KXmlParser;->require(ILjava/lang/String;Ljava/lang/String;)V -HSPLorg/kxml2/io/KXmlParser;->setFeature(Ljava/lang/String;Z)V -HSPLorg/kxml2/io/KXmlParser;->setInput(Ljava/io/InputStream;Ljava/lang/String;)V -HSPLorg/kxml2/io/KXmlParser;->setInput(Ljava/io/Reader;)V -HSPLorg/kxml2/io/KXmlSerializer;-><init>()V -HSPLorg/kxml2/io/KXmlSerializer;->append(Ljava/lang/String;II)V -HSPLorg/kxml2/io/KXmlSerializer;->attribute(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lorg/xmlpull/v1/XmlSerializer; -HSPLorg/kxml2/io/KXmlSerializer;->check(Z)V -HSPLorg/kxml2/io/KXmlSerializer;->endDocument()V -HSPLorg/kxml2/io/KXmlSerializer;->endTag(Ljava/lang/String;Ljava/lang/String;)Lorg/xmlpull/v1/XmlSerializer; -HSPLorg/kxml2/io/KXmlSerializer;->flush()V -HSPLorg/kxml2/io/KXmlSerializer;->setFeature(Ljava/lang/String;Z)V -HSPLorg/kxml2/io/KXmlSerializer;->setOutput(Ljava/io/OutputStream;Ljava/lang/String;)V -HSPLorg/kxml2/io/KXmlSerializer;->setOutput(Ljava/io/Writer;)V -HSPLorg/kxml2/io/KXmlSerializer;->startDocument(Ljava/lang/String;Ljava/lang/Boolean;)V -HSPLorg/kxml2/io/KXmlSerializer;->startTag(Ljava/lang/String;Ljava/lang/String;)Lorg/xmlpull/v1/XmlSerializer; -HSPLorg/kxml2/io/KXmlSerializer;->writeEscaped(Ljava/lang/String;I)V +HSPLcom/android/org/kxml2/io/KXmlParser$ValueContext;-><init>(Ljava/lang/String;I)V +HSPLcom/android/org/kxml2/io/KXmlParser;-><init>()V +HSPLcom/android/org/kxml2/io/KXmlParser;->adjustNsp()Z +HSPLcom/android/org/kxml2/io/KXmlParser;->close()V +HSPLcom/android/org/kxml2/io/KXmlParser;->fillBuffer(I)Z +HSPLcom/android/org/kxml2/io/KXmlParser;->getAttributeCount()I +HSPLcom/android/org/kxml2/io/KXmlParser;->getAttributeName(I)Ljava/lang/String; +HSPLcom/android/org/kxml2/io/KXmlParser;->getAttributeNamespace(I)Ljava/lang/String; +HSPLcom/android/org/kxml2/io/KXmlParser;->getAttributePrefix(I)Ljava/lang/String; +HSPLcom/android/org/kxml2/io/KXmlParser;->getAttributeType(I)Ljava/lang/String; +HSPLcom/android/org/kxml2/io/KXmlParser;->getAttributeValue(I)Ljava/lang/String; +HSPLcom/android/org/kxml2/io/KXmlParser;->getAttributeValue(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; +HSPLcom/android/org/kxml2/io/KXmlParser;->getColumnNumber()I +HSPLcom/android/org/kxml2/io/KXmlParser;->getDepth()I +HSPLcom/android/org/kxml2/io/KXmlParser;->getEventType()I +HSPLcom/android/org/kxml2/io/KXmlParser;->getFeature(Ljava/lang/String;)Z +HSPLcom/android/org/kxml2/io/KXmlParser;->getLineNumber()I +HSPLcom/android/org/kxml2/io/KXmlParser;->getName()Ljava/lang/String; +HSPLcom/android/org/kxml2/io/KXmlParser;->getNamespace()Ljava/lang/String; +HSPLcom/android/org/kxml2/io/KXmlParser;->getNamespace(Ljava/lang/String;)Ljava/lang/String; +HSPLcom/android/org/kxml2/io/KXmlParser;->getNamespaceCount(I)I +HSPLcom/android/org/kxml2/io/KXmlParser;->getPositionDescription()Ljava/lang/String; +HSPLcom/android/org/kxml2/io/KXmlParser;->getPrefix()Ljava/lang/String; +HSPLcom/android/org/kxml2/io/KXmlParser;->getText()Ljava/lang/String; +HSPLcom/android/org/kxml2/io/KXmlParser;->getTextCharacters([I)[C +HSPLcom/android/org/kxml2/io/KXmlParser;->next()I +HSPLcom/android/org/kxml2/io/KXmlParser;->next(Z)I +HSPLcom/android/org/kxml2/io/KXmlParser;->nextTag()I +HSPLcom/android/org/kxml2/io/KXmlParser;->nextText()Ljava/lang/String; +HSPLcom/android/org/kxml2/io/KXmlParser;->nextToken()I +HSPLcom/android/org/kxml2/io/KXmlParser;->parseStartTag(ZZ)V +HSPLcom/android/org/kxml2/io/KXmlParser;->peekType(Z)I +HSPLcom/android/org/kxml2/io/KXmlParser;->read(C)V +HSPLcom/android/org/kxml2/io/KXmlParser;->read([C)V +HSPLcom/android/org/kxml2/io/KXmlParser;->readComment(Z)Ljava/lang/String; +HSPLcom/android/org/kxml2/io/KXmlParser;->readEndTag()V +HSPLcom/android/org/kxml2/io/KXmlParser;->readEntity(Ljava/lang/StringBuilder;ZZLcom/android/org/kxml2/io/KXmlParser$ValueContext;)V +HSPLcom/android/org/kxml2/io/KXmlParser;->readName()Ljava/lang/String; +HSPLcom/android/org/kxml2/io/KXmlParser;->readUntil([CZ)Ljava/lang/String; +HSPLcom/android/org/kxml2/io/KXmlParser;->readValue(CZZLcom/android/org/kxml2/io/KXmlParser$ValueContext;)Ljava/lang/String; +HSPLcom/android/org/kxml2/io/KXmlParser;->readXmlDeclaration()V +HSPLcom/android/org/kxml2/io/KXmlParser;->require(ILjava/lang/String;Ljava/lang/String;)V +HSPLcom/android/org/kxml2/io/KXmlParser;->setFeature(Ljava/lang/String;Z)V +HSPLcom/android/org/kxml2/io/KXmlParser;->setInput(Ljava/io/InputStream;Ljava/lang/String;)V +HSPLcom/android/org/kxml2/io/KXmlParser;->setInput(Ljava/io/Reader;)V +HSPLcom/android/org/kxml2/io/KXmlSerializer;-><init>()V +HSPLcom/android/org/kxml2/io/KXmlSerializer;->append(Ljava/lang/String;II)V +HSPLcom/android/org/kxml2/io/KXmlSerializer;->attribute(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lorg/xmlpull/v1/XmlSerializer; +HSPLcom/android/org/kxml2/io/KXmlSerializer;->check(Z)V +HSPLcom/android/org/kxml2/io/KXmlSerializer;->endDocument()V +HSPLcom/android/org/kxml2/io/KXmlSerializer;->endTag(Ljava/lang/String;Ljava/lang/String;)Lorg/xmlpull/v1/XmlSerializer; +HSPLcom/android/org/kxml2/io/KXmlSerializer;->flush()V +HSPLcom/android/org/kxml2/io/KXmlSerializer;->setFeature(Ljava/lang/String;Z)V +HSPLcom/android/org/kxml2/io/KXmlSerializer;->setOutput(Ljava/io/OutputStream;Ljava/lang/String;)V +HSPLcom/android/org/kxml2/io/KXmlSerializer;->setOutput(Ljava/io/Writer;)V +HSPLcom/android/org/kxml2/io/KXmlSerializer;->startDocument(Ljava/lang/String;Ljava/lang/Boolean;)V +HSPLcom/android/org/kxml2/io/KXmlSerializer;->startTag(Ljava/lang/String;Ljava/lang/String;)Lorg/xmlpull/v1/XmlSerializer; +HSPLcom/android/org/kxml2/io/KXmlSerializer;->writeEscaped(Ljava/lang/String;I)V HSPLorg/w3c/dom/Attr;->getName()Ljava/lang/String; HSPLorg/w3c/dom/Attr;->getOwnerElement()Lorg/w3c/dom/Element; HSPLorg/w3c/dom/Attr;->getSchemaTypeInfo()Lorg/w3c/dom/TypeInfo; @@ -63397,9 +63397,9 @@ Lorg/json/JSONObject; Lorg/json/JSONStringer$Scope; Lorg/json/JSONStringer; Lorg/json/JSONTokener; -Lorg/kxml2/io/KXmlParser$ValueContext; -Lorg/kxml2/io/KXmlParser; -Lorg/kxml2/io/KXmlSerializer; +Lcom/android/org/kxml2/io/KXmlParser$ValueContext; +Lcom/android/org/kxml2/io/KXmlParser; +Lcom/android/org/kxml2/io/KXmlSerializer; Lorg/w3c/dom/CharacterData; Lorg/w3c/dom/DOMImplementation; Lorg/w3c/dom/Document; diff --git a/config/hiddenapi-light-greylist.txt b/config/hiddenapi-light-greylist.txt index 3a96cd3f3eca..196f89cd69ee 100644 --- a/config/hiddenapi-light-greylist.txt +++ b/config/hiddenapi-light-greylist.txt @@ -455,59 +455,6 @@ Landroid/database/IContentObserver$Stub;->asInterface(Landroid/os/IBinder;)Landr Landroid/database/IContentObserver;->onChange(ZLandroid/net/Uri;I)V Landroid/hardware/biometrics/BiometricConstants;->BIOMETRIC_ERROR_VENDOR_BASE:I Landroid/hardware/biometrics/BiometricFingerprintConstants;->FINGERPRINT_ERROR_VENDOR_BASE:I -Landroid/hardware/camera2/CameraCharacteristics;->CONTROL_AVAILABLE_HIGH_SPEED_VIDEO_CONFIGURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->CONTROL_MAX_REGIONS:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->DEPTH_AVAILABLE_DEPTH_MIN_FRAME_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->DEPTH_AVAILABLE_DEPTH_STALL_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->DEPTH_AVAILABLE_DEPTH_STREAM_CONFIGURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->LED_AVAILABLE_LEDS:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->LENS_INFO_SHADING_MAP_SIZE:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->LOGICAL_MULTI_CAMERA_PHYSICAL_IDS:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->QUIRKS_USE_PARTIAL_RESULT:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->REQUEST_AVAILABLE_CHARACTERISTICS_KEYS:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->REQUEST_AVAILABLE_PHYSICAL_CAMERA_REQUEST_KEYS:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->REQUEST_AVAILABLE_REQUEST_KEYS:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->REQUEST_AVAILABLE_RESULT_KEYS:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->REQUEST_AVAILABLE_SESSION_KEYS:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->REQUEST_MAX_NUM_OUTPUT_STREAMS:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->SCALER_AVAILABLE_FORMATS:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->SCALER_AVAILABLE_JPEG_MIN_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->SCALER_AVAILABLE_JPEG_SIZES:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->SCALER_AVAILABLE_MIN_FRAME_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->SCALER_AVAILABLE_PROCESSED_MIN_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->SCALER_AVAILABLE_PROCESSED_SIZES:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->SCALER_AVAILABLE_STALL_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->SCALER_AVAILABLE_STREAM_CONFIGURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CaptureRequest;->JPEG_GPS_COORDINATES:Landroid/hardware/camera2/CaptureRequest$Key; -Landroid/hardware/camera2/CaptureRequest;->JPEG_GPS_PROCESSING_METHOD:Landroid/hardware/camera2/CaptureRequest$Key; -Landroid/hardware/camera2/CaptureRequest;->JPEG_GPS_TIMESTAMP:Landroid/hardware/camera2/CaptureRequest$Key; -Landroid/hardware/camera2/CaptureRequest;->LED_TRANSMIT:Landroid/hardware/camera2/CaptureRequest$Key; -Landroid/hardware/camera2/CaptureRequest;->REQUEST_ID:Landroid/hardware/camera2/CaptureRequest$Key; -Landroid/hardware/camera2/CaptureRequest;->TONEMAP_CURVE_BLUE:Landroid/hardware/camera2/CaptureRequest$Key; -Landroid/hardware/camera2/CaptureRequest;->TONEMAP_CURVE_GREEN:Landroid/hardware/camera2/CaptureRequest$Key; -Landroid/hardware/camera2/CaptureRequest;->TONEMAP_CURVE_RED:Landroid/hardware/camera2/CaptureRequest$Key; -Landroid/hardware/camera2/CaptureResult;->JPEG_GPS_COORDINATES:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->JPEG_GPS_PROCESSING_METHOD:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->JPEG_GPS_TIMESTAMP:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->LED_TRANSMIT:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->QUIRKS_PARTIAL_RESULT:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->REQUEST_FRAME_COUNT:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->REQUEST_ID:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->STATISTICS_FACE_IDS:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->STATISTICS_FACE_LANDMARKS:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->STATISTICS_FACE_RECTANGLES:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->STATISTICS_FACE_SCORES:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->STATISTICS_LENS_SHADING_MAP:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->STATISTICS_OIS_TIMESTAMPS:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->STATISTICS_OIS_X_SHIFTS:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->STATISTICS_OIS_Y_SHIFTS:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->STATISTICS_PREDICTED_COLOR_GAINS:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->STATISTICS_PREDICTED_COLOR_TRANSFORM:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->SYNC_FRAME_NUMBER:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->TONEMAP_CURVE_BLUE:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->TONEMAP_CURVE_GREEN:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->TONEMAP_CURVE_RED:Landroid/hardware/camera2/CaptureResult$Key; Landroid/hardware/display/IDisplayManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/hardware/display/IDisplayManager; Landroid/hardware/display/IDisplayManager;->getDisplayInfo(I)Landroid/view/DisplayInfo; Landroid/hardware/fingerprint/IFingerprintService$Stub$Proxy;-><init>(Landroid/os/IBinder;)V @@ -2480,7 +2427,6 @@ Lcom/android/internal/textservice/ITextServicesManager$Stub$Proxy;-><init>(Landr Lcom/android/internal/util/HexDump;->toHexString([BZ)Ljava/lang/String; Lcom/android/internal/view/BaseIWindow;-><init>()V Lcom/android/internal/view/IInputMethod$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/view/IInputMethod; -Lcom/android/internal/view/IInputMethod;->attachToken(Landroid/os/IBinder;)V Lcom/android/internal/view/IInputMethod;->bindInput(Landroid/view/inputmethod/InputBinding;)V Lcom/android/internal/view/IInputMethod;->hideSoftInput(ILandroid/os/ResultReceiver;)V Lcom/android/internal/view/IInputMethod;->setSessionEnabled(Lcom/android/internal/view/IInputMethodSession;Z)V @@ -2492,8 +2438,6 @@ Lcom/android/internal/view/IInputMethodManager$Stub$Proxy;-><init>(Landroid/os/I Lcom/android/internal/view/IInputMethodManager$Stub$Proxy;->getEnabledInputMethodList()Ljava/util/List; Lcom/android/internal/view/IInputMethodManager$Stub;-><init>()V Lcom/android/internal/view/IInputMethodManager$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/view/IInputMethodManager; -Lcom/android/internal/view/IInputMethodManager;->addClient(Lcom/android/internal/view/IInputMethodClient;Lcom/android/internal/view/IInputContext;II)V -Lcom/android/internal/view/IInputMethodManager;->removeClient(Lcom/android/internal/view/IInputMethodClient;)V Lcom/android/internal/view/IInputMethodSession$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/view/IInputMethodSession; Lcom/android/internal/widget/ILockSettings$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/widget/ILockSettings; Lcom/android/internal/widget/ILockSettings;->getBoolean(Ljava/lang/String;ZI)Z diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java index b644e23223b7..dfa30a22597e 100644 --- a/core/java/android/content/res/ResourcesImpl.java +++ b/core/java/android/content/res/ResourcesImpl.java @@ -1349,9 +1349,11 @@ public class ResourcesImpl { @StyleableRes int[] attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { + Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "obtainStyledAttributes"); + TypedArray array; synchronized (mKey) { final int len = attrs.length; - final TypedArray array = TypedArray.obtain(wrapper.getResources(), len); + array = TypedArray.obtain(wrapper.getResources(), len); // XXX note that for now we only work with compiled XML files. // To support generic XML files we will need to manually parse @@ -1362,8 +1364,9 @@ public class ResourcesImpl { array.mDataAddress, array.mIndicesAddress); array.mTheme = wrapper; array.mXml = parser; - return array; } + Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); + return array; } @NonNull diff --git a/core/java/android/net/IpConfiguration.java b/core/java/android/net/IpConfiguration.java index 7543920e363f..3319f33878c1 100644 --- a/core/java/android/net/IpConfiguration.java +++ b/core/java/android/net/IpConfiguration.java @@ -35,7 +35,7 @@ public class IpConfiguration implements Parcelable { * with staticIpConfiguration */ @UnsupportedAppUsage STATIC, - /* Use dynamically configured IP settigns */ + /* Use dynamically configured IP settings */ DHCP, /* no IP details are assigned, this is used to indicate * that any existing IP settings should be retained */ diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java index 599ccb287a26..34e9476b3e08 100644 --- a/core/java/android/net/NetworkUtils.java +++ b/core/java/android/net/NetworkUtils.java @@ -22,6 +22,7 @@ import android.util.Log; import android.util.Pair; import java.io.FileDescriptor; +import java.io.IOException; import java.math.BigInteger; import java.net.Inet4Address; import java.net.Inet6Address; @@ -131,6 +132,17 @@ public class NetworkUtils { public native static boolean queryUserAccess(int uid, int netId); /** + * Add an entry into the ARP cache. + */ + public static void addArpEntry(Inet4Address ipv4Addr, MacAddress ethAddr, String ifname, + FileDescriptor fd) throws IOException { + addArpEntry(ethAddr.toByteArray(), ipv4Addr.getAddress(), ifname, fd); + } + + private static native void addArpEntry(byte[] ethAddr, byte[] netAddr, String ifname, + FileDescriptor fd) throws IOException; + + /** * @see #intToInet4AddressHTL(int) * @deprecated Use either {@link #intToInet4AddressHTH(int)} * or {@link #intToInet4AddressHTL(int)} @@ -149,7 +161,7 @@ public class NetworkUtils { * @param hostAddress an int coding for an IPv4 address, where higher-order int byte is * lower-order IPv4 address byte */ - public static InetAddress intToInet4AddressHTL(int hostAddress) { + public static Inet4Address intToInet4AddressHTL(int hostAddress) { return intToInet4AddressHTH(Integer.reverseBytes(hostAddress)); } @@ -157,14 +169,14 @@ public class NetworkUtils { * Convert a IPv4 address from an integer to an InetAddress (0x01020304 -> 1.2.3.4) * @param hostAddress an int coding for an IPv4 address */ - public static InetAddress intToInet4AddressHTH(int hostAddress) { + public static Inet4Address intToInet4AddressHTH(int hostAddress) { byte[] addressBytes = { (byte) (0xff & (hostAddress >> 24)), (byte) (0xff & (hostAddress >> 16)), (byte) (0xff & (hostAddress >> 8)), (byte) (0xff & hostAddress) }; try { - return InetAddress.getByAddress(addressBytes); + return (Inet4Address) InetAddress.getByAddress(addressBytes); } catch (UnknownHostException e) { throw new AssertionError(); } @@ -397,6 +409,28 @@ public class NetworkUtils { } /** + * Get a prefix mask as Inet4Address for a given prefix length. + * + * <p>For example 20 -> 255.255.240.0 + */ + public static Inet4Address getPrefixMaskAsInet4Address(int prefixLength) + throws IllegalArgumentException { + return intToInet4AddressHTH(prefixLengthToV4NetmaskIntHTH(prefixLength)); + } + + /** + * Get the broadcast address for a given prefix. + * + * <p>For example 192.168.0.1/24 -> 192.168.0.255 + */ + public static Inet4Address getBroadcastAddress(Inet4Address addr, int prefixLength) + throws IllegalArgumentException { + final int intBroadcastAddr = inet4AddressToIntHTH(addr) + | ~prefixLengthToV4NetmaskIntHTH(prefixLength); + return intToInet4AddressHTH(intBroadcastAddr); + } + + /** * Check if IP address type is consistent between two InetAddress. * @return true if both are the same type. False otherwise. */ diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java index 067e8493717b..732d3778ec6d 100644 --- a/core/java/android/os/ZygoteProcess.java +++ b/core/java/android/os/ZygoteProcess.java @@ -384,7 +384,10 @@ public class ZygoteProcess { argsForZygote.add("--mount-external-read"); } else if (mountExternal == Zygote.MOUNT_EXTERNAL_WRITE) { argsForZygote.add("--mount-external-write"); + } else if (mountExternal == Zygote.MOUNT_EXTERNAL_FULL) { + argsForZygote.add("--mount-external-full"); } + argsForZygote.add("--target-sdk-version=" + targetSdkVersion); // --setgroups is a comma-separated list diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index c037cd062b82..a7f14b6bf0c6 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -44,6 +44,7 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put("settings_audio_switcher", "true"); DEFAULT_FLAGS.put("settings_systemui_theme", "true"); DEFAULT_FLAGS.put("settings_dynamic_homepage", "false"); + DEFAULT_FLAGS.put("settings_mobile_network_v2", "false"); DEFAULT_FLAGS.put(HEARING_AID_SETTINGS, "true"); } diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 8fa8ef88a3ad..35ff6cc23499 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -16,8 +16,6 @@ package android.widget; -import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; - import android.annotation.ColorInt; import android.annotation.DimenRes; import android.annotation.NonNull; @@ -364,29 +362,12 @@ public class RemoteViews implements Parcelable, Filter { /** @hide */ public static class OnClickHandler { - private int mEnterAnimationId; - @UnsupportedAppUsage - public boolean onClickHandler(View view, PendingIntent pendingIntent, - Intent fillInIntent) { - return onClickHandler(view, pendingIntent, fillInIntent, WINDOWING_MODE_UNDEFINED); - } - - public boolean onClickHandler(View view, PendingIntent pendingIntent, - Intent fillInIntent, int windowingMode) { + public boolean onClickHandler(View view, PendingIntent pendingIntent, Intent fillInIntent) { try { // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT? Context context = view.getContext(); - ActivityOptions opts; - if (mEnterAnimationId != 0) { - opts = ActivityOptions.makeCustomAnimation(context, mEnterAnimationId, 0); - } else { - opts = ActivityOptions.makeBasic(); - } - - if (windowingMode != WINDOWING_MODE_UNDEFINED) { - opts.setLaunchWindowingMode(windowingMode); - } + ActivityOptions opts = getActivityOptions(context); context.startIntentSender( pendingIntent.getIntentSender(), fillInIntent, Intent.FLAG_ACTIVITY_NEW_TASK, @@ -402,8 +383,26 @@ public class RemoteViews implements Parcelable, Filter { return true; } - public void setEnterAnimationId(int enterAnimationId) { - mEnterAnimationId = enterAnimationId; + /** @hide */ + protected ActivityOptions getActivityOptions(Context context) { + if (context.getResources().getBoolean( + com.android.internal.R.bool.config_overrideRemoteViewsActivityTransition)) { + TypedArray windowStyle = context.getTheme().obtainStyledAttributes( + com.android.internal.R.styleable.Window); + int windowAnimations = windowStyle.getResourceId( + com.android.internal.R.styleable.Window_windowAnimationStyle, 0); + TypedArray windowAnimationStyle = context.obtainStyledAttributes( + windowAnimations, com.android.internal.R.styleable.WindowAnimation); + int enterAnimationId = windowAnimationStyle.getResourceId(com.android.internal.R + .styleable.WindowAnimation_activityOpenRemoteViewsEnterAnimation, 0); + windowStyle.recycle(); + windowAnimationStyle.recycle(); + + if (enterAnimationId != 0) { + return ActivityOptions.makeCustomAnimation(context, enterAnimationId, 0); + } + } + return ActivityOptions.makeBasic(); } } @@ -3324,8 +3323,6 @@ public class RemoteViews implements Parcelable, Filter { RemoteViews rvToApply = getRemoteViewsToApply(context); View result = inflateView(context, rvToApply, parent); - loadTransitionOverride(context, handler); - rvToApply.performApply(result, parent, handler); return result; @@ -3355,24 +3352,6 @@ public class RemoteViews implements Parcelable, Filter { return v; } - private static void loadTransitionOverride(Context context, - RemoteViews.OnClickHandler handler) { - if (handler != null && context.getResources().getBoolean( - com.android.internal.R.bool.config_overrideRemoteViewsActivityTransition)) { - TypedArray windowStyle = context.getTheme().obtainStyledAttributes( - com.android.internal.R.styleable.Window); - int windowAnimations = windowStyle.getResourceId( - com.android.internal.R.styleable.Window_windowAnimationStyle, 0); - TypedArray windowAnimationStyle = context.obtainStyledAttributes( - windowAnimations, com.android.internal.R.styleable.WindowAnimation); - handler.setEnterAnimationId(windowAnimationStyle.getResourceId( - com.android.internal.R.styleable. - WindowAnimation_activityOpenRemoteViewsEnterAnimation, 0)); - windowStyle.recycle(); - windowAnimationStyle.recycle(); - } - } - /** * Implement this interface to receive a callback when * {@link #applyAsync} or {@link #reapplyAsync} is finished. @@ -3445,7 +3424,6 @@ public class RemoteViews implements Parcelable, Filter { mHandler = handler; mResult = result; - loadTransitionOverride(context, handler); } @Override diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java index f89a9d990b76..0c6f832ff33a 100644 --- a/core/java/com/android/internal/content/FileSystemProvider.java +++ b/core/java/com/android/internal/content/FileSystemProvider.java @@ -374,8 +374,12 @@ public abstract class FileSystemProvider extends DocumentsProvider { final File parent = getFileForDocId(parentDocumentId); final MatrixCursor result = new DirectoryCursor( resolveProjection(projection), parentDocumentId, parent); - for (File file : parent.listFiles()) { - includeFile(result, null, file); + if (parent.isDirectory()) { + for (File file : FileUtils.listFilesOrEmpty(parent)) { + includeFile(result, null, file); + } + } else { + Log.w(TAG, "parentDocumentId '" + parentDocumentId + "' is not Directory"); } return result; } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index c65dd6fc1bb2..07f74f0506b2 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -6724,7 +6724,7 @@ public class BatteryStatsImpl extends BatteryStats { int mWifiBatchedScanBinStarted = NO_BATCHED_SCAN_STARTED; StopwatchTimer[] mWifiBatchedScanTimer; - boolean mWifiMulticastEnabled; + int mWifiMulticastWakelockCount; StopwatchTimer mWifiMulticastTimer; StopwatchTimer mAudioTurnedOnTimer; @@ -7183,8 +7183,7 @@ public class BatteryStatsImpl extends BatteryStats { @Override public void noteWifiMulticastEnabledLocked(long elapsedRealtimeMs) { - if (!mWifiMulticastEnabled) { - mWifiMulticastEnabled = true; + if (mWifiMulticastWakelockCount == 0) { if (mWifiMulticastTimer == null) { mWifiMulticastTimer = new StopwatchTimer(mBsi.mClocks, Uid.this, WIFI_MULTICAST_ENABLED, mBsi.mWifiMulticastTimers, mBsi.mOnBatteryTimeBase); @@ -7194,12 +7193,17 @@ public class BatteryStatsImpl extends BatteryStats { StatsLog.WIFI_MULTICAST_LOCK_STATE_CHANGED, getUid(), null, StatsLog.WIFI_MULTICAST_LOCK_STATE_CHANGED__STATE__ON); } + mWifiMulticastWakelockCount++; } @Override public void noteWifiMulticastDisabledLocked(long elapsedRealtimeMs) { - if (mWifiMulticastEnabled) { - mWifiMulticastEnabled = false; + if (mWifiMulticastWakelockCount == 0) { + return; + } + + mWifiMulticastWakelockCount--; + if (mWifiMulticastWakelockCount == 0) { mWifiMulticastTimer.stopRunningLocked(elapsedRealtimeMs); StatsLog.write_non_chained( StatsLog.WIFI_MULTICAST_LOCK_STATE_CHANGED, getUid(), null, @@ -7932,7 +7936,7 @@ public class BatteryStatsImpl extends BatteryStats { } if (mWifiMulticastTimer != null) { active |= !mWifiMulticastTimer.reset(false); - active |= mWifiMulticastEnabled; + active |= (mWifiMulticastWakelockCount > 0); } active |= !resetIfNotNull(mAudioTurnedOnTimer, false); @@ -8590,7 +8594,7 @@ public class BatteryStatsImpl extends BatteryStats { mWifiBatchedScanTimer[i] = null; } } - mWifiMulticastEnabled = false; + mWifiMulticastWakelockCount = 0; if (in.readInt() != 0) { mWifiMulticastTimer = new StopwatchTimer(mBsi.mClocks, Uid.this, WIFI_MULTICAST_ENABLED, mBsi.mWifiMulticastTimers, mBsi.mOnBatteryTimeBase, in); @@ -14031,7 +14035,7 @@ public class BatteryStatsImpl extends BatteryStats { u.mWifiBatchedScanTimer[i].readSummaryFromParcelLocked(in); } } - u.mWifiMulticastEnabled = false; + u.mWifiMulticastWakelockCount = 0; if (in.readInt() != 0) { u.mWifiMulticastTimer.readSummaryFromParcelLocked(in); } diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index 413f89da571e..927322e97e28 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -81,6 +81,8 @@ public final class Zygote { public static final int MOUNT_EXTERNAL_READ = IVold.REMOUNT_MODE_READ; /** Read-write external storage should be mounted. */ public static final int MOUNT_EXTERNAL_WRITE = IVold.REMOUNT_MODE_WRITE; + /** Read-write external storage should be mounted instead of package sandbox */ + public static final int MOUNT_EXTERNAL_FULL = IVold.REMOUNT_MODE_FULL; private static final ZygoteHooks VM_HOOKS = new ZygoteHooks(); diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index b9c717f03749..b60b43a8d45d 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -644,7 +644,9 @@ class ZygoteConnection { mountExternal = Zygote.MOUNT_EXTERNAL_READ; } else if (arg.equals("--mount-external-write")) { mountExternal = Zygote.MOUNT_EXTERNAL_WRITE; - } else if (arg.equals("--query-abi-list")) { + } else if (arg.equals("--mount-external-full")) { + mountExternal = Zygote.MOUNT_EXTERNAL_FULL; + } else if (arg.equals("--query-abi-list")) { abiListQuery = true; } else if (arg.equals("--get-pid")) { pidQuery = true; diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp index 823f1cc36225..9b138ebb760a 100644 --- a/core/jni/android_net_NetUtils.cpp +++ b/core/jni/android_net_NetUtils.cpp @@ -323,6 +323,55 @@ static jboolean android_net_utils_queryUserAccess(JNIEnv *env, jobject thiz, jin return (jboolean) !queryUserAccess(uid, netId); } +static bool checkLenAndCopy(JNIEnv* env, const jbyteArray& addr, int len, void* dst) +{ + if (env->GetArrayLength(addr) != len) { + return false; + } + env->GetByteArrayRegion(addr, 0, len, reinterpret_cast<jbyte*>(dst)); + return true; +} + +static void android_net_utils_addArpEntry(JNIEnv *env, jobject thiz, jbyteArray ethAddr, + jbyteArray ipv4Addr, jstring ifname, jobject javaFd) +{ + struct arpreq req = {}; + struct sockaddr_in& netAddrStruct = *reinterpret_cast<sockaddr_in*>(&req.arp_pa); + struct sockaddr& ethAddrStruct = req.arp_ha; + + ethAddrStruct.sa_family = ARPHRD_ETHER; + if (!checkLenAndCopy(env, ethAddr, ETH_ALEN, ethAddrStruct.sa_data)) { + jniThrowException(env, "java/io/IOException", "Invalid ethAddr length"); + return; + } + + netAddrStruct.sin_family = AF_INET; + if (!checkLenAndCopy(env, ipv4Addr, sizeof(in_addr), &netAddrStruct.sin_addr)) { + jniThrowException(env, "java/io/IOException", "Invalid ipv4Addr length"); + return; + } + + int ifLen = env->GetStringLength(ifname); + // IFNAMSIZ includes the terminating NULL character + if (ifLen >= IFNAMSIZ) { + jniThrowException(env, "java/io/IOException", "ifname too long"); + return; + } + env->GetStringUTFRegion(ifname, 0, ifLen, req.arp_dev); + + req.arp_flags = ATF_COM; // Completed entry (ha valid) + int fd = jniGetFDFromFileDescriptor(env, javaFd); + if (fd < 0) { + jniThrowExceptionFmt(env, "java/io/IOException", "Invalid file descriptor"); + return; + } + // See also: man 7 arp + if (ioctl(fd, SIOCSARP, &req)) { + jniThrowExceptionFmt(env, "java/io/IOException", "ioctl error: %s", strerror(errno)); + return; + } +} + // ---------------------------------------------------------------------------- @@ -337,6 +386,7 @@ static const JNINativeMethod gNetworkUtilMethods[] = { { "bindSocketToNetwork", "(II)I", (void*) android_net_utils_bindSocketToNetwork }, { "protectFromVpn", "(I)Z", (void*)android_net_utils_protectFromVpn }, { "queryUserAccess", "(II)Z", (void*)android_net_utils_queryUserAccess }, + { "addArpEntry", "([B[BLjava/lang/String;Ljava/io/FileDescriptor;)V", (void*) android_net_utils_addArpEntry }, { "attachDhcpFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDhcpFilter }, { "attachRaFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachRaFilter }, { "attachControlPacketFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachControlPacketFilter }, diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 19691e2cfbe3..364393e1c649 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -93,6 +93,7 @@ enum MountExternalKind { MOUNT_EXTERNAL_DEFAULT = 1, MOUNT_EXTERNAL_READ = 2, MOUNT_EXTERNAL_WRITE = 3, + MOUNT_EXTERNAL_FULL = 4, }; static void RuntimeAbort(JNIEnv* env, int line, const char* msg) { @@ -416,7 +417,7 @@ static bool MountEmulatedStorage(uid_t uid, jint mount_mode, storageSource = "/mnt/runtime/read"; } else if (mount_mode == MOUNT_EXTERNAL_WRITE) { storageSource = "/mnt/runtime/write"; - } else if (!force_mount_namespace) { + } else if (mount_mode != MOUNT_EXTERNAL_FULL && !force_mount_namespace) { // Sane default of no storage visible return true; } @@ -433,19 +434,44 @@ static bool MountEmulatedStorage(uid_t uid, jint mount_mode, } if (GetBoolProperty(kIsolatedStorage, false)) { - if (package_name == nullptr) { - return true; - } - - std::string pkgSandboxDir("/mnt/user"); - if (!createPkgSandbox(uid, package_name, pkgSandboxDir, error_msg)) { - return false; - } - if (TEMP_FAILURE_RETRY(mount(pkgSandboxDir.c_str(), "/storage", - nullptr, MS_BIND | MS_REC | MS_SLAVE, nullptr)) == -1) { - *error_msg = CREATE_ERROR("Failed to mount %s to /storage: %s", - pkgSandboxDir.c_str(), strerror(errno)); - return false; + if (mount_mode == MOUNT_EXTERNAL_FULL) { + storageSource = "/mnt/runtime/write"; + if (TEMP_FAILURE_RETRY(mount(storageSource.string(), "/storage", + NULL, MS_BIND | MS_REC | MS_SLAVE, NULL)) == -1) { + *error_msg = CREATE_ERROR("Failed to mount %s to /storage: %s", + storageSource.string(), + strerror(errno)); + return false; + } + + // Mount user-specific symlink helper into place + userid_t user_id = multiuser_get_user_id(uid); + const String8 userSource(String8::format("/mnt/user/%d", user_id)); + if (fs_prepare_dir(userSource.string(), 0751, 0, 0) == -1) { + *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", userSource.string()); + return false; + } + if (TEMP_FAILURE_RETRY(mount(userSource.string(), "/storage/self", + NULL, MS_BIND, NULL)) == -1) { + *error_msg = CREATE_ERROR("Failed to mount %s to /storage/self: %s", + userSource.string(), + strerror(errno)); + return false; + } + } else { + if (package_name == nullptr) { + return true; + } + std::string pkgSandboxDir("/mnt/user"); + if (!createPkgSandbox(uid, package_name, pkgSandboxDir, error_msg)) { + return false; + } + if (TEMP_FAILURE_RETRY(mount(pkgSandboxDir.c_str(), "/storage", + nullptr, MS_BIND | MS_REC | MS_SLAVE, nullptr)) == -1) { + *error_msg = CREATE_ERROR("Failed to mount %s to /storage: %s", + pkgSandboxDir.c_str(), strerror(errno)); + return false; + } } } else { if (TEMP_FAILURE_RETRY(mount(storageSource.string(), "/storage", diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index c6931aa97a8b..c664abf55100 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3517,4 +3517,11 @@ <!-- Whether or not battery saver should be "sticky" when manually enabled. --> <bool name="config_batterySaverStickyBehaviourDisabled">false</bool> + + <!-- Model of potentially misprovisioned devices. If none is specified in an overlay, an + empty string is passed in. --> + <string name="config_misprovisionedDeviceModel" translatable="false"></string> + + <!-- Brand value for attestation of misprovisioned device. --> + <string name="config_misprovisionedBrandValue" translatable="false"></string> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 7fbad164add1..9cf6ac6a61af 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3446,4 +3446,7 @@ <java-symbol type="array" name="config_disableApksUnlessMatchedSku_apk_list" /> <java-symbol type="array" name="config_disableApkUnlessMatchedSku_skus_list" /> + + <java-symbol type="string" name="config_misprovisionedDeviceModel" /> + <java-symbol type="string" name="config_misprovisionedBrandValue" /> </resources> diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java index a8094ead2972..613de45173d6 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java @@ -24,6 +24,7 @@ import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; @@ -302,6 +303,113 @@ public class BatteryStatsImplTest { assertArrayEquals(expected, mBatteryStatsImpl.addCpuTimes(timesA, timesB)); } + @Test + public void testMulticastWakelockAcqRel() { + final int testUid = 10032; + final int acquireTimeMs = 1000; + final int releaseTimeMs = 1005; + final int currentTimeMs = 1011; + + mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0); + + // Create a Uid Object + final BatteryStats.Uid u = mBatteryStatsImpl.getUidStatsLocked(testUid); + assertNotNull(u); + + // Acquire and release the lock + u.noteWifiMulticastEnabledLocked(acquireTimeMs); + u.noteWifiMulticastDisabledLocked(releaseTimeMs); + + // Get the total acquisition time + long totalTime = u.getWifiMulticastTime(currentTimeMs*1000, + BatteryStats.STATS_SINCE_UNPLUGGED); + assertEquals("Miscalculations of Multicast wakelock acquisition time", + (releaseTimeMs - acquireTimeMs) * 1000, totalTime); + } + + @Test + public void testMulticastWakelockAcqNoRel() { + final int testUid = 10032; + final int acquireTimeMs = 1000; + final int currentTimeMs = 1011; + + mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0); + + // Create a Uid Object + final BatteryStats.Uid u = mBatteryStatsImpl.getUidStatsLocked(testUid); + assertNotNull(u); + + // Acquire the lock + u.noteWifiMulticastEnabledLocked(acquireTimeMs); + + // Get the total acquisition time + long totalTime = u.getWifiMulticastTime(currentTimeMs*1000, + BatteryStats.STATS_SINCE_UNPLUGGED); + assertEquals("Miscalculations of Multicast wakelock acquisition time", + (currentTimeMs - acquireTimeMs) * 1000, totalTime); + } + + @Test + public void testMulticastWakelockAcqAcqRelRel() { + final int testUid = 10032; + final int acquireTimeMs_1 = 1000; + final int acquireTimeMs_2 = 1002; + + final int releaseTimeMs_1 = 1005; + final int releaseTimeMs_2 = 1009; + final int currentTimeMs = 1011; + + mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0); + + // Create a Uid Object + final BatteryStats.Uid u = mBatteryStatsImpl.getUidStatsLocked(testUid); + assertNotNull(u); + + // Acquire and release the lock (twice in nested way) + u.noteWifiMulticastEnabledLocked(acquireTimeMs_1); + u.noteWifiMulticastEnabledLocked(acquireTimeMs_2); + + u.noteWifiMulticastDisabledLocked(releaseTimeMs_1); + u.noteWifiMulticastDisabledLocked(releaseTimeMs_2); + + // Get the total acquisition time + long totalTime = u.getWifiMulticastTime(currentTimeMs*1000, + BatteryStats.STATS_SINCE_UNPLUGGED); + assertEquals("Miscalculations of Multicast wakelock acquisition time", + (releaseTimeMs_2 - acquireTimeMs_1) * 1000, totalTime); + } + + @Test + public void testMulticastWakelockAcqRelAcqRel() { + final int testUid = 10032; + final int acquireTimeMs_1 = 1000; + final int acquireTimeMs_2 = 1005; + + final int releaseTimeMs_1 = 1002; + final int releaseTimeMs_2 = 1009; + final int currentTimeMs = 1011; + + mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0); + + // Create a Uid Object + final BatteryStats.Uid u = mBatteryStatsImpl.getUidStatsLocked(testUid); + assertNotNull(u); + + // Acquire and release the lock (twice) + u.noteWifiMulticastEnabledLocked(acquireTimeMs_1); + u.noteWifiMulticastDisabledLocked(releaseTimeMs_1); + + u.noteWifiMulticastEnabledLocked(acquireTimeMs_2); + u.noteWifiMulticastDisabledLocked(releaseTimeMs_2); + + // Get the total acquisition time + long totalTime = u.getWifiMulticastTime(currentTimeMs*1000, + BatteryStats.STATS_SINCE_UNPLUGGED); + assertEquals("Miscalculations of Multicast wakelock acquisition time", + ((releaseTimeMs_1 - acquireTimeMs_1) + (releaseTimeMs_2 - acquireTimeMs_2)) + * 1000, totalTime); + } + private void addIsolatedUid(int parentUid, int childUid) { final BatteryStatsImpl.Uid u = mBatteryStatsImpl.getUidStatsLocked(parentUid); u.addIsolatedUid(childUid); diff --git a/keystore/java/android/security/keystore/AttestationUtils.java b/keystore/java/android/security/keystore/AttestationUtils.java index 1be8309bcf5a..3d2a2718a9c8 100644 --- a/keystore/java/android/security/keystore/AttestationUtils.java +++ b/keystore/java/android/security/keystore/AttestationUtils.java @@ -22,9 +22,9 @@ import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.TestApi; import android.content.Context; +import android.content.res.Resources; import android.os.Build; import android.security.KeyStore; -import android.security.KeyStoreException; import android.security.keymaster.KeymasterArguments; import android.security.keymaster.KeymasterCertificateChain; import android.security.keymaster.KeymasterDefs; @@ -117,6 +117,37 @@ public abstract class AttestationUtils { @NonNull public static KeymasterArguments prepareAttestationArguments(Context context, @NonNull int[] idTypes, @NonNull byte[] attestationChallenge) throws DeviceIdAttestationException { + return prepareAttestationArguments(context, idTypes,attestationChallenge, Build.BRAND); + } + + /** + * Prepares Keymaster Arguments with attestation data for misprovisioned Pixel 2 device. + * See http://go/keyAttestationFailure and http://b/69471841 for more info. + * @hide should only be used by KeyChain. + */ + @NonNull public static KeymasterArguments prepareAttestationArgumentsIfMisprovisioned( + Context context, @NonNull int[] idTypes, @NonNull byte[] attestationChallenge) throws + DeviceIdAttestationException { + if (!isPotentiallyMisprovisionedDevice(context)) { + return null; + } + Resources resources = context.getResources(); + String misprovisionedBrand = resources.getString( + com.android.internal.R.string.config_misprovisionedBrandValue); + return prepareAttestationArguments( + context, idTypes, attestationChallenge, misprovisionedBrand); + } + + @NonNull private static boolean isPotentiallyMisprovisionedDevice(Context context) { + Resources resources = context.getResources(); + String misprovisionedModel = resources.getString( + com.android.internal.R.string.config_misprovisionedDeviceModel); + return (Build.MODEL.equals(misprovisionedModel)); + } + + @NonNull private static KeymasterArguments prepareAttestationArguments(Context context, + @NonNull int[] idTypes, @NonNull byte[] attestationChallenge, String brand) throws + DeviceIdAttestationException { // Check method arguments, retrieve requested device IDs and prepare attestation arguments. if (attestationChallenge == null) { throw new NullPointerException("Missing attestation challenge"); @@ -169,7 +200,7 @@ public abstract class AttestationUtils { } } attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_BRAND, - Build.BRAND.getBytes(StandardCharsets.UTF_8)); + brand.getBytes(StandardCharsets.UTF_8)); attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_DEVICE, Build.DEVICE.getBytes(StandardCharsets.UTF_8)); attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_PRODUCT, diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index 76fc9e350ecc..d45acdfa627e 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -405,7 +405,7 @@ import java.util.Map; <p> The codec in turn will return a read-only output buffer via the {@link Callback#onOutputBufferAvailable onOutputBufferAvailable} callback in asynchronous mode, or in - response to a {@link #dequeueOutputBuffer dequeuOutputBuffer} call in synchronous mode. After the + response to a {@link #dequeueOutputBuffer dequeueOutputBuffer} call in synchronous mode. After the output buffer has been processed, call one of the {@link #releaseOutputBuffer releaseOutputBuffer} methods to return the buffer to the codec. <p> diff --git a/packages/PackageInstaller/res/drawable/ic_file_download.xml b/packages/PackageInstaller/res/drawable/ic_file_download.xml index 7ea91f5baa2d..16c6080b46d5 100644 --- a/packages/PackageInstaller/res/drawable/ic_file_download.xml +++ b/packages/PackageInstaller/res/drawable/ic_file_download.xml @@ -18,7 +18,8 @@ android:width="24dp" android:height="24dp" android:viewportWidth="24" - android:viewportHeight="24"> + android:viewportHeight="24" + android:tint="?android:attr/colorControlNormal"> <path android:fillColor="#000000" diff --git a/packages/SettingsLib/res/color/dark_mode_icon_color_dual_tone_background.xml b/packages/SettingsLib/res/color/dark_mode_icon_color_dual_tone_background.xml index c8a80ac57c00..6b45a479836e 100644 --- a/packages/SettingsLib/res/color/dark_mode_icon_color_dual_tone_background.xml +++ b/packages/SettingsLib/res/color/dark_mode_icon_color_dual_tone_background.xml @@ -14,5 +14,5 @@ limitations under the License. --> <selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:alpha="0.24" android:color="?android:attr/colorBackground" /> + <item android:alpha="0.24" android:color="@android:color/black" /> </selector>
\ No newline at end of file diff --git a/packages/SettingsLib/res/color/dark_mode_icon_color_dual_tone_fill.xml b/packages/SettingsLib/res/color/dark_mode_icon_color_dual_tone_fill.xml index 8dcfdbb8cf1e..5ef085b32dd6 100644 --- a/packages/SettingsLib/res/color/dark_mode_icon_color_dual_tone_fill.xml +++ b/packages/SettingsLib/res/color/dark_mode_icon_color_dual_tone_fill.xml @@ -14,5 +14,5 @@ limitations under the License. --> <selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:alpha="0.47" android:color="?android:attr/colorBackground" /> + <item android:alpha="0.47" android:color="@android:color/black" /> </selector>
\ No newline at end of file diff --git a/packages/SettingsLib/res/color/light_mode_icon_color_dual_tone_background.xml b/packages/SettingsLib/res/color/light_mode_icon_color_dual_tone_background.xml index 34de5489a28b..81f63a48fb9a 100644 --- a/packages/SettingsLib/res/color/light_mode_icon_color_dual_tone_background.xml +++ b/packages/SettingsLib/res/color/light_mode_icon_color_dual_tone_background.xml @@ -14,5 +14,5 @@ limitations under the License. --> <selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:alpha="0.3" android:color="?android:attr/colorForeground" /> + <item android:alpha="0.3" android:color="@android:color/white" /> </selector>
\ No newline at end of file diff --git a/packages/SettingsLib/res/color/light_mode_icon_color_dual_tone_fill.xml b/packages/SettingsLib/res/color/light_mode_icon_color_dual_tone_fill.xml index 15944c3a2a07..61f4e26cf00b 100644 --- a/packages/SettingsLib/res/color/light_mode_icon_color_dual_tone_fill.xml +++ b/packages/SettingsLib/res/color/light_mode_icon_color_dual_tone_fill.xml @@ -14,5 +14,5 @@ limitations under the License. --> <selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:color="?android:attr/colorForeground" /> + <item android:color="@android:color/white" /> </selector>
\ No newline at end of file diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java index a91c45dd59af..a84222909072 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java @@ -57,9 +57,7 @@ public interface BluetoothCallback { default void onDeviceAdded(CachedBluetoothDevice cachedDevice) {} /** - * It will be called when a remote device that was - * found in the last discovery and is not found in the current discovery. - * It is listening {@link android.bluetooth.BluetoothDevice#ACTION_DISAPPEARED} + * It will be called when requiring to remove a remote device from CachedBluetoothDevice list * * @param cachedDevice the Bluetooth device. */ diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java index 049761836541..25e683100ee8 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java @@ -81,7 +81,6 @@ public class BluetoothEventManager { addHandler(BluetoothAdapter.ACTION_DISCOVERY_STARTED, new ScanningStateChangedHandler(true)); addHandler(BluetoothAdapter.ACTION_DISCOVERY_FINISHED, new ScanningStateChangedHandler(false)); addHandler(BluetoothDevice.ACTION_FOUND, new DeviceFoundHandler()); - addHandler(BluetoothDevice.ACTION_DISAPPEARED, new DeviceDisappearedHandler()); addHandler(BluetoothDevice.ACTION_NAME_CHANGED, new NameChangedHandler()); addHandler(BluetoothDevice.ACTION_ALIAS_CHANGED, new NameChangedHandler()); @@ -106,6 +105,7 @@ public class BluetoothEventManager { new AudioModeChangedHandler()); mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter, null, mReceiverHandler); + mContext.registerReceiver(mProfileBroadcastReceiver, mProfileIntentFilter, null, mReceiverHandler); } public void setReceiverHandler(android.os.Handler handler) { @@ -298,24 +298,6 @@ public class BluetoothEventManager { } } - private class DeviceDisappearedHandler implements Handler { - public void onReceive(Context context, Intent intent, - BluetoothDevice device) { - CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); - if (cachedDevice == null) { - Log.w(TAG, "received ACTION_DISAPPEARED for an unknown device: " + device); - return; - } - if (CachedBluetoothDeviceManager.onDeviceDisappeared(cachedDevice)) { - synchronized (mCallbacks) { - for (BluetoothCallback callback : mCallbacks) { - callback.onDeviceDeleted(cachedDevice); - } - } - } - } - } - private class NameChangedHandler implements Handler { public void onReceive(Context context, Intent intent, BluetoothDevice device) { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index 33ee56991932..68e1dfcfe959 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -72,14 +72,6 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> private final Collection<Callback> mCallbacks = new ArrayList<Callback>(); - // Following constants indicate the user's choices of Phone book/message access settings - // User hasn't made any choice or settings app has wiped out the memory - public final static int ACCESS_UNKNOWN = 0; - // User has accepted the connection and let Settings app remember the decision - public final static int ACCESS_ALLOWED = 1; - // User has rejected the connection and let Settings app remember the decision - public final static int ACCESS_REJECTED = 2; - // How many times user should reject the connection to make the choice persist. private final static int MESSAGE_REJECTION_COUNT_LIMIT_TO_PERSIST = 2; @@ -659,9 +651,9 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> void onBondingStateChanged(int bondState) { if (bondState == BluetoothDevice.BOND_NONE) { mProfiles.clear(); - setPhonebookPermissionChoice(ACCESS_UNKNOWN); - setMessagePermissionChoice(ACCESS_UNKNOWN); - setSimPermissionChoice(ACCESS_UNKNOWN); + mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_UNKNOWN); + mDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_UNKNOWN); + mDevice.setSimAccessPermission(BluetoothDevice.ACCESS_UNKNOWN); mMessageRejectionCount = 0; saveMessageRejectionCount(); } @@ -767,26 +759,6 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> void onDeviceAttributesChanged(); } - public int getPhonebookPermissionChoice() { - int permission = mDevice.getPhonebookAccessPermission(); - if (permission == BluetoothDevice.ACCESS_ALLOWED) { - return ACCESS_ALLOWED; - } else if (permission == BluetoothDevice.ACCESS_REJECTED) { - return ACCESS_REJECTED; - } - return ACCESS_UNKNOWN; - } - - public void setPhonebookPermissionChoice(int permissionChoice) { - int permission = BluetoothDevice.ACCESS_UNKNOWN; - if (permissionChoice == ACCESS_ALLOWED) { - permission = BluetoothDevice.ACCESS_ALLOWED; - } else if (permissionChoice == ACCESS_REJECTED) { - permission = BluetoothDevice.ACCESS_REJECTED; - } - mDevice.setPhonebookAccessPermission(permission); - } - // Migrates data from old data store (in Settings app's shared preferences) to new (in Bluetooth // app's shared preferences). private void migratePhonebookPermissionChoice() { @@ -797,10 +769,11 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> } if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_UNKNOWN) { - int oldPermission = preferences.getInt(mDevice.getAddress(), ACCESS_UNKNOWN); - if (oldPermission == ACCESS_ALLOWED) { + int oldPermission = + preferences.getInt(mDevice.getAddress(), BluetoothDevice.ACCESS_UNKNOWN); + if (oldPermission == BluetoothDevice.ACCESS_ALLOWED) { mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED); - } else if (oldPermission == ACCESS_REJECTED) { + } else if (oldPermission == BluetoothDevice.ACCESS_REJECTED) { mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED); } } @@ -810,46 +783,6 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> editor.commit(); } - public int getMessagePermissionChoice() { - int permission = mDevice.getMessageAccessPermission(); - if (permission == BluetoothDevice.ACCESS_ALLOWED) { - return ACCESS_ALLOWED; - } else if (permission == BluetoothDevice.ACCESS_REJECTED) { - return ACCESS_REJECTED; - } - return ACCESS_UNKNOWN; - } - - public void setMessagePermissionChoice(int permissionChoice) { - int permission = BluetoothDevice.ACCESS_UNKNOWN; - if (permissionChoice == ACCESS_ALLOWED) { - permission = BluetoothDevice.ACCESS_ALLOWED; - } else if (permissionChoice == ACCESS_REJECTED) { - permission = BluetoothDevice.ACCESS_REJECTED; - } - mDevice.setMessageAccessPermission(permission); - } - - public int getSimPermissionChoice() { - int permission = mDevice.getSimAccessPermission(); - if (permission == BluetoothDevice.ACCESS_ALLOWED) { - return ACCESS_ALLOWED; - } else if (permission == BluetoothDevice.ACCESS_REJECTED) { - return ACCESS_REJECTED; - } - return ACCESS_UNKNOWN; - } - - void setSimPermissionChoice(int permissionChoice) { - int permission = BluetoothDevice.ACCESS_UNKNOWN; - if (permissionChoice == ACCESS_ALLOWED) { - permission = BluetoothDevice.ACCESS_ALLOWED; - } else if (permissionChoice == ACCESS_REJECTED) { - permission = BluetoothDevice.ACCESS_REJECTED; - } - mDevice.setSimAccessPermission(permission); - } - // Migrates data from old data store (in Settings app's shared preferences) to new (in Bluetooth // app's shared preferences). private void migrateMessagePermissionChoice() { @@ -860,10 +793,11 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> } if (mDevice.getMessageAccessPermission() == BluetoothDevice.ACCESS_UNKNOWN) { - int oldPermission = preferences.getInt(mDevice.getAddress(), ACCESS_UNKNOWN); - if (oldPermission == ACCESS_ALLOWED) { + int oldPermission = + preferences.getInt(mDevice.getAddress(), BluetoothDevice.ACCESS_UNKNOWN); + if (oldPermission == BluetoothDevice.ACCESS_ALLOWED) { mDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_ALLOWED); - } else if (oldPermission == ACCESS_REJECTED) { + } else if (oldPermission == BluetoothDevice.ACCESS_REJECTED) { mDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_REJECTED); } } @@ -908,14 +842,14 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> if (BluetoothUuid.containsAnyUuid(uuids, PbapServerProfile.PBAB_CLIENT_UUIDS)) { // The pairing dialog now warns of phone-book access for paired devices. // No separate prompt is displayed after pairing. - if (getPhonebookPermissionChoice() == CachedBluetoothDevice.ACCESS_UNKNOWN) { + if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_UNKNOWN) { if (mDevice.getBluetoothClass().getDeviceClass() == BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE || mDevice.getBluetoothClass().getDeviceClass() == BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET) { - setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_ALLOWED); + mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED); } else { - setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_REJECTED); + mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED); } } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java index e05f9fdc1eeb..f223176795b8 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java @@ -241,7 +241,6 @@ public class LocalBluetoothProfileManagerTest { mShadowBluetoothAdapter.setSupportedProfiles(null); mProfileManager = new LocalBluetoothProfileManager(mContext, mLocalBluetoothAdapter, mDeviceManager, mEventManager); - mEventManager.registerProfileIntentReceiver(); // Refer to BluetoothControllerImpl, it will call setReceiverHandler after // LocalBluetoothProfileManager created. mEventManager.setReceiverHandler(null); diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index c9ba26804e89..091350334835 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -32,6 +32,7 @@ java_library { android_library { name: "SystemUI-core", srcs: [ + "src/**/*.kt", "src/**/*.java", "src/**/I*.aidl", ], @@ -73,6 +74,59 @@ android_library { ], } +android_library { + name: "SystemUI-tests", + manifest: "tests/AndroidManifest.xml", + resource_dirs: [ + "tests/res", + "res-keyguard", + "res", + ], + srcs: [ + "tests/src/**/*.kt", + "tests/src/**/*.java", + "src/**/*.kt", + "src/**/*.java", + "src/**/I*.aidl", + ], + static_libs: [ + "SystemUIPluginLib", + "SystemUISharedLib", + "SettingsLib", + "androidx.car_car", + "androidx.legacy_legacy-support-v4", + "androidx.recyclerview_recyclerview", + "androidx.preference_preference", + "androidx.appcompat_appcompat", + "androidx.mediarouter_mediarouter", + "androidx.palette_palette", + "androidx.legacy_legacy-preference-v14", + "androidx.leanback_leanback", + "androidx.slice_slice-core", + "androidx.slice_slice-view", + "androidx.slice_slice-builders", + "androidx.arch.core_core-runtime", + "androidx.lifecycle_lifecycle-extensions", + "SystemUI-tags", + "SystemUI-proto", + "metrics-helper-lib", + "android-support-test", + "mockito-target-inline-minus-junit4", + "testables", + "truth-prebuilt", + ], + libs: [ + "android.test.runner", + "telephony-common", + "android.car", + "android.test.base", + ], + aaptflags: [ + "--extra-packages", + "com.android.keyguard:com.android.systemui", + ], +} + android_app { name: "SystemUI", static_libs: [ diff --git a/packages/SystemUI/docs/kotlin-in-sysui.md b/packages/SystemUI/docs/kotlin-in-sysui.md new file mode 100644 index 000000000000..1bf24f63498b --- /dev/null +++ b/packages/SystemUI/docs/kotlin-in-sysui.md @@ -0,0 +1,22 @@ +# Kotlin in SystemUI + +Queue "it's happening" gif. + +Kotlin is probably going to be a bit of a wild west for a while, but please +try to follow these guidelines as much as possible. + + - No semi-colons: they are optional, we probably don't want them in the + future, so let's just not add them. + - No DSLs: sysui is complicated enough as is, let's not add more layers at + the moment. + - Only use extension functions for keeping complex code locality: Don't use + extension functions to add methods to android classes that you always wished + were there, instead add them directly to the class and save us the extension. + - inline, reified, and de-compisition can all be great things: just make sure + you know what they do and why you are using them. + +# Recommended reading + + - [Kotlin](https://kotlinlang.org/) + - [AndroidX-KTX](https://www.youtube.com/watch?v=st1XVfkDWqk) + - [Performance and Kotlin tricks](https://www.youtube.com/watch?v=6P20npkvcb8) diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 412e89ad169a..7e35f5f70575 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -952,6 +952,7 @@ <dimen name="nav_content_padding">0dp</dimen> <dimen name="nav_quick_scrub_track_edge_padding">24dp</dimen> <dimen name="nav_quick_scrub_track_thickness">10dp</dimen> + <dimen name="nav_home_back_gesture_drag_limit">60dp</dimen> <!-- Navigation bar shadow params. --> <dimen name="nav_key_button_shadow_offset_x">0dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 2deec5e30e32..36f97cd64642 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1038,6 +1038,15 @@ <!-- Message for add user confirmation dialog - short version. [CHAR LIMIT=none] --> <string name="user_add_user_message_short" msgid="1511354412249044381">When you add a new user, that person needs to set up their space.\n\nAny user can update apps for all other users. </string> + <!-- Title for the dialog that lets users know that the maximum allowed number of users on the device has been reached. [CHAR LIMIT=35]--> + <string name="user_limit_reached_title">User limit reached</string> + + <!-- Message that tells people what's the maximum number of uses allowed on the device. [CHAR_LIMIT=NONE]--> + <plurals name="user_limit_reached_message"> + <item quantity="one">Only one user can be created.</item> + <item quantity="other">You can add up to <xliff:g id="count" example="3">%d</xliff:g> users.</item> + </plurals> + <!-- Title of the confirmation dialog for deleting a user [CHAR LIMIT=NONE] --> <string name="user_remove_user_title">Remove user?</string> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 47ec5cd0175d..8442dd13a85b 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -321,12 +321,15 @@ <item name="*android:isLightTheme">false</item> </style> - <style name="Theme.SystemUI.Light" parent="@*android:style/Theme.DeviceDefault.QuickSettings"> + <style name="Theme.SystemUI.Light"> <item name="wallpaperTextColor">@*android:color/primary_text_material_light</item> <item name="wallpaperTextColorSecondary">@*android:color/secondary_text_material_light</item> <item name="android:colorError">@*android:color/error_color_material_light</item> <item name="android:colorControlHighlight">#40000000</item> <item name="passwordStyle">@style/PasswordTheme.Light</item> + + <!-- Needed for MediaRoute chooser dialog --> + <item name="*android:isLightTheme">true</item> </style> <style name="LockPatternStyle"> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java index 9bf781678962..5ffdc7b221f3 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java @@ -145,6 +145,8 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit mLockPatternView = findViewById(R.id.lockPatternView); mLockPatternView.setSaveEnabled(false); mLockPatternView.setOnPatternListener(new UnlockPatternListener()); + mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled( + KeyguardUpdateMonitor.getCurrentUser())); // vibrate mode will be the same for the life of this screen mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled()); diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java index d5383b975211..f6fec5456ed8 100644 --- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java +++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java @@ -82,7 +82,6 @@ public class BatteryMeterView extends LinearLayout implements private int mLightModeBackgroundColor; private int mLightModeFillColor; - private float mDarkIntensity; private int mUser; /** @@ -321,8 +320,6 @@ public class BatteryMeterView extends LinearLayout implements @Override public void onDarkChanged(Rect area, float darkIntensity, int tint) { - mDarkIntensity = darkIntensity; - float intensity = DarkIconDispatcher.isInArea(area, this) ? darkIntensity : 0; mNonAdaptedForegroundColor = getColorForDarkIntensity( intensity, mLightModeFillColor, mDarkModeFillColor); @@ -342,14 +339,6 @@ public class BatteryMeterView extends LinearLayout implements } } - public void setFillColor(int color) { - if (mLightModeFillColor == color) { - return; - } - mLightModeFillColor = color; - onDarkChanged(new Rect(), mDarkIntensity, DarkIconDispatcher.DEFAULT_ICON_TINT); - } - private int getColorForDarkIntensity(float darkIntensity, int lightColor, int darkColor) { return (int) ArgbEvaluator.getInstance().evaluate(darkIntensity, lightColor, darkColor); } diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java index 39e315566daf..acc7b49d9c35 100644 --- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java @@ -274,7 +274,9 @@ public class ImageWallpaper extends WallpaperService { // Sometimes a wallpaper is not large enough to cover the screen in one dimension. // Call updateSurfaceSize -- it will only actually do the update if the dimensions // should change - if (newRotation != mLastRotation) { + if (newRotation != mLastRotation + || mDisplayWidthAtLastSurfaceSizeUpdate != displayInfo.logicalWidth + || mDisplayHeightAtLastSurfaceSizeUpdate != displayInfo.logicalHeight) { // Update surface size (if necessary) if (!updateSurfaceSize(getSurfaceHolder(), displayInfo, true /* forDraw */)) { return; // had to reload wallpaper, will retry later diff --git a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java index ad853f18993e..cafdc10beefa 100644 --- a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java @@ -89,6 +89,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private @InteractionType int mInteractionFlags; private boolean mIsEnabled; private int mCurrentBoundedUserId = -1; + private float mBackButtonAlpha; private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() { @@ -180,6 +181,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } long token = Binder.clearCallingIdentity(); try { + mBackButtonAlpha = alpha; mHandler.post(() -> { notifyBackButtonAlphaChanged(alpha, animate); }); @@ -309,6 +311,10 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } } + public float getBackButtonAlpha() { + return mBackButtonAlpha; + } + public void startConnectionToCurrentUser() { if (mHandler.getLooper() != Looper.myLooper()) { mHandler.post(mConnectionRunnable); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java index 6de054313cb7..929713c2c50b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY; import android.app.ActivityManager; +import android.app.ActivityOptions; import android.app.PendingIntent; import android.app.RemoteInput; import android.content.Context; @@ -113,7 +114,7 @@ public class NotificationRemoteInputManager implements Dumpable { } catch (RemoteException e) { } return mCallback.handleRemoteViewClick(view, pendingIntent, fillInIntent, - () -> superOnClickHandler(view, pendingIntent, fillInIntent)); + () -> super.onClickHandler(view, pendingIntent, fillInIntent)); } private void logActionClick(View view) { @@ -151,10 +152,11 @@ public class NotificationRemoteInputManager implements Dumpable { return null; } - private boolean superOnClickHandler(View view, PendingIntent pendingIntent, - Intent fillInIntent) { - return super.onClickHandler(view, pendingIntent, fillInIntent, - WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY); + @Override + protected ActivityOptions getActivityOptions(Context context) { + ActivityOptions options = super.getActivityOptions(context); + options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY); + return options; } private boolean handleRemoteInput(View view, PendingIntent pendingIntent) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java index b376c007bbe4..da6401c2d62a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java @@ -164,7 +164,6 @@ public class UserGridRecyclerView extends PagedListView implements private final Resources mRes; private final String mGuestName; private final String mNewUserName; - private AlertDialog mDialog; // View that holds the add user button. Used to enable/disable the view private View mAddUserView; // User record for the add user. Need to call notifyUserSelected only if the user @@ -221,23 +220,9 @@ public class UserGridRecyclerView extends PagedListView implements // Disable button so it cannot be clicked multiple times mAddUserView = holder.mView; mAddUserView.setEnabled(false); - - String message = mRes.getString(R.string.user_add_user_message_setup) - .concat(System.getProperty("line.separator")) - .concat(System.getProperty("line.separator")) - .concat(mRes.getString(R.string.user_add_user_message_update)); - mAddUserRecord = userRecord; - mDialog = new Builder(mContext, R.style.Theme_Car_Dark_Dialog_Alert) - .setTitle(R.string.user_add_user_title) - .setMessage(message) - .setNegativeButton(android.R.string.cancel, this) - .setPositiveButton(android.R.string.ok, this) - .setOnCancelListener(this) - .create(); - // Sets window flags for the SysUI dialog - SystemUIDialog.applyFlags(mDialog); - mDialog.show(); + + handleAddUserClicked(); return; } // If the user doesn't want to be a guest or add a user, switch to the user selected @@ -247,6 +232,47 @@ public class UserGridRecyclerView extends PagedListView implements } + private void handleAddUserClicked() { + if (mCarUserManagerHelper.isUserLimitReached()) { + mAddUserView.setEnabled(true); + showMaxUserLimitReachedDialog(); + } else { + showConfirmAddUserDialog(); + } + } + + private void showMaxUserLimitReachedDialog() { + AlertDialog maxUsersDialog = new Builder(mContext, R.style.Theme_Car_Dark_Dialog_Alert) + .setTitle(R.string.user_limit_reached_title) + .setMessage(getResources().getQuantityString( + R.plurals.user_limit_reached_message, + mCarUserManagerHelper.getMaxSupportedRealUsers(), + mCarUserManagerHelper.getMaxSupportedRealUsers())) + .setPositiveButton(android.R.string.ok, null) + .create(); + // Sets window flags for the SysUI dialog + SystemUIDialog.applyFlags(maxUsersDialog); + maxUsersDialog.show(); + } + + private void showConfirmAddUserDialog() { + String message = mRes.getString(R.string.user_add_user_message_setup) + .concat(System.getProperty("line.separator")) + .concat(System.getProperty("line.separator")) + .concat(mRes.getString(R.string.user_add_user_message_update)); + + AlertDialog addUserDialog = new Builder(mContext, R.style.Theme_Car_Dark_Dialog_Alert) + .setTitle(R.string.user_add_user_title) + .setMessage(message) + .setNegativeButton(android.R.string.cancel, this) + .setPositiveButton(android.R.string.ok, this) + .setOnCancelListener(this) + .create(); + // Sets window flags for the SysUI dialog + SystemUIDialog.applyFlags(addUserDialog); + addUserDialog.show(); + } + private void notifyUserSelected(UserRecord userRecord) { // Notify the listener which user was selected if (mUserSelectionListener != null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java index 4c1938a62137..46019e3b48ea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java @@ -417,7 +417,6 @@ public abstract class ExpandableView extends FrameLayout { public void setWillBeGone(boolean willBeGone) { mWillBeGone = willBeGone; - invalidate(); } public int getMinClipTopAmount() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java index f48c3f5f5594..587b40d926d2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java @@ -163,12 +163,16 @@ public class ButtonDispatcher { } public void setAlpha(float alpha, boolean animate) { + setAlpha(alpha, animate, (getAlpha() < alpha) ? FADE_DURATION_IN : FADE_DURATION_OUT); + } + + public void setAlpha(float alpha, boolean animate, long duration) { if (animate) { if (mFadeAnimator != null) { mFadeAnimator.cancel(); } mFadeAnimator = ValueAnimator.ofFloat(getAlpha(), alpha); - mFadeAnimator.setDuration(getAlpha() < alpha? FADE_DURATION_IN : FADE_DURATION_OUT); + mFadeAnimator.setDuration(duration); mFadeAnimator.setInterpolator(getAlpha() < alpha ? ALPHA_IN : ALPHA_OUT); mFadeAnimator.addListener(mFadeListener); mFadeAnimator.addUpdateListener(mAlphaListener); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.java deleted file mode 100644 index 9d40f173bf26..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * 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.phone; - -import android.content.Context; -import android.content.om.IOverlayManager; -import android.content.pm.ActivityInfo; -import android.content.res.Configuration; -import android.os.LocaleList; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.UserHandle; - -import com.android.systemui.ConfigurationChangedReceiver; -import com.android.systemui.statusbar.policy.ConfigurationController; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -public class ConfigurationControllerImpl implements ConfigurationController, - ConfigurationChangedReceiver { - - private final ArrayList<ConfigurationListener> mListeners = new ArrayList<>(); - private final Configuration mLastConfig = new Configuration(); - private int mDensity; - private float mFontScale; - private boolean mInCarMode; - private int mUiMode; - private LocaleList mLocaleList; - - public ConfigurationControllerImpl(Context context) { - Configuration currentConfig = context.getResources().getConfiguration(); - mFontScale = currentConfig.fontScale; - mDensity = currentConfig.densityDpi; - mInCarMode = (currentConfig.uiMode & Configuration.UI_MODE_TYPE_MASK) - == Configuration.UI_MODE_TYPE_CAR; - mUiMode = currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK; - mLocaleList = currentConfig.getLocales(); - } - - @Override - public void notifyThemeChanged() { - ArrayList<ConfigurationListener> listeners = new ArrayList<>(mListeners); - - listeners.forEach(l -> { - if (mListeners.contains(l)) { - l.onThemeChanged(); - } - }); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - // Avoid concurrent modification exception - ArrayList<ConfigurationListener> listeners = new ArrayList<>(mListeners); - - listeners.forEach(l -> { - if (mListeners.contains(l)) { - l.onConfigChanged(newConfig); - } - }); - final float fontScale = newConfig.fontScale; - final int density = newConfig.densityDpi; - int uiMode = newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK; - boolean uiModeChanged = uiMode != mUiMode; - if (density != mDensity || fontScale != mFontScale - || (mInCarMode && uiModeChanged)) { - listeners.forEach(l -> { - if (mListeners.contains(l)) { - l.onDensityOrFontScaleChanged(); - } - }); - mDensity = density; - mFontScale = fontScale; - } - - final LocaleList localeList = newConfig.getLocales(); - if (!localeList.equals(mLocaleList)) { - mLocaleList = localeList; - listeners.forEach(l -> { - if (mListeners.contains(l)) { - l.onLocaleListChanged(); - } - }); - } - - if (uiModeChanged) { - mUiMode = uiMode; - listeners.forEach(l -> { - if (mListeners.contains(l)) { - l.onUiModeChanged(); - } - }); - } - - if ((mLastConfig.updateFrom(newConfig) & ActivityInfo.CONFIG_ASSETS_PATHS) != 0) { - listeners.forEach(l -> { - if (mListeners.contains(l)) { - l.onOverlayChanged(); - } - }); - } - } - - @Override - public void addCallback(ConfigurationListener listener) { - mListeners.add(listener); - listener.onDensityOrFontScaleChanged(); - } - - @Override - public void removeCallback(ConfigurationListener listener) { - mListeners.remove(listener); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt new file mode 100644 index 000000000000..81b596c0345e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2018 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.phone + +import android.content.Context +import android.content.pm.ActivityInfo +import android.content.res.Configuration +import android.os.LocaleList + +import com.android.systemui.ConfigurationChangedReceiver +import com.android.systemui.statusbar.policy.ConfigurationController + +import java.util.ArrayList + +class ConfigurationControllerImpl(context: Context) + : ConfigurationController, ConfigurationChangedReceiver { + + private val listeners: MutableList<ConfigurationController.ConfigurationListener> = ArrayList() + private val lastConfig = Configuration() + private var density: Int = 0 + private var fontScale: Float = 0.toFloat() + private val inCarMode: Boolean + private var uiMode: Int = 0 + private var localeList: LocaleList? = null + + init { + val currentConfig = context.resources.configuration + fontScale = currentConfig.fontScale + density = currentConfig.densityDpi + inCarMode = currentConfig.uiMode and Configuration.UI_MODE_TYPE_MASK == + Configuration.UI_MODE_TYPE_CAR + uiMode = currentConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK + localeList = currentConfig.locales + } + + override fun notifyThemeChanged() { + val listeners = ArrayList(listeners) + + listeners.filterForEach({ this.listeners.contains(it) }) { + it.onThemeChanged() + } + } + + override fun onConfigurationChanged(newConfig: Configuration) { + // Avoid concurrent modification exception + val listeners = ArrayList(listeners) + + listeners.filterForEach({ this.listeners.contains(it) }) { + it.onConfigChanged(newConfig) + } + val fontScale = newConfig.fontScale + val density = newConfig.densityDpi + val uiMode = newConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK + val uiModeChanged = uiMode != this.uiMode + if (density != this.density || fontScale != this.fontScale || + inCarMode && uiModeChanged) { + listeners.filterForEach({ this.listeners.contains(it) }) { + it.onDensityOrFontScaleChanged() + } + this.density = density + this.fontScale = fontScale + } + + val localeList = newConfig.locales + if (localeList != this.localeList) { + this.localeList = localeList + listeners.filterForEach({ this.listeners.contains(it) }) { + it.onLocaleListChanged() + } + } + + if (uiModeChanged) { + this.uiMode = uiMode + listeners.filterForEach({ this.listeners.contains(it) }) { + it.onUiModeChanged() + } + } + + if (lastConfig.updateFrom(newConfig) and ActivityInfo.CONFIG_ASSETS_PATHS != 0) { + listeners.filterForEach({ this.listeners.contains(it) }) { + it.onOverlayChanged() + } + } + } + + override fun addCallback(listener: ConfigurationController.ConfigurationListener) { + listeners.add(listener) + listener.onDensityOrFontScaleChanged() + } + + override fun removeCallback(listener: ConfigurationController.ConfigurationListener) { + listeners.remove(listener) + } +} + +// This could be done with a Collection.filter and Collection.forEach, but Collection.filter +// creates a new array to store them in and we really don't need that here, so this provides +// a little more optimized inline version. +inline fun <T> Collection<T>.filterForEach(f: (T) -> Boolean, execute: (T) -> Unit) { + forEach { + if (f.invoke(it)) { + execute.invoke(it) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java index 20ea27a3f9c9..5630da6662e9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java @@ -447,8 +447,8 @@ public class KeyguardStatusBarView extends RelativeLayout R.color.light_mode_icon_color_single_tone); float intensity = textColor == Color.WHITE ? 0 : 1; mCarrierLabel.setTextColor(iconColor); - mBatteryView.setFillColor(iconColor); mIconManager.setTint(iconColor); + mBatteryView.setColorsFromContext(mContext); Rect tintArea = new Rect(0, 0, 0, 0); applyDarkness(R.id.battery, tintArea, intensity, iconColor); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java index 40c8fde3f845..b84ee037b9fd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -195,8 +195,14 @@ public class NavigationBarFragment extends Fragment implements Callbacks { @Override public void onBackButtonAlphaChanged(float alpha, boolean animate) { final ButtonDispatcher backButton = mNavigationBarView.getBackButton(); - backButton.setVisibility(alpha > 0 ? View.VISIBLE : View.INVISIBLE); - backButton.setAlpha(alpha, animate); + if (QuickStepController.shouldhideBackButton()) { + // If property was changed to hide/show back button, going home will trigger + // launcher to to change the back button alpha to reflect property change + backButton.setVisibility(View.GONE); + } else { + backButton.setVisibility(alpha > 0 ? View.VISIBLE : View.INVISIBLE); + backButton.setAlpha(alpha, animate); + } } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 0d9c623b0e8f..1892d37c62d6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -663,7 +663,8 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav // Always disable recents when alternate car mode UI is active. boolean disableRecent = mUseCarModeUi || !isOverviewEnabled(); - boolean disableBack = ((mDisabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0) && !useAltBack; + boolean disableBack = QuickStepController.shouldhideBackButton() + || (((mDisabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0) && !useAltBack); // When screen pinning, don't hide back and home when connected service or back and // recents buttons when disconnected from launcher service in screen pinning mode, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index 5d65b5645330..33ddfde845a5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -2967,7 +2967,6 @@ public class NotificationPanelView extends PanelView implements } public void updateNotificationViews() { - if (mNotificationStackScroller == null) return; mNotificationStackScroller.updateSpeedBumpIndex(); mNotificationStackScroller.updateFooter(); updateShowEmptyShadeView(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java index a13bebdcdc0a..bd4d790a00b0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java @@ -23,6 +23,7 @@ import static com.android.systemui.Interpolators.ALPHA_OUT; import static com.android.systemui.OverviewProxyService.DEBUG_OVERVIEW_PROXY; import static com.android.systemui.OverviewProxyService.TAG_OPS; import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_DEAD_ZONE; +import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_HOME; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -37,15 +38,24 @@ import android.graphics.Paint; import android.graphics.RadialGradient; import android.graphics.Rect; import android.graphics.Shader; +import android.hardware.input.InputManager; import android.os.Handler; import android.os.RemoteException; +import android.os.SystemClock; +import android.os.SystemProperties; import android.util.FloatProperty; import android.util.Log; import android.util.Slog; +import android.view.HapticFeedbackConstants; +import android.view.InputDevice; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; +import android.view.ViewPropertyAnimator; import android.view.WindowManagerGlobal; import com.android.systemui.Dependency; +import com.android.systemui.Interpolators; import com.android.systemui.OverviewProxyService; import com.android.systemui.R; import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper; @@ -64,10 +74,25 @@ public class QuickStepController implements GestureHelper { private static final float TRACK_SCALE = 0.95f; private static final float GRADIENT_WIDTH = .75f; + /** Experiment to swipe home button left to execute a back key press */ + private static final String PULL_HOME_GO_BACK_PROP = "persist.quickstepcontroller.homegoesback"; + private static final String HIDE_BACK_BUTTON_PROP = "persist.quickstepcontroller.hideback"; + private static final long BACK_BUTTON_FADE_OUT_ALPHA = 60; + private static final long BACK_BUTTON_FADE_IN_ALPHA = 150; + private static final long BACK_GESTURE_POLL_TIMEOUT = 1000; + + /** When the home-swipe-back gesture is disallowed, make it harder to pull */ + private static final float DISALLOW_GESTURE_DAMPING_FACTOR = 0.16f; + + /** When dragging the home button too far during back gesture, make it harder to pull */ + private static final float EXCEED_DRAG_HOME_DAMPING_FACTOR = 0.33f; + private NavigationBarView mNavigationBarView; private boolean mQuickScrubActive; private boolean mAllowGestureDetection; + private boolean mBackGestureActive; + private boolean mCanPerformBack; private boolean mQuickStepStarted; private boolean mNotificationsVisibleOnDown; private int mTouchDownX; @@ -81,6 +106,7 @@ public class QuickStepController implements GestureHelper { private RadialGradient mHighlight; private float mHighlightCenter; private AnimatorSet mTrackAnimator; + private ViewPropertyAnimator mHomeAnimator; private ButtonDispatcher mHitTarget; private View mCurrentNavigationBarView; private boolean mIsInScreenPinning; @@ -90,11 +116,20 @@ public class QuickStepController implements GestureHelper { private final OverviewProxyService mOverviewEventSender; private final int mTrackThickness; private final int mTrackEndPadding; + private final int mHomeBackGestureDragLimit; private final Context mContext; private final Matrix mTransformGlobalMatrix = new Matrix(); private final Matrix mTransformLocalMatrix = new Matrix(); private final Paint mTrackPaint = new Paint(); + public static boolean swipeHomeGoBackGestureEnabled() { + return SystemProperties.getBoolean(PULL_HOME_GO_BACK_PROP, false); + } + public static boolean shouldhideBackButton() { + return swipeHomeGoBackGestureEnabled() + && SystemProperties.getBoolean(HIDE_BACK_BUTTON_PROP, false); + } + private final FloatProperty<QuickStepController> mTrackAlphaProperty = new FloatProperty<QuickStepController>("TrackAlpha") { @Override @@ -148,18 +183,34 @@ public class QuickStepController implements GestureHelper { } }; + private final Runnable mExecuteBackRunnable = new Runnable() { + @Override + public void run() { + if (canPerformHomeBackGesture()) { + performBack(); + mHandler.postDelayed(this, BACK_GESTURE_POLL_TIMEOUT); + } + } + }; + public QuickStepController(Context context) { final Resources res = context.getResources(); mContext = context; mOverviewEventSender = Dependency.get(OverviewProxyService.class); mTrackThickness = res.getDimensionPixelSize(R.dimen.nav_quick_scrub_track_thickness); mTrackEndPadding = res.getDimensionPixelSize(R.dimen.nav_quick_scrub_track_edge_padding); + mHomeBackGestureDragLimit = + res.getDimensionPixelSize(R.dimen.nav_home_back_gesture_drag_limit); mTrackPaint.setAntiAlias(true); mTrackPaint.setDither(true); } public void setComponents(NavigationBarView navigationBarView) { mNavigationBarView = navigationBarView; + + mNavigationBarView.getBackButton().setVisibility(shouldhideBackButton() + ? View.GONE + : View.VISIBLE); } /** @@ -218,8 +269,10 @@ public class QuickStepController implements GestureHelper { mNavigationBarView.transformMatrixToGlobal(mTransformGlobalMatrix); mNavigationBarView.transformMatrixToLocal(mTransformLocalMatrix); mQuickStepStarted = false; + mBackGestureActive = false; mAllowGestureDetection = true; mNotificationsVisibleOnDown = !mNavigationBarView.isNotificationsFullyCollapsed(); + mCanPerformBack = canPerformHomeBackGesture(); break; } case MotionEvent.ACTION_MOVE: { @@ -255,7 +308,7 @@ public class QuickStepController implements GestureHelper { } // Decide to start quickstep if dragging away from the navigation bar, otherwise in // the parallel direction, decide to start quickscrub. Only one may run. - if (!mQuickScrubActive && exceededSwipeUpTouchSlop) { + if (!mBackGestureActive && !mQuickScrubActive && exceededSwipeUpTouchSlop) { if (mNavigationBarView.isQuickStepSwipeUpEnabled() && !mNotificationsVisibleOnDown) { startQuickStep(event); @@ -275,10 +328,14 @@ public class QuickStepController implements GestureHelper { final boolean allowDrag = !mDragPositive ? offset < 0 && pos < touchDown : offset >= 0 && pos > touchDown; float scrubFraction = Utilities.clamp(Math.abs(offset) * 1f / trackSize, 0, 1); - if (allowDrag) { + if (!mQuickScrubActive && !mBackGestureActive && exceededScrubTouchSlop) { // Passing the drag slop then touch slop will start quick step - if (!mQuickScrubActive && exceededScrubTouchSlop) { + if (allowDrag) { startQuickScrub(); + } else if (swipeHomeGoBackGestureEnabled() + && mNavigationBarView.getDownHitTarget() == HIT_TARGET_HOME + && mDragPositive ? pos < touchDown : pos > touchDown) { + startBackGesture(); } } @@ -294,23 +351,41 @@ public class QuickStepController implements GestureHelper { } mHighlightCenter = x; mNavigationBarView.invalidate(); + } else if (mBackGestureActive) { + int diff = pos - touchDown; + // If dragging the incorrect direction after starting back gesture or unable + // to execute back functionality, then move home but dampen its distance + if (!mCanPerformBack || (mDragPositive ? diff > 0 : diff < 0)) { + diff *= DISALLOW_GESTURE_DAMPING_FACTOR; + } if (Math.abs(diff) > mHomeBackGestureDragLimit) { + // Once the user drags the home button past a certain limit, the distance + // will lessen as the home button dampens showing that it was pulled too far + float distanceAfterDragLimit = (Math.abs(diff) - mHomeBackGestureDragLimit) + * EXCEED_DRAG_HOME_DAMPING_FACTOR; + diff = (int)(distanceAfterDragLimit + mHomeBackGestureDragLimit); + if (mDragPositive) { + diff *= -1; + } + } + moveHomeButton(diff); } break; } case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: endQuickScrub(true /* animate */); + endBackGesture(); break; } if (shouldProxyEvents(action)) { proxyMotionEvents(event); } - return mQuickScrubActive || mQuickStepStarted || deadZoneConsumed; + return mBackGestureActive || mQuickScrubActive || mQuickStepStarted || deadZoneConsumed; } private boolean shouldProxyEvents(int action) { - if (!mQuickScrubActive && !mIsInScreenPinning) { + if (!mBackGestureActive && !mQuickScrubActive && !mIsInScreenPinning) { // Allow down, cancel and up events, move and other events are passed if notifications // are not showing and disabled gestures (such as long press) are not executed switch (action) { @@ -501,6 +576,42 @@ public class QuickStepController implements GestureHelper { } } + private void startBackGesture() { + if (!mBackGestureActive) { + mBackGestureActive = true; + mNavigationBarView.getHomeButton().abortCurrentGesture(); + if (mCanPerformBack) { + if (!shouldhideBackButton()) { + mNavigationBarView.getBackButton().setAlpha(0 /* alpha */, true /* animate */, + BACK_BUTTON_FADE_OUT_ALPHA); + } + performBack(); + } + mHandler.removeCallbacks(mExecuteBackRunnable); + mHandler.postDelayed(mExecuteBackRunnable, BACK_GESTURE_POLL_TIMEOUT); + } + } + + private void endBackGesture() { + if (mBackGestureActive) { + mHandler.removeCallbacks(mExecuteBackRunnable); + mHomeAnimator = mNavigationBarView.getHomeButton().getCurrentView() + .animate() + .setDuration(BACK_BUTTON_FADE_IN_ALPHA) + .setInterpolator(Interpolators.FAST_OUT_SLOW_IN); + if (mIsVertical) { + mHomeAnimator.translationY(0); + } else { + mHomeAnimator.translationX(0); + } + mHomeAnimator.start(); + if (!shouldhideBackButton()) { + mNavigationBarView.getBackButton().setAlpha( + mOverviewEventSender.getBackButtonAlpha(), true /* animate */); + } + } + } + private void animateEnd() { if (mTrackAnimator != null) { mTrackAnimator.cancel(); @@ -530,6 +641,19 @@ public class QuickStepController implements GestureHelper { updateHighlight(); } + private void moveHomeButton(float pos) { + if (mHomeAnimator != null) { + mHomeAnimator.cancel(); + mHomeAnimator = null; + } + final View homeButton = mNavigationBarView.getHomeButton().getCurrentView(); + if (mIsVertical) { + homeButton.setTranslationY(pos); + } else { + homeButton.setTranslationX(pos); + } + } + private void updateHighlight() { if (mTrackRect.isEmpty()) { return; @@ -548,6 +672,25 @@ public class QuickStepController implements GestureHelper { mTrackPaint.setShader(mHighlight); } + private boolean canPerformHomeBackGesture() { + return swipeHomeGoBackGestureEnabled() && mOverviewEventSender.getBackButtonAlpha() > 0; + } + + private void performBack() { + sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK); + sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK); + mNavigationBarView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); + } + + private void sendEvent(int action, int code) { + long when = SystemClock.uptimeMillis(); + final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */, + 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */, + KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY, + InputDevice.SOURCE_KEYBOARD); + InputManager.getInstance().injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); + } + private boolean proxyMotionEvents(MotionEvent event) { final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy(); event.transform(mTransformGlobalMatrix); diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk index 9ee55324efa2..6057614f26ee 100644 --- a/packages/SystemUI/tests/Android.mk +++ b/packages/SystemUI/tests/Android.mk @@ -21,28 +21,12 @@ LOCAL_MODULE_TAGS := tests LOCAL_JACK_FLAGS := --multi-dex native LOCAL_DX_FLAGS := --multi-dex -LOCAL_PROTOC_OPTIMIZE_TYPE := nano -LOCAL_PROTOC_FLAGS := -I$(LOCAL_PATH)/.. -LOCAL_PROTO_JAVA_OUTPUT_PARAMS := optional_field_style=accessors - LOCAL_PACKAGE_NAME := SystemUITests LOCAL_PRIVATE_PLATFORM_APIS := true LOCAL_COMPATIBILITY_SUITE := device-tests -LOCAL_SRC_FILES := $(call all-java-files-under, src) \ - $(call all-Iaidl-files-under, src) - -LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res - LOCAL_STATIC_ANDROID_LIBRARIES := \ - SystemUI-core - -LOCAL_STATIC_JAVA_LIBRARIES := \ - metrics-helper-lib \ - android-support-test \ - mockito-target-inline-minus-junit4 \ - testables \ - truth-prebuilt \ + SystemUI-tests LOCAL_MULTILIB := both diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml index 64f96da09b08..8b1324a77a5e 100644 --- a/packages/SystemUI/tests/AndroidManifest.xml +++ b/packages/SystemUI/tests/AndroidManifest.xml @@ -72,6 +72,7 @@ tools:replace="android:authorities" android:authorities="${applicationId}.lifecycle-tests" android:exported="false" + android:enabled="false" android:multiprocess="true" /> <provider android:name="com.android.systemui.keyguard.KeyguardSliceProvider" android:authorities="com.android.systemui.test.keyguard.disabled" @@ -83,6 +84,7 @@ android:name="androidx.core.content.FileProvider" android:authorities="com.android.systemui.test.fileprovider" android:exported="false" + android:enabled="false" tools:replace="android:authorities" android:grantUriPermissions="true" /> </application> diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt new file mode 100644 index 000000000000..0d13e975e56a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2018 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.phone + +import android.support.test.filters.SmallTest +import android.testing.AndroidTestingRunner +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.doAnswer +import org.mockito.Mockito.mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class ConfigurationControllerImplTest : SysuiTestCase() { + + private val mConfigurationController = + com.android.systemui.statusbar.phone.ConfigurationControllerImpl(mContext) + + @Test + fun testThemeChange() { + val listener = mock(ConfigurationListener::class.java) + mConfigurationController.addCallback(listener) + + mConfigurationController.notifyThemeChanged() + verify(listener).onThemeChanged() + } + + @Test + fun testRemoveListenerDuringCallback() { + val listener = mock(ConfigurationListener::class.java) + mConfigurationController.addCallback(listener) + val listener2 = mock(ConfigurationListener::class.java) + mConfigurationController.addCallback(listener2) + + doAnswer { + mConfigurationController.removeCallback(listener2) + null + }.`when`(listener).onThemeChanged() + + mConfigurationController.notifyThemeChanged() + verify(listener).onThemeChanged() + verify(listener2, never()).onThemeChanged() + } +} diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java index b3f6bd1d41cc..998e441f67d8 100644 --- a/services/core/java/com/android/server/AppOpsService.java +++ b/services/core/java/com/android/server/AppOpsService.java @@ -45,8 +45,10 @@ import android.os.ServiceManager; import android.os.ShellCallback; import android.os.ShellCommand; import android.os.SystemClock; +import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; +import android.os.storage.StorageManager; import android.os.storage.StorageManagerInternal; import android.provider.Settings; import android.util.ArrayMap; @@ -657,33 +659,35 @@ public class AppOpsService extends IAppOpsService.Stub { } }); - StorageManagerInternal storageManagerInternal = LocalServices.getService( - StorageManagerInternal.class); - storageManagerInternal.addExternalStoragePolicy( - new StorageManagerInternal.ExternalStorageMountPolicy() { - @Override - public int getMountMode(int uid, String packageName) { - if (Process.isIsolated(uid)) { - return Zygote.MOUNT_EXTERNAL_NONE; - } - if (noteOperation(AppOpsManager.OP_READ_EXTERNAL_STORAGE, uid, - packageName) != AppOpsManager.MODE_ALLOWED) { - return Zygote.MOUNT_EXTERNAL_NONE; - } - if (noteOperation(AppOpsManager.OP_WRITE_EXTERNAL_STORAGE, uid, - packageName) != AppOpsManager.MODE_ALLOWED) { - return Zygote.MOUNT_EXTERNAL_READ; + if (!SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)) { + StorageManagerInternal storageManagerInternal = LocalServices.getService( + StorageManagerInternal.class); + storageManagerInternal.addExternalStoragePolicy( + new StorageManagerInternal.ExternalStorageMountPolicy() { + @Override + public int getMountMode(int uid, String packageName) { + if (Process.isIsolated(uid)) { + return Zygote.MOUNT_EXTERNAL_NONE; + } + if (noteOperation(AppOpsManager.OP_READ_EXTERNAL_STORAGE, uid, + packageName) != AppOpsManager.MODE_ALLOWED) { + return Zygote.MOUNT_EXTERNAL_NONE; + } + if (noteOperation(AppOpsManager.OP_WRITE_EXTERNAL_STORAGE, uid, + packageName) != AppOpsManager.MODE_ALLOWED) { + return Zygote.MOUNT_EXTERNAL_READ; + } + return Zygote.MOUNT_EXTERNAL_WRITE; } - return Zygote.MOUNT_EXTERNAL_WRITE; - } - @Override - public boolean hasExternalStorage(int uid, String packageName) { - final int mountMode = getMountMode(uid, packageName); - return mountMode == Zygote.MOUNT_EXTERNAL_READ - || mountMode == Zygote.MOUNT_EXTERNAL_WRITE; - } - }); + @Override + public boolean hasExternalStorage(int uid, String packageName) { + final int mountMode = getMountMode(uid, packageName); + return mountMode == Zygote.MOUNT_EXTERNAL_READ + || mountMode == Zygote.MOUNT_EXTERNAL_WRITE; + } + }); + } } public void packageRemoved(int uid, String packageName) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 1d01a572bbfb..a8269824da0e 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -15466,12 +15466,6 @@ public class ActivityManagerService extends IActivityManager.Stub synchronized(this) { // !!! TODO: currently no check here that we're already bound - BatteryStatsImpl.Uid.Pkg.Serv ss = null; - BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics(); - synchronized (stats) { - ss = stats.getServiceStatsLocked(app.uid, app.packageName, app.name); - } - // Backup agent is now in use, its package can't be stopped. try { AppGlobals.getPackageManager().setPackageStoppedState( @@ -15482,7 +15476,7 @@ public class ActivityManagerService extends IActivityManager.Stub + app.packageName + ": " + e); } - BackupRecord r = new BackupRecord(ss, app, backupMode); + BackupRecord r = new BackupRecord(app, backupMode); ComponentName hostingName = (backupMode == ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL) ? new ComponentName(app.packageName, app.backupAgentName) diff --git a/services/core/java/com/android/server/am/BackupRecord.java b/services/core/java/com/android/server/am/BackupRecord.java index 5fa7e6a37133..40ad383e7d58 100644 --- a/services/core/java/com/android/server/am/BackupRecord.java +++ b/services/core/java/com/android/server/am/BackupRecord.java @@ -16,8 +16,6 @@ package com.android.server.am; -import com.android.internal.os.BatteryStatsImpl; - import android.content.pm.ApplicationInfo; /** @hide */ @@ -28,7 +26,6 @@ final class BackupRecord { public static final int RESTORE = 2; public static final int RESTORE_FULL = 3; - final BatteryStatsImpl.Uid.Pkg.Serv stats; String stringName; // cached toString() output final ApplicationInfo appInfo; // information about BackupAgent's app final int backupMode; // full backup / incremental / restore @@ -36,9 +33,7 @@ final class BackupRecord { // ----- Implementation ----- - BackupRecord(BatteryStatsImpl.Uid.Pkg.Serv _agentStats, ApplicationInfo _appInfo, - int _backupMode) { - stats = _agentStats; + BackupRecord(ApplicationInfo _appInfo, int _backupMode) { appInfo = _appInfo; backupMode = _backupMode; } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 18bed54d42c1..59c4697e4f1e 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -24,6 +24,7 @@ import static android.Manifest.permission.READ_EXTERNAL_STORAGE; import static android.Manifest.permission.REQUEST_DELETE_PACKAGES; import static android.Manifest.permission.SET_HARMFUL_APP_WARNINGS; import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; +import static android.Manifest.permission.WRITE_MEDIA_STORAGE; import static android.content.Intent.ACTION_MAIN; import static android.content.Intent.CATEGORY_DEFAULT; import static android.content.Intent.CATEGORY_HOME; @@ -20034,6 +20035,11 @@ public class PackageManagerService extends IPackageManager.Stub if (Process.isIsolated(uid)) { return Zygote.MOUNT_EXTERNAL_NONE; } + if (SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)) { + return checkUidPermission(WRITE_MEDIA_STORAGE, uid) == PERMISSION_GRANTED + ? Zygote.MOUNT_EXTERNAL_FULL + : Zygote.MOUNT_EXTERNAL_WRITE; + } if (checkUidPermission(READ_EXTERNAL_STORAGE, uid) == PERMISSION_DENIED) { return Zygote.MOUNT_EXTERNAL_DEFAULT; } diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index a78cb3360a4a..5befc1f99cf3 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -171,8 +171,11 @@ public final class DefaultPermissionGrantPolicy { @Deprecated private static final Set<String> STORAGE_PERMISSIONS = new ArraySet<>(); static { - STORAGE_PERMISSIONS.add(Manifest.permission.READ_EXTERNAL_STORAGE); - STORAGE_PERMISSIONS.add(Manifest.permission.WRITE_EXTERNAL_STORAGE); + // STOPSHIP(b/112545973): remove once feature enabled by default + if (!SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)) { + STORAGE_PERMISSIONS.add(Manifest.permission.READ_EXTERNAL_STORAGE); + STORAGE_PERMISSIONS.add(Manifest.permission.WRITE_EXTERNAL_STORAGE); + } } private static final Set<String> MEDIA_AURAL_PERMISSIONS = new ArraySet<>(); diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java index 2f189a6bcdb9..df97027da64f 100644 --- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java +++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java @@ -16,20 +16,18 @@ package com.android.server.wm; -import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS; +import static com.android.server.wm.ScreenRotationAnimationProto.ANIMATION_RUNNING; +import static com.android.server.wm.ScreenRotationAnimationProto.STARTED; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_SURFACE_ALLOC; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER; import static com.android.server.wm.WindowStateAnimator.WINDOW_FREEZE_LAYER; -import static com.android.server.wm.ScreenRotationAnimationProto.ANIMATION_RUNNING; -import static com.android.server.wm.ScreenRotationAnimationProto.STARTED; import android.content.Context; import android.graphics.Matrix; import android.graphics.Rect; -import android.os.IBinder; import android.util.Slog; import android.util.proto.ProtoOutputStream; import android.view.Display; @@ -37,7 +35,6 @@ import android.view.DisplayInfo; import android.view.Surface; import android.view.Surface.OutOfResourcesException; import android.view.SurfaceControl; -import android.view.SurfaceSession; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.animation.Transformation; @@ -268,6 +265,12 @@ class ScreenRotationAnimation { .setSecure(isSecure) .build(); + // In case display bounds change, screenshot buffer and surface may mismatch so set a + // scaling mode. + SurfaceControl.Transaction t2 = new SurfaceControl.Transaction(); + t2.setOverrideScalingMode(mSurfaceControl, Surface.SCALING_MODE_SCALE_TO_WINDOW); + t2.apply(true /* sync */); + // Capture a screenshot into the surface we just created. final int displayId = display.getDisplayId(); final Surface surface = new Surface(); diff --git a/services/net/java/android/net/dhcp/DhcpDiscoverPacket.java b/services/net/java/android/net/dhcp/DhcpDiscoverPacket.java index 91e6bd6469d8..11f2b6118e24 100644 --- a/services/net/java/android/net/dhcp/DhcpDiscoverPacket.java +++ b/services/net/java/android/net/dhcp/DhcpDiscoverPacket.java @@ -24,10 +24,17 @@ import java.nio.ByteBuffer; */ class DhcpDiscoverPacket extends DhcpPacket { /** + * The IP address of the client which sent this packet. + */ + final Inet4Address mSrcIp; + + /** * Generates a DISCOVER packet with the specified parameters. */ - DhcpDiscoverPacket(int transId, short secs, byte[] clientMac, boolean broadcast) { - super(transId, secs, INADDR_ANY, INADDR_ANY, INADDR_ANY, INADDR_ANY, clientMac, broadcast); + DhcpDiscoverPacket(int transId, short secs, Inet4Address relayIp, byte[] clientMac, + boolean broadcast, Inet4Address srcIp) { + super(transId, secs, INADDR_ANY, INADDR_ANY, INADDR_ANY, relayIp, clientMac, broadcast); + mSrcIp = srcIp; } public String toString() { @@ -41,8 +48,8 @@ class DhcpDiscoverPacket extends DhcpPacket { */ public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) { ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH); - fillInPacket(encap, INADDR_BROADCAST, INADDR_ANY, destUdp, - srcUdp, result, DHCP_BOOTREQUEST, mBroadcast); + fillInPacket(encap, INADDR_BROADCAST, mSrcIp, destUdp, srcUdp, result, DHCP_BOOTREQUEST, + mBroadcast); result.flip(); return result; } diff --git a/services/net/java/android/net/dhcp/DhcpLease.java b/services/net/java/android/net/dhcp/DhcpLease.java new file mode 100644 index 000000000000..d2a15b37dcc0 --- /dev/null +++ b/services/net/java/android/net/dhcp/DhcpLease.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2018 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.dhcp; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.MacAddress; +import android.os.SystemClock; +import android.text.TextUtils; + +import com.android.internal.util.HexDump; + +import java.net.Inet4Address; +import java.util.Arrays; +import java.util.Objects; + +/** + * An IPv4 address assignment done through DHCPv4. + * @hide + */ +public class DhcpLease { + public static final long EXPIRATION_NEVER = Long.MAX_VALUE; + public static final String HOSTNAME_NONE = null; + + @Nullable + private final byte[] mClientId; + @NonNull + private final MacAddress mHwAddr; + @NonNull + private final Inet4Address mNetAddr; + /** + * Expiration time for the lease, to compare with {@link SystemClock#elapsedRealtime()}. + */ + private final long mExpTime; + @Nullable + private final String mHostname; + + public DhcpLease(@Nullable byte[] clientId, @NonNull MacAddress hwAddr, + @NonNull Inet4Address netAddr, long expTime, @Nullable String hostname) { + mClientId = (clientId == null ? null : Arrays.copyOf(clientId, clientId.length)); + mHwAddr = hwAddr; + mNetAddr = netAddr; + mExpTime = expTime; + mHostname = hostname; + } + + @Nullable + public byte[] getClientId() { + if (mClientId == null) { + return null; + } + return Arrays.copyOf(mClientId, mClientId.length); + } + + @NonNull + public MacAddress getHwAddr() { + return mHwAddr; + } + + @Nullable + public String getHostname() { + return mHostname; + } + + @NonNull + public Inet4Address getNetAddr() { + return mNetAddr; + } + + public long getExpTime() { + return mExpTime; + } + + /** + * Push back the expiration time of this lease. If the provided time is sooner than the original + * expiration time, the lease time will not be updated. + * + * <p>The lease hostname is updated with the provided one if set. + * @return A {@link DhcpLease} with expiration time set to max(expTime, currentExpTime) + */ + public DhcpLease renewedLease(long expTime, @Nullable String hostname) { + return new DhcpLease(mClientId, mHwAddr, mNetAddr, Math.max(expTime, mExpTime), + (hostname == null ? mHostname : hostname)); + } + + public boolean matchesClient(@Nullable byte[] clientId, @NonNull MacAddress hwAddr) { + if (mClientId != null) { + return Arrays.equals(mClientId, clientId); + } else { + return clientId == null && mHwAddr.equals(hwAddr); + } + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof DhcpLease)) { + return false; + } + final DhcpLease other = (DhcpLease)obj; + return Arrays.equals(mClientId, other.mClientId) + && mHwAddr.equals(other.mHwAddr) + && mNetAddr.equals(other.mNetAddr) + && mExpTime == other.mExpTime + && TextUtils.equals(mHostname, other.mHostname); + } + + @Override + public int hashCode() { + return Objects.hash(mClientId, mHwAddr, mNetAddr, mHostname, mExpTime); + } + + static String clientIdToString(byte[] bytes) { + if (bytes == null) { + return "null"; + } + return HexDump.toHexString(bytes); + } + + @Override + public String toString() { + return String.format("clientId: %s, hwAddr: %s, netAddr: %s, expTime: %d, hostname: %s", + clientIdToString(mClientId), mHwAddr.toString(), mNetAddr, mExpTime, mHostname); + } +} diff --git a/services/net/java/android/net/dhcp/DhcpLeaseRepository.java b/services/net/java/android/net/dhcp/DhcpLeaseRepository.java new file mode 100644 index 000000000000..7e57c9f46350 --- /dev/null +++ b/services/net/java/android/net/dhcp/DhcpLeaseRepository.java @@ -0,0 +1,538 @@ +/* + * Copyright (C) 2018 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.dhcp; + +import static android.net.NetworkUtils.inet4AddressToIntHTH; +import static android.net.NetworkUtils.intToInet4AddressHTH; +import static android.net.NetworkUtils.prefixLengthToV4NetmaskIntHTH; +import static android.net.dhcp.DhcpLease.EXPIRATION_NEVER; +import static android.net.util.NetworkConstants.IPV4_ADDR_BITS; + +import static java.lang.Math.min; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.IpPrefix; +import android.net.MacAddress; +import android.net.util.SharedLog; +import android.os.SystemClock; +import android.util.ArrayMap; + +import java.net.Inet4Address; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.function.Function; + +/** + * A repository managing IPv4 address assignments through DHCPv4. + * + * <p>This class is not thread-safe. All public methods should be called on a common thread or + * use some synchronization mechanism. + * + * <p>Methods are optimized for a small number of allocated leases, assuming that most of the time + * only 2~10 addresses will be allocated, which is the common case. Managing a large number of + * addresses is supported but will be slower: some operations have complexity in O(num_leases). + * @hide + */ +class DhcpLeaseRepository { + public static final byte[] CLIENTID_UNSPEC = null; + public static final Inet4Address INETADDR_UNSPEC = null; + + @NonNull + private final SharedLog mLog; + @NonNull + private final Clock mClock; + + @NonNull + private IpPrefix mPrefix; + @NonNull + private Set<Inet4Address> mReservedAddrs; + private int mSubnetAddr; + private int mSubnetMask; + private int mNumAddresses; + private long mLeaseTimeMs; + + public static class Clock { + /** + * @see SystemClock#elapsedRealtime() + */ + public long elapsedRealtime() { + return SystemClock.elapsedRealtime(); + } + } + + /** + * Next timestamp when committed or declined leases should be checked for expired ones. This + * will always be lower than or equal to the time for the first lease to expire: it's OK not to + * update this when removing entries, but it must always be updated when adding/updating. + */ + private long mNextExpirationCheck = EXPIRATION_NEVER; + + static class DhcpLeaseException extends Exception { + DhcpLeaseException(String message) { + super(message); + } + } + + static class OutOfAddressesException extends DhcpLeaseException { + OutOfAddressesException(String message) { + super(message); + } + } + + static class InvalidAddressException extends DhcpLeaseException { + InvalidAddressException(String message) { + super(message); + } + } + + /** + * Leases by IP address + */ + private final ArrayMap<Inet4Address, DhcpLease> mCommittedLeases = new ArrayMap<>(); + + /** + * Map address -> expiration timestamp in ms. Addresses are guaranteed to be valid as defined + * by {@link #isValidAddress(Inet4Address)}, but are not necessarily otherwise available for + * assignment. + */ + private final LinkedHashMap<Inet4Address, Long> mDeclinedAddrs = new LinkedHashMap<>(); + + public DhcpLeaseRepository(@NonNull IpPrefix prefix, @NonNull Set<Inet4Address> reservedAddrs, + long leaseTimeMs, @NonNull SharedLog log, @NonNull Clock clock) { + updateParams(prefix, reservedAddrs, leaseTimeMs); + mLog = log; + mClock = clock; + } + + public void updateParams(@NonNull IpPrefix prefix, @NonNull Set<Inet4Address> reservedAddrs, + long leaseTimeMs) { + mPrefix = prefix; + mReservedAddrs = Collections.unmodifiableSet(new HashSet<>(reservedAddrs)); + mSubnetMask = prefixLengthToV4NetmaskIntHTH(prefix.getPrefixLength()); + mSubnetAddr = inet4AddressToIntHTH((Inet4Address) prefix.getAddress()) & mSubnetMask; + mNumAddresses = 1 << (IPV4_ADDR_BITS - prefix.getPrefixLength()); + mLeaseTimeMs = leaseTimeMs; + + cleanMap(mCommittedLeases); + cleanMap(mDeclinedAddrs); + } + + /** + * From a map keyed by {@link Inet4Address}, remove entries where the key is invalid (as + * specified by {@link #isValidAddress(Inet4Address)}), or is a reserved address. + */ + private <T> void cleanMap(Map<Inet4Address, T> map) { + final Iterator<Entry<Inet4Address, T>> it = map.entrySet().iterator(); + while (it.hasNext()) { + final Inet4Address addr = it.next().getKey(); + if (!isValidAddress(addr) || mReservedAddrs.contains(addr)) { + it.remove(); + } + } + } + + /** + * Get a DHCP offer, to reply to a DHCPDISCOVER. Follows RFC2131 #4.3.1. + * + * @param clientId Client identifier option if specified, or {@link #CLIENTID_UNSPEC} + * @param relayAddr Internet address of the relay (giaddr), can be {@link Inet4Address#ANY} + * @param reqAddr Requested address by the client (option 50), or {@link #INETADDR_UNSPEC} + * @param hostname Client-provided hostname, or {@link DhcpLease#HOSTNAME_NONE} + * @throws OutOfAddressesException The server does not have any available address + * @throws InvalidAddressException The lease was requested from an unsupported subnet + */ + @NonNull + public DhcpLease getOffer(@Nullable byte[] clientId, @NonNull MacAddress hwAddr, + @NonNull Inet4Address relayAddr, + @Nullable Inet4Address reqAddr, @Nullable String hostname) + throws OutOfAddressesException, InvalidAddressException { + final long currentTime = mClock.elapsedRealtime(); + final long expTime = currentTime + mLeaseTimeMs; + + removeExpiredLeases(currentTime); + + // As per #4.3.1, addresses are assigned based on the relay address if present. This + // implementation only assigns addresses if the relayAddr is inside our configured subnet. + // This also applies when the client requested a specific address for consistency between + // requests, and with older behavior. + if (isIpAddrOutsidePrefix(mPrefix, relayAddr)) { + throw new InvalidAddressException("Lease requested by relay from outside of subnet"); + } + + final DhcpLease currentLease = findByClient(clientId, hwAddr); + final DhcpLease newLease; + if (currentLease != null) { + newLease = currentLease.renewedLease(expTime, hostname); + mLog.log("Offering extended lease " + newLease); + // Do not update lease time in the map: the offer is not committed yet. + } else if (reqAddr != null && isValidAddress(reqAddr) && isAvailable(reqAddr)) { + newLease = new DhcpLease(clientId, hwAddr, reqAddr, expTime, hostname); + mLog.log("Offering requested lease " + newLease); + } else { + newLease = makeNewOffer(clientId, hwAddr, expTime, hostname); + mLog.log("Offering new generated lease " + newLease); + } + return newLease; + } + + private static boolean isIpAddrOutsidePrefix(IpPrefix prefix, Inet4Address addr) { + return addr != null && !addr.equals(Inet4Address.ANY) && !prefix.contains(addr); + } + + @Nullable + private DhcpLease findByClient(@Nullable byte[] clientId, @NonNull MacAddress hwAddr) { + for (DhcpLease lease : mCommittedLeases.values()) { + if (lease.matchesClient(clientId, hwAddr)) { + return lease; + } + } + + // Note this differs from dnsmasq behavior, which would match by hwAddr if clientId was + // given but no lease keyed on clientId matched. This would prevent one interface from + // obtaining multiple leases with different clientId. + return null; + } + + /** + * Make a lease conformant to a client DHCPREQUEST or renew the client's existing lease, + * commit it to the repository and return it. + * + * <p>This method always succeeds and commits the lease if it does not throw, and has no side + * effects if it throws. + * + * @param clientId Client identifier option if specified, or {@link #CLIENTID_UNSPEC} + * @param reqAddr Requested address by the client (option 50), or {@link #INETADDR_UNSPEC} + * @param sidSet Whether the server identifier was set in the request + * @return The newly created or renewed lease + * @throws InvalidAddressException The client provided an address that conflicts with its + * current configuration, or other committed/reserved leases. + */ + @NonNull + public DhcpLease requestLease(@Nullable byte[] clientId, @NonNull MacAddress hwAddr, + @NonNull Inet4Address clientAddr, @Nullable Inet4Address reqAddr, boolean sidSet, + @Nullable String hostname) throws InvalidAddressException { + final long currentTime = mClock.elapsedRealtime(); + removeExpiredLeases(currentTime); + final DhcpLease assignedLease = findByClient(clientId, hwAddr); + + final Inet4Address leaseAddr = reqAddr != null ? reqAddr : clientAddr; + if (assignedLease != null) { + if (sidSet && reqAddr != null) { + // Client in SELECTING state; remove any current lease before creating a new one. + mCommittedLeases.remove(assignedLease.getNetAddr()); + } else if (!assignedLease.getNetAddr().equals(leaseAddr)) { + // reqAddr null (RENEWING/REBINDING): client renewing its own lease for clientAddr. + // reqAddr set with sid not set (INIT-REBOOT): client verifying configuration. + // In both cases, throw if clientAddr or reqAddr does not match the known lease. + throw new InvalidAddressException("Incorrect address for client in " + + (reqAddr != null ? "INIT-REBOOT" : "RENEWING/REBINDING")); + } + } + + // In the init-reboot case, RFC2131 #4.3.2 says that the server must not reply if + // assignedLease == null, but dnsmasq will let the client use the requested address if + // available, when configured with --dhcp-authoritative. This is preferable to avoid issues + // if the server lost the lease DB: the client would not get a reply because the server + // does not know their lease. + // Similarly in RENEWING/REBINDING state, create a lease when possible if the + // client-provided lease is unknown. + final DhcpLease lease = + checkClientAndMakeLease(clientId, hwAddr, leaseAddr, hostname, currentTime); + mLog.logf("DHCPREQUEST assignedLease %s, reqAddr=%s, sidSet=%s: created/renewed lease %s", + assignedLease, reqAddr, sidSet, lease); + return lease; + } + + /** + * Check that the client can request the specified address, make or renew the lease if yes, and + * commit it. + * + * <p>This method always succeeds and returns the lease if it does not throw, and has no + * side-effect if it throws. + * + * @return The newly created or renewed, committed lease + * @throws InvalidAddressException The client provided an address that conflicts with its + * current configuration, or other committed/reserved leases. + */ + private DhcpLease checkClientAndMakeLease(@Nullable byte[] clientId, @NonNull MacAddress hwAddr, + @NonNull Inet4Address addr, @Nullable String hostname, long currentTime) + throws InvalidAddressException { + final long expTime = currentTime + mLeaseTimeMs; + final DhcpLease currentLease = mCommittedLeases.getOrDefault(addr, null); + if (currentLease != null && !currentLease.matchesClient(clientId, hwAddr)) { + throw new InvalidAddressException("Address in use"); + } + + final DhcpLease lease; + if (currentLease == null) { + if (isValidAddress(addr) && !mReservedAddrs.contains(addr)) { + lease = new DhcpLease(clientId, hwAddr, addr, expTime, hostname); + } else { + throw new InvalidAddressException("Lease not found and address unavailable"); + } + } else { + lease = currentLease.renewedLease(expTime, hostname); + } + commitLease(lease); + return lease; + } + + private void commitLease(@NonNull DhcpLease lease) { + mCommittedLeases.put(lease.getNetAddr(), lease); + maybeUpdateEarliestExpiration(lease.getExpTime()); + } + + /** + * Delete a committed lease from the repository. + * + * @return true if a lease matching parameters was found. + */ + public boolean releaseLease(@Nullable byte[] clientId, @NonNull MacAddress hwAddr, + @NonNull Inet4Address addr) { + final DhcpLease currentLease = mCommittedLeases.getOrDefault(addr, null); + if (currentLease == null) { + mLog.w("Could not release unknown lease for " + addr); + return false; + } + if (currentLease.matchesClient(clientId, hwAddr)) { + mCommittedLeases.remove(addr); + mLog.log("Released lease " + currentLease); + return true; + } + mLog.w(String.format("Not releasing lease %s: does not match client (cid %s, hwAddr %s)", + currentLease, DhcpLease.clientIdToString(clientId), hwAddr)); + return false; + } + + public void markLeaseDeclined(@NonNull Inet4Address addr) { + if (mDeclinedAddrs.containsKey(addr) || !isValidAddress(addr)) { + mLog.logf("Not marking %s as declined: already declined or not assignable", addr); + return; + } + final long expTime = mClock.elapsedRealtime() + mLeaseTimeMs; + mDeclinedAddrs.put(addr, expTime); + mLog.logf("Marked %s as declined expiring %d", addr, expTime); + maybeUpdateEarliestExpiration(expTime); + } + + /** + * Get the list of currently valid committed leases in the repository. + */ + @NonNull + public List<DhcpLease> getCommittedLeases() { + removeExpiredLeases(mClock.elapsedRealtime()); + return new ArrayList<>(mCommittedLeases.values()); + } + + /** + * Get the set of addresses that have been marked as declined in the repository. + */ + @NonNull + public Set<Inet4Address> getDeclinedAddresses() { + removeExpiredLeases(mClock.elapsedRealtime()); + return new HashSet<>(mDeclinedAddrs.keySet()); + } + + /** + * Given the expiration time of a new committed lease or declined address, update + * {@link #mNextExpirationCheck} so it stays lower than or equal to the time for the first lease + * to expire. + */ + private void maybeUpdateEarliestExpiration(long expTime) { + if (expTime < mNextExpirationCheck) { + mNextExpirationCheck = expTime; + } + } + + /** + * Remove expired entries from a map keyed by {@link Inet4Address}. + * + * @param tag Type of lease in the map, for logging + * @param getExpTime Functor returning the expiration time for an object in the map. + * Must not return null. + * @return The lowest expiration time among entries remaining in the map + */ + private <T> long removeExpired(long currentTime, @NonNull Map<Inet4Address, T> map, + @NonNull String tag, @NonNull Function<T, Long> getExpTime) { + final Iterator<Entry<Inet4Address, T>> it = map.entrySet().iterator(); + long firstExpiration = EXPIRATION_NEVER; + while (it.hasNext()) { + final Entry<Inet4Address, T> lease = it.next(); + final long expTime = getExpTime.apply(lease.getValue()); + if (expTime <= currentTime) { + mLog.logf("Removing expired %s lease for %s (expTime=%s, currentTime=%s)", + tag, lease.getKey(), expTime, currentTime); + it.remove(); + } else { + firstExpiration = min(firstExpiration, expTime); + } + } + return firstExpiration; + } + + /** + * Go through committed and declined leases and remove the expired ones. + */ + private void removeExpiredLeases(long currentTime) { + if (currentTime < mNextExpirationCheck) { + return; + } + + final long commExp = removeExpired( + currentTime, mCommittedLeases, "committed", DhcpLease::getExpTime); + final long declExp = removeExpired( + currentTime, mDeclinedAddrs, "declined", Function.identity()); + + mNextExpirationCheck = min(commExp, declExp); + } + + private boolean isAvailable(@NonNull Inet4Address addr) { + return !mReservedAddrs.contains(addr) && !mCommittedLeases.containsKey(addr); + } + + /** + * Get the 0-based index of an address in the subnet. + * + * <p>Given ordering of addresses 5.6.7.8 < 5.6.7.9 < 5.6.8.0, the index on a subnet is defined + * so that the first address is 0, the second 1, etc. For example on a /16, 192.168.0.0 -> 0, + * 192.168.0.1 -> 1, 192.168.1.0 -> 256 + * + */ + private int getAddrIndex(int addr) { + return addr & ~mSubnetMask; + } + + private int getAddrByIndex(int index) { + return mSubnetAddr | index; + } + + /** + * Get a valid address starting from the supplied one. + * + * <p>This only checks that the address is numerically valid for assignment, not whether it is + * already in use. The return value is always inside the configured prefix, even if the supplied + * address is not. + * + * <p>If the provided address is valid, it is returned as-is. Otherwise, the next valid + * address (with the ordering in {@link #getAddrIndex(int)}) is returned. + */ + private int getValidAddress(int addr) { + final int lastByteMask = 0xff; + int addrIndex = getAddrIndex(addr); // 0-based index of the address in the subnet + + // Some OSes do not handle addresses in .255 or .0 correctly: avoid those. + final int lastByte = getAddrByIndex(addrIndex) & lastByteMask; + if (lastByte == lastByteMask) { + // Avoid .255 address, and .0 address that follows + addrIndex = (addrIndex + 2) % mNumAddresses; + } else if (lastByte == 0) { + // Avoid .0 address + addrIndex = (addrIndex + 1) % mNumAddresses; + } + + // Do not use first or last address of range + if (addrIndex == 0 || addrIndex == mNumAddresses - 1) { + // Always valid and not end of range since prefixLength is at most 30 in serving params + addrIndex = 1; + } + return getAddrByIndex(addrIndex); + } + + /** + * Returns whether the address is in the configured subnet and part of the assignable range. + */ + private boolean isValidAddress(Inet4Address addr) { + final int intAddr = inet4AddressToIntHTH(addr); + return getValidAddress(intAddr) == intAddr; + } + + private int getNextAddress(int addr) { + final int addrIndex = getAddrIndex(addr); + final int nextAddress = getAddrByIndex((addrIndex + 1) % mNumAddresses); + return getValidAddress(nextAddress); + } + + /** + * Calculate a first candidate address for a client by hashing the hardware address. + * + * <p>This will be a valid address as checked by {@link #getValidAddress(int)}, but may be + * in use. + * + * @return An IPv4 address encoded as 32-bit int + */ + private int getFirstClientAddress(MacAddress hwAddr) { + // This follows dnsmasq behavior. Advantages are: clients will often get the same + // offers for different DISCOVER even if the lease was not yet accepted or has expired, + // and address generation will generally not need to loop through many allocated addresses + // until it finds a free one. + int hash = 0; + for (byte b : hwAddr.toByteArray()) { + hash += b + (b << 8) + (b << 16); + } + // This implementation will not always result in the same IPs as dnsmasq would give out in + // Android <= P, because it includes invalid and reserved addresses in mNumAddresses while + // the configured ranges for dnsmasq did not. + final int addrIndex = hash % mNumAddresses; + return getValidAddress(getAddrByIndex(addrIndex)); + } + + /** + * Create a lease that can be offered to respond to a client DISCOVER. + * + * <p>This method always succeeds and returns the lease if it does not throw. If no non-declined + * address is available, it will try to offer the oldest declined address if valid. + * + * @throws OutOfAddressesException The server has no address left to offer + */ + private DhcpLease makeNewOffer(@Nullable byte[] clientId, @NonNull MacAddress hwAddr, + long expTime, @Nullable String hostname) throws OutOfAddressesException { + int intAddr = getFirstClientAddress(hwAddr); + // Loop until a free address is found, or there are no more addresses. + // There is slightly less than this many usable addresses, but some extra looping is OK + for (int i = 0; i < mNumAddresses; i++) { + final Inet4Address addr = intToInet4AddressHTH(intAddr); + if (isAvailable(addr) && !mDeclinedAddrs.containsKey(addr)) { + return new DhcpLease(clientId, hwAddr, addr, expTime, hostname); + } + intAddr = getNextAddress(intAddr); + } + + // Try freeing DECLINEd addresses if out of addresses. + final Iterator<Inet4Address> it = mDeclinedAddrs.keySet().iterator(); + while (it.hasNext()) { + final Inet4Address addr = it.next(); + it.remove(); + mLog.logf("Out of addresses in address pool: dropped declined addr %s", addr); + // isValidAddress() is always verified for entries in mDeclinedAddrs. + // However declined addresses may have been requested (typically by the machine that was + // already using the address) after being declined. + if (isAvailable(addr)) { + return new DhcpLease(clientId, hwAddr, addr, expTime, hostname); + } + } + + throw new OutOfAddressesException("No address available for offer"); + } +} diff --git a/services/net/java/android/net/dhcp/DhcpNakPacket.java b/services/net/java/android/net/dhcp/DhcpNakPacket.java index 6458232b8fa0..ef9af527d063 100644 --- a/services/net/java/android/net/dhcp/DhcpNakPacket.java +++ b/services/net/java/android/net/dhcp/DhcpNakPacket.java @@ -26,11 +26,9 @@ class DhcpNakPacket extends DhcpPacket { /** * Generates a NAK packet with the specified parameters. */ - DhcpNakPacket(int transId, short secs, Inet4Address clientIp, Inet4Address yourIp, - Inet4Address nextIp, Inet4Address relayIp, - byte[] clientMac) { - super(transId, secs, INADDR_ANY, INADDR_ANY, nextIp, relayIp, - clientMac, false); + DhcpNakPacket(int transId, short secs, Inet4Address nextIp, Inet4Address relayIp, + byte[] clientMac, boolean broadcast) { + super(transId, secs, INADDR_ANY, INADDR_ANY, nextIp, relayIp, clientMac, broadcast); } public String toString() { @@ -43,11 +41,11 @@ class DhcpNakPacket extends DhcpPacket { */ public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) { ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH); - Inet4Address destIp = mClientIp; - Inet4Address srcIp = mYourIp; + // Constructor does not set values for layers <= 3: use empty values + Inet4Address destIp = INADDR_ANY; + Inet4Address srcIp = INADDR_ANY; - fillInPacket(encap, destIp, srcIp, destUdp, srcUdp, result, - DHCP_BOOTREPLY, mBroadcast); + fillInPacket(encap, destIp, srcIp, destUdp, srcUdp, result, DHCP_BOOTREPLY, mBroadcast); result.flip(); return result; } diff --git a/services/net/java/android/net/dhcp/DhcpPacket.java b/services/net/java/android/net/dhcp/DhcpPacket.java index d90a4a235972..888821a8ad92 100644 --- a/services/net/java/android/net/dhcp/DhcpPacket.java +++ b/services/net/java/android/net/dhcp/DhcpPacket.java @@ -1,5 +1,6 @@ package android.net.dhcp; +import android.annotation.Nullable; import android.net.DhcpResults; import android.net.LinkAddress; import android.net.NetworkUtils; @@ -204,6 +205,7 @@ public abstract class DhcpPacket { protected static final byte DHCP_MESSAGE_TYPE_DECLINE = 4; protected static final byte DHCP_MESSAGE_TYPE_ACK = 5; protected static final byte DHCP_MESSAGE_TYPE_NAK = 6; + protected static final byte DHCP_MESSAGE_TYPE_RELEASE = 7; protected static final byte DHCP_MESSAGE_TYPE_INFORM = 8; /** @@ -252,6 +254,7 @@ public abstract class DhcpPacket { * DHCP Optional Type: DHCP Client Identifier */ protected static final byte DHCP_CLIENT_IDENTIFIER = 61; + protected byte[] mClientId; /** * DHCP zero-length option code: pad @@ -281,7 +284,7 @@ public abstract class DhcpPacket { protected final Inet4Address mClientIp; protected final Inet4Address mYourIp; private final Inet4Address mNextIp; - private final Inet4Address mRelayIp; + protected final Inet4Address mRelayIp; /** * Does the client request a broadcast response? @@ -338,13 +341,28 @@ public abstract class DhcpPacket { return mClientMac; } + // TODO: refactor DhcpClient to set clientId when constructing packets and remove + // hasExplicitClientId logic /** - * Returns the client ID. This follows RFC 2132 and is based on the hardware address. + * Returns whether a client ID was set in the options for this packet. + */ + public boolean hasExplicitClientId() { + return mClientId != null; + } + + /** + * Returns the client ID. If not set explicitly, this follows RFC 2132 and creates a client ID + * based on the hardware address. */ public byte[] getClientId() { - byte[] clientId = new byte[mClientMac.length + 1]; - clientId[0] = CLIENT_ID_ETHER; - System.arraycopy(mClientMac, 0, clientId, 1, mClientMac.length); + final byte[] clientId; + if (hasExplicitClientId()) { + clientId = Arrays.copyOf(mClientId, mClientId.length); + } else { + clientId = new byte[mClientMac.length + 1]; + clientId[0] = CLIENT_ID_ETHER; + System.arraycopy(mClientMac, 0, clientId, 1, mClientMac.length); + } return clientId; } @@ -531,8 +549,10 @@ public abstract class DhcpPacket { /** * Adds an optional parameter containing an array of bytes. + * + * <p>This method is a no-op if the payload argument is null. */ - protected static void addTlv(ByteBuffer buf, byte type, byte[] payload) { + protected static void addTlv(ByteBuffer buf, byte type, @Nullable byte[] payload) { if (payload != null) { if (payload.length > MAX_OPTION_LEN) { throw new IllegalArgumentException("DHCP option too long: " @@ -546,8 +566,10 @@ public abstract class DhcpPacket { /** * Adds an optional parameter containing an IP address. + * + * <p>This method is a no-op if the address argument is null. */ - protected static void addTlv(ByteBuffer buf, byte type, Inet4Address addr) { + protected static void addTlv(ByteBuffer buf, byte type, @Nullable Inet4Address addr) { if (addr != null) { addTlv(buf, type, addr.getAddress()); } @@ -555,8 +577,10 @@ public abstract class DhcpPacket { /** * Adds an optional parameter containing a list of IP addresses. + * + * <p>This method is a no-op if the addresses argument is null or empty. */ - protected static void addTlv(ByteBuffer buf, byte type, List<Inet4Address> addrs) { + protected static void addTlv(ByteBuffer buf, byte type, @Nullable List<Inet4Address> addrs) { if (addrs == null || addrs.size() == 0) return; int optionLen = 4 * addrs.size(); @@ -574,9 +598,11 @@ public abstract class DhcpPacket { } /** - * Adds an optional parameter containing a short integer + * Adds an optional parameter containing a short integer. + * + * <p>This method is a no-op if the value argument is null. */ - protected static void addTlv(ByteBuffer buf, byte type, Short value) { + protected static void addTlv(ByteBuffer buf, byte type, @Nullable Short value) { if (value != null) { buf.put(type); buf.put((byte) 2); @@ -585,9 +611,11 @@ public abstract class DhcpPacket { } /** - * Adds an optional parameter containing a simple integer + * Adds an optional parameter containing a simple integer. + * + * <p>This method is a no-op if the value argument is null. */ - protected static void addTlv(ByteBuffer buf, byte type, Integer value) { + protected static void addTlv(ByteBuffer buf, byte type, @Nullable Integer value) { if (value != null) { buf.put(type); buf.put((byte) 4); @@ -597,12 +625,16 @@ public abstract class DhcpPacket { /** * Adds an optional parameter containing an ASCII string. + * + * <p>This method is a no-op if the string argument is null. */ - protected static void addTlv(ByteBuffer buf, byte type, String str) { - try { - addTlv(buf, type, str.getBytes("US-ASCII")); - } catch (UnsupportedEncodingException e) { - throw new IllegalArgumentException("String is not US-ASCII: " + str); + protected static void addTlv(ByteBuffer buf, byte type, @Nullable String str) { + if (str != null) { + try { + addTlv(buf, type, str.getBytes("US-ASCII")); + } catch (UnsupportedEncodingException e) { + throw new IllegalArgumentException("String is not US-ASCII: " + str); + } } } @@ -740,6 +772,7 @@ public abstract class DhcpPacket { Inet4Address nextIp; Inet4Address relayIp; byte[] clientMac; + byte[] clientId = null; List<Inet4Address> dnsServers = new ArrayList<>(); List<Inet4Address> gateways = new ArrayList<>(); // aka router Inet4Address serverIdentifier = null; @@ -1038,8 +1071,8 @@ public abstract class DhcpPacket { throw new ParseException(DhcpErrorEvent.DHCP_NO_MSG_TYPE, "No DHCP message type option"); case DHCP_MESSAGE_TYPE_DISCOVER: - newPacket = new DhcpDiscoverPacket( - transactionId, secs, clientMac, broadcast); + newPacket = new DhcpDiscoverPacket(transactionId, secs, relayIp, clientMac, + broadcast, ipSrc); break; case DHCP_MESSAGE_TYPE_OFFER: newPacket = new DhcpOfferPacket( @@ -1047,7 +1080,7 @@ public abstract class DhcpPacket { break; case DHCP_MESSAGE_TYPE_REQUEST: newPacket = new DhcpRequestPacket( - transactionId, secs, clientIp, clientMac, broadcast); + transactionId, secs, clientIp, relayIp, clientMac, broadcast); break; case DHCP_MESSAGE_TYPE_DECLINE: newPacket = new DhcpDeclinePacket( @@ -1060,8 +1093,15 @@ public abstract class DhcpPacket { break; case DHCP_MESSAGE_TYPE_NAK: newPacket = new DhcpNakPacket( - transactionId, secs, clientIp, yourIp, nextIp, relayIp, - clientMac); + transactionId, secs, nextIp, relayIp, clientMac, broadcast); + break; + case DHCP_MESSAGE_TYPE_RELEASE: + if (serverIdentifier == null) { + throw new ParseException(DhcpErrorEvent.MISC_ERROR, + "DHCPRELEASE without server identifier"); + } + newPacket = new DhcpReleasePacket( + transactionId, serverIdentifier, clientIp, relayIp, clientMac); break; case DHCP_MESSAGE_TYPE_INFORM: newPacket = new DhcpInformPacket( @@ -1074,6 +1114,7 @@ public abstract class DhcpPacket { } newPacket.mBroadcastAddress = bcAddr; + newPacket.mClientId = clientId; newPacket.mDnsServers = dnsServers; newPacket.mDomainName = domainName; newPacket.mGateways = gateways; @@ -1173,8 +1214,8 @@ public abstract class DhcpPacket { */ public static ByteBuffer buildDiscoverPacket(int encap, int transactionId, short secs, byte[] clientMac, boolean broadcast, byte[] expectedParams) { - DhcpPacket pkt = new DhcpDiscoverPacket( - transactionId, secs, clientMac, broadcast); + DhcpPacket pkt = new DhcpDiscoverPacket(transactionId, secs, INADDR_ANY /* relayIp */, + clientMac, broadcast, INADDR_ANY /* srcIp */); pkt.mRequestedParams = expectedParams; return pkt.buildPacket(encap, DHCP_SERVER, DHCP_CLIENT); } @@ -1223,12 +1264,11 @@ public abstract class DhcpPacket { /** * Builds a DHCP-NAK packet from the required specified parameters. */ - public static ByteBuffer buildNakPacket(int encap, int transactionId, - Inet4Address serverIpAddr, Inet4Address clientIpAddr, byte[] mac) { - DhcpPacket pkt = new DhcpNakPacket(transactionId, (short) 0, clientIpAddr, - serverIpAddr, serverIpAddr, serverIpAddr, mac); - pkt.mMessage = "requested address not available"; - pkt.mRequestedIp = clientIpAddr; + public static ByteBuffer buildNakPacket(int encap, int transactionId, Inet4Address serverIpAddr, + byte[] mac, boolean broadcast, String message) { + DhcpPacket pkt = new DhcpNakPacket( + transactionId, (short) 0, serverIpAddr, serverIpAddr, mac, broadcast); + pkt.mMessage = message; return pkt.buildPacket(encap, DHCP_CLIENT, DHCP_SERVER); } @@ -1240,7 +1280,7 @@ public abstract class DhcpPacket { byte[] clientMac, Inet4Address requestedIpAddress, Inet4Address serverIdentifier, byte[] requestedParams, String hostName) { DhcpPacket pkt = new DhcpRequestPacket(transactionId, secs, clientIp, - clientMac, broadcast); + INADDR_ANY /* relayIp */, clientMac, broadcast); pkt.mRequestedIp = requestedIpAddress; pkt.mServerIdentifier = serverIdentifier; pkt.mHostName = hostName; diff --git a/services/net/java/android/net/dhcp/DhcpPacketListener.java b/services/net/java/android/net/dhcp/DhcpPacketListener.java new file mode 100644 index 000000000000..498fd93fff59 --- /dev/null +++ b/services/net/java/android/net/dhcp/DhcpPacketListener.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2018 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.dhcp; + +import android.annotation.Nullable; +import android.net.util.FdEventsReader; +import android.net.util.PacketReader; +import android.os.Handler; +import android.system.Os; + +import java.io.FileDescriptor; +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; + +/** + * A {@link FdEventsReader} to receive and parse {@link DhcpPacket}. + * @hide + */ +abstract class DhcpPacketListener extends FdEventsReader<DhcpPacketListener.Payload> { + static final class Payload { + final byte[] bytes = new byte[DhcpPacket.MAX_LENGTH]; + Inet4Address srcAddr; + } + + public DhcpPacketListener(Handler handler) { + super(handler, new Payload()); + } + + @Override + protected int recvBufSize(Payload buffer) { + return buffer.bytes.length; + } + + @Override + protected final void handlePacket(Payload recvbuf, int length) { + if (recvbuf.srcAddr == null) { + return; + } + + try { + final DhcpPacket packet = DhcpPacket.decodeFullPacket(recvbuf.bytes, length, + DhcpPacket.ENCAP_BOOTP); + onReceive(packet, recvbuf.srcAddr); + } catch (DhcpPacket.ParseException e) { + logParseError(recvbuf.bytes, length, e); + } + } + + @Override + protected int readPacket(FileDescriptor fd, Payload packetBuffer) throws Exception { + final InetSocketAddress addr = new InetSocketAddress(); + final int read = Os.recvfrom( + fd, packetBuffer.bytes, 0, packetBuffer.bytes.length, 0 /* flags */, addr); + + // Buffers with null srcAddr will be dropped in handlePacket() + packetBuffer.srcAddr = inet4AddrOrNull(addr); + return read; + } + + @Nullable + private static Inet4Address inet4AddrOrNull(InetSocketAddress addr) { + return addr.getAddress() instanceof Inet4Address + ? (Inet4Address) addr.getAddress() + : null; + } + + protected abstract void onReceive(DhcpPacket packet, Inet4Address srcAddr); + protected abstract void logParseError(byte[] packet, int length, DhcpPacket.ParseException e); +} diff --git a/services/net/java/android/net/dhcp/DhcpReleasePacket.java b/services/net/java/android/net/dhcp/DhcpReleasePacket.java new file mode 100644 index 000000000000..39583032c20d --- /dev/null +++ b/services/net/java/android/net/dhcp/DhcpReleasePacket.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2018 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.dhcp; + +import java.net.Inet4Address; +import java.nio.ByteBuffer; + +/** + * Implements DHCP-RELEASE + */ +class DhcpReleasePacket extends DhcpPacket { + + final Inet4Address mClientAddr; + + /** + * Generates a RELEASE packet with the specified parameters. + */ + public DhcpReleasePacket(int transId, Inet4Address serverId, Inet4Address clientAddr, + Inet4Address relayIp, byte[] clientMac) { + super(transId, (short)0, clientAddr, INADDR_ANY /* yourIp */, INADDR_ANY /* nextIp */, + relayIp, clientMac, false /* broadcast */); + mServerIdentifier = serverId; + mClientAddr = clientAddr; + } + + + @Override + public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) { + ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH); + fillInPacket(encap, mServerIdentifier /* destIp */, mClientIp /* srcIp */, destUdp, srcUdp, + result, DHCP_BOOTREPLY, mBroadcast); + result.flip(); + return result; + } + + @Override + void finishPacket(ByteBuffer buffer) { + addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_RELEASE); + addTlv(buffer, DHCP_CLIENT_IDENTIFIER, getClientId()); + addTlv(buffer, DHCP_SERVER_IDENTIFIER, mServerIdentifier); + addCommonClientTlvs(buffer); + addTlvEnd(buffer); + } +} diff --git a/services/net/java/android/net/dhcp/DhcpRequestPacket.java b/services/net/java/android/net/dhcp/DhcpRequestPacket.java index 4f9aa01151ca..231d04576c28 100644 --- a/services/net/java/android/net/dhcp/DhcpRequestPacket.java +++ b/services/net/java/android/net/dhcp/DhcpRequestPacket.java @@ -28,9 +28,9 @@ class DhcpRequestPacket extends DhcpPacket { /** * Generates a REQUEST packet with the specified parameters. */ - DhcpRequestPacket(int transId, short secs, Inet4Address clientIp, byte[] clientMac, - boolean broadcast) { - super(transId, secs, clientIp, INADDR_ANY, INADDR_ANY, INADDR_ANY, clientMac, broadcast); + DhcpRequestPacket(int transId, short secs, Inet4Address clientIp, Inet4Address relayIp, + byte[] clientMac, boolean broadcast) { + super(transId, secs, clientIp, INADDR_ANY, INADDR_ANY, relayIp, clientMac, broadcast); } public String toString() { diff --git a/services/net/java/android/net/dhcp/DhcpServingParams.java b/services/net/java/android/net/dhcp/DhcpServingParams.java new file mode 100644 index 000000000000..ba9d116d0bd5 --- /dev/null +++ b/services/net/java/android/net/dhcp/DhcpServingParams.java @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2018 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.dhcp; + +import static android.net.NetworkUtils.getPrefixMaskAsInet4Address; +import static android.net.dhcp.DhcpPacket.INFINITE_LEASE; +import static android.net.util.NetworkConstants.IPV4_MAX_MTU; +import static android.net.util.NetworkConstants.IPV4_MIN_MTU; + +import static java.lang.Integer.toUnsignedLong; + +import android.annotation.NonNull; +import android.net.IpPrefix; +import android.net.LinkAddress; +import android.net.NetworkUtils; + +import java.net.Inet4Address; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * Parameters used by the DhcpServer to serve requests. + * + * <p>Instances are immutable. Use {@link DhcpServingParams.Builder} to instantiate. + * @hide + */ +public class DhcpServingParams { + public static final int MTU_UNSET = 0; + public static final int MIN_PREFIX_LENGTH = 16; + public static final int MAX_PREFIX_LENGTH = 30; + + /** Server inet address and prefix to serve */ + @NonNull + public final LinkAddress serverAddr; + + /** + * Default routers to be advertised to DHCP clients. May be empty. + * This set is provided by {@link DhcpServingParams.Builder} and is immutable. + */ + @NonNull + public final Set<Inet4Address> defaultRouters; + + /** + * DNS servers to be advertised to DHCP clients. May be empty. + * This set is provided by {@link DhcpServingParams.Builder} and is immutable. + */ + @NonNull + public final Set<Inet4Address> dnsServers; + + /** + * Excluded addresses that the DHCP server is not allowed to assign to clients. + * This set is provided by {@link DhcpServingParams.Builder} and is immutable. + */ + @NonNull + public final Set<Inet4Address> excludedAddrs; + + // DHCP uses uint32. Use long for clearer code, and check range when building. + public final long dhcpLeaseTimeSecs; + public final int linkMtu; + + /** + * Checked exception thrown when some parameters used to build {@link DhcpServingParams} are + * missing or invalid. + */ + public static class InvalidParameterException extends Exception { + public InvalidParameterException(String message) { + super(message); + } + } + + private DhcpServingParams(@NonNull LinkAddress serverAddr, + @NonNull Set<Inet4Address> defaultRouters, + @NonNull Set<Inet4Address> dnsServers, @NonNull Set<Inet4Address> excludedAddrs, + long dhcpLeaseTimeSecs, int linkMtu) { + this.serverAddr = serverAddr; + this.defaultRouters = defaultRouters; + this.dnsServers = dnsServers; + this.excludedAddrs = excludedAddrs; + this.dhcpLeaseTimeSecs = dhcpLeaseTimeSecs; + this.linkMtu = linkMtu; + } + + @NonNull + public Inet4Address getServerInet4Addr() { + return (Inet4Address) serverAddr.getAddress(); + } + + /** + * Get the served prefix mask as an IPv4 address. + * + * <p>For example, if the served prefix is 192.168.42.0/24, this will return 255.255.255.0. + */ + @NonNull + public Inet4Address getPrefixMaskAsAddress() { + return getPrefixMaskAsInet4Address(serverAddr.getPrefixLength()); + } + + /** + * Get the server broadcast address. + * + * <p>For example, if the server {@link LinkAddress} is 192.168.42.1/24, this will return + * 192.168.42.255. + */ + @NonNull + public Inet4Address getBroadcastAddress() { + return NetworkUtils.getBroadcastAddress(getServerInet4Addr(), serverAddr.getPrefixLength()); + } + + /** + * Utility class to create new instances of {@link DhcpServingParams} while checking validity + * of the parameters. + */ + public static class Builder { + private LinkAddress serverAddr; + private Set<Inet4Address> defaultRouters; + private Set<Inet4Address> dnsServers; + private Set<Inet4Address> excludedAddrs; + private long dhcpLeaseTimeSecs; + private int linkMtu = MTU_UNSET; + + /** + * Set the server address and served prefix for the DHCP server. + * + * <p>This parameter is required. + */ + public Builder setServerAddr(@NonNull LinkAddress serverAddr) { + this.serverAddr = serverAddr; + return this; + } + + /** + * Set the default routers to be advertised to DHCP clients. + * + * <p>Each router must be inside the served prefix. This may be an empty set, but it must + * always be set explicitly before building the {@link DhcpServingParams}. + */ + public Builder setDefaultRouters(@NonNull Set<Inet4Address> defaultRouters) { + this.defaultRouters = defaultRouters; + return this; + } + + /** + * Set the DNS servers to be advertised to DHCP clients. + * + * <p>This may be an empty set, but it must always be set explicitly before building the + * {@link DhcpServingParams}. + */ + public Builder setDnsServers(@NonNull Set<Inet4Address> dnsServers) { + this.dnsServers = dnsServers; + return this; + } + + /** + * Set excluded addresses that the DHCP server is not allowed to assign to clients. + * + * <p>This parameter is optional. DNS servers and default routers are always excluded + * and do not need to be set here. + */ + public Builder setExcludedAddrs(@NonNull Set<Inet4Address> excludedAddrs) { + this.excludedAddrs = excludedAddrs; + return this; + } + + /** + * Set the lease time for leases assigned by the DHCP server. + * + * <p>This parameter is required. + */ + public Builder setDhcpLeaseTimeSecs(long dhcpLeaseTimeSecs) { + this.dhcpLeaseTimeSecs = dhcpLeaseTimeSecs; + return this; + } + + /** + * Set the link MTU to be advertised to DHCP clients. + * + * <p>If set to {@link #MTU_UNSET}, no MTU will be advertised to clients. This parameter + * is optional and defaults to {@link #MTU_UNSET}. + */ + public Builder setLinkMtu(int linkMtu) { + this.linkMtu = linkMtu; + return this; + } + + /** + * Create a new {@link DhcpServingParams} instance based on parameters set in the builder. + * + * <p>This method has no side-effects. If it does not throw, a valid + * {@link DhcpServingParams} is returned. + * @return The constructed parameters. + * @throws InvalidParameterException At least one parameter is missing or invalid. + */ + @NonNull + public DhcpServingParams build() throws InvalidParameterException { + if (serverAddr == null) { + throw new InvalidParameterException("Missing serverAddr"); + } + if (defaultRouters == null) { + throw new InvalidParameterException("Missing defaultRouters"); + } + if (dnsServers == null) { + // Empty set is OK, but enforce explicitly setting it + throw new InvalidParameterException("Missing dnsServers"); + } + if (dhcpLeaseTimeSecs <= 0 || dhcpLeaseTimeSecs > toUnsignedLong(INFINITE_LEASE)) { + throw new InvalidParameterException("Invalid lease time: " + dhcpLeaseTimeSecs); + } + if (linkMtu != MTU_UNSET && (linkMtu < IPV4_MIN_MTU || linkMtu > IPV4_MAX_MTU)) { + throw new InvalidParameterException("Invalid link MTU: " + linkMtu); + } + if (!serverAddr.isIPv4()) { + throw new InvalidParameterException("serverAddr must be IPv4"); + } + if (serverAddr.getPrefixLength() < MIN_PREFIX_LENGTH + || serverAddr.getPrefixLength() > MAX_PREFIX_LENGTH) { + throw new InvalidParameterException("Prefix length is not in supported range"); + } + + final IpPrefix prefix = makeIpPrefix(serverAddr); + for (Inet4Address addr : defaultRouters) { + if (!prefix.contains(addr)) { + throw new InvalidParameterException(String.format( + "Default router %s is not in server prefix %s", addr, serverAddr)); + } + } + + final Set<Inet4Address> excl = new HashSet<>(); + if (excludedAddrs != null) { + excl.addAll(excludedAddrs); + } + excl.add((Inet4Address) serverAddr.getAddress()); + excl.addAll(defaultRouters); + excl.addAll(dnsServers); + + return new DhcpServingParams(serverAddr, + Collections.unmodifiableSet(new HashSet<>(defaultRouters)), + Collections.unmodifiableSet(new HashSet<>(dnsServers)), + Collections.unmodifiableSet(excl), + dhcpLeaseTimeSecs, linkMtu); + } + } + + @NonNull + static IpPrefix makeIpPrefix(@NonNull LinkAddress addr) { + return new IpPrefix(addr.getAddress(), addr.getPrefixLength()); + } +} diff --git a/services/net/java/android/net/ip/IpClient.java b/services/net/java/android/net/ip/IpClient.java index 7f821ffd369a..b77da28401d6 100644 --- a/services/net/java/android/net/ip/IpClient.java +++ b/services/net/java/android/net/ip/IpClient.java @@ -524,7 +524,7 @@ public class IpClient extends StateMachine { return false; } // There no more than one IPv4 address - if (ipAddresses.stream().filter(Inet4Address.class::isInstance).count() > 1) { + if (ipAddresses.stream().filter(LinkAddress::isIPv4).count() > 1) { return false; } diff --git a/services/net/java/android/net/util/FdEventsReader.java b/services/net/java/android/net/util/FdEventsReader.java new file mode 100644 index 000000000000..575444f89bc3 --- /dev/null +++ b/services/net/java/android/net/util/FdEventsReader.java @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2016 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.util; + +import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT; +import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Handler; +import android.os.Looper; +import android.os.MessageQueue; +import android.system.ErrnoException; +import android.system.OsConstants; + +import libcore.io.IoUtils; + +import java.io.FileDescriptor; + + +/** + * This class encapsulates the mechanics of registering a file descriptor + * with a thread's Looper and handling read events (and errors). + * + * Subclasses MUST implement createFd() and SHOULD override handlePacket(). They MAY override + * onStop() and onStart(). + * + * Subclasses can expect a call life-cycle like the following: + * + * [1] when a client calls start(), createFd() is called, followed by the onStart() hook if all + * goes well. Implementations may override onStart() for additional initialization. + * + * [2] yield, waiting for read event or error notification: + * + * [a] readPacket() && handlePacket() + * + * [b] if (no error): + * goto 2 + * else: + * goto 3 + * + * [3] when a client calls stop(), the onStop() hook is called (unless already stopped or never + * started). Implementations may override onStop() for additional cleanup. + * + * The packet receive buffer is recycled on every read call, so subclasses + * should make any copies they would like inside their handlePacket() + * implementation. + * + * All public methods MUST only be called from the same thread with which + * the Handler constructor argument is associated. + * + * @hide + */ +public abstract class FdEventsReader<BufferType> { + private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR; + private static final int UNREGISTER_THIS_FD = 0; + + @NonNull + private final Handler mHandler; + @NonNull + private final MessageQueue mQueue; + @NonNull + private final BufferType mBuffer; + @Nullable + private FileDescriptor mFd; + private long mPacketsReceived; + + protected static void closeFd(FileDescriptor fd) { + IoUtils.closeQuietly(fd); + } + + protected FdEventsReader(@NonNull Handler h, @NonNull BufferType buffer) { + mHandler = h; + mQueue = mHandler.getLooper().getQueue(); + mBuffer = buffer; + } + + public final void start() { + if (onCorrectThread()) { + createAndRegisterFd(); + } else { + mHandler.post(() -> { + logError("start() called from off-thread", null); + createAndRegisterFd(); + }); + } + } + + public final void stop() { + if (onCorrectThread()) { + unregisterAndDestroyFd(); + } else { + mHandler.post(() -> { + logError("stop() called from off-thread", null); + unregisterAndDestroyFd(); + }); + } + } + + @NonNull + public Handler getHandler() { return mHandler; } + + protected abstract int recvBufSize(@NonNull BufferType buffer); + + public int recvBufSize() { return recvBufSize(mBuffer); } + + /** + * Get the number of successful calls to {@link #readPacket(FileDescriptor, Object)}. + * + * <p>A call was successful if {@link #readPacket(FileDescriptor, Object)} returned a value > 0. + */ + public final long numPacketsReceived() { return mPacketsReceived; } + + /** + * Subclasses MUST create the listening socket here, including setting + * all desired socket options, interface or address/port binding, etc. + */ + @Nullable + protected abstract FileDescriptor createFd(); + + /** + * Implementations MUST return the bytes read or throw an Exception. + * + * <p>The caller may throw a {@link ErrnoException} with {@link OsConstants#EAGAIN} or + * {@link OsConstants#EINTR}, in which case {@link FdEventsReader} will ignore the buffer + * contents and respectively wait for further input or retry the read immediately. For all other + * exceptions, the {@link FdEventsReader} will be stopped with no more interactions with this + * method. + */ + protected abstract int readPacket(@NonNull FileDescriptor fd, @NonNull BufferType buffer) + throws Exception; + + /** + * Called by the main loop for every packet. Any desired copies of + * |recvbuf| should be made in here, as the underlying byte array is + * reused across all reads. + */ + protected void handlePacket(@NonNull BufferType recvbuf, int length) {} + + /** + * Called by the main loop to log errors. In some cases |e| may be null. + */ + protected void logError(@NonNull String msg, @Nullable Exception e) {} + + /** + * Called by start(), if successful, just prior to returning. + */ + protected void onStart() {} + + /** + * Called by stop() just prior to returning. + */ + protected void onStop() {} + + private void createAndRegisterFd() { + if (mFd != null) return; + + try { + mFd = createFd(); + if (mFd != null) { + // Force the socket to be non-blocking. + IoUtils.setBlocking(mFd, false); + } + } catch (Exception e) { + logError("Failed to create socket: ", e); + closeFd(mFd); + mFd = null; + } + + if (mFd == null) return; + + mQueue.addOnFileDescriptorEventListener( + mFd, + FD_EVENTS, + (fd, events) -> { + // Always call handleInput() so read/recvfrom are given + // a proper chance to encounter a meaningful errno and + // perhaps log a useful error message. + if (!isRunning() || !handleInput()) { + unregisterAndDestroyFd(); + return UNREGISTER_THIS_FD; + } + return FD_EVENTS; + }); + onStart(); + } + + private boolean isRunning() { return (mFd != null) && mFd.valid(); } + + // Keep trying to read until we get EAGAIN/EWOULDBLOCK or some fatal error. + private boolean handleInput() { + while (isRunning()) { + final int bytesRead; + + try { + bytesRead = readPacket(mFd, mBuffer); + if (bytesRead < 1) { + if (isRunning()) logError("Socket closed, exiting", null); + break; + } + mPacketsReceived++; + } catch (ErrnoException e) { + if (e.errno == OsConstants.EAGAIN) { + // We've read everything there is to read this time around. + return true; + } else if (e.errno == OsConstants.EINTR) { + continue; + } else { + if (isRunning()) logError("readPacket error: ", e); + break; + } + } catch (Exception e) { + if (isRunning()) logError("readPacket error: ", e); + break; + } + + try { + handlePacket(mBuffer, bytesRead); + } catch (Exception e) { + logError("handlePacket error: ", e); + break; + } + } + + return false; + } + + private void unregisterAndDestroyFd() { + if (mFd == null) return; + + mQueue.removeOnFileDescriptorEventListener(mFd); + closeFd(mFd); + mFd = null; + onStop(); + } + + private boolean onCorrectThread() { + return (mHandler.getLooper() == Looper.myLooper()); + } +} diff --git a/services/net/java/android/net/util/NetworkConstants.java b/services/net/java/android/net/util/NetworkConstants.java index de04fd0ced04..3defe56939f5 100644 --- a/services/net/java/android/net/util/NetworkConstants.java +++ b/services/net/java/android/net/util/NetworkConstants.java @@ -77,10 +77,12 @@ public final class NetworkConstants { /** * IPv4 constants. * - * See als: + * See also: * - https://tools.ietf.org/html/rfc791 */ public static final int IPV4_HEADER_MIN_LEN = 20; + public static final int IPV4_MIN_MTU = 68; + public static final int IPV4_MAX_MTU = 65_535; public static final int IPV4_IHL_MASK = 0xf; public static final int IPV4_FLAGS_OFFSET = 6; public static final int IPV4_FRAGMENT_MASK = 0x1fff; diff --git a/services/net/java/android/net/util/PacketReader.java b/services/net/java/android/net/util/PacketReader.java index 10da2a551e21..4aec6b6753a6 100644 --- a/services/net/java/android/net/util/PacketReader.java +++ b/services/net/java/android/net/util/PacketReader.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2018 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. @@ -16,236 +16,46 @@ package android.net.util; -import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT; -import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR; +import static java.lang.Math.max; -import android.annotation.Nullable; import android.os.Handler; -import android.os.Looper; -import android.os.MessageQueue; -import android.os.MessageQueue.OnFileDescriptorEventListener; -import android.system.ErrnoException; import android.system.Os; -import android.system.OsConstants; - -import libcore.io.IoUtils; import java.io.FileDescriptor; -import java.io.IOException; - /** - * This class encapsulates the mechanics of registering a file descriptor - * with a thread's Looper and handling read events (and errors). - * - * Subclasses MUST implement createFd() and SHOULD override handlePacket(). - - * Subclasses can expect a call life-cycle like the following: - * - * [1] start() calls createFd() and (if all goes well) onStart() - * - * [2] yield, waiting for read event or error notification: - * - * [a] readPacket() && handlePacket() - * - * [b] if (no error): - * goto 2 - * else: - * goto 3 - * - * [3] stop() calls onStop() if not previously stopped - * - * The packet receive buffer is recycled on every read call, so subclasses - * should make any copies they would like inside their handlePacket() - * implementation. - * - * All public methods MUST only be called from the same thread with which - * the Handler constructor argument is associated. + * Specialization of {@link FdEventsReader} that reads packets into a byte array. * * TODO: rename this class to something more correctly descriptive (something * like [or less horrible than] FdReadEventsHandler?). * * @hide */ -public abstract class PacketReader { - private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR; - private static final int UNREGISTER_THIS_FD = 0; +public abstract class PacketReader extends FdEventsReader<byte[]> { public static final int DEFAULT_RECV_BUF_SIZE = 2 * 1024; - private final Handler mHandler; - private final MessageQueue mQueue; - private final byte[] mPacket; - private FileDescriptor mFd; - private long mPacketsReceived; - - protected static void closeFd(FileDescriptor fd) { - IoUtils.closeQuietly(fd); - } - protected PacketReader(Handler h) { this(h, DEFAULT_RECV_BUF_SIZE); } - protected PacketReader(Handler h, int recvbufsize) { - mHandler = h; - mQueue = mHandler.getLooper().getQueue(); - mPacket = new byte[Math.max(recvbufsize, DEFAULT_RECV_BUF_SIZE)]; - } - - public final void start() { - if (onCorrectThread()) { - createAndRegisterFd(); - } else { - mHandler.post(() -> { - logError("start() called from off-thread", null); - createAndRegisterFd(); - }); - } + protected PacketReader(Handler h, int recvBufSize) { + super(h, new byte[max(recvBufSize, DEFAULT_RECV_BUF_SIZE)]); } - public final void stop() { - if (onCorrectThread()) { - unregisterAndDestroyFd(); - } else { - mHandler.post(() -> { - logError("stop() called from off-thread", null); - unregisterAndDestroyFd(); - }); - } + @Override + protected final int recvBufSize(byte[] buffer) { + return buffer.length; } - public Handler getHandler() { return mHandler; } - - public final int recvBufSize() { return mPacket.length; } - - public final long numPacketsReceived() { return mPacketsReceived; } - - /** - * Subclasses MUST create the listening socket here, including setting - * all desired socket options, interface or address/port binding, etc. - */ - protected abstract FileDescriptor createFd(); - /** * Subclasses MAY override this to change the default read() implementation * in favour of, say, recvfrom(). * * Implementations MUST return the bytes read or throw an Exception. */ + @Override protected int readPacket(FileDescriptor fd, byte[] packetBuffer) throws Exception { return Os.read(fd, packetBuffer, 0, packetBuffer.length); } - - /** - * Called by the main loop for every packet. Any desired copies of - * |recvbuf| should be made in here, as the underlying byte array is - * reused across all reads. - */ - protected void handlePacket(byte[] recvbuf, int length) {} - - /** - * Called by the main loop to log errors. In some cases |e| may be null. - */ - protected void logError(String msg, Exception e) {} - - /** - * Called by start(), if successful, just prior to returning. - */ - protected void onStart() {} - - /** - * Called by stop() just prior to returning. - */ - protected void onStop() {} - - private void createAndRegisterFd() { - if (mFd != null) return; - - try { - mFd = createFd(); - if (mFd != null) { - // Force the socket to be non-blocking. - IoUtils.setBlocking(mFd, false); - } - } catch (Exception e) { - logError("Failed to create socket: ", e); - closeFd(mFd); - mFd = null; - return; - } - - if (mFd == null) return; - - mQueue.addOnFileDescriptorEventListener( - mFd, - FD_EVENTS, - new OnFileDescriptorEventListener() { - @Override - public int onFileDescriptorEvents(FileDescriptor fd, int events) { - // Always call handleInput() so read/recvfrom are given - // a proper chance to encounter a meaningful errno and - // perhaps log a useful error message. - if (!isRunning() || !handleInput()) { - unregisterAndDestroyFd(); - return UNREGISTER_THIS_FD; - } - return FD_EVENTS; - } - }); - onStart(); - } - - private boolean isRunning() { return (mFd != null) && mFd.valid(); } - - // Keep trying to read until we get EAGAIN/EWOULDBLOCK or some fatal error. - private boolean handleInput() { - while (isRunning()) { - final int bytesRead; - - try { - bytesRead = readPacket(mFd, mPacket); - if (bytesRead < 1) { - if (isRunning()) logError("Socket closed, exiting", null); - break; - } - mPacketsReceived++; - } catch (ErrnoException e) { - if (e.errno == OsConstants.EAGAIN) { - // We've read everything there is to read this time around. - return true; - } else if (e.errno == OsConstants.EINTR) { - continue; - } else { - if (isRunning()) logError("readPacket error: ", e); - break; - } - } catch (Exception e) { - if (isRunning()) logError("readPacket error: ", e); - break; - } - - try { - handlePacket(mPacket, bytesRead); - } catch (Exception e) { - logError("handlePacket error: ", e); - break; - } - } - - return false; - } - - private void unregisterAndDestroyFd() { - if (mFd == null) return; - - mQueue.removeOnFileDescriptorEventListener(mFd); - closeFd(mFd); - mFd = null; - onStop(); - } - - private boolean onCorrectThread() { - return (mHandler.getLooper() == Looper.myLooper()); - } } diff --git a/services/net/java/android/net/util/SharedLog.java b/services/net/java/android/net/util/SharedLog.java index bbd3d13efbd6..f7bf393f367b 100644 --- a/services/net/java/android/net/util/SharedLog.java +++ b/services/net/java/android/net/util/SharedLog.java @@ -16,6 +16,7 @@ package android.net.util; +import android.annotation.NonNull; import android.text.TextUtils; import android.util.LocalLog; import android.util.Log; @@ -90,6 +91,13 @@ public class SharedLog { Log.e(mTag, record(Category.ERROR, msg)); } + /** + * Log an error due to an exception, with the exception stacktrace. + */ + public void e(@NonNull String msg, @NonNull Throwable e) { + Log.e(mTag, record(Category.ERROR, msg + ": " + e.getMessage()), e); + } + public void i(String msg) { Log.i(mTag, record(Category.NONE, msg)); } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java index 9d617a96e60f..48c89025e865 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java @@ -18,13 +18,13 @@ package com.android.server.hdmi; import static com.google.common.truth.Truth.assertThat; import android.annotation.Nullable; +import android.app.Instrumentation; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.tv.cec.V1_0.SendMessageResult; import android.os.Looper; import android.os.test.TestLooper; - +import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -44,13 +44,16 @@ public class ArcTerminationActionFromAvrTest { private boolean mShouldDispatchReportArcTerminated; private boolean mArcEnabled; private boolean mSetArcStatusCalled; + private Instrumentation mInstrumentation; @Before public void setUp() { mDeviceInfoForTests = new HdmiDeviceInfo(1000, 1); + mInstrumentation = InstrumentationRegistry.getInstrumentation(); + HdmiControlService hdmiControlService = - new HdmiControlService(null) { + new HdmiControlService(mInstrumentation.getTargetContext()) { @Override void sendCecCommand( HdmiCecMessage command, @Nullable SendMessageCallback callback) { diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java index 81145103f689..3736df547b05 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java @@ -22,9 +22,8 @@ import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.tv.cec.V1_0.SendMessageResult; import android.os.Looper; import android.os.test.TestLooper; - +import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; - import com.android.server.hdmi.HdmiCecLocalDeviceAudioSystem.TvSystemAudioModeSupportedCallback; import org.junit.Before; @@ -50,7 +49,7 @@ public class DetectTvSystemAudioModeSupportActionTest { public void SetUp() { mDeviceInfoForTests = new HdmiDeviceInfo(1001, 1234); HdmiControlService hdmiControlService = - new HdmiControlService(null) { + new HdmiControlService(InstrumentationRegistry.getTargetContext()) { @Override void sendCecCommand( diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java index cc005ed266b3..da840be9bca7 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java @@ -18,7 +18,6 @@ package com.android.server.hdmi; import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM; import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_PLAYBACK; import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_TV; - import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM; import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1; import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2; @@ -26,18 +25,15 @@ import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_3; import static com.android.server.hdmi.Constants.ADDR_SPECIFIC_USE; import static com.android.server.hdmi.Constants.ADDR_TV; import static com.android.server.hdmi.Constants.ADDR_UNREGISTERED; - import static junit.framework.Assert.assertEquals; import android.content.Context; import android.hardware.tv.cec.V1_0.SendMessageResult; import android.os.Looper; import android.os.test.TestLooper; - +import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; - import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -84,7 +80,7 @@ public class HdmiCecControllerTest { public void SetUp() { mMyLooper = mTestLooper.getLooper(); mMyLooper = mTestLooper.getLooper(); - mHdmiControlService = new MyHdmiControlService(null); + mHdmiControlService = new MyHdmiControlService(InstrumentationRegistry.getTargetContext()); mNativeWrapper = new FakeNativeWrapper(); mHdmiCecController = HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java index 6dbbbfe8e9e4..0e15ba6d0f46 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java @@ -30,14 +30,13 @@ import android.media.AudioManager; import android.os.Looper; import android.os.SystemProperties; import android.os.test.TestLooper; - +import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; - import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource; import org.junit.Before; -import org.junit.Test; import org.junit.Ignore; +import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -48,7 +47,6 @@ import java.util.ArrayList; /** Tests for {@link HdmiCecLocalDeviceAudioSystem} class. */ public class HdmiCecLocalDeviceAudioSystemTest { - private static final String TAG = "HdmiCecLocalDeviceAudioSystemTest"; private HdmiControlService mHdmiControlService; private HdmiCecController mHdmiCecController; private HdmiCecLocalDeviceAudioSystem mHdmiCecLocalDeviceAudioSystem; @@ -61,9 +59,9 @@ public class HdmiCecLocalDeviceAudioSystemTest { private boolean mMusicMute; @Before - public void SetUp() { + public void setUp() { mHdmiControlService = - new HdmiControlService(null) { + new HdmiControlService(InstrumentationRegistry.getTargetContext()) { @Override AudioManager getAudioManager() { return new AudioManager() { diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java index daf35dd382fa..910af78b2d7e 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java @@ -30,9 +30,9 @@ import static junit.framework.Assert.assertTrue; import android.hardware.hdmi.HdmiControlManager; import android.os.test.TestLooper; - +import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; - +import java.util.Arrays; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -95,7 +95,7 @@ public class HdmiCecLocalDeviceTest { @Before public void SetUp() { mHdmiControlService = - new HdmiControlService(null) { + new HdmiControlService(InstrumentationRegistry.getTargetContext()) { @Override boolean isControlEnabled() { return isControlEnabled; diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java index 71af71e711ea..18c9a653dc7d 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java @@ -25,9 +25,8 @@ import static junit.framework.Assert.assertTrue; import android.os.Looper; import android.os.test.TestLooper; - +import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -107,7 +106,7 @@ public class HdmiControlServiceTest { @Before public void SetUp() { mHdmiControlService = - new HdmiControlService(null) { + new HdmiControlService(InstrumentationRegistry.getTargetContext()) { @Override boolean isStandbyMessageReceived() { return mStandbyMessageReceived; diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java index 6ff1c0f2cef1..d914b9a090a2 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java @@ -26,9 +26,8 @@ import android.hardware.tv.cec.V1_0.SendMessageResult; import android.media.AudioManager; import android.os.Looper; import android.os.test.TestLooper; - +import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -55,7 +54,7 @@ public class SystemAudioInitiationActionFromAvrTest { public void SetUp() { mDeviceInfoForTests = new HdmiDeviceInfo(1001, 1234); HdmiControlService hdmiControlService = - new HdmiControlService(null) { + new HdmiControlService(InstrumentationRegistry.getTargetContext()) { @Override void sendCecCommand( diff --git a/telephony/java/android/telephony/CellIdentity.java b/telephony/java/android/telephony/CellIdentity.java index c240dbb684e6..76a002681710 100644 --- a/telephony/java/android/telephony/CellIdentity.java +++ b/telephony/java/android/telephony/CellIdentity.java @@ -129,6 +129,12 @@ public abstract class CellIdentity implements Parcelable { return mAlphaShort; } + /** + * @return a CellLocation object for this CellIdentity + * @hide + */ + public abstract CellLocation asCellLocation(); + @Override public boolean equals(Object other) { if (!(other instanceof CellIdentity)) { diff --git a/telephony/java/android/telephony/CellIdentityCdma.java b/telephony/java/android/telephony/CellIdentityCdma.java index 5b67dc44fe3c..9218bdc31fa8 100644 --- a/telephony/java/android/telephony/CellIdentityCdma.java +++ b/telephony/java/android/telephony/CellIdentityCdma.java @@ -19,7 +19,7 @@ package android.telephony; import android.annotation.Nullable; import android.annotation.UnsupportedAppUsage; import android.os.Parcel; -import android.text.TextUtils; +import android.telephony.cdma.CdmaCellLocation; import java.util.Objects; @@ -178,6 +178,18 @@ public final class CellIdentityCdma extends CellIdentity { super.hashCode()); } + /** @hide */ + @Override + public CdmaCellLocation asCellLocation() { + CdmaCellLocation cl = new CdmaCellLocation(); + int bsid = mBasestationId != Integer.MAX_VALUE ? mBasestationId : -1; + int sid = mSystemId != Integer.MAX_VALUE ? mSystemId : -1; + int nid = mNetworkId != Integer.MAX_VALUE ? mNetworkId : -1; + // lat and long already use Integer.MAX_VALUE for invalid/unknown + cl.setCellLocationData(bsid, mLatitude, mLongitude, sid, nid); + return cl; + } + @Override public boolean equals(Object other) { if (this == other) { diff --git a/telephony/java/android/telephony/CellIdentityGsm.java b/telephony/java/android/telephony/CellIdentityGsm.java index 5c847914b57b..cb9dbf369d7e 100644 --- a/telephony/java/android/telephony/CellIdentityGsm.java +++ b/telephony/java/android/telephony/CellIdentityGsm.java @@ -19,6 +19,7 @@ package android.telephony; import android.annotation.Nullable; import android.annotation.UnsupportedAppUsage; import android.os.Parcel; +import android.telephony.gsm.GsmCellLocation; import android.text.TextUtils; import java.util.Objects; @@ -198,6 +199,17 @@ public final class CellIdentityGsm extends CellIdentity { return Integer.MAX_VALUE; } + /** @hide */ + @Override + public GsmCellLocation asCellLocation() { + GsmCellLocation cl = new GsmCellLocation(); + int lac = mLac != Integer.MAX_VALUE ? mLac : -1; + int cid = mCid != Integer.MAX_VALUE ? mCid : -1; + cl.setLacAndCid(lac, cid); + cl.setPsc(-1); + return cl; + } + @Override public int hashCode() { return Objects.hash(mLac, mCid, super.hashCode()); diff --git a/telephony/java/android/telephony/CellIdentityLte.java b/telephony/java/android/telephony/CellIdentityLte.java index 65c904b8548b..b44e891fa870 100644 --- a/telephony/java/android/telephony/CellIdentityLte.java +++ b/telephony/java/android/telephony/CellIdentityLte.java @@ -19,6 +19,7 @@ package android.telephony; import android.annotation.Nullable; import android.annotation.UnsupportedAppUsage; import android.os.Parcel; +import android.telephony.gsm.GsmCellLocation; import android.text.TextUtils; import java.util.Objects; @@ -200,6 +201,28 @@ public final class CellIdentityLte extends CellIdentity { return mEarfcn; } + /** + * A hack to allow tunneling of LTE information via GsmCellLocation + * so that older Network Location Providers can return some information + * on LTE only networks, see bug 9228974. + * + * The tunnel'd LTE information is returned as follows: + * LAC = TAC field + * CID = CI field + * PSC = 0. + * + * @hide + */ + @Override + public GsmCellLocation asCellLocation() { + GsmCellLocation cl = new GsmCellLocation(); + int tac = mTac != Integer.MAX_VALUE ? mTac : -1; + int cid = mCi != Integer.MAX_VALUE ? mCi : -1; + cl.setLacAndCid(tac, cid); + cl.setPsc(0); + return cl; + } + @Override public int hashCode() { return Objects.hash(mCi, mPci, mTac, super.hashCode()); diff --git a/telephony/java/android/telephony/CellIdentityTdscdma.java b/telephony/java/android/telephony/CellIdentityTdscdma.java index 21b9601bcad9..5a9e474806a3 100644 --- a/telephony/java/android/telephony/CellIdentityTdscdma.java +++ b/telephony/java/android/telephony/CellIdentityTdscdma.java @@ -17,6 +17,7 @@ package android.telephony; import android.os.Parcel; +import android.telephony.gsm.GsmCellLocation; import java.util.Objects; @@ -134,6 +135,17 @@ public final class CellIdentityTdscdma extends CellIdentity { return mUarfcn; } + /** @hide */ + @Override + public GsmCellLocation asCellLocation() { + GsmCellLocation cl = new GsmCellLocation(); + int lac = mLac != Integer.MAX_VALUE ? mLac : -1; + int cid = mCid != Integer.MAX_VALUE ? mCid : -1; + cl.setLacAndCid(lac, cid); + cl.setPsc(-1); // There is no PSC for TD-SCDMA; not using this for CPI to stem shenanigans + return cl; + } + @Override public boolean equals(Object other) { if (this == other) { diff --git a/telephony/java/android/telephony/CellIdentityWcdma.java b/telephony/java/android/telephony/CellIdentityWcdma.java index e26fcb314afa..727d9908b9b1 100644 --- a/telephony/java/android/telephony/CellIdentityWcdma.java +++ b/telephony/java/android/telephony/CellIdentityWcdma.java @@ -19,6 +19,7 @@ package android.telephony; import android.annotation.Nullable; import android.annotation.UnsupportedAppUsage; import android.os.Parcel; +import android.telephony.gsm.GsmCellLocation; import android.text.TextUtils; import java.util.Objects; @@ -191,6 +192,19 @@ public final class CellIdentityWcdma extends CellIdentity { return mUarfcn; } + /** @hide */ + @Override + public GsmCellLocation asCellLocation() { + GsmCellLocation cl = new GsmCellLocation(); + int lac = mLac != Integer.MAX_VALUE ? mLac : -1; + int cid = mCid != Integer.MAX_VALUE ? mCid : -1; + int psc = mPsc != Integer.MAX_VALUE ? mPsc : -1; + cl.setLacAndCid(lac, cid); + cl.setPsc(psc); + + return cl; + } + @Override public boolean equals(Object other) { if (this == other) { diff --git a/tests/net/java/android/net/NetworkUtilsTest.java b/tests/net/java/android/net/NetworkUtilsTest.java index 2b172dac4865..3452819835f5 100644 --- a/tests/net/java/android/net/NetworkUtilsTest.java +++ b/tests/net/java/android/net/NetworkUtilsTest.java @@ -24,6 +24,8 @@ import static android.net.NetworkUtils.intToInet4AddressHTL; import static android.net.NetworkUtils.netmaskToPrefixLength; import static android.net.NetworkUtils.prefixLengthToV4NetmaskIntHTH; import static android.net.NetworkUtils.prefixLengthToV4NetmaskIntHTL; +import static android.net.NetworkUtils.getBroadcastAddress; +import static android.net.NetworkUtils.getPrefixMaskAsInet4Address; import static junit.framework.Assert.assertEquals; @@ -125,7 +127,6 @@ public class NetworkUtilsTest { assertInvalidNetworkMask(IPv4Address("255.255.0.255")); } - @Test public void testPrefixLengthToV4NetmaskIntHTL() { assertEquals(0, prefixLengthToV4NetmaskIntHTL(0)); @@ -266,4 +267,44 @@ public class NetworkUtilsTest { assertEquals(BigInteger.valueOf(7l - 4 + 4 + 16 + 65536), NetworkUtils.routedIPv6AddressCount(set)); } + + @Test + public void testGetPrefixMaskAsAddress() { + assertEquals("255.255.240.0", getPrefixMaskAsInet4Address(20).getHostAddress()); + assertEquals("255.0.0.0", getPrefixMaskAsInet4Address(8).getHostAddress()); + assertEquals("0.0.0.0", getPrefixMaskAsInet4Address(0).getHostAddress()); + assertEquals("255.255.255.255", getPrefixMaskAsInet4Address(32).getHostAddress()); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetPrefixMaskAsAddress_PrefixTooLarge() { + getPrefixMaskAsInet4Address(33); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetPrefixMaskAsAddress_NegativePrefix() { + getPrefixMaskAsInet4Address(-1); + } + + @Test + public void testGetBroadcastAddress() { + assertEquals("192.168.15.255", + getBroadcastAddress(IPv4Address("192.168.0.123"), 20).getHostAddress()); + assertEquals("192.255.255.255", + getBroadcastAddress(IPv4Address("192.168.0.123"), 8).getHostAddress()); + assertEquals("192.168.0.123", + getBroadcastAddress(IPv4Address("192.168.0.123"), 32).getHostAddress()); + assertEquals("255.255.255.255", + getBroadcastAddress(IPv4Address("192.168.0.123"), 0).getHostAddress()); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetBroadcastAddress_PrefixTooLarge() { + getBroadcastAddress(IPv4Address("192.168.0.123"), 33); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetBroadcastAddress_NegativePrefix() { + getBroadcastAddress(IPv4Address("192.168.0.123"), -1); + } } diff --git a/tests/net/java/android/net/dhcp/DhcpLeaseRepositoryTest.java b/tests/net/java/android/net/dhcp/DhcpLeaseRepositoryTest.java new file mode 100644 index 000000000000..edadd6ead667 --- /dev/null +++ b/tests/net/java/android/net/dhcp/DhcpLeaseRepositoryTest.java @@ -0,0 +1,519 @@ +/* + * Copyright (C) 2018 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.dhcp; + +import static android.net.dhcp.DhcpLease.HOSTNAME_NONE; +import static android.net.dhcp.DhcpLeaseRepository.CLIENTID_UNSPEC; +import static android.net.dhcp.DhcpLeaseRepository.INETADDR_UNSPEC; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.when; + +import static java.lang.String.format; +import static java.net.InetAddress.parseNumericAddress; + +import android.annotation.NonNull; +import android.net.IpPrefix; +import android.net.MacAddress; +import android.net.dhcp.DhcpLeaseRepository.Clock; +import android.net.util.SharedLog; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.net.Inet4Address; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class DhcpLeaseRepositoryTest { + private static final Inet4Address INET4_ANY = (Inet4Address) Inet4Address.ANY; + private static final Inet4Address TEST_DEF_ROUTER = parseAddr4("192.168.42.247"); + private static final Inet4Address TEST_SERVER_ADDR = parseAddr4("192.168.42.241"); + private static final Inet4Address TEST_RESERVED_ADDR = parseAddr4("192.168.42.243"); + private static final MacAddress TEST_MAC_1 = MacAddress.fromBytes( + new byte[] { 5, 4, 3, 2, 1, 0 }); + private static final MacAddress TEST_MAC_2 = MacAddress.fromBytes( + new byte[] { 0, 1, 2, 3, 4, 5 }); + private static final MacAddress TEST_MAC_3 = MacAddress.fromBytes( + new byte[] { 0, 1, 2, 3, 4, 6 }); + private static final Inet4Address TEST_INETADDR_1 = parseAddr4("192.168.42.248"); + private static final Inet4Address TEST_INETADDR_2 = parseAddr4("192.168.42.249"); + private static final String TEST_HOSTNAME_1 = "hostname1"; + private static final String TEST_HOSTNAME_2 = "hostname2"; + private static final IpPrefix TEST_IP_PREFIX = new IpPrefix(TEST_SERVER_ADDR, 22); + private static final long TEST_TIME = 100L; + private static final int TEST_LEASE_TIME_MS = 3_600_000; + private static final Set<Inet4Address> TEST_EXCL_SET = + Collections.unmodifiableSet(new HashSet<>(Arrays.asList( + TEST_SERVER_ADDR, TEST_DEF_ROUTER, TEST_RESERVED_ADDR))); + + @NonNull + private SharedLog mLog; + @NonNull @Mock + private Clock mClock; + @NonNull + private DhcpLeaseRepository mRepo; + + private static Inet4Address parseAddr4(String inet4Addr) { + return (Inet4Address) parseNumericAddress(inet4Addr); + } + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mLog = new SharedLog("DhcpLeaseRepositoryTest"); + when(mClock.elapsedRealtime()).thenReturn(TEST_TIME); + mRepo = new DhcpLeaseRepository( + TEST_IP_PREFIX, TEST_EXCL_SET, TEST_LEASE_TIME_MS, mLog, mClock); + } + + /** + * Request a number of addresses through offer/request. Useful to test address exhaustion. + * @param nAddr Number of addresses to request. + */ + private void requestAddresses(byte nAddr) throws Exception { + final HashSet<Inet4Address> addrs = new HashSet<>(); + byte[] hwAddrBytes = new byte[] { 8, 4, 3, 2, 1, 0 }; + for (byte i = 0; i < nAddr; i++) { + hwAddrBytes[5] = i; + MacAddress newMac = MacAddress.fromBytes(hwAddrBytes); + final String hostname = "host_" + i; + final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, newMac, + INETADDR_UNSPEC /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, hostname); + + assertNotNull(lease); + assertEquals(newMac, lease.getHwAddr()); + assertEquals(hostname, lease.getHostname()); + assertTrue(format("Duplicate address allocated: %s in %s", lease.getNetAddr(), addrs), + addrs.add(lease.getNetAddr())); + + mRepo.requestLease(null, newMac, null, lease.getNetAddr(), true, hostname); + } + } + + @Test + public void testAddressExhaustion() throws Exception { + // Use a /28 to quickly run out of addresses + mRepo.updateParams(new IpPrefix(TEST_SERVER_ADDR, 28), TEST_EXCL_SET, TEST_LEASE_TIME_MS); + + // /28 should have 16 addresses, 14 w/o the first/last, 11 w/o excluded addresses + requestAddresses((byte)11); + + try { + mRepo.getOffer(null, TEST_MAC_2, + null /* relayAddr */, null /* reqAddr */, HOSTNAME_NONE); + fail("Should be out of addresses"); + } catch (DhcpLeaseRepository.OutOfAddressesException e) { + // Expected + } + } + + @Test + public void testUpdateParams_LeaseCleanup() throws Exception { + // Inside /28: + final Inet4Address reqAddrIn28 = parseAddr4("192.168.42.242"); + final Inet4Address declinedAddrIn28 = parseAddr4("192.168.42.245"); + + // Inside /28, but not available there (first address of the range) + final Inet4Address declinedFirstAddrIn28 = parseAddr4("192.168.42.240"); + + final DhcpLease reqAddrIn28Lease = mRepo.requestLease( + CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, reqAddrIn28, false, HOSTNAME_NONE); + mRepo.markLeaseDeclined(declinedAddrIn28); + mRepo.markLeaseDeclined(declinedFirstAddrIn28); + + // Inside /22, but outside /28: + final Inet4Address reqAddrIn22 = parseAddr4("192.168.42.3"); + final Inet4Address declinedAddrIn22 = parseAddr4("192.168.42.4"); + + final DhcpLease reqAddrIn22Lease = mRepo.requestLease( + CLIENTID_UNSPEC, TEST_MAC_3, INET4_ANY, reqAddrIn22, false, HOSTNAME_NONE); + mRepo.markLeaseDeclined(declinedAddrIn22); + + // Address that will be reserved in the updateParams call below + final Inet4Address reservedAddr = parseAddr4("192.168.42.244"); + final DhcpLease reservedAddrLease = mRepo.requestLease( + CLIENTID_UNSPEC, TEST_MAC_2, INET4_ANY, reservedAddr, false, HOSTNAME_NONE); + + // Update from /22 to /28 and add another reserved address + Set<Inet4Address> newReserved = new HashSet<>(TEST_EXCL_SET); + newReserved.add(reservedAddr); + mRepo.updateParams(new IpPrefix(TEST_SERVER_ADDR, 28), newReserved, TEST_LEASE_TIME_MS); + + assertHasLease(reqAddrIn28Lease); + assertDeclined(declinedAddrIn28); + + assertNotDeclined(declinedFirstAddrIn28); + + assertNoLease(reqAddrIn22Lease); + assertNotDeclined(declinedAddrIn22); + + assertNoLease(reservedAddrLease); + } + + @Test + public void testGetOffer_StableAddress() throws Exception { + for (final MacAddress macAddr : new MacAddress[] { TEST_MAC_1, TEST_MAC_2, TEST_MAC_3 }) { + final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, macAddr, + INETADDR_UNSPEC /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + + // Same lease is offered twice + final DhcpLease newLease = mRepo.getOffer(CLIENTID_UNSPEC, macAddr, + INETADDR_UNSPEC /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + assertEquals(lease, newLease); + } + } + + @Test + public void testUpdateParams_UsesNewPrefix() throws Exception { + final IpPrefix newPrefix = new IpPrefix(parseAddr4("192.168.123.0"), 24); + mRepo.updateParams(newPrefix, TEST_EXCL_SET, TEST_LEASE_TIME_MS); + + DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, + INETADDR_UNSPEC, INETADDR_UNSPEC, HOSTNAME_NONE); + assertTrue(newPrefix.contains(lease.getNetAddr())); + } + + @Test + public void testGetOffer_ExistingLease() throws Exception { + mRepo.requestLease( + CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, TEST_INETADDR_1, false, TEST_HOSTNAME_1); + + DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, + INETADDR_UNSPEC, INETADDR_UNSPEC, HOSTNAME_NONE); + assertEquals(TEST_INETADDR_1, offer.getNetAddr()); + assertEquals(TEST_HOSTNAME_1, offer.getHostname()); + } + + @Test + public void testGetOffer_ClientIdHasExistingLease() throws Exception { + final byte[] clientId = new byte[] { 1, 2 }; + mRepo.requestLease(clientId, TEST_MAC_1, INET4_ANY, TEST_INETADDR_1, false, + TEST_HOSTNAME_1); + + // Different MAC, but same clientId + DhcpLease offer = mRepo.getOffer(clientId, TEST_MAC_2, + INETADDR_UNSPEC, INETADDR_UNSPEC, HOSTNAME_NONE); + assertEquals(TEST_INETADDR_1, offer.getNetAddr()); + assertEquals(TEST_HOSTNAME_1, offer.getHostname()); + } + + @Test + public void testGetOffer_DifferentClientId() throws Exception { + final byte[] clientId1 = new byte[] { 1, 2 }; + final byte[] clientId2 = new byte[] { 3, 4 }; + mRepo.requestLease(clientId1, TEST_MAC_1, INET4_ANY, TEST_INETADDR_1, false, + TEST_HOSTNAME_1); + + // Same MAC, different client ID + DhcpLease offer = mRepo.getOffer(clientId2, TEST_MAC_1, + INETADDR_UNSPEC, INETADDR_UNSPEC, HOSTNAME_NONE); + // Obtains a different address + assertNotEquals(TEST_INETADDR_1, offer.getNetAddr()); + assertEquals(HOSTNAME_NONE, offer.getHostname()); + assertEquals(TEST_MAC_1, offer.getHwAddr()); + } + + @Test + public void testGetOffer_RequestedAddress() throws Exception { + DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, + TEST_INETADDR_1, TEST_HOSTNAME_1); + assertEquals(TEST_INETADDR_1, offer.getNetAddr()); + assertEquals(TEST_HOSTNAME_1, offer.getHostname()); + } + + @Test + public void testGetOffer_RequestedAddressInUse() throws Exception { + mRepo.requestLease( + CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, TEST_INETADDR_1, false, HOSTNAME_NONE); + DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_2, INET4_ANY, + TEST_INETADDR_1, HOSTNAME_NONE); + assertNotEquals(TEST_INETADDR_1, offer.getNetAddr()); + } + + @Test + public void testGetOffer_RequestedAddressReserved() throws Exception { + DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, + TEST_RESERVED_ADDR, HOSTNAME_NONE); + assertNotEquals(TEST_RESERVED_ADDR, offer.getNetAddr()); + } + + @Test + public void testGetOffer_RequestedAddressInvalid() throws Exception { + final Inet4Address invalidAddr = parseAddr4("192.168.42.0"); + DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, + invalidAddr, HOSTNAME_NONE); + assertNotEquals(invalidAddr, offer.getNetAddr()); + } + + @Test + public void testGetOffer_RequestedAddressOutsideSubnet() throws Exception { + final Inet4Address invalidAddr = parseAddr4("192.168.254.2"); + DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, + invalidAddr, HOSTNAME_NONE); + assertNotEquals(invalidAddr, offer.getNetAddr()); + } + + @Test(expected = DhcpLeaseRepository.InvalidAddressException.class) + public void testGetOffer_RelayInInvalidSubnet() throws Exception { + mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, + parseAddr4("192.168.254.2") /* relayAddr */, INETADDR_UNSPEC, HOSTNAME_NONE); + } + + @Test + public void testRequestLease_SelectingTwice() throws Exception { + DhcpLease lease1 = mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, + TEST_INETADDR_1, true /* sidSet */, TEST_HOSTNAME_1); + + // Second request from same client for a different address + DhcpLease lease2 = mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, + TEST_INETADDR_2, true /* sidSet */, TEST_HOSTNAME_2); + + assertEquals(TEST_INETADDR_1, lease1.getNetAddr()); + assertEquals(TEST_HOSTNAME_1, lease1.getHostname()); + + assertEquals(TEST_INETADDR_2, lease2.getNetAddr()); + assertEquals(TEST_HOSTNAME_2, lease2.getHostname()); + + // First address freed when client requested a different one: another client can request it + DhcpLease lease3 = mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_2, INET4_ANY, + TEST_INETADDR_1, true /* sidSet */, HOSTNAME_NONE); + assertEquals(TEST_INETADDR_1, lease3.getNetAddr()); + } + + @Test(expected = DhcpLeaseRepository.InvalidAddressException.class) + public void testRequestLease_SelectingInvalid() throws Exception { + mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, + parseAddr4("192.168.254.5"), true /* sidSet */, HOSTNAME_NONE); + } + + @Test(expected = DhcpLeaseRepository.InvalidAddressException.class) + public void testRequestLease_SelectingInUse() throws Exception { + mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, + TEST_INETADDR_1, true /* sidSet */, HOSTNAME_NONE); + mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_2, INET4_ANY, + TEST_INETADDR_1, true /* sidSet */, HOSTNAME_NONE); + } + + @Test(expected = DhcpLeaseRepository.InvalidAddressException.class) + public void testRequestLease_SelectingReserved() throws Exception { + mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, + TEST_RESERVED_ADDR, true /* sidSet */, HOSTNAME_NONE); + } + + @Test + public void testRequestLease_InitReboot() throws Exception { + // Request address once + mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, + TEST_INETADDR_1, true /* sidSet */, HOSTNAME_NONE); + + final long newTime = TEST_TIME + 100; + when(mClock.elapsedRealtime()).thenReturn(newTime); + + // init-reboot (sidSet == false): verify configuration + DhcpLease lease = mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, + TEST_INETADDR_1, false, HOSTNAME_NONE); + assertEquals(TEST_INETADDR_1, lease.getNetAddr()); + assertEquals(newTime + TEST_LEASE_TIME_MS, lease.getExpTime()); + } + + @Test(expected = DhcpLeaseRepository.InvalidAddressException.class) + public void testRequestLease_InitRebootWrongAddr() throws Exception { + // Request address once + mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, + TEST_INETADDR_1, true /* sidSet */, HOSTNAME_NONE); + // init-reboot with different requested address + mRepo.requestLease( + CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, TEST_INETADDR_2, false, HOSTNAME_NONE); + } + + @Test + public void testRequestLease_InitRebootUnknownAddr() throws Exception { + // init-reboot with unknown requested address + DhcpLease lease = mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, + TEST_INETADDR_2, false, HOSTNAME_NONE); + // RFC2131 says we should not reply to accommodate other servers, but since we are + // authoritative we allow creating the lease to avoid issues with lost lease DB (same as + // dnsmasq behavior) + assertEquals(TEST_INETADDR_2, lease.getNetAddr()); + } + + @Test(expected = DhcpLeaseRepository.InvalidAddressException.class) + public void testRequestLease_InitRebootWrongSubnet() throws Exception { + mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, + parseAddr4("192.168.254.2"), false /* sidSet */, HOSTNAME_NONE); + } + + @Test + public void testRequestLease_Renewing() throws Exception { + mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, + INET4_ANY /* clientAddr */, TEST_INETADDR_1 /* reqAddr */, true, HOSTNAME_NONE); + + final long newTime = TEST_TIME + 100; + when(mClock.elapsedRealtime()).thenReturn(newTime); + + // Renewing: clientAddr filled in, no reqAddr + DhcpLease lease = mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, + TEST_INETADDR_1 /* clientAddr */, INETADDR_UNSPEC /* reqAddr */, false, + HOSTNAME_NONE); + + assertEquals(TEST_INETADDR_1, lease.getNetAddr()); + assertEquals(newTime + TEST_LEASE_TIME_MS, lease.getExpTime()); + } + + @Test + public void testRequestLease_RenewingUnknownAddr() throws Exception { + final long newTime = TEST_TIME + 100; + when(mClock.elapsedRealtime()).thenReturn(newTime); + DhcpLease lease = mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, + TEST_INETADDR_1 /* clientAddr */, INETADDR_UNSPEC /* reqAddr */, false, + HOSTNAME_NONE); + // Allows renewing an unknown address if available + assertEquals(TEST_INETADDR_1, lease.getNetAddr()); + assertEquals(newTime + TEST_LEASE_TIME_MS, lease.getExpTime()); + } + + @Test(expected = DhcpLeaseRepository.InvalidAddressException.class) + public void testRequestLease_RenewingAddrInUse() throws Exception { + mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_2, + INET4_ANY /* clientAddr */, TEST_INETADDR_1 /* reqAddr */, true, HOSTNAME_NONE); + mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, + TEST_INETADDR_1 /* clientAddr */, INETADDR_UNSPEC /* reqAddr */, false, + HOSTNAME_NONE); + } + + @Test(expected = DhcpLeaseRepository.InvalidAddressException.class) + public void testRequestLease_RenewingInvalidAddr() throws Exception { + mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, parseAddr4("192.168.254.2") /* clientAddr */, + INETADDR_UNSPEC /* reqAddr */, false, HOSTNAME_NONE); + } + + @Test + public void testReleaseLease() throws Exception { + DhcpLease lease1 = mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, + TEST_INETADDR_1, true /* sidSet */, HOSTNAME_NONE); + + assertHasLease(lease1); + assertTrue(mRepo.releaseLease(CLIENTID_UNSPEC, TEST_MAC_1, TEST_INETADDR_1)); + assertNoLease(lease1); + + DhcpLease lease2 = mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_2, INET4_ANY, + TEST_INETADDR_1, true /* sidSet */, HOSTNAME_NONE); + + assertEquals(TEST_INETADDR_1, lease2.getNetAddr()); + } + + @Test + public void testReleaseLease_UnknownLease() { + assertFalse(mRepo.releaseLease(CLIENTID_UNSPEC, TEST_MAC_1, TEST_INETADDR_1)); + } + + @Test + public void testReleaseLease_StableOffer() throws Exception { + for (MacAddress mac : new MacAddress[] { TEST_MAC_1, TEST_MAC_2, TEST_MAC_3 }) { + final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, mac, + INETADDR_UNSPEC /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + mRepo.requestLease( + CLIENTID_UNSPEC, mac, INET4_ANY, lease.getNetAddr(), true, + HOSTNAME_NONE); + mRepo.releaseLease(CLIENTID_UNSPEC, mac, lease.getNetAddr()); + + // Same lease is offered after it was released + final DhcpLease newLease = mRepo.getOffer(CLIENTID_UNSPEC, mac, + INETADDR_UNSPEC /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + assertEquals(lease.getNetAddr(), newLease.getNetAddr()); + } + } + + @Test + public void testMarkLeaseDeclined() throws Exception { + final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, + INETADDR_UNSPEC /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + + mRepo.markLeaseDeclined(lease.getNetAddr()); + + // Same lease is not offered again + final DhcpLease newLease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, + INETADDR_UNSPEC /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + assertNotEquals(lease.getNetAddr(), newLease.getNetAddr()); + } + + @Test + public void testMarkLeaseDeclined_UsedIfOutOfAddresses() throws Exception { + // Use a /28 to quickly run out of addresses + mRepo.updateParams(new IpPrefix(TEST_SERVER_ADDR, 28), TEST_EXCL_SET, TEST_LEASE_TIME_MS); + + mRepo.markLeaseDeclined(TEST_INETADDR_1); + mRepo.markLeaseDeclined(TEST_INETADDR_2); + + // /28 should have 16 addresses, 14 w/o the first/last, 11 w/o excluded addresses + requestAddresses((byte)9); + + // Last 2 addresses: addresses marked declined should be used + final DhcpLease firstLease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, + INETADDR_UNSPEC /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, TEST_HOSTNAME_1); + mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, firstLease.getNetAddr(), true, + HOSTNAME_NONE); + + final DhcpLease secondLease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_2, + INETADDR_UNSPEC /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, TEST_HOSTNAME_2); + mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_2, INET4_ANY, secondLease.getNetAddr(), true, + HOSTNAME_NONE); + + // Now out of addresses + try { + mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_3, INETADDR_UNSPEC /* relayAddr */, + INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + fail("Repository should be out of addresses and throw"); + } catch (DhcpLeaseRepository.OutOfAddressesException e) { /* expected */ } + + assertEquals(TEST_INETADDR_1, firstLease.getNetAddr()); + assertEquals(TEST_HOSTNAME_1, firstLease.getHostname()); + assertEquals(TEST_INETADDR_2, secondLease.getNetAddr()); + assertEquals(TEST_HOSTNAME_2, secondLease.getHostname()); + } + + private void assertNoLease(DhcpLease lease) { + assertFalse("Leases contain " + lease, mRepo.getCommittedLeases().contains(lease)); + } + + private void assertHasLease(DhcpLease lease) { + assertTrue("Leases do not contain " + lease, mRepo.getCommittedLeases().contains(lease)); + } + + private void assertNotDeclined(Inet4Address addr) { + assertFalse("Address is declined: " + addr, mRepo.getDeclinedAddresses().contains(addr)); + } + + private void assertDeclined(Inet4Address addr) { + assertTrue("Address is not declined: " + addr, mRepo.getDeclinedAddresses().contains(addr)); + } +} diff --git a/tests/net/java/android/net/dhcp/DhcpServingParamsTest.java b/tests/net/java/android/net/dhcp/DhcpServingParamsTest.java new file mode 100644 index 000000000000..dfa09a9f205c --- /dev/null +++ b/tests/net/java/android/net/dhcp/DhcpServingParamsTest.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2018 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.dhcp; + +import static android.net.dhcp.DhcpServingParams.MTU_UNSET; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static java.net.InetAddress.parseNumericAddress; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.LinkAddress; +import android.net.dhcp.DhcpServingParams.InvalidParameterException; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.Inet4Address; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class DhcpServingParamsTest { + @NonNull + private DhcpServingParams.Builder mBuilder; + + private static final Set<Inet4Address> TEST_DEFAULT_ROUTERS = new HashSet<>( + Arrays.asList(parseAddr("192.168.0.123"), parseAddr("192.168.0.124"))); + private static final long TEST_LEASE_TIME_SECS = 3600L; + private static final Set<Inet4Address> TEST_DNS_SERVERS = new HashSet<>( + Arrays.asList(parseAddr("192.168.0.126"), parseAddr("192.168.0.127"))); + private static final Inet4Address TEST_SERVER_ADDR = parseAddr("192.168.0.2"); + private static final LinkAddress TEST_LINKADDR = new LinkAddress(TEST_SERVER_ADDR, 20); + private static final int TEST_MTU = 1500; + private static final Set<Inet4Address> TEST_EXCLUDED_ADDRS = new HashSet<>( + Arrays.asList(parseAddr("192.168.0.200"), parseAddr("192.168.0.201"))); + + @Before + public void setUp() { + mBuilder = new DhcpServingParams.Builder() + .setDefaultRouters(TEST_DEFAULT_ROUTERS) + .setDhcpLeaseTimeSecs(TEST_LEASE_TIME_SECS) + .setDnsServers(TEST_DNS_SERVERS) + .setServerAddr(TEST_LINKADDR) + .setLinkMtu(TEST_MTU) + .setExcludedAddrs(TEST_EXCLUDED_ADDRS); + } + + @Test + public void testBuild_Immutable() throws InvalidParameterException { + final Set<Inet4Address> routers = new HashSet<>(TEST_DEFAULT_ROUTERS); + final Set<Inet4Address> dnsServers = new HashSet<>(TEST_DNS_SERVERS); + final Set<Inet4Address> excludedAddrs = new HashSet<>(TEST_EXCLUDED_ADDRS); + + final DhcpServingParams params = mBuilder + .setDefaultRouters(routers) + .setDnsServers(dnsServers) + .setExcludedAddrs(excludedAddrs) + .build(); + + // Modifications to source objects should not affect builder or final parameters + final Inet4Address addedAddr = parseAddr("192.168.0.223"); + routers.add(addedAddr); + dnsServers.add(addedAddr); + excludedAddrs.add(addedAddr); + + assertEquals(TEST_DEFAULT_ROUTERS, params.defaultRouters); + assertEquals(TEST_LEASE_TIME_SECS, params.dhcpLeaseTimeSecs); + assertEquals(TEST_DNS_SERVERS, params.dnsServers); + assertEquals(TEST_LINKADDR, params.serverAddr); + assertEquals(TEST_MTU, params.linkMtu); + + assertContains(params.excludedAddrs, TEST_EXCLUDED_ADDRS); + assertContains(params.excludedAddrs, TEST_DEFAULT_ROUTERS); + assertContains(params.excludedAddrs, TEST_DNS_SERVERS); + assertContains(params.excludedAddrs, TEST_SERVER_ADDR); + + assertFalse("excludedAddrs should not contain " + addedAddr, + params.excludedAddrs.contains(addedAddr)); + } + + @Test(expected = InvalidParameterException.class) + public void testBuild_NegativeLeaseTime() throws InvalidParameterException { + mBuilder.setDhcpLeaseTimeSecs(-1).build(); + } + + @Test(expected = InvalidParameterException.class) + public void testBuild_LeaseTimeTooLarge() throws InvalidParameterException { + // Set lease time larger than max value for uint32 + mBuilder.setDhcpLeaseTimeSecs(1L << 32).build(); + } + + @Test + public void testBuild_InfiniteLeaseTime() throws InvalidParameterException { + final long infiniteLeaseTime = 0xffffffffL; + final DhcpServingParams params = mBuilder + .setDhcpLeaseTimeSecs(infiniteLeaseTime).build(); + assertEquals(infiniteLeaseTime, params.dhcpLeaseTimeSecs); + assertTrue(params.dhcpLeaseTimeSecs > 0L); + } + + @Test + public void testBuild_UnsetMtu() throws InvalidParameterException { + final DhcpServingParams params = mBuilder.setLinkMtu(MTU_UNSET).build(); + assertEquals(MTU_UNSET, params.linkMtu); + } + + @Test(expected = InvalidParameterException.class) + public void testBuild_MtuTooSmall() throws InvalidParameterException { + mBuilder.setLinkMtu(20).build(); + } + + @Test(expected = InvalidParameterException.class) + public void testBuild_MtuTooLarge() throws InvalidParameterException { + mBuilder.setLinkMtu(65_536).build(); + } + + @Test(expected = InvalidParameterException.class) + public void testBuild_IPv6Addr() throws InvalidParameterException { + mBuilder.setServerAddr(new LinkAddress(parseNumericAddress("fe80::1111"), 120)).build(); + } + + @Test(expected = InvalidParameterException.class) + public void testBuild_PrefixTooLarge() throws InvalidParameterException { + mBuilder.setServerAddr(new LinkAddress(TEST_SERVER_ADDR, 15)).build(); + } + + @Test(expected = InvalidParameterException.class) + public void testBuild_PrefixTooSmall() throws InvalidParameterException { + mBuilder.setDefaultRouters(Collections.singleton(parseAddr("192.168.0.254"))) + .setServerAddr(new LinkAddress(TEST_SERVER_ADDR, 31)) + .build(); + } + + @Test(expected = InvalidParameterException.class) + public void testBuild_RouterNotInPrefix() throws InvalidParameterException { + mBuilder.setDefaultRouters(Collections.singleton(parseAddr("192.168.254.254"))).build(); + } + + private static <T> void assertContains(@NonNull Set<T> set, @NonNull Set<T> subset) { + for (final T elem : subset) { + assertContains(set, elem); + } + } + + private static <T> void assertContains(@NonNull Set<T> set, @Nullable T elem) { + assertTrue("Set does not contain " + elem, set.contains(elem)); + } + + @NonNull + private static Inet4Address parseAddr(@NonNull String inet4Addr) { + return (Inet4Address) parseNumericAddress(inet4Addr); + } +} diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp index 55a32c8b512c..c5c78d9d3827 100644 --- a/tools/aapt2/link/ManifestFixer.cpp +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -357,6 +357,7 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, manifest_action["uses-permission"]; manifest_action["uses-permission-sdk-23"]; manifest_action["permission"]; + manifest_action["permission"]["meta-data"] = meta_data_action; manifest_action["permission-tree"]; manifest_action["permission-group"]; manifest_action["uses-configuration"]; @@ -366,6 +367,8 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, manifest_action["compatible-screens"]; manifest_action["compatible-screens"]["screen"]; manifest_action["supports-gl-texture"]; + manifest_action["restrict-update"]; + manifest_action["package-verifier"]; manifest_action["meta-data"] = meta_data_action; manifest_action["uses-split"].Action(RequiredNameIsJavaPackage); @@ -387,6 +390,7 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, uses_static_library_action.Action(RequiredNameIsJavaPackage); uses_static_library_action.Action(RequiredAndroidAttribute("version")); uses_static_library_action.Action(RequiredAndroidAttribute("certDigest")); + uses_static_library_action["additional-certificate"]; if (options_.debug_mode) { application_action.Action([&](xml::Element* el) -> bool { diff --git a/tools/aosp/aosp_sha.sh b/tools/aosp/aosp_sha.sh new file mode 100755 index 000000000000..29bf74c7a8b9 --- /dev/null +++ b/tools/aosp/aosp_sha.sh @@ -0,0 +1,18 @@ +#!/bin/bash +LOCAL_DIR="$( dirname ${BASH_SOURCE} )" + +if git branch -vv | grep "^*" | grep "\[aosp/master" > /dev/null; then + # Change appears to be in AOSP + exit 0 +else + # Change appears to be non-AOSP; search for files + git show --name-only --pretty=format: $1 | grep $2 | while read file; do + echo + echo -e "\033[0;31mThe source of truth for '$file' is in AOSP.\033[0m" + echo + echo "If your change contains no confidential details, please upload and merge" + echo "this change at https://android-review.googlesource.com/." + echo + exit 77 + done +fi diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp index e519909aa026..8038a3a4f44e 100644 --- a/tools/stats_log_api_gen/main.cpp +++ b/tools/stats_log_api_gen/main.cpp @@ -349,8 +349,7 @@ static int write_stats_log_cpp(FILE *out, const Atoms &atoms, argIndex++; } fprintf(out, ");\n"); - fprintf(out, " if (ret >= 0) { return retry; }\n"); - + fprintf(out, " if (ret >= 0) { break; }\n"); fprintf(out, " {\n"); fprintf(out, " std::lock_guard<std::mutex> lock(mLogdRetryMutex);\n"); @@ -360,6 +359,9 @@ static int write_stats_log_cpp(FILE *out, const Atoms &atoms, fprintf(out, " }\n"); fprintf(out, " std::this_thread::sleep_for(std::chrono::milliseconds(10));\n"); fprintf(out, " }\n"); + fprintf(out, " if (ret < 0) {\n"); + fprintf(out, " note_log_drop();\n"); + fprintf(out, " }\n"); fprintf(out, " return ret;\n"); fprintf(out, "}\n"); fprintf(out, "\n"); @@ -439,7 +441,7 @@ static int write_stats_log_cpp(FILE *out, const Atoms &atoms, argIndex++; } fprintf(out, ");\n"); - fprintf(out, " if (ret >= 0) { return retry; }\n"); + fprintf(out, " if (ret >= 0) { break; }\n"); fprintf(out, " {\n"); fprintf(out, " std::lock_guard<std::mutex> lock(mLogdRetryMutex);\n"); @@ -450,7 +452,10 @@ static int write_stats_log_cpp(FILE *out, const Atoms &atoms, fprintf(out, " std::this_thread::sleep_for(std::chrono::milliseconds(10));\n"); fprintf(out, " }\n"); - fprintf(out, " return ret;\n"); + fprintf(out, " if (ret < 0) {\n"); + fprintf(out, " note_log_drop();\n"); + fprintf(out, " }\n"); + fprintf(out, " return ret;\n\n"); fprintf(out, "}\n"); fprintf(out, "\n"); |