martedì 10 gennaio 2017

Message Api in Android Wear

Le Message API permettono di scambiare messaggi tra dispositivi Android, nel caso specifico tra il telefono e l'orologio mediante Android Wear
Nell'esempio in esame vengono sincronizzati due NumberPicker



Il progetto completo e' disponibile su GitHub ed e' basato sull'esempio a questo link

Per NumberPicker sul telefono, piu' che altro per la gestione del font, e' stato usato questa modifica



Lato Telefono
build.gradle
--------------------------------------
apply plugin: 'com.android.application'

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.2"
    defaultConfig {
        applicationId "com.luca_innocenti.apticmetro"
        minSdkVersion 21
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
        multiDexEnabled true
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    wearApp project(':wear')
    compile 'com.google.android.gms:play-services:10.0.1'
    compile 'com.android.support:appcompat-v7:25.1.0'
    compile 'com.shawnlin:number-picker:2.3.0'

    testCompile 'junit:junit:4.12'
}
--------------------------------------

MainActivity
--------------------------------------
package com.luca_innocenti.apticmetro;

import android.icu.lang.UCharacter;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ImageButton;
//import android.widget.NumberPicker;
import com.shawnlin.numberpicker.NumberPicker;

import android.widget.Toast;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.wearable.MessageApi;
import com.google.android.gms.wearable.Node;
import com.google.android.gms.wearable.NodeApi;
import com.google.android.gms.wearable.Wearable;



// Applicazione Mobile
public class MainActivity extends AppCompatActivity implements
        GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener
{

    private static final String TAG = "MainActivity";
    private GoogleApiClient googleApiClient;

    private ImageButton play;
    private NumberPicker bpm;
    private int counter;

    public MainActivity() {
    }


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        googleApiClient = new GoogleApiClient.Builder(this)
                .addApi(Wearable.API)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .build();

        //play = (ImageButton) findViewById(R.id.imageButton);
        bpm = (NumberPicker) findViewById(R.id.number_picker);


        bpm.setMinValue(40);
        bpm.setMaxValue(150);
        bpm.setValue(80);
        bpm.setWrapSelectorWheel(false);


        bpm.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
            @Override
            public void onValueChange(NumberPicker picker, int oldVal, int newVal){
                //Display the newly selected number from picker
                Log.d(TAG,Integer.toString(newVal));
                String sendMessage = Integer.toString(newVal);
                new SendToDataLayerThread("/path", sendMessage).start();
            }
        });



        /*play.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //Toast.makeText(MainActivity.this, "Inviato", Toast.LENGTH_SHORT).show();
                String sendMessage = "Start";
                counter++;

                new SendToDataLayerThread("/path", sendMessage).start();

                Log.d(TAG, "SendToDataLayerThread()");


            }
        });*/
    }


    @Override
    protected void onStart() {
        super.onStart();
        Log.d(TAG, "onStart()");
        googleApiClient.connect();
    }

    // data layer connection
    @Override
    public void onConnected(Bundle bundle) {
        Log.d(TAG, "onConnected()");
    }

    // Activity stop
    @Override
    protected void onStop() {
        if (null != googleApiClient && googleApiClient.isConnected()) {
            googleApiClient.disconnect();
        }
        super.onStop();

        Log.d(TAG, "onStop()");

    }

    @Override
    public void onConnectionSuspended(int cause) { }

    @Override
    public void onConnectionFailed(ConnectionResult connectionResult) { }

    private class SendToDataLayerThread extends Thread{
        String path;
        String handheldMessage;

        public SendToDataLayerThread(String pth, String message) {
            //path = "/messaggio";
            path = pth;
            handheldMessage = message;
        }
        public void run() {
            Log.d(TAG, "SendToDataLayerThread()");

            NodeApi.GetConnectedNodesResult nodeResult = Wearable.NodeApi.getConnectedNodes(googleApiClient).await();
            for (Node node : nodeResult.getNodes()) {
                MessageApi.SendMessageResult result =
                        Wearable.MessageApi.sendMessage(googleApiClient, node.getId(), path, handheldMessage.getBytes()).await();

                if (result.getStatus().isSuccess()) {
                    Log.d(TAG, "To: " + node.getDisplayName());
                    Log.d(TAG, "Message = " + handheldMessage );
                }
                else {
                    Log.d(TAG, "Send error");
                }
            }
        }
    }
}
--------------------------------------


AndroidManifest.xml
--------------------------------------
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.luca_innocenti.apticmetro">

    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.VIBRATE"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <meta-data
            android:name="com.google.android.gms.version"
            android:value="@integer/google_play_services_version" />

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
--------------------------------------



Lato Orologio 
build.gradle
--------------------------------------
apply plugin: 'com.android.application'

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.2"
    defaultConfig {
        applicationId "com.luca_innocenti.apticmetro"
        minSdkVersion 21
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    compile 'com.google.android.support:wearable:2.0.0-beta1'
    compile 'com.google.android.gms:play-services-wearable:10.0.1'
    provided 'com.google.android.wearable:wearable:2.0.0-beta1'
}
--------------------------------------

Manifest.xml
--------------------------------------
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.luca_innocenti.apticmetro">

    <uses-feature android:name="android.hardware.type.watch" />

    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.VIBRATE" />

    <uses-library android:name="com.google.android.wearable" android:required="false" />



    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@android:style/Theme.DeviceDefault">
        <uses-library
            android:name="com.google.android.wearable"
            android:required="false" />


        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:theme="@android:style/Theme.DeviceDefault.Light">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service android:name=".WearService">
            <intent-filter>
                <action android:name="com.google.android.gms.wearable.DATA_CHANGED" />
                <action android:name="com.google.android.gms.wearable.MESSAGE_RECEIVED" />
                <data android:scheme="wear" android:host="*" android:pathPrefix="/path" />
            </intent-filter>
        </service>
    </application>


</manifest>
--------------------------------------

MainActivity
--------------------------------------
package com.luca_innocenti.apticmetro;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Vibrator;
import android.support.v4.content.LocalBroadcastManager;
import android.support.wearable.activity.WearableActivity;
import android.support.wearable.view.BoxInsetLayout;
import android.util.Log;
import android.view.View;
import android.widget.ImageButton;
import android.widget.NumberPicker;
import android.os.Handler;

import com.google.android.gms.wearable.DataMap;
import com.google.android.gms.wearable.MessageEvent;



// Lato orologio
public class MainActivity extends WearableActivity {

    private BoxInsetLayout mContainerView;
    private NumberPicker bpm;
    private ImageButton play;
    private int stato;
    long startTime = 0;
    long valore_bpm;

    private String receivedMessage = null;
    private String message = "";
    private static final String TAG = "WearMainActivity";




    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setAmbientEnabled();

        Log.v(TAG, "onCreate()");
        IntentFilter messageFilter = new IntentFilter(Intent.ACTION_SEND);
        MessageReceiver messageReceiver = new MessageReceiver();
        LocalBroadcastManager.getInstance(getApplication()).registerReceiver(messageReceiver, messageFilter);


        final Handler timerHandler = new Handler();
        final Runnable timerRunnable = new Runnable() {

            @Override
            public void run() {
                Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
                vibrator.vibrate((100));
                timerHandler.postDelayed(this, valore_bpm);
            }
        };


        stato = 0;

        mContainerView = (BoxInsetLayout) findViewById(R.id.container);
        bpm = (NumberPicker) findViewById(R.id.numberPicker);
        play =(ImageButton) findViewById(R.id.imageButton);

        bpm.setMinValue(40);
        bpm.setMaxValue(150);
        bpm.setValue(80);
        bpm.setWrapSelectorWheel(false);

        play.setOnClickListener(new View.OnClickListener()
        {
            public void onClick(View v)
            {
                Log.d("Aptic","BPM "+bpm.getValue());
                valore_bpm = Math.round((60.0/bpm.getValue())*1000.0);
                Log.d("Valore bmp millisecondi","BMP3 "+valore_bpm);
                if (stato == 0) {
                    play.setImageResource(R.drawable.stop_icon);
                    stato = 1;
                    startTime = System.currentTimeMillis();
                    //Toast.makeText(MainActivity.this, Long.toString(valore_bpm), Toast.LENGTH_SHORT).show();
                    timerHandler.postDelayed(timerRunnable, 0);
                }
                else
                {
                    play.setImageResource(R.drawable.play_icon);
                    stato = 0;
                    timerHandler.removeCallbacks(timerRunnable);

                }
            }
        });

    }

    public class MessageReceiver extends BroadcastReceiver {
        private static final String TAG = "MessageReceiver";

        @Override
        public void onReceive(Context context, Intent intent) {

            receivedMessage = intent.getStringExtra("message");
            Log.d("MessageReceiver", "onReceive() receivedMessage = "+receivedMessage);

            if (receivedMessage != null) {
                // Display message in UI
                message = receivedMessage;
                Log.d("MessageReceiver", "receivedMessage =" + receivedMessage);
                bpm.setValue(Integer.parseInt(receivedMessage));
            } else {
                receivedMessage = "No Message";
                Log.d("MessageReceiver", "receivedMessage = No Message");
            }


        }
    }




    @Override
    public void onEnterAmbient(Bundle ambientDetails) {
        super.onEnterAmbient(ambientDetails);
        updateDisplay();
    }

    @Override
    public void onUpdateAmbient() {
        super.onUpdateAmbient();
        updateDisplay();
    }

    @Override
    public void onExitAmbient() {
        updateDisplay();
        super.onExitAmbient();
    }

    private void updateDisplay() {
        if (isAmbient()) {
            mContainerView.setBackgroundColor(getResources().getColor(android.R.color.black));
            play.setVisibility(View.INVISIBLE);
        } else {
            mContainerView.setBackground(null);
            play.setVisibility(View.VISIBLE);
        }
    }




}
--------------------------------------

WearService
--------------------------------------
package com.luca_innocenti.apticmetro;

import android.content.Intent;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;

import com.google.android.gms.wearable.DataEventBuffer;
import com.google.android.gms.wearable.MessageEvent;
import com.google.android.gms.wearable.WearableListenerService;

/**
 * Created by lucainnocenti on 05/01/17.
 */

public class WearService extends WearableListenerService {
    private static final String TAG = "WearService";

    @Override
    public void onDataChanged(DataEventBuffer dataEvents) {
        super.onDataChanged(dataEvents);
        Log.v(TAG, "onDataChanged" );
    }

    @Override
    public void onMessageReceived(MessageEvent messageEvent) {
        super.onMessageReceived(messageEvent);
        Log.v(TAG, "onMessageReceived" );

        if (messageEvent.getPath().equals("/path")) {

            final String message = new String(messageEvent.getData());

            Log.d(TAG, "Message path received on watch is: " + messageEvent.getPath());
            Log.d(TAG, "Message received on watch is: " + message);

            // Broadcast message to wearable activity for display
            Intent messageIntent = new Intent();
            messageIntent.setAction(Intent.ACTION_SEND);
            messageIntent.putExtra("message", message);
            LocalBroadcastManager.getInstance(this).sendBroadcast(messageIntent);

        }
        else {
            super.onMessageReceived(messageEvent);
        }
    }

}





lunedì 9 gennaio 2017

Dentro ad Amazon Fire

La bambina e' riuscita, facendo cadere di piatto lato schermo il tablet per terra, ad aprirlo in un colpo solo


Una occasione per vedere come e' fatto dentro. Curiosa la posizione dello speaker (mono)
PS: il tablet e' sopravvissuto...e' decisamente resistente

mercoledì 4 gennaio 2017

Mandelbrot su Pebble

Visto che sono riuscito ad installare SDK Pebble perche' non provarlo con un classico??



Per il codice mi sono basato su altro esempio di Mandelbrot su Pebble presente su GitHub (peraltro fatto molto meglio del mio)

La gestione dello schermo non e' banalissima perche' e' presente una gerarchia di livelli sovrapposti.

Una cosa significativa: non e' possibile avere dati di debug in formato float (non ne capisco il motivo ma e' cosi')

---------------------------------------------
#include <pebble.h>

static Window *schermo;
static Layer *render_layer;

static void disegna(Layer *layer, GContext *ctx)
{
float re_min = -2.0;
float im_min = -1.2;
float re_max = 1.0;
float im_max = 1.2;
int r = 0;

int itera = 40;
int xres=0;
int yres=0;

float a,b,x,y,x_new,y_new;
int i,j,k;

GRect layer_bounds = layer_get_bounds(layer);

xres=layer_bounds.size.w;
yres=layer_bounds.size.h;

a = 0.0;
b = 0.0;
x_new = 0.0;
y_new = 0.0;

float re_factor = (re_max-re_min);
float im_factor = (im_max-im_min);

// scrive messaggi di debug che possono essere letti in shell con il comando
// pebble logs
APP_LOG(APP_LOG_LEVEL_DEBUG,"WIDTH=%d HEIGHT=%d",xres,yres);

for (i=0;i<yres;i++)
    {
    a = re_min+(i*re_factor/yres);
    for (j=0;j<xres;j++)
{

b = im_min+(j*im_factor/xres);
x = 0.0;
y = 0.0;
//APP_LOG(APP_LOG_LEVEL_DEBUG,"X=%d Y=%d",j,i);
for (k=1;k<itera;k++)
{
x_new = (x*x)-(y*y)+a;
y_new = (2*x*y)+b;
if (((x_new*x_new)+(y_new*y_new))>4)
   {
   r = k%2;
   //APP_LOG(APP_LOG_LEVEL_DEBUG,"K=%d R=%d",k,r);
   if (r == 1)
{
GPoint punto = GPoint(j,i);
//window_set_backgroung_color(&windows,GColorBlack);
graphics_draw_circle(ctx,punto,1);
}
   break;
   }
x = x_new;
y = y_new;
}
}
    }

}

static void carica_schermo(Window *window)
{
APP_LOG(APP_LOG_LEVEL_DEBUG,"CARICA SCHERMO");
Layer *window_layer = window_get_root_layer(window);
GRect bounds = layer_get_bounds(window_layer);
render_layer = layer_create(bounds);
layer_set_update_proc(render_layer,disegna);
layer_add_child(window_layer, render_layer);
}

static void scarica_schermo(Window *window){
}

static void init(void){
APP_LOG(APP_LOG_LEVEL_DEBUG,"INIT");
schermo = window_create();
#ifdef PBL_SDK_2
window_set_fullscreen(schermo,true);
window_set_backgroung_color(schermo,GColorWhite);
#endif

window_set_window_handlers(schermo,(WindowHandlers){
   .load = carica_schermo,
   .unload = scarica_schermo,
   });
const bool animated=true;
window_stack_push(schermo,animated);
}

static void deinit(void)
{
window_destroy(schermo);
}

int main(void) {
  APP_LOG(APP_LOG_LEVEL_DEBUG,"MAIN");
  init();
  APP_LOG(APP_LOG_LEVEL_DEBUG,"%p",schermo);
  app_event_loop();
  deinit();
}
---------------------------------------------

e visto che ci siamo perche' non provare a pubblicarla sullo store di Pebble (non e' necessario un pagamento)



ed infine la prova reale (tempo di calcolo circa 6 secondi)


martedì 3 gennaio 2017

Pebble SDK su Centos 7

Colpo di genio del nuovo anno : comprare un Pebble (usato) e scoprire un paio di giorni dopo che Pebble e' stato acquistato da FitBit e che quindi dismetteranno tutto il supporto compreso il cloud !!

Mi sono quindi affrettato almeno a configurarmi l'SDK di Pebble prima che diventi non disponibile



Le istruzioni riportate sul sito sono per distribuzioni Linux basate su apt ma ho provato lo stesso l'installazione su Centos
Il pacchetto dell'SDK 64 di Pebble si scarica da qui

-----------------------------
mkdir ~/pebble-dev/
cd ~/pebble-dev/
tar -jxf ~/Downloads/pebble-sdk-4.5-linux64.tar.bz2
echo 'export PATH=~/pebble-dev/pebble-sdk-4.5-linux64/bin:$PATH' >> ~/.bash_profile
. ~/.bash_profile

-----------------------------

si deve poi installare pip ed virtualenv di Python (e nessun problema)
---------------------------
cd ~/pebble-dev/pebble-sdk-4.5-linux64 
virtualenv --no-site-packages .env 
source .env/bin/activate 
pip install -r requirements.txt 
deactivate
---------------------------
in questo caso deactivate mi ha dato errore ma non ha creato problemi per i passi futuri
Vengono scaricati diversi pacchetti ma non sono riuscito a trovare una opzione per configurare il proxy server.
Si devono poi installare le dipendenze per il compilare. Nessun problema per installare libpixman e libsdl (peraltro gia' presenti)  ma su Centos non esiste un pacchetto precompilato per la libreria libfdt e si deve quindi partire dai sorgenti.
Si scarica il file tgz da GitHub e si lancia

make

nella sottodirectory libfdt viene creata la libreria libfdt-1.3.0.so. Il problema e' che l'emulatore di Pebble ricerca la libreria libfdt.so.1. Quindi e' necessario o rinominare il file oppure creare un symlink con il nome richiesto.
Usanso make install non funziona e quindi ho copiato a mano la libreria compilato in /usr/lib e /usr/lib64

L'installazione delle applicazioni sul telefono non avviene mediante il cavo, che di fatto e' funzionale solo alla ricarica, ma passa attraverso il telefono a cui e' accoppiato l'orologio


Si deve quindi attivare il pulsante a scorrimento. Non funziona con il telefono in modalita' HotSpot ma in associazione ad un vero e proprio Access Point


per creare un nuovo progetto (Javascript o C) si usa
pebble new-project [--simple] [--javascript] [--worker] [--rocky] NAME

che genera un template su cui iniziare a lavorare

pebble build per compilare

ed infine per installare si usa
pebble install --phone IP (per il telefono)
pebble install --emulator basalt (per l'emulatore. i nomi dei vari dispositivi sono riportati qui sotto)




Emulatore in esecuzione
Per interagire con l'emulatore si usano i tasti freccia

freccia su = Up
freccia giu' = Down
freccia destra = Select
freccia sinistra = Back

Per visualizzare i log si usa

pebble log

per fare piu' in fretta si puo' fare
pebble wipe & pebble build & pebble install --emulator diorite --logs




Cerberus App

In questi giorni sta avendo diffusione il video di un giovane filmmaker che racconta le vicessitudini sul furto (voluto) di un telefono Android




Invece di utilizzare le applicazioni tipiche di Android e IOS per tracciare la posizione del telefono, e' stata impiegata un app specifica Cerberus....vale la pena darci un'occhio

Una prima differenza: su Play Store esiste una versione del software mentre sul sito se ne possono scaricare due versioni differente (la prima e' identica a quella del Play Store la seconda utilizza dei metodi piu' avanzati per nascondersi come app di sistema)

Ovviamente per la prova ho utilizzato un telefono di prova settato su un account di prova..il software prende il controllo completo del telefono e quindi se cade nelle mani sbagliate sono guai. Cerbarus ha subito nella sua storia un leak delle informazioni degli utenti

Al momento attuale l'applicazione e' gratuita ma per una sola settimana ed per un solo dispositivo. Dopo l'abbonamento e' di 5 euro/anno

Schermata di installazione
Tutti i servizi passano dai server di Cerberus (di fatto non si ha una diretta interazione con il telefono remoto ma solo con l'interfaccia web ospitata sui server Cerberus). L'username e password di login sono le stesse per loggarsi sul sito e per configurare l'app




alcune funzioni richiedono che il terminale sia rootato



Un aspetto divertente e' che se si cerca di disinstallare l'applicazione nel modo standard il pulsante non e' cliccabile. 



Altrettanto curiosamente non e' necessaria nessuna password per procedere...basta andare Impostazione/Sicurezza/Amministratori dispositivo e togliere la spunta a Cerberus. Dopo questa operazione l'applicazione e' disistallabile. A meno di un blocco remoto quindi chi ha in mano il telefono puo' sempre rimuovere il blocco



Nell'interfaccia web sulla sinistra si selezionano i comandi da inviare al terminale e sulla destra si ha il log



Si possono scattare foto da remoto ed acquisire l'audio ambientale




Visto che l'apk e' semplicemente ho provato a disassemblarlo con Jadx 

Librerie offuscate

Come era facile da prevedere il codice principale e' stato offuscato ma si sono un po' di cose interessanti. Prima di tutto il file Manifest piu' mostruoso che si possa immaginare (la API di Google e' stata modificata)
----------------------------
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="11" android:="2.1.2" package="com.lsdroid.cerberus.client" platformBuildVersionCode="22" platformBuildVersionName="5.1.1-1819727">
    <uses-sdk android:="15" android:="22" />
    <uses-permission android:="android.permission.SEND_SMS" />
    <uses-permission android:="android.permission.READ_CONTACTS" />
    <uses-permission android:="android.permission.PROCESS_OUTGOING_CALLS" />
    <uses-permission android:="android.permission.RECEIVE_SMS" />
    <uses-permission android:="android.permission.READ_SMS" />
    <uses-permission android:="android.permission.READ_PHONE_STATE" />
    <uses-permission android:="android.permission.INTERNET" />
    <uses-permission android:="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:="com.google.android.providers.gsf.permission.READ_GSERVICES" />
    <uses-permission android:="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:="android.permission.CHANGE_NETWORK_STATE" />
    <uses-permission android:="android.permission.WRITE_SETTINGS" />
    <uses-feature android:="20000" android:="true" />
    <uses-feature android:="android.hardware.telephony" android:="false" />
    <application android:="@style/" android:="@string/" android:="@drawable/icon" android:="o.ᑊ">
        <activity android:="@style/" android:="@string/" android:="o.ᐠ" android:="2" android:="fb0" android:="2" />
        <activity android:="@style/" android:="@string/" android:="o.ᕀ" android:="1" android:="fb0" android:="2" />
        <activity android:="@string/" android:=".StartActivity" android:="1" android:="fb0" android:="2">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:="@string/" android:="o.ᔊ" android:="1" android:="fb0" android:="2" />
        <activity android:="@style/" android:="@string/" android:="o.เ" android:="1b0" android:="@string/" android:="2" />
        <receiver android:=".StartReceiver">
            <intent-filter android:="2147483647">
                <action android:name="android.intent.action.NEW_OUTGOING_CALL" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </receiver>
        <receiver android:=".SmsReceiver">
            <intent-filter android:="100">
                <action android:name="android.intent.action.DATA_SMS_RECEIVED" />
                <data android:="sms" android:="*" android:="12346" />
            </intent-filter>
        </receiver>
        <service android:="o.ˆ" />
        <service android:="o.ᕑ" />
        <service android:="o.ˀ" />
        <service android:="com.google.android.gms.analytics.CampaignTrackingService" />
        <receiver android:="com.google.android.gms.analytics.CampaignTrackingReceiver" android:="true">
            <intent-filter>
                <action android:name="com.android.vending.INSTALL_REFERRER" />
            </intent-filter>
        </receiver>
        <meta-data android:="com.google.android.gms.version" android:="@integer/" />
        <meta-data android:="com.google.android.maps.v2.API_KEY" android:="AIzaSyCLvVIVynRBR7yC2Wa18i" />
    </application>
</manifest>
----------------------------

c'e' anche l'uso della libreria okhttp 
Ovviamente,e per fortuna, per l'uso del software ci vuole almeno un accesso fisico al dispositivo e cio' ne limita l'uso non idoneo alla sua funzione principale. In definitiva comunque un buon software (se non fosse che la licenza d'uso permette agli sviluppatori di divulgare dati...e visto il controllo di informazioni che si possono raccogliere e' meglio stare attenti a non dare la propria chiave di casa in mani sconosciute)


Debugger integrato ESP32S3

Aggiornamento In realta' il Jtag USB funziona anche sui moduli cinesi Il problema risiede  nell'ID USB della porta Jtag. Nel modulo...