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 all 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_
143 changes: 143 additions & 0 deletions testing/android/native_activity/native_activity.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// 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());
*out_size = 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