[controller] agregado código qr para compartir contenido. y corregir arranque de proxy

This commit is contained in:
Luis Guzmán 2026-04-02 21:00:51 -06:00
parent af7655b572
commit 6ff9fc48c0
8 changed files with 334 additions and 3 deletions

View File

@ -79,4 +79,6 @@ dependencies {
implementation 'androidx.biometric:biometric:1.1.0'
implementation 'com.google.android.material:material:1.11.0'
implementation 'androidx.webkit:webkit:1.12.0'
// ZXing for QR Code generation
implementation 'com.google.zxing:core:3.5.2'
}

View File

@ -42,13 +42,17 @@
</intent-filter>
</receiver>
<!-- VPN Recovery Receiver (The Boomerang) -->
<!-- VPN Recovery Receiver -->
<receiver android:name=".VpnRecoveryReceiver" android:exported="false">
<intent-filter>
<action android:name="org.iiab.controller.RECOVER_VPN" />
</intent-filter>
</receiver>
<activity android:name=".QrActivity"
android:exported="false"
android:theme="@style/Theme.TransparentQR" />
<!-- Termux Result Callback Receiver -->
<receiver android:name=".TermuxCallbackReceiver" android:exported="false">
<intent-filter>

View File

@ -122,6 +122,8 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
private String serverTransitionText = "";
private final Handler timeoutHandler = new Handler(android.os.Looper.getMainLooper());
private Runnable timeoutRunnable;
private boolean isWifiActive = false;
private boolean isHotspotActive = false;
private String currentTargetUrl = null;
private long pulseStartTime = 0;
@ -267,6 +269,7 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
deckContainer = findViewById(R.id.deck_container);
btnServerControl = findViewById(R.id.btn_server_control);
ImageButton btnShareQr = findViewById(R.id.btn_share_qr);
dashboardManager = new DashboardManager(this, findViewById(android.R.id.content), () -> {
handleControlClick();
@ -335,6 +338,23 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
}
});
// --- QR Share Button Logic ---
btnShareQr.setOnClickListener(v -> {
if (!isServerAlive) {
// Rule 1: Server must be running
Snackbar.make(findViewById(android.R.id.content), R.string.qr_error_no_server, Snackbar.LENGTH_LONG).show();
return;
}
if (!isWifiActive && !isHotspotActive) {
// Rule 2: At least one network must be active
Snackbar.make(findViewById(android.R.id.content), R.string.qr_error_no_network, Snackbar.LENGTH_LONG).show();
return;
}
// Launch the new QrActivity
startActivity(new Intent(MainActivity.this, QrActivity.class));
});
connectionLog.setMovementMethod(new ScrollingMovementMethod());
connectionLog.setTextIsSelectable(true);
connectionLog.setOnTouchListener((v, event) -> {
@ -1003,8 +1023,13 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
intent.putExtra("com.termux.RUN_COMMAND_PATH", "/data/data/com.termux/files/usr/bin/iiab-termux");
intent.putExtra("com.termux.RUN_COMMAND_ARGUMENTS", new String[]{actionFlag});
intent.putExtra("com.termux.RUN_COMMAND_WORKDIR", "/data/data/com.termux/files/home");
intent.putExtra("com.termux.RUN_COMMAND_BACKGROUND", false);
intent.putExtra("com.termux.RUN_COMMAND_SESSION_ACTION", "0");
intent.putExtra("com.termux.RUN_COMMAND_PATH", "/data/data/com.termux/files/usr/bin/env");
intent.putExtra("com.termux.RUN_COMMAND_ARGUMENTS", new String[]{
"INTENT_MODE=headless",
"/data/data/com.termux/files/usr/bin/bash",
"/data/data/com.termux/files/usr/bin/iiab-termux",
actionFlag
});
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@ -1043,6 +1068,10 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
} catch (Exception ex) {}
}
// Store states for the QR button logic
this.isWifiActive = isWifiOn;
this.isHotspotActive = isHotspotOn;
// Let the Dashboard handle the LEDs!
if (dashboardManager != null) dashboardManager.updateConnectivityLeds(isWifiOn, isHotspotOn);
}

View File

@ -0,0 +1,184 @@
package org.iiab.controller;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ImageButton;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.Collections;
import java.util.List;
public class QrActivity extends AppCompatActivity {
private TextView titleText;
private TextView ipText;
private ImageView qrImageView;
private ImageButton btnFlip;
private View cardContainer;
private String wifiIp = null;
private String hotspotIp = null;
private boolean showingWifi = true; // Tracks which network is currently displayed
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_qr); // Rename your dialog_qr.xml to this
titleText = findViewById(R.id.qr_network_title);
ipText = findViewById(R.id.qr_ip_text);
qrImageView = findViewById(R.id.qr_image_view);
btnFlip = findViewById(R.id.btn_flip_qr);
cardContainer = findViewById(R.id.qr_card_container);
Button btnClose = findViewById(R.id.btn_close_qr);
// Improve 3D perspective to avoid visual clipping during rotation
float distance = 8000 * getResources().getDisplayMetrics().density;
cardContainer.setCameraDistance(distance);
btnClose.setOnClickListener(v -> finish());
btnFlip.setOnClickListener(v -> {
// Disable button during animation to prevent spam
btnFlip.setEnabled(false);
animateCardFlip();
});
// 1. Fetch real physical IPs with strict interface naming
fetchNetworkInterfaces();
// 2. Determine initial state and button visibility
if (wifiIp != null && hotspotIp != null) {
btnFlip.setVisibility(View.VISIBLE); // Both active, enable flipping
showingWifi = true;
} else if (wifiIp != null) {
btnFlip.setVisibility(View.GONE);
showingWifi = true;
} else if (hotspotIp != null) {
btnFlip.setVisibility(View.GONE);
showingWifi = false;
} else {
// Fallback just in case they died between the MainActivity click and this onCreate
finish();
return;
}
updateQrDisplay();
}
/**
* Performs a 3D flip animation. Swaps the data halfway through when the card is invisible.
*/
private void animateCardFlip() {
// Phase 1: Rotate out (0 to 90 degrees)
ObjectAnimator flipOut = ObjectAnimator.ofFloat(cardContainer, "rotationY", 0f, 90f);
flipOut.setDuration(200); // 200ms
flipOut.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// Card is edge-on (invisible). Swap the data!
showingWifi = !showingWifi;
updateQrDisplay();
// Phase 2: Rotate in from the other side (-90 to 0 degrees)
cardContainer.setRotationY(-90f);
ObjectAnimator flipIn = ObjectAnimator.ofFloat(cardContainer, "rotationY", -90f, 0f);
flipIn.setDuration(200);
flipIn.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
btnFlip.setEnabled(true); // Unlock button
}
});
flipIn.start();
}
});
flipOut.start();
}
/**
* Updates the UI text and generates the new QR Code
*/
private void updateQrDisplay() {
String currentIp = showingWifi ? wifiIp : hotspotIp;
String title = showingWifi ? getString(R.string.qr_title_wifi) : getString(R.string.qr_title_hotspot);
// 8085 is the default port for the IIAB interface
String url = "http://" + currentIp + ":8085";
titleText.setText(title);
ipText.setText(url);
Bitmap qrBitmap = generateQrCode(url);
if (qrBitmap != null) {
qrImageView.setImageBitmap(qrBitmap);
}
}
/**
* Strictly categorizes network interfaces to avoid Hotspot being labeled as Wi-Fi.
*/
private void fetchNetworkInterfaces() {
try {
List<NetworkInterface> interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface intf : interfaces) {
String name = intf.getName();
if (!intf.isUp()) continue;
// Strict categorizations
boolean isStrictWifi = name.equals("wlan0");
boolean isHotspot = name.startsWith("ap") || name.startsWith("swlan") || name.equals("wlan1") || name.equals("wlan2");
if (isStrictWifi || isHotspot) {
List<InetAddress> addrs = Collections.list(intf.getInetAddresses());
for (InetAddress addr : addrs) {
if (!addr.isLoopbackAddress() && addr instanceof Inet4Address) {
if (isStrictWifi) wifiIp = addr.getHostAddress();
if (isHotspot) hotspotIp = addr.getHostAddress();
}
}
}
}
} catch (Exception ignored) { }
}
/**
* Generates a pure Black & White Bitmap using ZXing.
*/
private Bitmap generateQrCode(String text) {
QRCodeWriter writer = new QRCodeWriter();
try {
// 800x800 guarantees high resolution on any screen
BitMatrix bitMatrix = writer.encode(text, BarcodeFormat.QR_CODE, 800, 800);
int width = bitMatrix.getWidth();
int height = bitMatrix.getHeight();
Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
bmp.setPixel(x, y, bitMatrix.get(x, y) ? Color.BLACK : Color.WHITE);
}
}
return bmp;
} catch (WriterException e) {
return null;
}
}
}

View File

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#DD000000"
android:padding="24dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center">
<LinearLayout
android:id="@+id/qr_card_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@drawable/rounded_button"
android:backgroundTint="#1A1A1A"
android:elevation="8dp"
android:padding="16dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp">
<TextView
android:id="@+id/qr_network_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_toStartOf="@+id/btn_flip_qr"
android:text="Wi-Fi Network"
android:textColor="#FFFFFF"
android:textSize="22sp"
android:textStyle="bold" />
<ImageButton
android:id="@+id/btn_flip_qr"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@android:drawable/ic_popup_sync"
android:contentDescription="@string/qr_flip_network"
android:tint="#FFFFFF"
android:scaleType="fitCenter"
android:padding="8dp"
android:visibility="gone" />
</RelativeLayout>
<TextView
android:id="@+id/qr_ip_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="http://---"
android:textColor="#AAAAAA"
android:textSize="16sp"
android:layout_marginBottom="16dp" />
<ImageView
android:id="@+id/qr_image_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
android:background="#FFFFFF"
android:padding="16dp" />
</LinearLayout>
</LinearLayout>
<Button
android:id="@+id/btn_close_qr"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_marginTop="24dp"
android:text="Close"
android:backgroundTint="@color/btn_danger"
android:textColor="#FFFFFF" />
</LinearLayout>

View File

@ -31,6 +31,19 @@
android:textSize="18sp"
android:textStyle="bold" />
<ImageButton
android:id="@+id/btn_share_qr"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_toStartOf="@id/btn_settings"
android:layout_centerVertical="true"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@android:drawable/ic_menu_share"
android:contentDescription="Share via QR"
android:padding="12dp"
android:scaleType="fitCenter"
android:tint="#FFFFFF" />
<ImageButton
android:id="@+id/btn_settings"
android:layout_width="48dp"

View File

@ -90,4 +90,10 @@
<string name="battery_opt_xiaomi_extra">\n\nXiaomi detected: Please set battery saver to \'No restrictions\' in settings.</string>
<string name="battery_opt_denied">For the app to work 100%, please disable battery optimization.</string>
<string name="fix_action">FIX</string>
<string name="qr_error_no_server">Please start the server to share over the network.</string>
<string name="qr_error_no_network">Please enable Wi-Fi or Hotspot to share over the network.</string>
<string name="qr_title_wifi">Wi-Fi Network</string>
<string name="qr_title_hotspot">Hotspot Network</string>
<string name="qr_flip_network">Switch Network</string>
</resources>

View File

@ -11,4 +11,10 @@
<item name="sectionBackground">@color/section_body_bg_light</item>
<item name="sectionHeaderBackground">@color/section_header_bg</item>
</style>
<style name="Theme.TransparentQR" parent="Theme.AppCompat.NoActionBar">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowNoTitle">true</item>
<item name="android:backgroundDimEnabled">false</item>
</style>
</resources>