Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ group("unittests") {
public_deps = []
if (is_android) {
public_deps += [
"//flutter/impeller/toolkit/android:apk_unittests",
"//flutter/impeller/toolkit/android:unittests",
"//flutter/shell/platform/android:flutter_shell_native_unittests",
]
Expand Down
28 changes: 24 additions & 4 deletions impeller/toolkit/android/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import("//flutter/testing/android/native_activity/native_activity.gni")
import("../../tools/impeller.gni")

config("public_android_config") {
Expand Down Expand Up @@ -37,13 +38,11 @@ test_fixtures("unittests_fixtures") {
fixtures = []
}

executable("unittests") {
assert(is_android)
source_set("unittests_lib") {
visibility = [ ":*" ]

testonly = true

output_name = "impeller_toolkit_android_unittests"

sources = [ "toolkit_android_unittests.cc" ]

deps = [
Expand All @@ -52,3 +51,24 @@ executable("unittests") {
"//flutter/testing",
]
}

executable("unittests") {
assert(is_android)

testonly = true

output_name = "impeller_toolkit_android_unittests"

deps = [ ":unittests_lib" ]
}

native_activity_apk("apk_unittests") {
apk_name = "impeller_toolkit_android_unittests"

testonly = true

deps = [
":unittests_lib",
"//flutter/testing/android/native_activity:gtest_activity",
]
}
2 changes: 2 additions & 0 deletions testing/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ source_set("testing_lib") {
"debugger_detection.h",
"display_list_testing.cc",
"display_list_testing.h",
"logger_listener.cc",
"logger_listener.h",
"mock_canvas.cc",
"mock_canvas.h",
"post_task_sync.cc",
Expand Down
20 changes: 20 additions & 0 deletions testing/android/native_activity/AndroidManifest.xml.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="dev.flutter.testing.{{apk-library-name}}"
android:versionCode="1"
android:versionName="1.0"
>
<uses-sdk android:minSdkVersion="23"/>
<application android:hasCode="false">
<activity android:name="android.app.NativeActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="true">
<meta-data android:name="android.app.lib_name"
android:value="{{apk-library-name}}" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
41 changes: 41 additions & 0 deletions testing/android/native_activity/BUILD.gn
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Copyright 2013 The Flutter Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

# To create an native activity, deps in this source set in a
# `native_activity_apk` target and make sure to add the implementation of
# `NativeActivityMain` which returns a `flutter::NativeActivity` subclass.
source_set("native_activity") {
assert(is_android)

sources = [
"native_activity.cc",
"native_activity.h",
]

public_deps = [
"//flutter/fml",
"//flutter/impeller/toolkit/android",
]

libs = [
"android",
"log",
]
}

source_set("gtest_activity") {
assert(is_android)

testonly = true

sources = [
"gtest_activity.cc",
"gtest_activity.h",
]

public_deps = [
":native_activity",
"//flutter/testing:testing_lib",
]
}
31 changes: 31 additions & 0 deletions testing/android/native_activity/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
Native Activity
===============

Executables packaged as native activities in an Android APK. These activities
contain no Java code.

To create an APK of your existing `exectuable` target, replace `exectuable` with
`native_activity_apk` from the `native_activity.gni` template and give it an
`apk_name`.

## Example

```
native_activity_apk("apk_unittests") {
apk_name = "toolkit_unittests"

testonly = true

sources = [ "toolkit_android_unittests.cc" ]

deps = [
":unittests_lib",
"//flutter/testing/android/native_activity:gtest_activity",
]
}
```

One of the translation units in must contain an implementation of
`flutter::NativeActivityMain`. The `gtest_activity` target contains an
implementation of an activity that run GoogleTests. That can be used off the
shelf.
Binary file added testing/android/native_activity/debug.keystore
Binary file not shown.
49 changes: 49 additions & 0 deletions testing/android/native_activity/gtest_activity.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "flutter/testing/android/native_activity/gtest_activity.h"

#include "flutter/impeller/toolkit/android/native_window.h"
#include "flutter/testing/logger_listener.h"
#include "flutter/testing/test_timeout_listener.h"

namespace flutter {

GTestActivity::GTestActivity(ANativeActivity* activity)
: NativeActivity(activity) {}

GTestActivity::~GTestActivity() = default;

static void StartTestSuite(const impeller::android::NativeWindow& window) {
auto timeout_listener = new flutter::testing::TestTimeoutListener(
fml::TimeDelta::FromSeconds(120u));
auto logger_listener = new flutter::testing::LoggerListener();

auto& listeners = ::testing::UnitTest::GetInstance()->listeners();

listeners.Append(timeout_listener);
listeners.Append(logger_listener);

int result = RUN_ALL_TESTS();

delete listeners.Release(timeout_listener);
delete listeners.Release(logger_listener);

FML_CHECK(result == 0);
}

// |NativeActivity|
void GTestActivity::OnNativeWindowCreated(ANativeWindow* window) {
auto handle = std::make_shared<impeller::android::NativeWindow>(window);
background_thread_.GetTaskRunner()->PostTask(
[handle]() { StartTestSuite(*handle); });
}

std::unique_ptr<NativeActivity> NativeActivityMain(
ANativeActivity* activity,
std::unique_ptr<fml::Mapping> saved_state) {
return std::make_unique<GTestActivity>(activity);
}

} // namespace flutter
40 changes: 40 additions & 0 deletions testing/android/native_activity/gtest_activity.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef FLUTTER_TESTING_ANDROID_NATIVE_ACTIVITY_GTEST_ACTIVITY_H_
#define FLUTTER_TESTING_ANDROID_NATIVE_ACTIVITY_GTEST_ACTIVITY_H_

#include "flutter/fml/macros.h"
#include "flutter/fml/thread.h"
#include "flutter/testing/android/native_activity/native_activity.h"

namespace flutter {

//------------------------------------------------------------------------------
/// @brief A native activity subclass an in implementation of
/// `flutter::NativeActivityMain` that return it.
///
/// This class runs a Google Test harness on a background thread and
/// redirects progress updates to `logcat` instead of STDOUT.
///
class GTestActivity final : public NativeActivity {
public:
explicit GTestActivity(ANativeActivity* activity);

~GTestActivity() override;

GTestActivity(const GTestActivity&) = delete;

GTestActivity& operator=(const GTestActivity&) = delete;

// |NativeActivity|
void OnNativeWindowCreated(ANativeWindow* window) override;

private:
fml::Thread background_thread_;
};

} // namespace flutter

#endif // FLUTTER_TESTING_ANDROID_NATIVE_ACTIVITY_GTEST_ACTIVITY_H_
142 changes: 142 additions & 0 deletions testing/android/native_activity/native_activity.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "flutter/testing/android/native_activity/native_activity.h"

#include "flutter/fml/message_loop.h"

namespace flutter {

NativeActivity::NativeActivity(ANativeActivity* activity)
: activity_(activity) {
fml::MessageLoop::EnsureInitializedForCurrentThread();

activity->instance = this;

activity->callbacks->onStart = [](ANativeActivity* activity) {
reinterpret_cast<NativeActivity*>(activity->instance)->OnStart();
};
activity->callbacks->onStop = [](ANativeActivity* activity) {
reinterpret_cast<NativeActivity*>(activity->instance)->OnStop();
};
activity->callbacks->onPause = [](ANativeActivity* activity) {
reinterpret_cast<NativeActivity*>(activity->instance)->OnPause();
};
activity->callbacks->onResume = [](ANativeActivity* activity) {
reinterpret_cast<NativeActivity*>(activity->instance)->OnResume();
};
activity->callbacks->onDestroy = [](ANativeActivity* activity) {
delete reinterpret_cast<NativeActivity*>(activity->instance);
};
activity->callbacks->onSaveInstanceState = [](ANativeActivity* activity,
size_t* out_size) -> void* {
auto mapping = reinterpret_cast<NativeActivity*>(activity->instance)
->OnSaveInstanceState();
if (mapping == nullptr || mapping->GetMapping() == nullptr) {
*out_size = 0;
return nullptr;
}

// This will be `free`d by the framework.
auto copied = malloc(mapping->GetSize());
FML_CHECK(copied != nullptr)
<< "Allocation failure while saving instance state.";
memcpy(copied, mapping->GetMapping(), mapping->GetSize());
return copied;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also set *out_size to mapping->GetSize()

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

};
activity->callbacks->onWindowFocusChanged = [](ANativeActivity* activity,
int has_focus) {
reinterpret_cast<NativeActivity*>(activity->instance)
->OnWindowFocusChanged(has_focus);
};
activity->callbacks->onNativeWindowCreated = [](ANativeActivity* activity,
ANativeWindow* window) {
reinterpret_cast<NativeActivity*>(activity->instance)
->OnNativeWindowCreated(window);
};
activity->callbacks->onNativeWindowResized = [](ANativeActivity* activity,
ANativeWindow* window) {
reinterpret_cast<NativeActivity*>(activity->instance)
->OnNativeWindowResized(window);
};
activity->callbacks->onNativeWindowRedrawNeeded =
[](ANativeActivity* activity, ANativeWindow* window) {
reinterpret_cast<NativeActivity*>(activity->instance)
->OnNativeWindowRedrawNeeded(window);
};
activity->callbacks->onNativeWindowDestroyed = [](ANativeActivity* activity,
ANativeWindow* window) {
reinterpret_cast<NativeActivity*>(activity->instance)
->OnNativeWindowDestroyed(window);
};
activity->callbacks->onInputQueueCreated = [](ANativeActivity* activity,
AInputQueue* queue) {
reinterpret_cast<NativeActivity*>(activity->instance)
->OnInputQueueCreated(queue);
};
activity->callbacks->onInputQueueDestroyed = [](ANativeActivity* activity,
AInputQueue* queue) {
reinterpret_cast<NativeActivity*>(activity->instance)
->OnInputQueueDestroyed(queue);
};
activity->callbacks->onConfigurationChanged = [](ANativeActivity* activity) {
reinterpret_cast<NativeActivity*>(activity->instance)
->OnConfigurationChanged();
};
activity->callbacks->onLowMemory = [](ANativeActivity* activity) {
reinterpret_cast<NativeActivity*>(activity->instance)->OnLowMemory();
};
}

NativeActivity::~NativeActivity() = default;

void NativeActivity::OnStart() {}

void NativeActivity::OnStop() {}

void NativeActivity::OnPause() {}

void NativeActivity::OnResume() {}

std::shared_ptr<fml::Mapping> NativeActivity::OnSaveInstanceState() {
return nullptr;
}

void NativeActivity::OnWindowFocusChanged(bool has_focus) {}

void NativeActivity::OnNativeWindowCreated(ANativeWindow* window) {}

void NativeActivity::OnNativeWindowResized(ANativeWindow* window) {}

void NativeActivity::OnNativeWindowRedrawNeeded(ANativeWindow* window) {}

void NativeActivity::OnNativeWindowDestroyed(ANativeWindow* window) {}

void NativeActivity::OnInputQueueCreated(AInputQueue* queue) {}

void NativeActivity::OnInputQueueDestroyed(AInputQueue* queue) {}

void NativeActivity::OnConfigurationChanged() {}

void NativeActivity::OnLowMemory() {}

void NativeActivity::Terminate() {
ANativeActivity_finish(activity_);
}

} // namespace flutter

extern "C" __attribute__((visibility("default"))) void ANativeActivity_onCreate(
ANativeActivity* activity,
void* saved_state,
size_t saved_state_size) {
std::unique_ptr<fml::Mapping> saved_state_mapping;
if (saved_state_size > 0u) {
saved_state_mapping = std::make_unique<fml::MallocMapping>(
fml::MallocMapping::Copy(saved_state, saved_state_size));
}
flutter::NativeActivityMain(activity, std::move(saved_state_mapping))
.release(); // Will be freed when the frame calls the onDestroy. See the
// delete in that callback.
}
Loading