Move dead submodules in-tree

Signed-off-by: swurl <swurl@swurl.xyz>
This commit is contained in:
swurl 2025-05-31 02:33:02 -04:00
parent c0cceff365
commit 6c655321e6
No known key found for this signature in database
GPG key ID: A5A7629F109C8FD1
4081 changed files with 1185566 additions and 45 deletions

View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,45 @@
Mega Drone
==========
Ever wondered what 100 square waves sound like when played together? Well now you can find out!
Mega Drone is an app which creates 100 oscillators, combines their output in a mixer and plays the resulting sound.
This sample demonstrates how to obtain the lowest latency and optimal computational throughput by:
1) Leaving Oboe to choose the best default stream properties for the current device
2) Setting performance mode to LowLatency
3) Setting sharing mode to Exclusive
4) Setting the buffer size to 2 bursts
5) Using the `-Ofast` compiler optimization flag, even when building the `Debug` variant
6) Using [`getExclusiveCores`](https://developer.android.com/reference/android/os/Process#getExclusiveCores()) (API 24+) and thread affinity to bind the audio thread to the best available CPU core(s)
This code was presented at [AES Milan](http://www.aes.org/events/144/) and [Droidcon Berlin](https://www.de.droidcon.com/) as part of a talk on Oboe.
The [following article explaining how to debug CPU performance problems](https://medium.com/@donturner/debugging-audio-glitches-on-android-ed10782f9c64) may also be useful when looking at this code.
Implementation details
---
The stream properties are left to Oboe as such the app must output audio data in a format which matches that of the stream.
Four different formats are supported:
|Channel count|Format|
|-------------|------|
|1 - Mono|16-bit int|
|2 - Stereo|16-bit int|
|1 - Mono|Float|
|2 - Stereo|Float|
The signal chain for mono streams is:
Oscillators->Mixer
For stereo chains a mono to stereo converter is added to the end of the chain:
Oscillators->Mixer->MonoToStereo
The compiler optimization flag `-Ofast` can be found in [CMakeLists.txt](CMakeLists.txt).
Screenshots
-----------
![megadrone-screenshot](megadrone-screenshot.png)

View file

@ -0,0 +1,53 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
android {
defaultConfig {
applicationId "com.google.oboe.samples.megadrone"
minSdkVersion 21
targetSdkVersion 35
compileSdkVersion 35
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags "-std=c++17"
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}
}
}
signingConfigs {
release {
storeFile new File("${System.properties['user.home']}/.android/debug.keystore")
storePassword 'android'
storeType "jks"
keyAlias 'androiddebugkey'
keyPassword 'android'
}
}
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
debuggable false
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_18
targetCompatibility JavaVersion.VERSION_18
}
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
}
}
namespace 'com.google.oboe.samples.megadrone'
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View file

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name="com.google.oboe.samples.megadrone.MainActivity"
android:screenOrientation="portrait"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View file

@ -0,0 +1,29 @@
cmake_minimum_required(VERSION 3.4.1)
### INCLUDE OBOE LIBRARY ###
# Set the path to the Oboe library directory
set (OBOE_DIR ../../../../..)
# Add the Oboe library as a subproject. Since Oboe is an out-of-tree source library we must also
# specify a binary directory
add_subdirectory(${OBOE_DIR} ./oboe-bin)
# Include the Oboe headers
include_directories(${OBOE_DIR}/include ${OBOE_DIR}/samples/shared ${OBOE_DIR}/samples/debug-utils)
### END OBOE INCLUDE SECTION ###
add_library( megadrone SHARED
native-lib.cpp
MegaDroneEngine.cpp
)
target_link_libraries(megadrone log oboe )
target_link_options(megadrone PRIVATE "-Wl,-z,max-page-size=16384")
# Enable optimization flags: if having problems with source level debugging,
# disable -Ofast ( and debug ), re-enable it after done debugging.
target_compile_options(megadrone PRIVATE -Wall -Werror -Ofast)

View file

@ -0,0 +1,126 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <memory>
#include "MegaDroneEngine.h"
/**
* Main audio engine for the MegaDrone sample. It is responsible for:
*
* - Creating the callback object which will be supplied when constructing the audio stream
* - Setting the CPU core IDs to which the callback thread should bind to
* - Creating the playback stream, including setting the callback object
* - Creating `Synth` which will render the audio inside the callback
* - Starting the playback stream
* - Restarting the playback stream when `restart()` is called by the callback object
*
* @param cpuIds
*/
MegaDroneEngine::MegaDroneEngine(std::vector<int> cpuIds) {
createCallback(cpuIds);
}
MegaDroneEngine::~MegaDroneEngine() {
if (mStream) {
LOGE("MegaDroneEngine destructor was called without calling stop()."
"Please call stop() to ensure stream resources are not leaked.");
stop();
}
}
void MegaDroneEngine::tap(bool isDown) {
mAudioSource->tap(isDown);
}
void MegaDroneEngine::restart() {
stop();
start();
}
// Create the playback stream
oboe::Result MegaDroneEngine::createPlaybackStream() {
oboe::AudioStreamBuilder builder;
return builder.setSharingMode(oboe::SharingMode::Exclusive)
->setPerformanceMode(oboe::PerformanceMode::LowLatency)
->setFormat(oboe::AudioFormat::Float)
->setDataCallback(mDataCallback)
->setErrorCallback(mErrorCallback)
->openStream(mStream);
}
// Create the callback and set its thread affinity to the supplied CPU core IDs
void MegaDroneEngine::createCallback(std::vector<int> cpuIds){
mDataCallback = std::make_shared<DefaultDataCallback>();
// Create the error callback, we supply ourselves as the parent so that we can restart the stream
// when it's disconnected
mErrorCallback = std::make_shared<DefaultErrorCallback>(*this);
// Bind the audio callback to specific CPU cores as this can help avoid underruns caused by
// core migrations
mDataCallback->setCpuIds(cpuIds);
mDataCallback->setThreadAffinityEnabled(true);
}
bool MegaDroneEngine::start() {
// It is possible for a stream's device to become disconnected during stream open or between
// stream open and stream start.
// If the stream fails to start, close the old stream and try again.
bool didStart = false;
int tryCount = 0;
do {
if (tryCount > 0) {
usleep(20 * 1000); // Sleep between tries to give the system time to settle.
}
didStart = attemptStart();
} while (!didStart && tryCount++ < 3);
if (!didStart) {
LOGE("Failed at starting the stream");
}
return didStart;
}
bool MegaDroneEngine::attemptStart() {
auto result = createPlaybackStream();
if (result == Result::OK) {
// Create our synthesizer audio source using the properties of the stream
mAudioSource = std::make_shared<Synth>(mStream->getSampleRate(), mStream->getChannelCount());
mDataCallback->reset();
mDataCallback->setSource(std::dynamic_pointer_cast<IRenderableAudio>(mAudioSource));
result = mStream->start();
if (result == Result::OK) {
return true;
} else {
LOGW("Failed attempt at starting the playback stream. Error: %s", convertToText(result));
return false;
}
} else {
LOGW("Failed attempt at creating the playback stream. Error: %s", convertToText(result));
return false;
}
}
bool MegaDroneEngine::stop() {
if(mStream && mStream->getState() != oboe::StreamState::Closed) {
mStream->stop();
mStream->close();
}
mStream.reset();
return true;
}

View file

@ -0,0 +1,59 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef MEGADRONE_ENGINE_H
#define MEGADRONE_ENGINE_H
#include <oboe/Oboe.h>
#include <vector>
#include "Synth.h"
#include <DefaultDataCallback.h>
#include <TappableAudioSource.h>
#include <IRestartable.h>
#include <DefaultErrorCallback.h>
using namespace oboe;
class MegaDroneEngine : public IRestartable {
public:
MegaDroneEngine(std::vector<int> cpuIds);
virtual ~MegaDroneEngine();
void tap(bool isDown);
// from IRestartable
virtual void restart() override;
bool start();
bool stop();
private:
std::shared_ptr<AudioStream> mStream;
std::shared_ptr<TappableAudioSource> mAudioSource;
std::shared_ptr<DefaultDataCallback> mDataCallback;
std::shared_ptr<DefaultErrorCallback> mErrorCallback;
bool attemptStart();
oboe::Result createPlaybackStream();
void createCallback(std::vector<int> cpuIds);
};
#endif //MEGADRONE_ENGINE_H

View file

@ -0,0 +1,71 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef MEGADRONE_SYNTH_H
#define MEGADRONE_SYNTH_H
#include <array>
#include <TappableAudioSource.h>
#include <Oscillator.h>
#include <Mixer.h>
#include <MonoToStereo.h>
constexpr int kNumOscillators = 100;
constexpr float kOscBaseFrequency = 116.0;
constexpr float kOscDivisor = 33;
constexpr float kOscAmplitude = 0.009;
class Synth : public TappableAudioSource {
public:
Synth(int32_t sampleRate, int32_t channelCount) :
TappableAudioSource(sampleRate, channelCount) {
for (int i = 0; i < kNumOscillators; ++i) {
mOscs[i].setSampleRate(mSampleRate);
mOscs[i].setFrequency(kOscBaseFrequency + (static_cast<float>(i) / kOscDivisor));
mOscs[i].setAmplitude(kOscAmplitude);
mMixer.addTrack(&mOscs[i]);
}
if (mChannelCount == oboe::ChannelCount::Stereo) {
mOutputStage = &mConverter;
} else {
mOutputStage = &mMixer;
}
}
void tap(bool isOn) override {
for (auto &osc : mOscs) osc.setWaveOn(isOn);
};
// From IRenderableAudio
void renderAudio(float *audioData, int32_t numFrames) override {
mOutputStage->renderAudio(audioData, numFrames);
};
virtual ~Synth() {
}
private:
// Rendering objects
std::array<Oscillator, kNumOscillators> mOscs;
Mixer mMixer;
MonoToStereo mConverter = MonoToStereo(&mMixer);
IRenderableAudio *mOutputStage; // This will point to either the mixer or converter, so it needs to be raw
};
#endif //MEGADRONE_SYNTH_H

View file

@ -0,0 +1,96 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <jni.h>
#include <string>
#include <vector>
#include "MegaDroneEngine.h"
std::vector<int> convertJavaArrayToVector(JNIEnv *env, jintArray intArray) {
std::vector<int> v;
jsize length = env->GetArrayLength(intArray);
if (length > 0) {
jint *elements = env->GetIntArrayElements(intArray, nullptr);
v.insert(v.end(), &elements[0], &elements[length]);
// Unpin the memory for the array, or free the copy.
env->ReleaseIntArrayElements(intArray, elements, 0);
}
return v;
}
extern "C" {
/**
* Start the audio engine
*
* @param env
* @param instance
* @param jCpuIds - CPU core IDs which the audio process should affine to
* @return a pointer to the audio engine. This should be passed to other methods
*/
JNIEXPORT jlong JNICALL
Java_com_google_oboe_samples_megadrone_MainActivity_startEngine(JNIEnv *env, jobject /*unused*/,
jintArray jCpuIds) {
std::vector<int> cpuIds = convertJavaArrayToVector(env, jCpuIds);
LOGD("cpu ids size: %d", static_cast<int>(cpuIds.size()));
MegaDroneEngine *engine = new MegaDroneEngine(std::move(cpuIds));
if (!engine->start()) {
LOGE("Failed to start MegaDrone Engine");
delete engine;
engine = nullptr;
} else {
LOGD("Engine Started");
}
return reinterpret_cast<jlong>(engine);
}
JNIEXPORT void JNICALL
Java_com_google_oboe_samples_megadrone_MainActivity_stopEngine(JNIEnv *env, jobject instance,
jlong jEngineHandle) {
auto engine = reinterpret_cast<MegaDroneEngine*>(jEngineHandle);
if (engine) {
engine->stop();
delete engine;
} else {
LOGD("Engine invalid, call startEngine() to create");
}
}
JNIEXPORT void JNICALL
Java_com_google_oboe_samples_megadrone_MainActivity_tap(JNIEnv *env, jobject instance,
jlong jEngineHandle, jboolean isDown) {
auto *engine = reinterpret_cast<MegaDroneEngine*>(jEngineHandle);
if (engine) {
engine->tap(isDown);
} else {
LOGE("Engine handle is invalid, call createEngine() to create a new one");
}
}
JNIEXPORT void JNICALL
Java_com_google_oboe_samples_megadrone_MainActivity_native_1setDefaultStreamValues(JNIEnv *env,
jclass type,
jint sampleRate,
jint framesPerBurst) {
oboe::DefaultStreamValues::SampleRate = (int32_t) sampleRate;
oboe::DefaultStreamValues::FramesPerBurst = (int32_t) framesPerBurst;
}
} // extern "C"

View file

@ -0,0 +1,106 @@
package com.google.oboe.samples.megadrone;
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import androidx.appcompat.app.AppCompatActivity;
import android.content.Context;
import android.media.AudioManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
public class MainActivity extends AppCompatActivity {
private final String TAG = MainActivity.class.toString();
private static long mEngineHandle = 0;
private native long startEngine(int[] cpuIds);
private native void stopEngine(long engineHandle);
private native void tap(long engineHandle, boolean isDown);
private static native void native_setDefaultStreamValues(int sampleRate, int framesPerBurst);
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("megadrone");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setDefaultStreamValues(this);
}
@Override
protected void onResume(){
super.onResume();
mEngineHandle = startEngine(getExclusiveCores());
}
@Override
protected void onPause(){
stopEngine(mEngineHandle);
super.onPause();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN){
tap(mEngineHandle, true);
} else if (event.getAction() == MotionEvent.ACTION_UP){
tap(mEngineHandle, false);
}
return super.onTouchEvent(event);
}
// Obtain CPU cores which are reserved for the foreground app. The audio thread can be
// bound to these cores to avoids the risk of it being migrated to slower or more contended
// core(s).
private int[] getExclusiveCores(){
int[] exclusiveCores = {};
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
Log.w(TAG, "getExclusiveCores() not supported. Only available on API " +
Build.VERSION_CODES.N + "+");
} else {
try {
exclusiveCores = android.os.Process.getExclusiveCores();
} catch (RuntimeException e){
Log.w(TAG, "getExclusiveCores() is not supported on this device.");
}
}
return exclusiveCores;
}
static void setDefaultStreamValues(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1){
AudioManager myAudioMgr = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
String sampleRateStr = myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
int defaultSampleRate = Integer.parseInt(sampleRateStr);
String framesPerBurstStr = myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
int defaultFramesPerBurst = Integer.parseInt(framesPerBurstStr);
native_setDefaultStreamValues(defaultSampleRate, defaultFramesPerBurst);
}
}
}

View file

@ -0,0 +1,34 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeWidth="1"
android:strokeColor="#00000000">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View file

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#008577"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/sample_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Tap anywhere to play"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#008577</color>
<color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#D81B60</color>
</resources>

View file

@ -0,0 +1,3 @@
<resources>
<string name="app_name">Mega Drone</string>
</resources>

View file

@ -0,0 +1,11 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>