use android_activity::input::{InputEvent, KeyCode, MotionAction, KeyState};
use android_activity::{AndroidApp, WindowEvent, PollEvent};
use softbuffer::GraphicsContext;
use log::info;
use std::time::{Duration, Instant};
use std::thread;
use jni::objects::{JObject, JValue};
use jni::JNIEnv;
// ---------- App info ----------
struct AppInfo {
name: String,
package: String,
class: String,
}
// Get installed launcher apps via JNI
fn get_installed_apps(env: &mut JNIEnv, context: JObject) -> Vec {
let mut apps = Vec::new();
let pm = match env.call_method(context, "getPackageManager", "()Landroid/content/pm/PackageManager;", &) {
Ok(v) => v.l().unwrap(),
Err(_) => return apps,
};
let intent = env.new_object("android/content/Intent", "(Ljava/lang/String;)V", &[
env.new_string("android.intent.action.MAIN").unwrap().into()
]).unwrap();
let category = env.new_string("android.intent.category.LAUNCHER").unwrap();
let _ = env.call_method(&intent, "addCategory", "(Ljava/lang/String;)Landroid/content/Intent;", &[category.into()]);
let resolve_list = env.call_method(&pm, "queryIntentActivities", "(Landroid/content/Intent;I)Ljava/util/List;", &[
intent.into(),
JValue::Int(0)
]).unwrap().l().unwrap();
let size = env.call_method(&resolve_list, "size", "()I", &).unwrap().i().unwrap();
for i in 0..size {
let item = env.call_method(&resolve_list, "get", "(I)Ljava/lang/Object;", &[JValue::Int(i)]).unwrap().l().unwrap();
let act_info = env.get_field(&item, "activityInfo", "Landroid/content/pm/ActivityInfo;").unwrap().l().unwrap();
let pkg = env.get_field(&act_info, "packageName", "Ljava/lang/String;").unwrap().l().unwrap();
let cls = env.get_field(&act_info, "name", "Ljava/lang/String;").unwrap().l().unwrap();
let app_info = env.get_field(&act_info, "applicationInfo", "Landroid/content/pm/ApplicationInfo;").unwrap().l().unwrap();
let label = env.call_method(&pm, "getApplicationLabel", "(Landroid/content/pm/ApplicationInfo;)Ljava/lang/CharSequence;", &[
app_info.into()
]).unwrap().l().unwrap();
let name: String = env.get_string(&label.into()).unwrap().into();
let package: String = env.get_string(&pkg.into()).unwrap().into();
let class: String = env.get_string(&cls.into()).unwrap().into();
apps.push(AppInfo { name, package, class });
}
apps
}
// Launch app
fn launch_app(env: &mut JNIEnv, context: JObject, package: &str, class: &str) {
let intent = env.new_object("android/content/Intent", "()V", &).unwrap();
let comp = env.new_object("android/content/ComponentName", "(Ljava/lang/String;Ljava/lang/String;)V", &[
env.new_string(package).unwrap().into(),
env.new_string(class).unwrap().into(),
]).unwrap();
let _ = env.call_method(&intent, "setComponent", "(Landroid/content/ComponentName;)Landroid/content/Intent;", &[comp.into()]);
let _ = env.call_method(&intent, "addFlags", "(I)Landroid/content/Intent;", &[JValue::Int(0x10000000)]);
let _ = env.call_method(context, "startActivity", "(Landroid/content/Intent;)V", &[intent.into()]);
}
// Open recent apps (system overview)
fn open_recent_apps(env: &mut JNIEnv, context: JObject) {
let intent = env.new_object("android/content/Intent", "()V", &).unwrap();
let _ = env.call_method(&intent, "setAction", "(Ljava/lang/String;)Landroid/content/Intent;", &[
env.new_string("android.intent.action.RECENT_APPS").unwrap().into()
]);
let _ = env.call_method(context, "startActivity", "(Landroid/content/Intent;)V", &[intent.into()]);
}
// Show toast
fn show_toast(env: &mut JNIEnv, context: JObject, msg: &str) {
let toast_class = env.find_class("android/widget/Toast").unwrap();
let jmsg = env.new_string(msg).unwrap();
let toast = env.call_static_method(toast_class, "makeText", "(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;", &[
context.into(),
jmsg.into(),
JValue::Int(0)
]).unwrap().l().unwrap();
let _ = env.call_method(&toast, "show", "()V", &);
}
// Free memory in MB
fn get_free_memory(env: &mut JNIEnv, context: JObject) -> i64 {
let am = env.call_method(context, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;", &[
env.new_string("activity").unwrap().into()
]).unwrap().l().unwrap();
let mem_info = env.new_object("android/app/ActivityManager$MemoryInfo", "()V", &).unwrap();
let _ = env.call_method(&am, "getMemoryInfo", "(Landroid/app/ActivityManager$MemoryInfo;)V", &[mem_info.into()]);
env.get_field(&mem_info, "availMem", "J").unwrap().j().unwrap() / (1024 * 1024)
}
// ---------- Performance manager ----------
struct PerformanceManager {
last_interaction: Instant,
frame_delay: Duration,
}
impl PerformanceManager {
fn new() -> Self {
Self { last_interaction: Instant::now(), frame_delay: Duration::from_millis(33) }
}
fn record_interaction(&mut self) { self.last_interaction = Instant::now(); }
fn update_frame_delay(&mut self) -> Duration {
let idle = self.last_interaction.elapsed();
self.frame_delay = if idle > Duration::from_secs(5) {
Duration::from_millis(166)
} else if idle > Duration::from_secs(2) {
Duration::from_millis(66)
} else {
Duration::from_millis(33)
};
self.frame_delay
}
}
// ---------- Drawing ----------
fn draw_circle(buffer: &mut [u32], width: usize, height: usize, cx: usize, cy: usize, radius: usize, color: u32) {
let x0 = cx.saturating_sub(radius);
let x1 = (cx + radius).min(width - 1);
let y0 = cy.saturating_sub(radius);
let y1 = (cy + radius).min(height - 1);
for y in y0..=y1 {
let dy = (y as isize - cy as isize).abs();
for x in x0..=x1 {
let dx = (x as isize - cx as isize).abs();
if dx * dx + dy * dy <= (radius * radius) as isize {
buffer[y * width + x] = color;
}
}
}
}
fn draw_rect(buffer: &mut [u32], width: usize, height: usize, x: usize, y: usize, w: usize, h: usize, color: u32) {
let x_end = (x + w).min(width);
let y_end = (y + h).min(height);
for yy in y..y_end {
let row_start = yy * width;
for xx in x..x_end {
buffer[row_start + xx] = color;
}
}
}
// ---------- Main ----------
fn android_main(app: AndroidApp) {
simple_logger::init_with_level(log::Level::Info).unwrap();
info!("EggLauncher v1.0 started");
let mut window = app.create_surface_window().unwrap();
let mut width = window.width();
let mut height = window.height();
let mut context = GraphicsContext::new(window.native_window()).unwrap();
let mut events = app.poll_events(Some(Duration::from_millis(0)));
let vm = app.vm().unwrap();
let mut env = vm.attach_current_thread().unwrap();
let context_obj = env.new_local_ref(app.context()).unwrap();
let apps = get_installed_apps(&mut env, context_obj.as_obj());
drop(env);
let mut perf = PerformanceManager::new();
let mut selected = 0;
let cols = 3;
let rows = (apps.len() + cols - 1).max(1);
let cell_w = width / cols;
let cell_h = height / rows;
let btn_w = 100;
let btn_h = 40;
let btn_x = if width > btn_w + 20 { width - btn_w - 20 } else { 10 };
let ram_btn = (btn_x, 20, btn_w, btn_h);
let recent_btn = (btn_x, 20 + btn_h + 10, btn_w, btn_h);
'main: loop {
let frame_delay = perf.update_frame_delay();
while let Some(event) = events.next_event() {
match event {
PollEvent::Window(win_event) => match win_event {
WindowEvent::Resized => {
width = window.width();
height = window.height();
context.resize(width as u32, height as u32);
}
WindowEvent::Redraw => {
let mut buffer = context.buffer_mut().unwrap();
// Black background
for pixel in buffer.iter_mut() { *pixel = 0x000000; }
// Abstract colourful circles (static)
let bg_circles = [(100,200,40,0xFF4D4D), (400,600,60,0x4CAF50), (700,300,50,0xFFD700)];
for (cx, cy, rad, col) in bg_circles {
draw_circle(&mut buffer, width, height, cx, cy, rad, col);
}
// App grid
for (i, _) in apps.iter().enumerate() {
let col = i % cols;
let row = i / cols;
let cx = (col * cell_w) + cell_w / 2;
let cy = (row * cell_h) + cell_h / 2;
let rad = cell_w.min(cell_h) / 4;
let color = if i == selected { 0xFFD700 } else { 0x666666 };
draw_circle(&mut buffer, width, height, cx, cy, rad, color);
}
// Buttons
draw_rect(&mut buffer, width, height, ram_btn.0, ram_btn.1, ram_btn.2, ram_btn.3, 0x333333);
draw_rect(&mut buffer, width, height, recent_btn.0, recent_btn.1, recent_btn.2, recent_btn.3, 0x444444);
buffer.present().unwrap();
}
_ => {}
},
PollEvent::Input(input_event) => match input_event {
InputEvent::Key(key) => {
perf.record_interaction();
if key.state() == KeyState::Pressed && key.key_code() == KeyCode::BACK {
break 'main;
}
}
InputEvent::Motion(motion) => {
perf.record_interaction();
if let Some(pointer) = motion.pointers().next() {
let x = pointer.x() as usize;
let y = pointer.y() as usize;
// RAM button
if x >= ram_btn.0 && x < ram_btn.0 + ram_btn.2 && y >= ram_btn.1 && y < ram_btn.1 + ram_btn.3 {
if motion.action() == MotionAction::ButtonRelease {
let mut env = vm.attach_current_thread().unwrap();
let mem = get_free_memory(&mut env, context_obj.as_obj());
show_toast(&mut env, context_obj.as_obj(), &format!("{} MB free", mem));
}
continue;
}
// Recent Apps button
if x >= recent_btn.0 && x < recent_btn.0 + recent_btn.2 && y >= recent_btn.1 && y < recent_btn.1 + recent_btn.3 {
if motion.action() == MotionAction::ButtonRelease {
let mut env = vm.attach_current_thread().unwrap();
open_recent_apps(&mut env, context_obj.as_obj());
}
continue;
}
// App selection
for (i, _) in apps.iter().enumerate() {
let col = i % cols;
let row = i / cols;
let left = col * cell_w;
let right = left + cell_w;
let top = row * cell_h;
let bottom = top + cell_h;
if x >= left && x < right && y >= top && y < bottom {
selected = i;
if motion.action() == MotionAction::ButtonRelease {
let mut env = vm.attach_current_thread().unwrap();
launch_app(&mut env, context_obj.as_obj(), &apps[selected].package, &apps[selected].class);
}
break;
}
}
}
}
_ => {}
},
_ => {}
}
}
thread::sleep(frame_delay);
window.refresh();
}
}
4 posts - 3 participants
Read full topic
🏷️ Rust_feed