diff options
40 files changed, 1328 insertions, 189 deletions
diff --git a/apct-tests/perftests/core/jni/Android.bp b/apct-tests/perftests/core/jni/Android.bp index 4c0f2aaba145..d160d48538c3 100644 --- a/apct-tests/perftests/core/jni/Android.bp +++ b/apct-tests/perftests/core/jni/Android.bp @@ -10,4 +10,5 @@ cc_library_shared { "-Wunused", "-Wunreachable-code", ], + header_libs: ["jni_headers"], } diff --git a/apex/Android.bp b/apex/Android.bp index 505c3fc12d05..380b4c6cc0be 100644 --- a/apex/Android.bp +++ b/apex/Android.bp @@ -76,6 +76,10 @@ java_defaults { // entry. shared_library: false, + // Prevent dependencies that do not specify an sdk_version from accessing the + // implementation library by default and force them to use stubs instead. + default_to_stubs: true, + // Enable api lint. This will eventually become the default for java_sdk_library // but it cannot yet be turned on because some usages have not been cleaned up. // TODO(b/156126315) - Remove when no longer needed. diff --git a/core/java/android/view/textclassifier/OWNERS b/core/java/android/view/textclassifier/OWNERS index 893a083398fa..be4fbaa6d06a 100644 --- a/core/java/android/view/textclassifier/OWNERS +++ b/core/java/android/view/textclassifier/OWNERS @@ -7,4 +7,8 @@ jalt@google.com joannechung@google.com svetoslavganov@google.com eugeniom@google.com -samsellem@google.com
\ No newline at end of file +samsellem@google.com +adamhe@google.com +augale@google.com +lpeter@google.com +tymtsai@google.com
\ No newline at end of file diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 9bc4adca1402..27415c58f367 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -231,7 +231,10 @@ cc_library_shared { "system/media/private/camera/include", ], - header_libs: ["bionic_libc_platform_headers"], + header_libs: [ + "bionic_libc_platform_headers", + "dnsproxyd_protocol_headers", + ], static_libs: [ "libasync_safe", diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp index ba7fe7ff8739..03b9793ccba8 100644 --- a/core/jni/android_net_NetUtils.cpp +++ b/core/jni/android_net_NetUtils.cpp @@ -27,12 +27,13 @@ #include <netinet/ip.h> #include <netinet/udp.h> +#include <DnsProxydProtocol.h> // NETID_USE_LOCAL_NAMESERVERS #include <android_runtime/AndroidRuntime.h> #include <cutils/properties.h> -#include <utils/misc.h> -#include <utils/Log.h> #include <nativehelper/JNIHelp.h> #include <nativehelper/ScopedLocalRef.h> +#include <utils/Log.h> +#include <utils/misc.h> #include "NetdClient.h" #include "core_jni_helpers.h" diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index b2aef78a79af..48a764e7c404 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -483,11 +483,8 @@ static void nativeSetMetadata(JNIEnv* env, jclass clazz, jlong transactionObj, auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); - std::vector<uint8_t> byteData(parcel->dataSize()); - memcpy(byteData.data(), parcel->data(), parcel->dataSize()); - SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject); - transaction->setMetadata(ctrl, id, std::move(byteData)); + transaction->setMetadata(ctrl, id, *parcel); } static void nativeSetColor(JNIEnv* env, jclass clazz, jlong transactionObj, diff --git a/core/tests/coretests/apks/install_jni_lib/Android.bp b/core/tests/coretests/apks/install_jni_lib/Android.bp index f20f59958736..df19fa09adcf 100644 --- a/core/tests/coretests/apks/install_jni_lib/Android.bp +++ b/core/tests/coretests/apks/install_jni_lib/Android.bp @@ -24,4 +24,5 @@ cc_test_library { "-Wall", "-Werror", ], + header_libs: ["jni_headers"] } diff --git a/core/tests/hosttests/test-apps/SharedUid/32/jni/Android.bp b/core/tests/hosttests/test-apps/SharedUid/32/jni/Android.bp index 6db0ba5f7387..9e6c17f0247a 100644 --- a/core/tests/hosttests/test-apps/SharedUid/32/jni/Android.bp +++ b/core/tests/hosttests/test-apps/SharedUid/32/jni/Android.bp @@ -26,6 +26,7 @@ cc_test_library { // All of the source files that we will compile. srcs: ["native.cpp"], + header_libs: ["jni_headers"], shared_libs: ["liblog"], cflags: [ diff --git a/core/tests/hosttests/test-apps/SharedUid/64/jni/Android.bp b/core/tests/hosttests/test-apps/SharedUid/64/jni/Android.bp index 582f2a7abfb4..91da6e44a8be 100644 --- a/core/tests/hosttests/test-apps/SharedUid/64/jni/Android.bp +++ b/core/tests/hosttests/test-apps/SharedUid/64/jni/Android.bp @@ -25,6 +25,7 @@ cc_test_library { // All of the source files that we will compile. srcs: ["native.cpp"], + header_libs: ["jni_headers"], shared_libs: ["liblog"], cflags: [ diff --git a/core/tests/hosttests/test-apps/SharedUid/dual/jni/Android.bp b/core/tests/hosttests/test-apps/SharedUid/dual/jni/Android.bp index 3e043afee7ec..662755dd35b0 100644 --- a/core/tests/hosttests/test-apps/SharedUid/dual/jni/Android.bp +++ b/core/tests/hosttests/test-apps/SharedUid/dual/jni/Android.bp @@ -27,6 +27,7 @@ cc_test_library { // All of the source files that we will compile. srcs: ["native.cpp"], + header_libs: ["jni_headers"], shared_libs: ["liblog"], cflags: [ diff --git a/libs/input/Android.bp b/libs/input/Android.bp index 89d3cc4f5083..fd4371cac795 100644 --- a/libs/input/Android.bp +++ b/libs/input/Android.bp @@ -17,9 +17,11 @@ cc_library_shared { srcs: [ "PointerController.cpp", "SpriteController.cpp", + "SpriteIcon.cpp", ], shared_libs: [ + "libbinder", "libcutils", "liblog", "libutils", diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp index abf083789c23..5c2ef15cbd1e 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -257,19 +257,24 @@ void PointerController::unfade(Transition transition) { void PointerController::setPresentation(Presentation presentation) { AutoMutex _l(mLock); - if (presentation == PRESENTATION_POINTER && mLocked.additionalMouseResources.empty()) { - mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources, - &mLocked.animationResources, mLocked.viewport.displayId); + if (mLocked.presentation == presentation) { + return; } - if (mLocked.presentation != presentation) { - mLocked.presentation = presentation; - mLocked.presentationChanged = true; + mLocked.presentation = presentation; + mLocked.presentationChanged = true; - if (presentation != PRESENTATION_SPOT) { - fadeOutAndReleaseAllSpotsLocked(); - } + if (!mLocked.viewport.isValid()) { + return; + } + if (presentation == PRESENTATION_POINTER) { + if (mLocked.additionalMouseResources.empty()) { + mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources, + &mLocked.animationResources, + mLocked.viewport.displayId); + } + fadeOutAndReleaseAllSpotsLocked(); updatePointerLocked(); } } @@ -291,6 +296,9 @@ void PointerController::setSpots(const PointerCoords* spotCoords, #endif AutoMutex _l(mLock); + if (!mLocked.viewport.isValid()) { + return; + } std::vector<Spot*> newSpots; std::map<int32_t, std::vector<Spot*>>::const_iterator iter = @@ -337,6 +345,9 @@ void PointerController::clearSpots() { #endif AutoMutex _l(mLock); + if (!mLocked.viewport.isValid()) { + return; + } fadeOutAndReleaseAllSpotsLocked(); } @@ -758,6 +769,10 @@ void PointerController::fadeOutAndReleaseAllSpotsLocked() { } void PointerController::loadResourcesLocked() REQUIRES(mLock) { + if (!mLocked.viewport.isValid()) { + return; + } + mPolicy->loadPointerResources(&mResources, mLocked.viewport.displayId); mPolicy->loadPointerIcon(&mLocked.pointerIcon, mLocked.viewport.displayId); diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h index 52305b8244a6..ebc622bae302 100644 --- a/libs/input/PointerController.h +++ b/libs/input/PointerController.h @@ -100,6 +100,7 @@ public: virtual int32_t getDisplayId() const; virtual void fade(Transition transition); virtual void unfade(Transition transition); + virtual void setDisplayViewport(const DisplayViewport& viewport); virtual void setPresentation(Presentation presentation); virtual void setSpots(const PointerCoords* spotCoords, @@ -108,7 +109,6 @@ public: void updatePointerIcon(int32_t iconId); void setCustomPointerIcon(const SpriteIcon& icon); - void setDisplayViewport(const DisplayViewport& viewport); void setInactivityTimeout(InactivityTimeout inactivityTimeout); void reloadPointerResources(); diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp index c1868d3a94d6..5f481ca67ba6 100644 --- a/libs/input/SpriteController.cpp +++ b/libs/input/SpriteController.cpp @@ -23,13 +23,6 @@ #include <utils/String8.h> #include <gui/Surface.h> -#include <SkBitmap.h> -#include <SkCanvas.h> -#include <SkColor.h> -#include <SkPaint.h> - -#include <android/native_window.h> - namespace android { // --- SpriteController --- @@ -132,8 +125,8 @@ void SpriteController::doUpdateSprites() { SpriteUpdate& update = updates.editItemAt(i); if (update.state.surfaceControl == NULL && update.state.wantSurfaceVisible()) { - update.state.surfaceWidth = update.state.icon.bitmap.width(); - update.state.surfaceHeight = update.state.icon.bitmap.height(); + update.state.surfaceWidth = update.state.icon.width(); + update.state.surfaceHeight = update.state.icon.height(); update.state.surfaceDrawn = false; update.state.surfaceVisible = false; update.state.surfaceControl = obtainSurface( @@ -154,8 +147,8 @@ void SpriteController::doUpdateSprites() { } if (update.state.wantSurfaceVisible()) { - int32_t desiredWidth = update.state.icon.bitmap.width(); - int32_t desiredHeight = update.state.icon.bitmap.height(); + int32_t desiredWidth = update.state.icon.width(); + int32_t desiredHeight = update.state.icon.height(); if (update.state.surfaceWidth < desiredWidth || update.state.surfaceHeight < desiredHeight) { needApplyTransaction = true; @@ -196,40 +189,9 @@ void SpriteController::doUpdateSprites() { if (update.state.surfaceControl != NULL && !update.state.surfaceDrawn && update.state.wantSurfaceVisible()) { sp<Surface> surface = update.state.surfaceControl->getSurface(); - ANativeWindow_Buffer outBuffer; - status_t status = surface->lock(&outBuffer, NULL); - if (status) { - ALOGE("Error %d locking sprite surface before drawing.", status); - } else { - SkBitmap surfaceBitmap; - ssize_t bpr = outBuffer.stride * bytesPerPixel(outBuffer.format); - surfaceBitmap.installPixels(SkImageInfo::MakeN32Premul(outBuffer.width, outBuffer.height), - outBuffer.bits, bpr); - - SkCanvas surfaceCanvas(surfaceBitmap); - - SkPaint paint; - paint.setBlendMode(SkBlendMode::kSrc); - surfaceCanvas.drawBitmap(update.state.icon.bitmap, 0, 0, &paint); - - if (outBuffer.width > update.state.icon.bitmap.width()) { - paint.setColor(0); // transparent fill color - surfaceCanvas.drawRect(SkRect::MakeLTRB(update.state.icon.bitmap.width(), 0, - outBuffer.width, update.state.icon.bitmap.height()), paint); - } - if (outBuffer.height > update.state.icon.bitmap.height()) { - paint.setColor(0); // transparent fill color - surfaceCanvas.drawRect(SkRect::MakeLTRB(0, update.state.icon.bitmap.height(), - outBuffer.width, outBuffer.height), paint); - } - - status = surface->unlockAndPost(); - if (status) { - ALOGE("Error %d unlocking and posting sprite surface after drawing.", status); - } else { - update.state.surfaceDrawn = true; - update.surfaceChanged = surfaceChanged = true; - } + if (update.state.icon.draw(surface)) { + update.state.surfaceDrawn = true; + update.surfaceChanged = surfaceChanged = true; } } } @@ -245,7 +207,8 @@ void SpriteController::doUpdateSprites() { if (update.state.surfaceControl != NULL && (becomingVisible || becomingHidden || (wantSurfaceVisibleAndDrawn && (update.state.dirty & (DIRTY_ALPHA | DIRTY_POSITION | DIRTY_TRANSFORMATION_MATRIX | DIRTY_LAYER - | DIRTY_VISIBILITY | DIRTY_HOTSPOT | DIRTY_DISPLAY_ID))))) { + | DIRTY_VISIBILITY | DIRTY_HOTSPOT | DIRTY_DISPLAY_ID + | DIRTY_ICON_STYLE))))) { needApplyTransaction = true; if (wantSurfaceVisibleAndDrawn @@ -274,6 +237,21 @@ void SpriteController::doUpdateSprites() { update.state.transformationMatrix.dtdy); } + if (wantSurfaceVisibleAndDrawn + && (becomingVisible + || (update.state.dirty & (DIRTY_HOTSPOT | DIRTY_ICON_STYLE)))) { + Parcel p; + p.writeInt32(update.state.icon.style); + p.writeFloat(update.state.icon.hotSpotX); + p.writeFloat(update.state.icon.hotSpotY); + + // Pass cursor metadata in the sprite surface so that when Android is running as a + // client OS (e.g. ARC++) the host OS can get the requested cursor metadata and + // update mouse cursor in the host OS. + t.setMetadata( + update.state.surfaceControl, METADATA_MOUSE_CURSOR, p); + } + int32_t surfaceLayer = mOverlayLayer + update.state.layer; if (wantSurfaceVisibleAndDrawn && (becomingVisible || (update.state.dirty & DIRTY_LAYER))) { @@ -397,9 +375,14 @@ void SpriteController::SpriteImpl::setIcon(const SpriteIcon& icon) { } else { dirty = DIRTY_BITMAP; } + + if (mLocked.state.icon.style != icon.style) { + mLocked.state.icon.style = icon.style; + dirty |= DIRTY_ICON_STYLE; + } } else if (mLocked.state.icon.isValid()) { mLocked.state.icon.bitmap.reset(); - dirty = DIRTY_BITMAP | DIRTY_HOTSPOT; + dirty = DIRTY_BITMAP | DIRTY_HOTSPOT | DIRTY_ICON_STYLE; } else { return; // setting to invalid icon and already invalid so nothing to do } diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h index 5b216f50d113..137b5646feae 100644 --- a/libs/input/SpriteController.h +++ b/libs/input/SpriteController.h @@ -22,7 +22,7 @@ #include <gui/SurfaceComposerClient.h> -#include <SkBitmap.h> +#include "SpriteIcon.h" namespace android { @@ -52,38 +52,6 @@ struct SpriteTransformationMatrix { }; /* - * Icon that a sprite displays, including its hotspot. - */ -struct SpriteIcon { - inline SpriteIcon() : hotSpotX(0), hotSpotY(0) { } - inline SpriteIcon(const SkBitmap& bitmap, float hotSpotX, float hotSpotY) : - bitmap(bitmap), hotSpotX(hotSpotX), hotSpotY(hotSpotY) { } - - SkBitmap bitmap; - float hotSpotX; - float hotSpotY; - - inline SpriteIcon copy() const { - SkBitmap bitmapCopy; - if (bitmapCopy.tryAllocPixels(bitmap.info().makeColorType(kN32_SkColorType))) { - bitmap.readPixels(bitmapCopy.info(), bitmapCopy.getPixels(), bitmapCopy.rowBytes(), - 0, 0); - } - return SpriteIcon(bitmapCopy, hotSpotX, hotSpotY); - } - - inline void reset() { - bitmap.reset(); - hotSpotX = 0; - hotSpotY = 0; - } - - inline bool isValid() const { - return !bitmap.isNull() && !bitmap.empty(); - } -}; - -/* * A sprite is a simple graphical object that is displayed on-screen above other layers. * The basic sprite class is an interface. * The implementation is provided by the sprite controller. @@ -149,15 +117,15 @@ public: SpriteController(const sp<Looper>& looper, int32_t overlayLayer); /* Creates a new sprite, initially invisible. */ - sp<Sprite> createSprite(); + virtual sp<Sprite> createSprite(); /* Opens or closes a transaction to perform a batch of sprite updates as part of * a single operation such as setPosition and setAlpha. It is not necessary to * open a transaction when updating a single property. * Calls to openTransaction() nest and must be matched by an equal number * of calls to closeTransaction(). */ - void openTransaction(); - void closeTransaction(); + virtual void openTransaction(); + virtual void closeTransaction(); private: enum { @@ -174,13 +142,14 @@ private: DIRTY_VISIBILITY = 1 << 5, DIRTY_HOTSPOT = 1 << 6, DIRTY_DISPLAY_ID = 1 << 7, + DIRTY_ICON_STYLE = 1 << 8, }; /* Describes the state of a sprite. * This structure is designed so that it can be copied during updates so that * surfaces can be resized and redrawn without blocking the client by holding a lock * on the sprites for a long time. - * Note that the SkBitmap holds a reference to a shared (and immutable) pixel ref. */ + * Note that the SpriteIcon holds a reference to a shared (and immutable) bitmap. */ struct SpriteState { inline SpriteState() : dirty(0), visible(false), diff --git a/libs/input/SpriteIcon.cpp b/libs/input/SpriteIcon.cpp new file mode 100644 index 000000000000..10cc458b9289 --- /dev/null +++ b/libs/input/SpriteIcon.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "SpriteIcon.h" + +#include <SkBitmap.h> +#include <SkCanvas.h> +#include <SkColor.h> +#include <SkPaint.h> + +#include <android/native_window.h> +#include <log/log.h> + +namespace android { + +bool SpriteIcon::draw(sp<Surface> surface) const { + ANativeWindow_Buffer outBuffer; + status_t status = surface->lock(&outBuffer, NULL); + if (status) { + ALOGE("Error %d locking sprite surface before drawing.", status); + return false; + } + + SkBitmap surfaceBitmap; + ssize_t bpr = outBuffer.stride * bytesPerPixel(outBuffer.format); + surfaceBitmap.installPixels(SkImageInfo::MakeN32Premul(outBuffer.width, outBuffer.height), + outBuffer.bits, bpr); + + SkCanvas surfaceCanvas(surfaceBitmap); + + SkPaint paint; + paint.setBlendMode(SkBlendMode::kSrc); + surfaceCanvas.drawBitmap(bitmap, 0, 0, &paint); + + if (outBuffer.width > width()) { + paint.setColor(0); // transparent fill color + surfaceCanvas.drawRect(SkRect::MakeLTRB(width(), 0, outBuffer.width, height()), paint); + } + if (outBuffer.height > height()) { + paint.setColor(0); // transparent fill color + surfaceCanvas.drawRect(SkRect::MakeLTRB(0, height(), outBuffer.width, outBuffer.height), + paint); + } + + status = surface->unlockAndPost(); + if (status) { + ALOGE("Error %d unlocking and posting sprite surface after drawing.", status); + } + return !status; +} + +} // namespace android diff --git a/libs/input/SpriteIcon.h b/libs/input/SpriteIcon.h new file mode 100644 index 000000000000..c535faf624f7 --- /dev/null +++ b/libs/input/SpriteIcon.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _UI_SPRITE_ICON_H +#define _UI_SPRITE_ICON_H + +#include <SkBitmap.h> +#include <gui/Surface.h> + +namespace android { + +/* + * Icon that a sprite displays, including its hotspot. + */ +struct SpriteIcon { + inline SpriteIcon() : style(0), hotSpotX(0), hotSpotY(0) { } + inline SpriteIcon(const SkBitmap& bitmap, int32_t style, float hotSpotX, float hotSpotY) : + bitmap(bitmap), style(style), hotSpotX(hotSpotX), hotSpotY(hotSpotY) { } + + SkBitmap bitmap; + int32_t style; + float hotSpotX; + float hotSpotY; + + inline SpriteIcon copy() const { + SkBitmap bitmapCopy; + if (bitmapCopy.tryAllocPixels(bitmap.info().makeColorType(kN32_SkColorType))) { + bitmap.readPixels(bitmapCopy.info(), bitmapCopy.getPixels(), bitmapCopy.rowBytes(), + 0, 0); + } + return SpriteIcon(bitmapCopy, style, hotSpotX, hotSpotY); + } + + inline void reset() { + bitmap.reset(); + style = 0; + hotSpotX = 0; + hotSpotY = 0; + } + + inline bool isValid() const { return !bitmap.isNull() && !bitmap.empty(); } + + inline int32_t width() const { return bitmap.width(); } + inline int32_t height() const { return bitmap.height(); } + + // Draw the bitmap onto the given surface. Returns true if it's successful, or false otherwise. + // Note it doesn't set any metadata to the surface. + bool draw(const sp<Surface> surface) const; +}; + + +} // namespace android + +#endif // _UI_SPRITE_ICON_H diff --git a/libs/input/TEST_MAPPING b/libs/input/TEST_MAPPING new file mode 100644 index 000000000000..fe74c62d4ec1 --- /dev/null +++ b/libs/input/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "libinputservice_test" + } + ] +} diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp new file mode 100644 index 000000000000..e83b2a78d180 --- /dev/null +++ b/libs/input/tests/Android.bp @@ -0,0 +1,45 @@ +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +cc_test { + name: "libinputservice_test", + srcs: [ + "PointerController_test.cpp", + ], + shared_libs: [ + "libinputservice", + "libgui", + "libhwui", + "libutils", + ], + static_libs: [ + "libgmock", + "libgtest", + ], + header_libs: [ + "libbase_headers", + "libinputflinger_headers", + ], + include_dirs: [ + "frameworks/base/libs", + ], + cflags: [ + "-Wall", + "-Werror", + "-Wextra", + ], + test_suites: [ + "general-tests", + ], +} diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp new file mode 100644 index 000000000000..a15742671dc7 --- /dev/null +++ b/libs/input/tests/PointerController_test.cpp @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "mocks/MockSprite.h" +#include "mocks/MockSpriteController.h" + +#include <input/PointerController.h> +#include <input/SpriteController.h> + +#include <atomic> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <thread> + +namespace android { + +enum TestCursorType { + CURSOR_TYPE_DEFAULT = 0, + CURSOR_TYPE_HOVER, + CURSOR_TYPE_TOUCH, + CURSOR_TYPE_ANCHOR, + CURSOR_TYPE_ADDITIONAL, + CURSOR_TYPE_ADDITIONAL_ANIM, + CURSOR_TYPE_CUSTOM = -1, +}; + +using ::testing::AllOf; +using ::testing::Field; +using ::testing::Mock; +using ::testing::NiceMock; +using ::testing::Return; +using ::testing::Test; + +std::pair<float, float> getHotSpotCoordinatesForType(int32_t type) { + return std::make_pair(type * 10, type * 10 + 5); +} + +class MockPointerControllerPolicyInterface : public PointerControllerPolicyInterface { +public: + virtual void loadPointerIcon(SpriteIcon* icon, int32_t displayId) override; + virtual void loadPointerResources(PointerResources* outResources, int32_t displayId) override; + virtual void loadAdditionalMouseResources(std::map<int32_t, SpriteIcon>* outResources, + std::map<int32_t, PointerAnimation>* outAnimationResources, int32_t displayId) override; + virtual int32_t getDefaultPointerIconId() override; + virtual int32_t getCustomPointerIconId() override; + + bool allResourcesAreLoaded(); + bool noResourcesAreLoaded(); + +private: + void loadPointerIconForType(SpriteIcon* icon, int32_t cursorType); + + bool pointerIconLoaded{false}; + bool pointerResourcesLoaded{false}; + bool additionalMouseResourcesLoaded{false}; +}; + +void MockPointerControllerPolicyInterface::loadPointerIcon(SpriteIcon* icon, int32_t) { + loadPointerIconForType(icon, CURSOR_TYPE_DEFAULT); + pointerIconLoaded = true; +} + +void MockPointerControllerPolicyInterface::loadPointerResources(PointerResources* outResources, + int32_t) { + loadPointerIconForType(&outResources->spotHover, CURSOR_TYPE_HOVER); + loadPointerIconForType(&outResources->spotTouch, CURSOR_TYPE_TOUCH); + loadPointerIconForType(&outResources->spotAnchor, CURSOR_TYPE_ANCHOR); + pointerResourcesLoaded = true; +} + +void MockPointerControllerPolicyInterface::loadAdditionalMouseResources( + std::map<int32_t, SpriteIcon>* outResources, + std::map<int32_t, PointerAnimation>* outAnimationResources, + int32_t) { + SpriteIcon icon; + PointerAnimation anim; + + // CURSOR_TYPE_ADDITIONAL doesn't have animation resource. + int32_t cursorType = CURSOR_TYPE_ADDITIONAL; + loadPointerIconForType(&icon, cursorType); + (*outResources)[cursorType] = icon; + + // CURSOR_TYPE_ADDITIONAL_ANIM has animation resource. + cursorType = CURSOR_TYPE_ADDITIONAL_ANIM; + loadPointerIconForType(&icon, cursorType); + anim.animationFrames.push_back(icon); + anim.durationPerFrame = 10; + (*outResources)[cursorType] = icon; + (*outAnimationResources)[cursorType] = anim; + + additionalMouseResourcesLoaded = true; +} + +int32_t MockPointerControllerPolicyInterface::getDefaultPointerIconId() { + return CURSOR_TYPE_DEFAULT; +} + +int32_t MockPointerControllerPolicyInterface::getCustomPointerIconId() { + return CURSOR_TYPE_CUSTOM; +} + +bool MockPointerControllerPolicyInterface::allResourcesAreLoaded() { + return pointerIconLoaded && pointerResourcesLoaded && additionalMouseResourcesLoaded; +} + +bool MockPointerControllerPolicyInterface::noResourcesAreLoaded() { + return !(pointerIconLoaded || pointerResourcesLoaded || additionalMouseResourcesLoaded); +} + +void MockPointerControllerPolicyInterface::loadPointerIconForType(SpriteIcon* icon, int32_t type) { + icon->style = type; + std::pair<float, float> hotSpot = getHotSpotCoordinatesForType(type); + icon->hotSpotX = hotSpot.first; + icon->hotSpotY = hotSpot.second; +} +class PointerControllerTest : public Test { +protected: + PointerControllerTest(); + ~PointerControllerTest(); + + void ensureDisplayViewportIsSet(); + + sp<MockSprite> mPointerSprite; + sp<MockPointerControllerPolicyInterface> mPolicy; + sp<MockSpriteController> mSpriteController; + sp<PointerController> mPointerController; + +private: + void loopThread(); + + std::atomic<bool> mRunning = true; + class MyLooper : public Looper { + public: + MyLooper() : Looper(false) {} + ~MyLooper() = default; + }; + sp<MyLooper> mLooper; + std::thread mThread; +}; + +PointerControllerTest::PointerControllerTest() : mPointerSprite(new NiceMock<MockSprite>), + mLooper(new MyLooper), mThread(&PointerControllerTest::loopThread, this) { + + mSpriteController = new NiceMock<MockSpriteController>(mLooper); + mPolicy = new MockPointerControllerPolicyInterface(); + + EXPECT_CALL(*mSpriteController, createSprite()) + .WillOnce(Return(mPointerSprite)); + + mPointerController = new PointerController(mPolicy, mLooper, mSpriteController); +} + +PointerControllerTest::~PointerControllerTest() { + mRunning.store(false, std::memory_order_relaxed); + mThread.join(); +} + +void PointerControllerTest::ensureDisplayViewportIsSet() { + DisplayViewport viewport; + viewport.displayId = ADISPLAY_ID_DEFAULT; + viewport.logicalRight = 1600; + viewport.logicalBottom = 1200; + viewport.physicalRight = 800; + viewport.physicalBottom = 600; + viewport.deviceWidth = 400; + viewport.deviceHeight = 300; + mPointerController->setDisplayViewport(viewport); + + // The first call to setDisplayViewport should trigger the loading of the necessary resources. + EXPECT_TRUE(mPolicy->allResourcesAreLoaded()); +} + +void PointerControllerTest::loopThread() { + Looper::setForThread(mLooper); + + while (mRunning.load(std::memory_order_relaxed)) { + mLooper->pollOnce(100); + } +} + +TEST_F(PointerControllerTest, useDefaultCursorTypeByDefault) { + ensureDisplayViewportIsSet(); + mPointerController->unfade(PointerController::TRANSITION_IMMEDIATE); + + std::pair<float, float> hotspot = getHotSpotCoordinatesForType(CURSOR_TYPE_DEFAULT); + EXPECT_CALL(*mPointerSprite, setVisible(true)); + EXPECT_CALL(*mPointerSprite, setAlpha(1.0f)); + EXPECT_CALL(*mPointerSprite, setIcon( + AllOf( + Field(&SpriteIcon::style, CURSOR_TYPE_DEFAULT), + Field(&SpriteIcon::hotSpotX, hotspot.first), + Field(&SpriteIcon::hotSpotY, hotspot.second)))); + mPointerController->reloadPointerResources(); +} + +TEST_F(PointerControllerTest, updatePointerIcon) { + ensureDisplayViewportIsSet(); + mPointerController->unfade(PointerController::TRANSITION_IMMEDIATE); + + int32_t type = CURSOR_TYPE_ADDITIONAL; + std::pair<float, float> hotspot = getHotSpotCoordinatesForType(type); + EXPECT_CALL(*mPointerSprite, setVisible(true)); + EXPECT_CALL(*mPointerSprite, setAlpha(1.0f)); + EXPECT_CALL(*mPointerSprite, setIcon( + AllOf( + Field(&SpriteIcon::style, type), + Field(&SpriteIcon::hotSpotX, hotspot.first), + Field(&SpriteIcon::hotSpotY, hotspot.second)))); + mPointerController->updatePointerIcon(type); +} + +TEST_F(PointerControllerTest, setCustomPointerIcon) { + ensureDisplayViewportIsSet(); + mPointerController->unfade(PointerController::TRANSITION_IMMEDIATE); + + int32_t style = CURSOR_TYPE_CUSTOM; + float hotSpotX = 15; + float hotSpotY = 20; + + SpriteIcon icon; + icon.style = style; + icon.hotSpotX = hotSpotX; + icon.hotSpotY = hotSpotY; + + EXPECT_CALL(*mPointerSprite, setVisible(true)); + EXPECT_CALL(*mPointerSprite, setAlpha(1.0f)); + EXPECT_CALL(*mPointerSprite, setIcon( + AllOf( + Field(&SpriteIcon::style, style), + Field(&SpriteIcon::hotSpotX, hotSpotX), + Field(&SpriteIcon::hotSpotY, hotSpotY)))); + mPointerController->setCustomPointerIcon(icon); +} + +TEST_F(PointerControllerTest, doesNotGetResourcesBeforeSettingViewport) { + mPointerController->setPresentation(PointerController::PRESENTATION_POINTER); + mPointerController->setSpots(nullptr, nullptr, BitSet32(), -1); + mPointerController->clearSpots(); + mPointerController->setPosition(1.0f, 1.0f); + mPointerController->move(1.0f, 1.0f); + mPointerController->unfade(PointerController::TRANSITION_IMMEDIATE); + mPointerController->fade(PointerController::TRANSITION_IMMEDIATE); + + EXPECT_TRUE(mPolicy->noResourcesAreLoaded()); + + ensureDisplayViewportIsSet(); +} + +} // namespace android diff --git a/libs/input/tests/mocks/MockSprite.h b/libs/input/tests/mocks/MockSprite.h new file mode 100644 index 000000000000..013b79c3a3bf --- /dev/null +++ b/libs/input/tests/mocks/MockSprite.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _MOCK_SPRITE_H +#define _MOCK_SPRITE_H + +#include <input/SpriteController.h> + +#include <gmock/gmock.h> + +namespace android { + +class MockSprite : public Sprite { +public: + virtual ~MockSprite() = default; + + MOCK_METHOD(void, setIcon, (const SpriteIcon& icon), (override)); + MOCK_METHOD(void, setVisible, (bool), (override)); + MOCK_METHOD(void, setPosition, (float, float), (override)); + MOCK_METHOD(void, setLayer, (int32_t), (override)); + MOCK_METHOD(void, setAlpha, (float), (override)); + MOCK_METHOD(void, setTransformationMatrix, (const SpriteTransformationMatrix&), (override)); + MOCK_METHOD(void, setDisplayId, (int32_t), (override)); +}; + +} // namespace android + +#endif // _MOCK_SPRITE_H diff --git a/libs/input/tests/mocks/MockSpriteController.h b/libs/input/tests/mocks/MockSpriteController.h new file mode 100644 index 000000000000..a034f66c9abf --- /dev/null +++ b/libs/input/tests/mocks/MockSpriteController.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _MOCK_SPRITE_CONTROLLER_H +#define _MOCK_SPRITE_CONTROLLER_H + +#include "MockSprite.h" + +#include <input/SpriteController.h> + +namespace android { + +class MockSpriteController : public SpriteController { + +public: + MockSpriteController(sp<Looper> looper) : SpriteController(looper, 0) {} + ~MockSpriteController() {} + + MOCK_METHOD(sp<Sprite>, createSprite, (), (override)); + MOCK_METHOD(void, openTransaction, (), (override)); + MOCK_METHOD(void, closeTransaction, (), (override)); +}; + +} // namespace android + +#endif // _MOCK_SPRITE_CONTROLLER_H diff --git a/media/java/android/mtp/MtpStorageManager.java b/media/java/android/mtp/MtpStorageManager.java index e783788d0158..c0eb5e8bbea9 100644 --- a/media/java/android/mtp/MtpStorageManager.java +++ b/media/java/android/mtp/MtpStorageManager.java @@ -229,9 +229,16 @@ public class MtpStorageManager { } private void setParent(MtpObject parent) { + if (this.getStorageId() != parent.getStorageId()) { + mStorage = Preconditions.checkNotNull(parent.getStorage()); + } mParent = parent; } + private MtpStorage getStorage() { + return mStorage; + } + private void setDir(boolean dir) { if (dir != mIsDir) { mIsDir = dir; diff --git a/packages/CtsShim/build/jni/Android.bp b/packages/CtsShim/build/jni/Android.bp index ea15b43416b4..4fc6e8dfaaee 100644 --- a/packages/CtsShim/build/jni/Android.bp +++ b/packages/CtsShim/build/jni/Android.bp @@ -17,5 +17,6 @@ cc_library_shared { name: "libshim_jni", srcs: ["Shim.c"], + header_libs: ["jni_headers"], sdk_version: "24", } diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp index 33c2b5828dac..4116afca9816 100644 --- a/packages/Tethering/Android.bp +++ b/packages/Tethering/Android.bp @@ -25,7 +25,7 @@ java_defaults { ], static_libs: [ "androidx.annotation_annotation", - "netd_aidl_interface-V3-java", + "netd_aidl_interface-unstable-java", "netlink-client", "networkstack-aidl-interfaces-java", "android.hardware.tetheroffload.config-V1.0-java", diff --git a/packages/Tethering/src/android/net/ip/IpServer.java b/packages/Tethering/src/android/net/ip/IpServer.java index f08429bb0696..088b88cb3145 100644 --- a/packages/Tethering/src/android/net/ip/IpServer.java +++ b/packages/Tethering/src/android/net/ip/IpServer.java @@ -33,7 +33,6 @@ import android.net.LinkAddress; import android.net.LinkProperties; import android.net.MacAddress; import android.net.RouteInfo; -import android.net.TetherOffloadRuleParcel; import android.net.TetheredClient; import android.net.TetheringManager; import android.net.TetheringRequestParcel; @@ -65,6 +64,8 @@ import androidx.annotation.Nullable; import com.android.internal.util.MessageUtils; import com.android.internal.util.State; import com.android.internal.util.StateMachine; +import com.android.networkstack.tethering.BpfCoordinator; +import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule; import com.android.networkstack.tethering.PrivateAddressCoordinator; import java.io.IOException; @@ -225,6 +226,8 @@ public class IpServer extends StateMachine { private final SharedLog mLog; private final INetd mNetd; + @NonNull + private final BpfCoordinator mBpfCoordinator; private final Callback mCallback; private final InterfaceController mInterfaceCtrl; private final PrivateAddressCoordinator mPrivateAddressCoordinator; @@ -269,40 +272,6 @@ public class IpServer extends StateMachine { } } - static class Ipv6ForwardingRule { - public final int upstreamIfindex; - public final int downstreamIfindex; - public final Inet6Address address; - public final MacAddress srcMac; - public final MacAddress dstMac; - - Ipv6ForwardingRule(int upstreamIfindex, int downstreamIfIndex, Inet6Address address, - MacAddress srcMac, MacAddress dstMac) { - this.upstreamIfindex = upstreamIfindex; - this.downstreamIfindex = downstreamIfIndex; - this.address = address; - this.srcMac = srcMac; - this.dstMac = dstMac; - } - - public Ipv6ForwardingRule onNewUpstream(int newUpstreamIfindex) { - return new Ipv6ForwardingRule(newUpstreamIfindex, downstreamIfindex, address, srcMac, - dstMac); - } - - // Don't manipulate TetherOffloadRuleParcel directly because implementing onNewUpstream() - // would be error-prone due to generated stable AIDL classes not having a copy constructor. - public TetherOffloadRuleParcel toTetherOffloadRuleParcel() { - final TetherOffloadRuleParcel parcel = new TetherOffloadRuleParcel(); - parcel.inputInterfaceIndex = upstreamIfindex; - parcel.outputInterfaceIndex = downstreamIfindex; - parcel.destination = address.getAddress(); - parcel.prefixLength = 128; - parcel.srcL2Address = srcMac.toByteArray(); - parcel.dstL2Address = dstMac.toByteArray(); - return parcel; - } - } private final LinkedHashMap<Inet6Address, Ipv6ForwardingRule> mIpv6ForwardingRules = new LinkedHashMap<>(); @@ -314,11 +283,13 @@ public class IpServer extends StateMachine { // object. It helps to reduce the arguments of the constructor. public IpServer( String ifaceName, Looper looper, int interfaceType, SharedLog log, - INetd netd, Callback callback, boolean usingLegacyDhcp, boolean usingBpfOffload, + INetd netd, @NonNull BpfCoordinator coordinator, Callback callback, + boolean usingLegacyDhcp, boolean usingBpfOffload, PrivateAddressCoordinator addressCoordinator, Dependencies deps) { super(ifaceName, looper); mLog = log.forSubComponent(ifaceName); mNetd = netd; + mBpfCoordinator = coordinator; mCallback = callback; mInterfaceCtrl = new InterfaceController(ifaceName, mNetd, mLog); mIfaceName = ifaceName; @@ -754,6 +725,14 @@ public class IpServer extends StateMachine { } upstreamIfindex = mDeps.getIfindex(upstreamIface); + + // Add upstream index to name mapping for the tether stats usage in the coordinator. + // Although this mapping could be added by both class Tethering and IpServer, adding + // mapping from IpServer guarantees that the mapping is added before the adding + // forwarding rules. That is because there are different state machines in both + // classes. It is hard to guarantee the link property update order between multiple + // state machines. + mBpfCoordinator.addUpstreamNameToLookupTable(upstreamIfindex, upstreamIface); } // If v6only is null, we pass in null to setRaParams(), which handles diff --git a/packages/Tethering/src/android/net/util/TetheringUtils.java b/packages/Tethering/src/android/net/util/TetheringUtils.java index dd67dddae1cd..b17b4ba77cfb 100644 --- a/packages/Tethering/src/android/net/util/TetheringUtils.java +++ b/packages/Tethering/src/android/net/util/TetheringUtils.java @@ -15,19 +15,94 @@ */ package android.net.util; +import android.net.TetherStatsParcel; import android.net.TetheringRequestParcel; +import androidx.annotation.NonNull; + import java.io.FileDescriptor; import java.net.SocketException; import java.util.Objects; /** - * Native methods for tethering utilization. + * The classes and the methods for tethering utilization. * * {@hide} */ public class TetheringUtils { /** + * The object which records offload Tx/Rx forwarded bytes/packets. + * TODO: Replace the inner class ForwardedStats of class OffloadHardwareInterface with + * this class as well. + */ + public static class ForwardedStats { + public final long rxBytes; + public final long rxPackets; + public final long txBytes; + public final long txPackets; + + public ForwardedStats() { + rxBytes = 0; + rxPackets = 0; + txBytes = 0; + txPackets = 0; + } + + public ForwardedStats(long rxBytes, long txBytes) { + this.rxBytes = rxBytes; + this.rxPackets = 0; + this.txBytes = txBytes; + this.txPackets = 0; + } + + public ForwardedStats(long rxBytes, long rxPackets, long txBytes, long txPackets) { + this.rxBytes = rxBytes; + this.rxPackets = rxPackets; + this.txBytes = txBytes; + this.txPackets = txPackets; + } + + public ForwardedStats(@NonNull TetherStatsParcel tetherStats) { + rxBytes = tetherStats.rxBytes; + rxPackets = tetherStats.rxPackets; + txBytes = tetherStats.txBytes; + txPackets = tetherStats.txPackets; + } + + public ForwardedStats(@NonNull ForwardedStats other) { + rxBytes = other.rxBytes; + rxPackets = other.rxPackets; + txBytes = other.txBytes; + txPackets = other.txPackets; + } + + /** Add Tx/Rx bytes/packets and return the result as a new object. */ + @NonNull + public ForwardedStats add(@NonNull ForwardedStats other) { + return new ForwardedStats(rxBytes + other.rxBytes, rxPackets + other.rxPackets, + txBytes + other.txBytes, txPackets + other.txPackets); + } + + /** Subtract Tx/Rx bytes/packets and return the result as a new object. */ + @NonNull + public ForwardedStats subtract(@NonNull ForwardedStats other) { + // TODO: Perhaps throw an exception if any negative difference value just in case. + final long rxBytesDiff = Math.max(rxBytes - other.rxBytes, 0); + final long rxPacketsDiff = Math.max(rxPackets - other.rxPackets, 0); + final long txBytesDiff = Math.max(txBytes - other.txBytes, 0); + final long txPacketsDiff = Math.max(txPackets - other.txPackets, 0); + return new ForwardedStats(rxBytesDiff, rxPacketsDiff, txBytesDiff, txPacketsDiff); + } + + /** Returns the string representation of this object. */ + @NonNull + public String toString() { + return String.format("ForwardedStats(rxb: %d, rxp: %d, txb: %d, txp: %d)", rxBytes, + rxPackets, txBytes, txPackets); + } + } + + /** * Configures a socket for receiving ICMPv6 router solicitations and sending advertisements. * @param fd the socket's {@link FileDescriptor}. * @param ifIndex the interface index. diff --git a/packages/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/packages/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java new file mode 100644 index 000000000000..6b854f2ac941 --- /dev/null +++ b/packages/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2020 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.networkstack.tethering; + +import static android.net.NetworkStats.DEFAULT_NETWORK_NO; +import static android.net.NetworkStats.METERED_NO; +import static android.net.NetworkStats.ROAMING_NO; +import static android.net.NetworkStats.SET_DEFAULT; +import static android.net.NetworkStats.TAG_NONE; +import static android.net.NetworkStats.UID_ALL; +import static android.net.NetworkStats.UID_TETHERING; + +import android.app.usage.NetworkStatsManager; +import android.net.INetd; +import android.net.MacAddress; +import android.net.NetworkStats; +import android.net.NetworkStats.Entry; +import android.net.TetherOffloadRuleParcel; +import android.net.TetherStatsParcel; +import android.net.netstats.provider.NetworkStatsProvider; +import android.net.util.SharedLog; +import android.net.util.TetheringUtils.ForwardedStats; +import android.os.Handler; +import android.os.RemoteException; +import android.os.ServiceSpecificException; +import android.util.Log; +import android.util.SparseArray; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.internal.annotations.VisibleForTesting; + +import java.net.Inet6Address; + +/** + * This coordinator is responsible for providing BPF offload relevant functionality. + * - Get tethering stats. + * + * @hide + */ +public class BpfCoordinator { + private static final String TAG = BpfCoordinator.class.getSimpleName(); + @VisibleForTesting + static final int DEFAULT_PERFORM_POLL_INTERVAL_MS = 5000; // TODO: Make it customizable. + + @VisibleForTesting + enum StatsType { + STATS_PER_IFACE, + STATS_PER_UID, + } + + @NonNull + private final Handler mHandler; + @NonNull + private final INetd mNetd; + @NonNull + private final SharedLog mLog; + @NonNull + private final Dependencies mDeps; + @Nullable + private final BpfTetherStatsProvider mStatsProvider; + private boolean mStarted = false; + + // Maps upstream interface index to offloaded traffic statistics. + // Always contains the latest total bytes/packets, since each upstream was started, received + // from the BPF maps for each interface. + private SparseArray<ForwardedStats> mStats = new SparseArray<>(); + + // Maps upstream interface index to interface names. + // Store all interface name since boot. Used for lookup what interface name it is from the + // tether stats got from netd because netd reports interface index to present an interface. + // TODO: Remove the unused interface name. + private SparseArray<String> mInterfaceNames = new SparseArray<>(); + + // Runnable that used by scheduling next polling of stats. + private final Runnable mScheduledPollingTask = () -> { + updateForwardedStatsFromNetd(); + maybeSchedulePollingStats(); + }; + + @VisibleForTesting + static class Dependencies { + int getPerformPollInterval() { + // TODO: Consider make this configurable. + return DEFAULT_PERFORM_POLL_INTERVAL_MS; + } + } + + BpfCoordinator(@NonNull Handler handler, @NonNull INetd netd, + @NonNull NetworkStatsManager nsm, @NonNull SharedLog log, @NonNull Dependencies deps) { + mHandler = handler; + mNetd = netd; + mLog = log.forSubComponent(TAG); + BpfTetherStatsProvider provider = new BpfTetherStatsProvider(); + try { + nsm.registerNetworkStatsProvider(getClass().getSimpleName(), provider); + } catch (RuntimeException e) { + // TODO: Perhaps not allow to use BPF offload because the reregistration failure + // implied that no data limit could be applies on a metered upstream if any. + Log.wtf(TAG, "Cannot register offload stats provider: " + e); + provider = null; + } + mStatsProvider = provider; + mDeps = deps; + } + + /** + * Start BPF tethering offload stats polling when the first upstream is started. + * Note that this can be only called on handler thread. + * TODO: Perhaps check BPF support before starting. + * TODO: Start the stats polling only if there is any client on the downstream. + */ + public void start() { + if (mStarted) return; + + mStarted = true; + maybeSchedulePollingStats(); + + mLog.i("BPF tethering coordinator started"); + } + + /** + * Stop BPF tethering offload stats polling and cleanup upstream parameters. + * Note that this can be only called on handler thread. + */ + public void stop() { + if (!mStarted) return; + + // Stop scheduled polling tasks and poll the latest stats from BPF maps. + if (mHandler.hasCallbacks(mScheduledPollingTask)) { + mHandler.removeCallbacks(mScheduledPollingTask); + } + updateForwardedStatsFromNetd(); + + mStarted = false; + + mLog.i("BPF tethering coordinator stopped"); + } + + /** + * Add upstream name to lookup table. The lookup table is used for tether stats interface name + * lookup because the netd only reports interface index in BPF tether stats but the service + * expects the interface name in NetworkStats object. + * Note that this can be only called on handler thread. + */ + public void addUpstreamNameToLookupTable(int upstreamIfindex, String upstreamIface) { + if (upstreamIfindex == 0) return; + + // The same interface index to name mapping may be added by different IpServer objects or + // re-added by reconnection on the same upstream interface. Ignore the duplicate one. + final String iface = mInterfaceNames.get(upstreamIfindex); + if (iface == null) { + mInterfaceNames.put(upstreamIfindex, upstreamIface); + } else if (iface != upstreamIface) { + Log.wtf(TAG, "The upstream interface name " + upstreamIface + + " is different from the existing interface name " + + iface + " for index " + upstreamIfindex); + } + } + + /** IPv6 forwarding rule class. */ + public static class Ipv6ForwardingRule { + public final int upstreamIfindex; + public final int downstreamIfindex; + public final Inet6Address address; + public final MacAddress srcMac; + public final MacAddress dstMac; + + public Ipv6ForwardingRule(int upstreamIfindex, int downstreamIfIndex, Inet6Address address, + MacAddress srcMac, MacAddress dstMac) { + this.upstreamIfindex = upstreamIfindex; + this.downstreamIfindex = downstreamIfIndex; + this.address = address; + this.srcMac = srcMac; + this.dstMac = dstMac; + } + + /** Return a new rule object which updates with new upstream index. */ + public Ipv6ForwardingRule onNewUpstream(int newUpstreamIfindex) { + return new Ipv6ForwardingRule(newUpstreamIfindex, downstreamIfindex, address, srcMac, + dstMac); + } + + /** + * Don't manipulate TetherOffloadRuleParcel directly because implementing onNewUpstream() + * would be error-prone due to generated stable AIDL classes not having a copy constructor. + */ + public TetherOffloadRuleParcel toTetherOffloadRuleParcel() { + final TetherOffloadRuleParcel parcel = new TetherOffloadRuleParcel(); + parcel.inputInterfaceIndex = upstreamIfindex; + parcel.outputInterfaceIndex = downstreamIfindex; + parcel.destination = address.getAddress(); + parcel.prefixLength = 128; + parcel.srcL2Address = srcMac.toByteArray(); + parcel.dstL2Address = dstMac.toByteArray(); + return parcel; + } + } + + /** + * A BPF tethering stats provider to provide network statistics to the system. + * Note that this class's data may only be accessed on the handler thread. + */ + @VisibleForTesting + class BpfTetherStatsProvider extends NetworkStatsProvider { + // The offloaded traffic statistics per interface that has not been reported since the + // last call to pushTetherStats. Only the interfaces that were ever tethering upstreams + // and has pending tether stats delta are included in this NetworkStats object. + private NetworkStats mIfaceStats = new NetworkStats(0L, 0); + + // The same stats as above, but counts network stats per uid. + private NetworkStats mUidStats = new NetworkStats(0L, 0); + + @Override + public void onRequestStatsUpdate(int token) { + mHandler.post(() -> pushTetherStats()); + } + + @Override + public void onSetAlert(long quotaBytes) { + // no-op + } + + @Override + public void onSetLimit(@NonNull String iface, long quotaBytes) { + // no-op + } + + @VisibleForTesting + void pushTetherStats() { + try { + // The token is not used for now. See b/153606961. + notifyStatsUpdated(0 /* token */, mIfaceStats, mUidStats); + + // Clear the accumulated tether stats delta after reported. Note that create a new + // empty object because NetworkStats#clear is @hide. + mIfaceStats = new NetworkStats(0L, 0); + mUidStats = new NetworkStats(0L, 0); + } catch (RuntimeException e) { + mLog.e("Cannot report network stats: ", e); + } + } + + private void accumulateDiff(@NonNull NetworkStats ifaceDiff, + @NonNull NetworkStats uidDiff) { + mIfaceStats = mIfaceStats.add(ifaceDiff); + mUidStats = mUidStats.add(uidDiff); + } + } + + @NonNull + private NetworkStats buildNetworkStats(@NonNull StatsType type, int ifIndex, + @NonNull ForwardedStats diff) { + NetworkStats stats = new NetworkStats(0L, 0); + final String iface = mInterfaceNames.get(ifIndex); + if (iface == null) { + // TODO: Use Log.wtf once the coordinator owns full control of tether stats from netd. + // For now, netd may add the empty stats for the upstream which is not monitored by + // the coordinator. Silently ignore it. + return stats; + } + final int uid = (type == StatsType.STATS_PER_UID) ? UID_TETHERING : UID_ALL; + // Note that the argument 'metered', 'roaming' and 'defaultNetwork' are not recorded for + // network stats snapshot. See NetworkStatsRecorder#recordSnapshotLocked. + return stats.addEntry(new Entry(iface, uid, SET_DEFAULT, TAG_NONE, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO, diff.rxBytes, diff.rxPackets, + diff.txBytes, diff.txPackets, 0L /* operations */)); + } + + private void updateForwardedStatsFromNetd() { + final TetherStatsParcel[] tetherStatsList; + try { + // The reported tether stats are total data usage for all currently-active upstream + // interfaces since tethering start. + tetherStatsList = mNetd.tetherOffloadGetStats(); + } catch (RemoteException | ServiceSpecificException e) { + mLog.e("Problem fetching tethering stats: ", e); + return; + } + + for (TetherStatsParcel tetherStats : tetherStatsList) { + final Integer ifIndex = tetherStats.ifIndex; + final ForwardedStats curr = new ForwardedStats(tetherStats); + final ForwardedStats base = mStats.get(ifIndex); + final ForwardedStats diff = (base != null) ? curr.subtract(base) : curr; + + // Update the local cache for counting tether stats delta. + mStats.put(ifIndex, curr); + + // Update the accumulated tether stats delta to the stats provider for the service + // querying. + if (mStatsProvider != null) { + try { + mStatsProvider.accumulateDiff( + buildNetworkStats(StatsType.STATS_PER_IFACE, ifIndex, diff), + buildNetworkStats(StatsType.STATS_PER_UID, ifIndex, diff)); + } catch (ArrayIndexOutOfBoundsException e) { + Log.wtf("Fail to update the accumulated stats delta for interface index " + + ifIndex + " : ", e); + } + } + } + } + + private void maybeSchedulePollingStats() { + if (!mStarted) return; + + if (mHandler.hasCallbacks(mScheduledPollingTask)) { + mHandler.removeCallbacks(mScheduledPollingTask); + } + + mHandler.postDelayed(mScheduledPollingTask, mDeps.getPerformPollInterval()); + } +} diff --git a/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java b/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java index 6eb10129efd5..00723ac73b43 100644 --- a/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java +++ b/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java @@ -232,6 +232,7 @@ public class Tethering { private final TetheringThreadExecutor mExecutor; private final TetheringNotificationUpdater mNotificationUpdater; private final UserManager mUserManager; + private final BpfCoordinator mBpfCoordinator; private final PrivateAddressCoordinator mPrivateAddressCoordinator; private int mActiveDataSubId = INVALID_SUBSCRIPTION_ID; // All the usage of mTetheringEventCallback should run in the same thread. @@ -284,6 +285,8 @@ public class Tethering { mUpstreamNetworkMonitor = mDeps.getUpstreamNetworkMonitor(mContext, mTetherMasterSM, mLog, TetherMasterSM.EVENT_UPSTREAM_CALLBACK); mForwardedDownstreams = new LinkedHashSet<>(); + mBpfCoordinator = mDeps.getBpfCoordinator( + mHandler, mNetd, mLog, new BpfCoordinator.Dependencies()); IntentFilter filter = new IntentFilter(); filter.addAction(ACTION_CARRIER_CONFIG_CHANGED); @@ -1704,6 +1707,9 @@ public class Tethering { chooseUpstreamType(true); mTryCell = false; } + + // TODO: Check the upstream interface if it is managed by BPF offload. + mBpfCoordinator.start(); } @Override @@ -1716,6 +1722,7 @@ public class Tethering { mTetherUpstream = null; reportUpstreamChanged(null); } + mBpfCoordinator.stop(); } private boolean updateUpstreamWanted() { @@ -2341,7 +2348,7 @@ public class Tethering { mLog.log("adding TetheringInterfaceStateMachine for: " + iface); final TetherState tetherState = new TetherState( - new IpServer(iface, mLooper, interfaceType, mLog, mNetd, + new IpServer(iface, mLooper, interfaceType, mLog, mNetd, mBpfCoordinator, makeControlCallback(), mConfig.enableLegacyDhcpServer, mConfig.enableBpfOffload, mPrivateAddressCoordinator, mDeps.getIpServerDependencies())); diff --git a/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java index ce546c701a61..d637c8646b4a 100644 --- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java +++ b/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java @@ -41,6 +41,17 @@ import java.util.ArrayList; */ public abstract class TetheringDependencies { /** + * Get a reference to the BpfCoordinator to be used by tethering. + */ + public @NonNull BpfCoordinator getBpfCoordinator( + @NonNull Handler handler, @NonNull INetd netd, @NonNull SharedLog log, + @NonNull BpfCoordinator.Dependencies deps) { + final NetworkStatsManager statsManager = + (NetworkStatsManager) getContext().getSystemService(Context.NETWORK_STATS_SERVICE); + return new BpfCoordinator(handler, netd, statsManager, log, deps); + } + + /** * Get a reference to the offload hardware interface to be used by tethering. */ public OffloadHardwareInterface getOffloadHardwareInterface(Handler h, SharedLog log) { diff --git a/packages/Tethering/src/com/android/networkstack/tethering/TetheringService.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringService.java index c11e86258d3c..613328d1c148 100644 --- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringService.java +++ b/packages/Tethering/src/com/android/networkstack/tethering/TetheringService.java @@ -17,8 +17,10 @@ package com.android.networkstack.tethering; import static android.Manifest.permission.ACCESS_NETWORK_STATE; +import static android.Manifest.permission.NETWORK_STACK; import static android.Manifest.permission.TETHER_PRIVILEGED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; import static android.net.TetheringManager.TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION; import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION; import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR; @@ -253,15 +255,26 @@ public class TetheringService extends Service { return false; } + private boolean hasNetworkStackPermission() { + return checkCallingOrSelfPermission(NETWORK_STACK) + || checkCallingOrSelfPermission(PERMISSION_MAINLINE_NETWORK_STACK); + } + private boolean hasTetherPrivilegedPermission() { - return mService.checkCallingOrSelfPermission(TETHER_PRIVILEGED) == PERMISSION_GRANTED; + return checkCallingOrSelfPermission(TETHER_PRIVILEGED); + } + + private boolean checkCallingOrSelfPermission(final String permission) { + return mService.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED; } private boolean hasTetherChangePermission(final String callerPkg, final String callingAttributionTag, final boolean onlyAllowPrivileged) { + if (onlyAllowPrivileged && !hasNetworkStackPermission()) return false; + if (hasTetherPrivilegedPermission()) return true; - if (onlyAllowPrivileged || mTethering.isTetherProvisioningRequired()) return false; + if (mTethering.isTetherProvisioningRequired()) return false; int uid = Binder.getCallingUid(); diff --git a/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java index 2fb7e607d0af..74df11370e50 100644 --- a/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java +++ b/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java @@ -339,7 +339,7 @@ public class EthernetTetheringTest { private MyTetheringEventCallback enableEthernetTethering(String iface) throws Exception { return enableEthernetTethering(iface, new TetheringRequest.Builder(TETHERING_ETHERNET) - .setExemptFromEntitlementCheck(true).build()); + .setShouldShowEntitlementUi(false).build()); } private int getMTU(TestNetworkInterface iface) throws SocketException { @@ -510,7 +510,7 @@ public class EthernetTetheringTest { LinkAddress clientAddr = client == null ? null : new LinkAddress(client); return new TetheringRequest.Builder(TETHERING_ETHERNET) .setStaticIpv4Addresses(localAddr, clientAddr) - .setExemptFromEntitlementCheck(true).build(); + .setShouldShowEntitlementUi(false).build(); } private void assertInvalidStaticIpv4Request(String iface, String local, String client) diff --git a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java index 0cda29a32f59..433aacfaff44 100644 --- a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java +++ b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java @@ -87,6 +87,7 @@ import android.text.TextUtils; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.networkstack.tethering.BpfCoordinator; import com.android.networkstack.tethering.PrivateAddressCoordinator; import org.junit.Before; @@ -126,6 +127,7 @@ public class IpServerTest { private final IpPrefix mBluetoothPrefix = new IpPrefix("192.168.44.0/24"); @Mock private INetd mNetd; + @Mock private BpfCoordinator mBpfCoordinator; @Mock private IpServer.Callback mCallback; @Mock private SharedLog mSharedLog; @Mock private IDhcpServer mDhcpServer; @@ -179,7 +181,7 @@ public class IpServerTest { neighborCaptor.capture()); mIpServer = new IpServer( - IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd, + IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd, mBpfCoordinator, mCallback, usingLegacyDhcp, usingBpfOffload, mAddressCoordinator, mDependencies); mIpServer.start(); mNeighborEventConsumer = neighborCaptor.getValue(); @@ -222,8 +224,8 @@ public class IpServerTest { when(mDependencies.getIpNeighborMonitor(any(), any(), any())) .thenReturn(mIpNeighborMonitor); mIpServer = new IpServer(IFACE_NAME, mLooper.getLooper(), TETHERING_BLUETOOTH, mSharedLog, - mNetd, mCallback, false /* usingLegacyDhcp */, DEFAULT_USING_BPF_OFFLOAD, - mAddressCoordinator, mDependencies); + mNetd, mBpfCoordinator, mCallback, false /* usingLegacyDhcp */, + DEFAULT_USING_BPF_OFFLOAD, mAddressCoordinator, mDependencies); mIpServer.start(); mLooper.dispatchAll(); verify(mCallback).updateInterfaceState( diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java new file mode 100644 index 000000000000..b029b43d194f --- /dev/null +++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2020 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.networkstack.tethering; + +import static android.net.NetworkStats.DEFAULT_NETWORK_NO; +import static android.net.NetworkStats.METERED_NO; +import static android.net.NetworkStats.ROAMING_NO; +import static android.net.NetworkStats.SET_DEFAULT; +import static android.net.NetworkStats.TAG_NONE; +import static android.net.NetworkStats.UID_ALL; +import static android.net.NetworkStats.UID_TETHERING; + +import static com.android.networkstack.tethering.BpfCoordinator + .DEFAULT_PERFORM_POLL_INTERVAL_MS; +import static com.android.networkstack.tethering.BpfCoordinator.StatsType; +import static com.android.networkstack.tethering.BpfCoordinator.StatsType.STATS_PER_IFACE; +import static com.android.networkstack.tethering.BpfCoordinator.StatsType.STATS_PER_UID; + +import static junit.framework.Assert.assertNotNull; + +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.annotation.NonNull; +import android.app.usage.NetworkStatsManager; +import android.net.INetd; +import android.net.NetworkStats; +import android.net.TetherStatsParcel; +import android.net.util.SharedLog; +import android.os.Handler; +import android.os.test.TestLooper; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.testutils.TestableNetworkStatsProviderCbBinder; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class BpfCoordinatorTest { + @Mock private NetworkStatsManager mStatsManager; + @Mock private INetd mNetd; + // Late init since methods must be called by the thread that created this object. + private TestableNetworkStatsProviderCbBinder mTetherStatsProviderCb; + private BpfCoordinator.BpfTetherStatsProvider mTetherStatsProvider; + private final ArgumentCaptor<ArrayList> mStringArrayCaptor = + ArgumentCaptor.forClass(ArrayList.class); + private final TestLooper mTestLooper = new TestLooper(); + private BpfCoordinator.Dependencies mDeps = + new BpfCoordinator.Dependencies() { + @Override + int getPerformPollInterval() { + return DEFAULT_PERFORM_POLL_INTERVAL_MS; + } + }; + + @Before public void setUp() { + MockitoAnnotations.initMocks(this); + } + + private void waitForIdle() { + mTestLooper.dispatchAll(); + } + + private void setupFunctioningNetdInterface() throws Exception { + when(mNetd.tetherOffloadGetStats()).thenReturn(new TetherStatsParcel[0]); + } + + @NonNull + private BpfCoordinator makeBpfCoordinator() throws Exception { + BpfCoordinator coordinator = new BpfCoordinator( + new Handler(mTestLooper.getLooper()), mNetd, mStatsManager, new SharedLog("test"), + mDeps); + final ArgumentCaptor<BpfCoordinator.BpfTetherStatsProvider> + tetherStatsProviderCaptor = + ArgumentCaptor.forClass(BpfCoordinator.BpfTetherStatsProvider.class); + verify(mStatsManager).registerNetworkStatsProvider(anyString(), + tetherStatsProviderCaptor.capture()); + mTetherStatsProvider = tetherStatsProviderCaptor.getValue(); + assertNotNull(mTetherStatsProvider); + mTetherStatsProviderCb = new TestableNetworkStatsProviderCbBinder(); + mTetherStatsProvider.setProviderCallbackBinder(mTetherStatsProviderCb); + return coordinator; + } + + @NonNull + private static NetworkStats.Entry buildTestEntry(@NonNull StatsType how, + @NonNull String iface, long rxBytes, long rxPackets, long txBytes, long txPackets) { + return new NetworkStats.Entry(iface, how == STATS_PER_IFACE ? UID_ALL : UID_TETHERING, + SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, rxBytes, + rxPackets, txBytes, txPackets, 0L); + } + + @NonNull + private static TetherStatsParcel buildTestTetherStatsParcel(@NonNull Integer ifIndex, + long rxBytes, long rxPackets, long txBytes, long txPackets) { + final TetherStatsParcel parcel = new TetherStatsParcel(); + parcel.ifIndex = ifIndex; + parcel.rxBytes = rxBytes; + parcel.rxPackets = rxPackets; + parcel.txBytes = txBytes; + parcel.txPackets = txPackets; + return parcel; + } + + private void setTetherOffloadStatsList(TetherStatsParcel[] tetherStatsList) throws Exception { + when(mNetd.tetherOffloadGetStats()).thenReturn(tetherStatsList); + mTestLooper.moveTimeForward(DEFAULT_PERFORM_POLL_INTERVAL_MS); + waitForIdle(); + } + + @Test + public void testGetForwardedStats() throws Exception { + setupFunctioningNetdInterface(); + + final BpfCoordinator coordinator = makeBpfCoordinator(); + coordinator.start(); + + final String wlanIface = "wlan0"; + final Integer wlanIfIndex = 100; + final String mobileIface = "rmnet_data0"; + final Integer mobileIfIndex = 101; + + // Add interface name to lookup table. In realistic case, the upstream interface name will + // be added by IpServer when IpServer has received with a new IPv6 upstream update event. + coordinator.addUpstreamNameToLookupTable(wlanIfIndex, wlanIface); + coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface); + + // [1] Both interface stats are changed. + // Setup the tether stats of wlan and mobile interface. Note that move forward the time of + // the looper to make sure the new tether stats has been updated by polling update thread. + setTetherOffloadStatsList(new TetherStatsParcel[] { + buildTestTetherStatsParcel(wlanIfIndex, 1000, 100, 2000, 200), + buildTestTetherStatsParcel(mobileIfIndex, 3000, 300, 4000, 400)}); + + final NetworkStats expectedIfaceStats = new NetworkStats(0L, 2) + .addEntry(buildTestEntry(STATS_PER_IFACE, wlanIface, 1000, 100, 2000, 200)) + .addEntry(buildTestEntry(STATS_PER_IFACE, mobileIface, 3000, 300, 4000, 400)); + + final NetworkStats expectedUidStats = new NetworkStats(0L, 2) + .addEntry(buildTestEntry(STATS_PER_UID, wlanIface, 1000, 100, 2000, 200)) + .addEntry(buildTestEntry(STATS_PER_UID, mobileIface, 3000, 300, 4000, 400)); + + // Force pushing stats update to verify the stats reported. + // TODO: Perhaps make #expectNotifyStatsUpdated to use test TetherStatsParcel object for + // verifying the notification. + mTetherStatsProvider.pushTetherStats(); + mTetherStatsProviderCb.expectNotifyStatsUpdated(expectedIfaceStats, expectedUidStats); + + // [2] Only one interface stats is changed. + // The tether stats of mobile interface is accumulated and The tether stats of wlan + // interface is the same. + setTetherOffloadStatsList(new TetherStatsParcel[] { + buildTestTetherStatsParcel(wlanIfIndex, 1000, 100, 2000, 200), + buildTestTetherStatsParcel(mobileIfIndex, 3010, 320, 4030, 440)}); + + final NetworkStats expectedIfaceStatsDiff = new NetworkStats(0L, 2) + .addEntry(buildTestEntry(STATS_PER_IFACE, wlanIface, 0, 0, 0, 0)) + .addEntry(buildTestEntry(STATS_PER_IFACE, mobileIface, 10, 20, 30, 40)); + + final NetworkStats expectedUidStatsDiff = new NetworkStats(0L, 2) + .addEntry(buildTestEntry(STATS_PER_UID, wlanIface, 0, 0, 0, 0)) + .addEntry(buildTestEntry(STATS_PER_UID, mobileIface, 10, 20, 30, 40)); + + // Force pushing stats update to verify that only diff of stats is reported. + mTetherStatsProvider.pushTetherStats(); + mTetherStatsProviderCb.expectNotifyStatsUpdated(expectedIfaceStatsDiff, + expectedUidStatsDiff); + + // [3] Stop coordinator. + // Shutdown the coordinator and clear the invocation history, especially the + // tetherOffloadGetStats() calls. + coordinator.stop(); + clearInvocations(mNetd); + + // Verify the polling update thread stopped. + mTestLooper.moveTimeForward(DEFAULT_PERFORM_POLL_INTERVAL_MS); + waitForIdle(); + verify(mNetd, never()).tetherOffloadGetStats(); + } +} diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt index 745468fdf378..7d5471f7703d 100644 --- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt +++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt @@ -130,7 +130,7 @@ class TetheringNotificationUpdaterTest { context = TestContext(InstrumentationRegistry.getInstrumentation().context) doReturn(notificationManager).`when`(mockContext) .getSystemService(Context.NOTIFICATION_SERVICE) - fakeTetheringThread = HandlerThread(this::class.simpleName) + fakeTetheringThread = HandlerThread(this::class.java.simpleName) fakeTetheringThread.start() notificationUpdater = WrappedNotificationUpdater(context, fakeTetheringThread.looper) setupResources() diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java index cf060ba95d25..7bba67b05f88 100644 --- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java +++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java @@ -281,22 +281,33 @@ public final class TetheringServiceTest { }); } + private void runStartTetheringAndVerifyNoPermission(final TestTetheringResult result) + throws Exception { + final TetheringRequestParcel request = new TetheringRequestParcel(); + request.tetheringType = TETHERING_WIFI; + request.exemptFromEntitlementCheck = true; + mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, + result); + result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); + verifyNoMoreInteractionsForTethering(); + } + @Test - public void testStartTetheringWithExemptFromEntitlementCheck() throws Exception { + public void testFailToBypassEntitlementWithoutNeworkStackPermission() throws Exception { final TetheringRequestParcel request = new TetheringRequestParcel(); request.tetheringType = TETHERING_WIFI; request.exemptFromEntitlementCheck = true; + runAsNoPermission((result) -> { + runStartTetheringAndVerifyNoPermission(result); + }); + runAsTetherPrivileged((result) -> { - runStartTethering(result, request); - verifyNoMoreInteractionsForTethering(); + runStartTetheringAndVerifyNoPermission(result); }); runAsWriteSettings((result) -> { - mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, - result); - result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); - verifyNoMoreInteractionsForTethering(); + runStartTetheringAndVerifyNoPermission(result); }); } diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java index 5fffaaedd8de..329d8a515c70 100644 --- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java +++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java @@ -203,6 +203,7 @@ public class TetheringTest { @Mock private ConnectivityManager mCm; @Mock private EthernetManager mEm; @Mock private TetheringNotificationUpdater mNotificationUpdater; + @Mock private BpfCoordinator mBpfCoordinator; private final MockIpServerDependencies mIpServerDependencies = spy(new MockIpServerDependencies()); @@ -337,6 +338,12 @@ public class TetheringTest { } @Override + public BpfCoordinator getBpfCoordinator(Handler handler, INetd netd, + SharedLog log, BpfCoordinator.Dependencies deps) { + return mBpfCoordinator; + } + + @Override public OffloadHardwareInterface getOffloadHardwareInterface(Handler h, SharedLog log) { return mOffloadHardwareInterface; } diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index d331d1f58b2d..89bcb7fc667a 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -166,6 +166,7 @@ static void loadSystemIconAsSpriteWithPointerIcon(JNIEnv* env, jobject contextOb outPointerIcon->bitmap.readPixels(bitmapCopy->info(), bitmapCopy->getPixels(), bitmapCopy->rowBytes(), 0, 0); } + outSpriteIcon->style = outPointerIcon->style; outSpriteIcon->hotSpotX = outPointerIcon->hotSpotX; outSpriteIcon->hotSpotY = outPointerIcon->hotSpotY; } @@ -318,7 +319,6 @@ private: void updateInactivityTimeoutLocked(); void handleInterceptActions(jint wmActions, nsecs_t when, uint32_t& policyFlags); void ensureSpriteControllerLocked(); - const DisplayViewport* findDisplayViewportLocked(int32_t displayId); int32_t getPointerDisplayId(); void updatePointerDisplayLocked(); static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName); @@ -396,16 +396,6 @@ bool NativeInputManager::checkAndClearExceptionFromCallback(JNIEnv* env, const c return false; } -const DisplayViewport* NativeInputManager::findDisplayViewportLocked(int32_t displayId) - REQUIRES(mLock) { - for (const DisplayViewport& v : mLocked.viewports) { - if (v.displayId == displayId) { - return &v; - } - } - return nullptr; -} - void NativeInputManager::setDisplayViewports(JNIEnv* env, jobjectArray viewportObjArray) { std::vector<DisplayViewport> viewports; @@ -554,6 +544,8 @@ void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outCon outConfig->setDisplayViewports(mLocked.viewports); + outConfig->defaultPointerDisplayId = mLocked.pointerDisplayId; + outConfig->disabledDevices = mLocked.disabledInputDevices; } // release lock } @@ -571,8 +563,6 @@ sp<PointerControllerInterface> NativeInputManager::obtainPointerController(int32 updateInactivityTimeoutLocked(); } - updatePointerDisplayLocked(); - return controller; } @@ -587,23 +577,6 @@ int32_t NativeInputManager::getPointerDisplayId() { return pointerDisplayId; } -void NativeInputManager::updatePointerDisplayLocked() REQUIRES(mLock) { - ATRACE_CALL(); - - sp<PointerController> controller = mLocked.pointerController.promote(); - if (controller != nullptr) { - const DisplayViewport* viewport = findDisplayViewportLocked(mLocked.pointerDisplayId); - if (viewport == nullptr) { - ALOGW("Can't find pointer display viewport, fallback to default display."); - viewport = findDisplayViewportLocked(ADISPLAY_ID_DEFAULT); - } - - if (viewport != nullptr) { - controller->setDisplayViewport(*viewport); - } - } -} - void NativeInputManager::ensureSpriteControllerLocked() REQUIRES(mLock) { if (mLocked.spriteController == nullptr) { JNIEnv* env = jniEnv(); @@ -1252,7 +1225,8 @@ void NativeInputManager::loadPointerIcon(SpriteIcon* icon, int32_t displayId) { status_t status = android_view_PointerIcon_load(env, pointerIconObj.get(), displayContext.get(), &pointerIcon); if (!status && !pointerIcon.isNullIcon()) { - *icon = SpriteIcon(pointerIcon.bitmap, pointerIcon.hotSpotX, pointerIcon.hotSpotY); + *icon = SpriteIcon( + pointerIcon.bitmap, pointerIcon.style, pointerIcon.hotSpotX, pointerIcon.hotSpotY); } else { *icon = SpriteIcon(); } @@ -1293,10 +1267,12 @@ void NativeInputManager::loadAdditionalMouseResources(std::map<int32_t, SpriteIc milliseconds_to_nanoseconds(pointerIcon.durationPerFrame); animationData.animationFrames.reserve(numFrames); animationData.animationFrames.push_back(SpriteIcon( - pointerIcon.bitmap, pointerIcon.hotSpotX, pointerIcon.hotSpotY)); + pointerIcon.bitmap, pointerIcon.style, + pointerIcon.hotSpotX, pointerIcon.hotSpotY)); for (size_t i = 0; i < numFrames - 1; ++i) { animationData.animationFrames.push_back(SpriteIcon( - pointerIcon.bitmapFrames[i], pointerIcon.hotSpotX, pointerIcon.hotSpotY)); + pointerIcon.bitmapFrames[i], pointerIcon.style, + pointerIcon.hotSpotX, pointerIcon.hotSpotY)); } } } @@ -1732,6 +1708,7 @@ static void nativeSetCustomPointerIcon(JNIEnv* env, jclass /* clazz */, pointerIcon.bitmap.readPixels(spriteInfo, spriteIcon.bitmap.getPixels(), spriteIcon.bitmap.rowBytes(), 0, 0); } + spriteIcon.style = pointerIcon.style; spriteIcon.hotSpotX = pointerIcon.hotSpotX; spriteIcon.hotSpotY = pointerIcon.hotSpotY; im->setCustomPointerIcon(spriteIcon); diff --git a/services/core/jni/com_android_server_tv_TvInputHal.cpp b/services/core/jni/com_android_server_tv_TvInputHal.cpp index 098b2ef6439d..4e1a23416330 100644 --- a/services/core/jni/com_android_server_tv_TvInputHal.cpp +++ b/services/core/jni/com_android_server_tv_TvInputHal.cpp @@ -301,6 +301,7 @@ private: JTvInputHal(JNIEnv* env, jobject thiz, sp<ITvInput> tvInput, const sp<Looper>& looper); Mutex mLock; + Mutex mStreamLock; jweak mThiz; sp<Looper> mLooper; @@ -338,6 +339,7 @@ JTvInputHal* JTvInputHal::createInstance(JNIEnv* env, jobject thiz, const sp<Loo } int JTvInputHal::addOrUpdateStream(int deviceId, int streamId, const sp<Surface>& surface) { + Mutex::Autolock autoLock(&mStreamLock); KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId); if (connections.indexOfKey(streamId) < 0) { connections.add(streamId, Connection()); @@ -412,6 +414,7 @@ int JTvInputHal::addOrUpdateStream(int deviceId, int streamId, const sp<Surface> } int JTvInputHal::removeStream(int deviceId, int streamId) { + Mutex::Autolock autoLock(&mStreamLock); KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId); if (connections.indexOfKey(streamId) < 0) { return BAD_VALUE; diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/Android.bp b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/Android.bp index a23ac38785f6..125deb521ddc 100644 --- a/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/Android.bp +++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/Android.bp @@ -33,5 +33,6 @@ cc_test_library { "-Werror", "-Wno-unused-parameter", ], + header_libs: ["jni_headers"], stl: "c++_static", } |