JavascriptProva

mercoledì 30 novembre 2016

Obiettivo: mettere un'AlertDialog

Obiettivo: mettere una casella di dialogo per la cancellazione di un elemento dal database.
Trovato:
        listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
            @Override
            public boolean onItemLongClick(AdapterView parent, View view, int position, long id) {

                //i valori delle textviews della row servono per la finestra di dialogo.
                final TextView txtNome=(TextView)view.findViewById(R.id.txtNome);
                final TextView txtCognome=(TextView)view.findViewById(R.id.txtCognome);

                //codice per la finestra di dialogo
                new AlertDialog.Builder(context)
                        .setMessage("SEI SICURO DI VOLER CANCELLARE "+txtNome.getText()+" "+txtCognome.getText()+"?")
                        .setPositiveButton("SI", new DialogInterface.OnClickListener(){

                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                helper.cancella(txtNome.getText().toString(),txtCognome.getText().toString());
                                Cursor c=helper.queryTotale(editNome.getText().toString(),editCognome.getText().toString());
                                crs=myCursorAdapter.swapCursor(c);

                            }
                        })
                        .setNegativeButton("NO",new DialogInterface.OnClickListener(){

                            @Override
                            public void onClick(DialogInterface dialog, int which) {

                            }
                        })
                        .show();
                //------FINE CODICE per la finestra di dialogo


                return true;


            }
        });
Funziona.
Per il NO ho lasciato vuoto il comando, dal momento che non intendo fare nulla.
Notevole (marcato in rosso) il fatto che ottengo un messaggio di errore che chiama in causa il Theme usato, se non imposto il context coerentemente con quello dell'Activity.
Per far questo ho aggiunto all'inizio:
public class MainActivity extends AppCompatActivity {
    Context context;
    Cursor crs;
    EditText editCognome;
    EditText editNome;
    MyCursorAdapter myCursorAdapter;
    ListView listView;
    Helper helper;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        context=this;
impostando poi a context il contesto della AlertDialog.Builder.
E così funziona.

Cancellare un record in risposta a un long click sulla ListView

Obiettivo: Cancellare un record in risposta a un long click sulla ListView.
Costruire nell'ambito dell'Helper la funzione che cancella un record dalla tabella del database.

    //funzione che cancella un record dalla tabella tblnomi del database
    public void cancella(String s){
        SQLiteDatabase db=this.getWritableDatabase();
        db.delete("tblnomi","nome=?",new String[]{s});

    }
Adesso la uso:
        listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
            @Override
            public boolean onItemLongClick(AdapterView parent, View view, int position, long id) {
                helper.cancella("Giuseppe");
                return true;
            }
        });
Al long click di qualunque elemento della ListView dovrei avere la cancellazione del record il cui campo "nome" equivale a "Giuseppe".
Proviamolo...

Sì, me lo ha cancellato.
Ho dimenticato di ricaricare l'adapter, però:
Procedo (con un altro nome perché di Giuseppe ce n'era uno solo).
        listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
            @Override
            public boolean onItemLongClick(AdapterView parent, View view, int position, long id) {
                helper.cancella("Giovanni");
                Cursor c=helper.queryTotale(editNome.getText().toString(),editCognome.getText().toString());
                crs=myCursorAdapter.swapCursor(c);
                return true;
            }
        });
Sì, funziona.

Ora devo però rendere la cosa specifica: devo risalire al nome riportato nell'elemento della listView.
Ecco, dopo aver trovato e sperimentato il modo di accedere alle textView in cui sono riportati nome e cognome, modifico la funzione di helper in modo da includere anche il cognome:
    //funzione che cancella un record dalla tabella tblnomi del database
    public void cancella(String n, String c){
        SQLiteDatabase db=this.getWritableDatabase();
        db.delete("tblnomi","nome=? and cognome=?",new String[]{n,c});
...e riscrivo la funzione in Main:
        listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
            @Override
            public boolean onItemLongClick(AdapterView parent, View view, int position, long id) {
                TextView txtNome=(TextView)view.findViewById(R.id.txtNome);
                TextView txtCognome=(TextView)view.findViewById(R.id.txtCognome);
                helper.cancella(txtNome.getText().toString(),txtCognome.getText().toString());
                Cursor c=helper.queryTotale(editNome.getText().toString(),editCognome.getText().toString());
                crs=myCursorAdapter.swapCursor(c);
                return true;
            }
        });
Vediamo se funziona...

Funziona egregiamente!!! Obiettivo raggiunto!

sabato 26 novembre 2016

Database SQLite: ottenere record unici per un campo o per una combinazione di campi.

Ho re-imparato come rendere unici i records di un database, rendendo unico il valore di un campo e rendendo unica una combinazione di valori di campi.

Rendere unico il valore di un singolo campo:
public class Helper extends SQLiteOpenHelper {
    public Helper(Context context) {
        super(context, "database.db",null,1);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL("CREATE TABLE NOMI(_ID INTEGER PRIMARY KEY, " +
                "NOME TEXT NOT NULL UNIQUE, COGNOME TEXT)");
    }

    public void save(String nome, String cognome){
        SQLiteDatabase db=this.getWritableDatabase();
        ContentValues values=new ContentValues();
        values.put("NOME", nome);
        values.put("COGNOME", cognome);
        try {
            db.insertOrThrow("NOMI", null, values);
        }catch(Exception e){
            System.out.println("ERRORE");
        }
    }
Per avere una gestione dell'errore devo usare insertOrThrow in luogo di insert.

Ora provo a inserire due records con il nome uguale.
        helper=new Helper(this);
        helper.save("Mario","Rossi");
        helper.save("Mario","Bianchi");
    }
}
11-26 17:55:24.511 31319-31319/? I/System.out: ERRORE

e il database contiene solo il primo record.
Rendere unico il valore di una combinazione di campi:
    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL("CREATE TABLE NOMI(_ID INTEGER PRIMARY KEY, " +
                "NOME TEXT, COGNOME TEXT, UNIQUE(NOME, COGNOME))");
    }

    public void save(String nome, String cognome){
        SQLiteDatabase db=this.getWritableDatabase();
        ContentValues values=new ContentValues();
        values.put("NOME", nome);
        values.put("COGNOME", cognome);
        try {
            db.insertOrThrow("NOMI", null, values);
        }catch(Exception e){
            System.out.println("ERRORE");
        }
    }
Ora riprovo a inserire i due record di prima con solo il nome uguale:
    Helper helper;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        helper=new Helper(this);
        helper.save("Mario","Rossi");
        helper.save("Mario","Bianchi");
    }
}
Nessuna segnalazione di errore:


...e la tabella del database contiene tutti e due i records.

Ora provo a inserire due record uguali per nome e per cognome:
    Helper helper;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        helper=new Helper(this);
        helper.save("Mario","Rossi");
        helper.save("Mario","Rossi");
    }
}
11-26 18:02:17.103 3062-3062/? I/System.out: ERRORE

...e il database contiene solo il primo record.

Ora vorrei un'altra funzione: cercare un record sul database, se presente restituire l'id, se assente inserire il nuovo dato.
Si potrebbe gestire con le funzioni di cui sopra...

giovedì 17 novembre 2016

Misure dei tasti J-Keyboard

Ho stabilito le misure per un tasto, almeno per quello DO MI FA SI:


Ora vado avanti con il posizionamento di altri tasti.

440 Hz audioTrack

Ecco il codice per l'emissione di un LA (440 Hz):
public class MainActivity extends AppCompatActivity {

    AudioTrack audioTrack;
    Button bottone;
    int minBufferSize=AudioTrack.getMinBufferSize(44100, AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_8BIT);
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        bottone=(Button)findViewById(R.id.button);

        playSound(440,10);


    }

    private void playSound(double frequency, int duration) {
        // AudioTrack definition
        int mBufferSize = AudioTrack.getMinBufferSize(44100,
                AudioFormat.CHANNEL_OUT_STEREO,
                AudioFormat.ENCODING_PCM_8BIT);

        AudioTrack mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 44100,
                AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT,
                mBufferSize, AudioTrack.MODE_STREAM);

        // Sine wave
        double[] mSound = new double[1000000];
        short[] mBuffer = new short[1000000];
        for (int i = 0; i < mSound.length; i++) {
            mSound[i] = Math.sin((2.0*Math.PI * i/(44100/frequency)));
            mBuffer[i] = (short) (mSound[i]*Short.MAX_VALUE);
        }

        mAudioTrack.setStereoVolume(AudioTrack.getMaxVolume(), AudioTrack.getMaxVolume());
        mAudioTrack.play();

        mAudioTrack.write(mBuffer, 0, mSound.length);
        mAudioTrack.stop();
        mAudioTrack.release();

    }

} 
Bene. Me lo salvo e poi vedrò come utilizzarlo.

martedì 15 novembre 2016

Audiotrack: per far emettere toni puri al cellulare.

Ora proviamo a costruire un dispositivo che emetta una frequenza sonora.
Dobbiamo costruire la classe AndroidAudioDevice.
Ci provo.

Dichiariamo intanto le due variabili, l'una che mi sembra la più importante, di tipo AudioTrack, e l'altra che è una matrice di short, il buffer.
    class AndroidAudioDevice{
        AudioTrack track;
        short[] buffer = new short[1024];
    }
Bene. E ora?
Ora c'è il costruttore, in cui: si ottiene un int minSize, che dovrebbe essere la minima dimensione del buffer.
Vediamolo
    class AndroidAudioDevice{
        AudioTrack track;
        short[] buffer = new short[1024];

        int minSize=AudioTrack.getMinBufferSize(44100,
                AudioFormat.CHANNEL_CONFIGURATION_MONO,
                AudioFormat.ENCODING_PCM_16BIT);
    }
Quindi si definisce track:
        public AndroidAudioDevice() {
            int minSize = AudioTrack.getMinBufferSize(44100,
                    AudioFormat.CHANNEL_CONFIGURATION_MONO,
                    AudioFormat.ENCODING_PCM_16BIT);

            track = new AudioTrack(AudioManager.STREAM_MUSIC,
                    44100,
                    AudioFormat.CHANNEL_CONFIGURATION_MONO,
                    AudioFormat.ENCODING_PCM_16BIT,
                    minSize,
                    AudioTrack.MODE_STREAM);
           track.play();
        }
Aggiungo track.play() ed ecco tutto il costruttore.
Ora bisognerebbe creare il metodo writeSamples che ha per parametri un array di float.
Provo...
        public void writeSamples(float[] samples){
            fillBuffer(samples);
        }
Questo passa subito al metodo privato fillBuffer (che ancora non esiste) il suo parametro array di float.
Quindi scrive su track quello che legge sul buffer che è "di ritorno" da fillBuffer.
        public void writeSamples(float[] samples){
            fillBuffer(samples);
            track.write(buffer,0,samples.length);
        }
E ora resta da definire fillBuffer.

        private void fillBuffer(float[] samples){
            
        }
nel quale prima si adeguano le dimensioni del buffer all'array di samples.
        private void fillBuffer(float[] samples){
            if(buffer.length < samples.length){
                buffer=new short[samples.length];
            }
        }
e quindi si prendono uno per uno tutti i float di samples e si convertono in short per trasferirli nell'array di short che è buffer.
                for(int i=0;i<samples.length;i++){
                    buffer[i]=(short)(samples[i]*Short.MAX_VALUE);
                }
Ecco.
Mi riscrivo qui il codice completo:
public class MainActivity extends AppCompatActivity {


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

    class AndroidAudioDevice{
        AudioTrack track;
        short[] buffer = new short[1024];

        public AndroidAudioDevice() {
            int minSize = AudioTrack.getMinBufferSize(44100,
                    AudioFormat.CHANNEL_CONFIGURATION_MONO,
                    AudioFormat.ENCODING_PCM_16BIT);

            track = new AudioTrack(AudioManager.STREAM_MUSIC,
                    44100,
                    AudioFormat.CHANNEL_CONFIGURATION_MONO,
                    AudioFormat.ENCODING_PCM_16BIT,
                    minSize,
                    AudioTrack.MODE_STREAM);
            track.play();
        }

        public void writeSamples(float[] samples){
            fillBuffer(samples);
            track.write(buffer,0,samples.length);
        }

        private void fillBuffer(float[] samples){
            if(buffer.length < samples.length){
                buffer=new short[samples.length];

                for(int i=0;i<samples.length;i++){
                    buffer[i]=(short)(samples[i]*Short.MAX_VALUE);
                }
            }
        }
    }
} 

Copiare un file da una parte all'altra del dispositivo.

Ho avuto non poche difficoltà nel copiare un file da una parte all'altra della memoria del cellulare.
Alla fine ci sono riuscito.
Credo di padroneggiare abbastanza le tecniche per la creazione di un file, ma ho avuto difficoltà nella copia mediante FileInputStream e FileOutputStream

Ora riprovo a trasferirmi un file dalla cartella DCIM/Camera alla cartella DCIM/Screenshots.
Il file è casa.jpg.

Ottengo l'accesso all'external storage.
File sd= Environment.getExternalStorageDirectory();
Voglio sapere qual è il Path di questo file. Come faccio?
Provo:
        File sd= Environment.getExternalStorageDirectory();
        System.out.println("Path: "+Environment.getExternalStorageDirectory().getAbsolutePath());
11-15 17:44:47.621 13976-13976/? I/System.out: Path: /storage/emulated/0
Ora voglio i nomi di tutti i sottofiles e le sottocartelle di questo External Storage.
        File sd= Environment.getExternalStorageDirectory();
        System.out.println("Path: "+Environment.getExternalStorageDirectory().getAbsolutePath());
        
        for(File f : sd.listFiles()){
            System.out.println(f.toString());
            
        }
Proviamo:
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/Samsung
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/Android
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/DCIM
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/Music
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/Podcasts
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/Ringtones
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/Alarms
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/Notifications
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/Pictures
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/Movies
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/Download
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/Playlists
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/WhatsApp
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/.aide
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/AppProjects
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/docs
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/Voice Recorder
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/com.facebook.katana
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/runtastic
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/com.facebook.orca
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/.facebook_cache
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/MBSTPH
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/MBSTGO
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/VIRAL
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/LazyList
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/Tencent
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/pacer_steps_chart.jpg
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/JavaScript
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/.mrnom
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/adb
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/CallRecordings
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/.SMT
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/.Studio
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/.SPenSDK30
Okay...

Ora voglio scendere in DCIM/Camera/casa.jpg.
DCIM è una sottocartella dell'External Storage.
Come faccio a ottenere un file?
        File sd= Environment.getExternalStorageDirectory();

        File src=new File(sd,"//DCIM//Camera//casa.jpg");

        File dst=new File(sd,"//DCIM//Screenshots//casa.jpg");
Ecco i files corrispondenti all'immagine originale e alla copia che voglio creare.

Ora devo usare gli Stream...
E qua c'è tutto l'apparato.
        File sd= Environment.getExternalStorageDirectory();

        File src=new File(sd,"//DCIM//Camera//casa.jpg");

        File dst=new File(sd,"//DCIM//Screenshots//casa.jpg");

        try {
            FileInputStream in=new FileInputStream(src);
            FileOutputStream out=new FileOutputStream(dst);
            byte[] buffer=new byte[1024];
            int len;
            while((len=in.read(buffer))!=-1) {
                out.write(buffer,0,len);
            }
            in.close();
            out.flush();
            out.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
Intanto vediamo se funziona...

Sì, è riuscito! In Screenshots mi trovo il file casa.jpg che funziona.


Ora vediamo come "funziona".
Il metodo read(buffer) di FileInputStream legge dei bytes dal file in un buffer di bytes..
Restituisce un int che rappresenta il numero di bytes letti, mentre se non ci sono più bytes da leggere dal file al buffer, restituisce -1.
Ecco perché
            while((len=in.read(buffer))!=-1) {
                out.write(buffer,0,len);
            }
...finché len, che è uguale al numero di bytes letto nel buffer, è diverso da -1, va scritto nel FileOutputStream quel numero di bytes preso dal buffer.
E forse, finalmente lo abbiamo capito.

lunedì 14 novembre 2016

AudioTrack, per far emettere toni allo smartphone. Scontro con i numeri in virgola mobile.

Questo è il link per la riproduzione di toni.

Bisogna costruire una classe AndroidAudioDevice.
Poi si definiscono delle variabili che sono una di tipo AudioTrack e una di tipo matrice di short (short[]).
Quindi il costruttore.
Nel costruttore si definisce minSize (che dovrebbe essere la dimensione minima del buffer).
minsize viene definito usando il metodo AudioTrack.getBufferSize.
Questo deve restituire un int.
Usa come parametri 44100, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT.

44100 è la frequenza di campionamento.
Il secondo parametro è MONO o STEREO.
Il terzo è il formato dei dati, 8 bit o 16 bit o altro (che al momento non mi viene in mente).

Segue il costruttore dell'oggetto di classe AudioTrack specificato fra le variabili iniziali, che usa come parametri il già visto minSize più i parametri che il metodo getMinSize utilizzava, più altri due parametri.
Quindi nel costruttore si dà un valore alla variabile track di classe AudioTrack, e quindi si invoca il metodo play() di questa AudioTrack.

La classe che sto creando, ossia AndroidAudioDevice, oltre al costruttore ha anche il metodo writeSamples().
   public void writeSamples(float[] samples) 
   { 
      fillBuffer( samples );
      track.write( buffer, 0, samples.length );
   }
Questo metodo ha come parametro una matrice di tipo float[] e la usa come parametro del metodo privato fillBuffer().
Vediamolo:
   private void fillBuffer( float[] samples )
   {
      if( buffer.length < samples.length )
         buffer = new short[samples.length];
 
      for( int i = 0; i < samples.length; i++ )
         buffer[i] = (short)(samples[i] * Short.MAX_VALUE);
   } 
Accoglie come parametro la matrice float[] che gli viene passata da writeSamples che lo invoca.
Con le prime due righe...
      if( buffer.length < samples.length )
         buffer = new short[samples.length];
...se la grandezza del buffer è di dimensioni inferiori a quella del parametro che viene passato, crea una nuova matrice come buffer, di dimensioni uguali a quelle del parametro;
quindi per ogni elemento contenuto nella matrice samples si prende un elemento del buffer e gli si dà il valore, castato a short, del corrispondente elemento di samples moltiplicato per Short.MAX_VALUE.
Cosa significa questo Short.MAX_VALUE?


Viene moltiplicato un tipo float per Short.MAX_VALUE.
Vediamo se riesco a trovare qualche ispirazione cercando intorno alla conversione short -> float.

Per capire tutto questo forse devo ripassarmi bene come sono strutturati i numeri in virgola mobile.

Quando io moltiplico un float per uno short, lo short viene preventivamente convertito in float?
Se do a uno short un valore superiore al suo massimo, che valore assume la variabile?

Vediamo...

Mi devo costruire dei modelli.

Ecco, come ricordavo, la moltiplicazione di un float per uno short dà un risultato di tipo float.
        float numeroFloat = 5.2f;

        short numeroShort=2;

        float risultato=numeroFloat * numeroShort;

        System.out.println(risultato);
Ottengo come risultato:
11-14 17:06:10.752 4130-4130/? I/System.out: 10.4

Se invece do al risultato il tipo short:
        float numeroFloat = 5.2f;

        short numeroShort=2;

        short risultato=numeroFloat * numeroShort;

        System.out.println(risultato);
...ottengo un errore, perché il risultato non può essere short ma è float.
Ora è il caso di ripassare a dovere i numeri in virgola mobile, perché credo che una risposta mi può venire solo da lì.

domenica 13 novembre 2016

Conflitti nell'attivazione del timer.

Vado cercando il modo di schematizzare quando sia necessario avere il timer in funzione e quando no.
Cerchiamo di iniziare dalle cose più semplici.

Innanzitutto, quando si avvia il programma il timer è già in funzione.
Questo innesca, dopo una quantità di minuti prestabilita, la comparsa della Activity B.
L'Activity B, una volta chiusa, innnesca di nuovo il timer per la ricomparsa di se stessa, e così via, fin quando non si interrompa il timer mediante un pulsante presente sull'Activity A.
Il primo timer è innescato da A, il secondo sempre da B che si richiama da solo alla scadenza del tempo prestabilito.

E' possibile che, se si schiaccia due volte il pulsante su A che innesca il timer, si abbiano due timer, in modo che B appaia due volte, ossia che ne vengano aperte più istanze?
Questo sarebbe problematico, e devo verificarlo.
Nel mio caso, il timer si avvia all'accensione dello switch.
Questo timer dovrebbe essere l'unico avviato dall'activity A.
Quindi dovrebbe proseguire l'innesco automatico del timer da parte dell'activity B che richiama se stessa.
Ci sono però altre tre occasioni in cui viene innescato il timer esternamente al normale ciclo dell'activity B che richiama se stessa:
  1. alla conferma di una nuova voce;
  2. alla pressione del tasto VISUALIZZA (indirettamente mediante apertura di activity B con successivo innesco del timer);
  3. dall'activity SETTINGS nell'evento onPause (se questo prelude al ritorno all'Activity A).
Me li rinfresco da codice:
        bttConferma=(Button)findViewById(R.id.bttConferma);
        bttConferma.setEnabled(false);
        bttConferma.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {

                helper.save(lista.get(0));
                bttConferma.setEnabled(false);
                bottone.setEnabled(true);
                avviaTimer();
                mOnAlarmChangeListener.OnAlarmChange();
            }
        });
        bttVisualizza=(Button)findViewById(R.id.bttVisualizza);
        bttVisualizza.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                cancellaTimer();
                Intent intent=new Intent(getApplicationContext(),Lista.class);
                startActivity(intent);
            }
        });


Activity SETTINGS:
    @Override
    protected void onPause(){
        super.onPause();

        if(permessoAvvioTimer)avviaTimer();
    }
Se il timer viene innescato da una di queste e successivamente dall'altra, si hanno due cicli di Timer sovrapposti.
Provo a causarlo coscientemente togliendo cancellaTimer() quando viene visualizzata l'altra activity dal pulsante su A.
        bttVisualizza=(Button)findViewById(R.id.bttVisualizza);
        bttVisualizza.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                //cancellaTimer();
                Intent intent=new Intent(getApplicationContext(),Lista.class);
                startActivity(intent);
            }
        });
E proviamo ad accendere l'interruttore e prima che si apra B a visualizzarla col pulsante.

Sì, ottengo un conflitto e vengono aperte due istanze di B!
Forse l'unica è inserire un cancellaTimer() ogni volta prima che il timer venga innescato esternamente.
Lo faccio nei punti prima considerati:
1) Interruttore:
        switch1.setOnCheckedChangeListener(new Switch.OnCheckedChangeListener(){

            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if(isChecked){
                    cancellaTimer();
                    avviaTimer();
                    mOnAlarmChangeListener.OnAlarmChange();
                }else{
                    cancellaTimer();
                    mOnAlarmChangeListener.OnAlarmChange();
                }
            }
        });


2) Conferma di una nuova voce:
        bttConferma.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {

                helper.save(lista.get(0));
                bttConferma.setEnabled(false);
                bottone.setEnabled(true);
                txtVocale.setText(null);
                cancellaTimer();
                avviaTimer();
                mOnAlarmChangeListener.OnAlarmChange();
            }
        });


3) Pressione del tasto Visualizza:
        bttVisualizza.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                cancellaTimer();
                Intent intent=new Intent(getApplicationContext(),Lista.class);
                startActivity(intent);
            }
        });
4) Da SETTINGS in onPause:
    @Override
    protected void onPause(){
        super.onPause();

        if(permessoAvvioTimer){
            cancellaTimer();
            avviaTimer();
        }
    }


Dovrei essere piuttosto sicuro, adesso...

Semplifico il codice dell'interruttore:
        switch1.setOnCheckedChangeListener(new Switch.OnCheckedChangeListener(){

            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                cancellaTimer();
                if(isChecked) avviaTimer();
                
                mOnAlarmChangeListener.OnAlarmChange();
                
                
            }
        });
E ora sperimentiamo nel tempo...

venerdì 11 novembre 2016

Come terminare una Activity e rimuoverla dai Recent Tasks.

A questo punto, le cose da fare sono diverse.
Per prima cosa, terminare la schermata iniziale.
Ho aggiunto un'altra voce nel menu:
<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/stopTimer"
        android:title="Stop Timer">

    </item>
    <item
        android:id="@+id/showSettings"
        android:title="Impostazioni" >
    </item>
    <item
        android:id="@+id/terminaSchermata"
        android:title="Nascondi" >
    </item>
</menu> 
E la gestisco con questo codice:
    @Override
    public boolean onOptionsItemSelected(MenuItem menuItem){
        switch(menuItem.getItemId()){
            case R.id.stopTimer:
                cancellaTimer();
                break;
            case R.id.showSettings:
                cancellaTimer();;
                Intent i=new Intent(MainActivity.this,Settings.class);
                startActivity(i);
                break;
            case R.id.terminaSchermata:
                    finish();
            }
                
        }
        return true;
    }
Però mi rimane nei recent tasks.
E' già impostato nel manifest autoRemoveFromRecents:
        <activity
            android:name=".Lista"
            android:autoRemoveFromRecents="true"
            android:screenOrientation="portrait" /> 
Ma, come ho imparato, nelle versioni superiori a 21 è necessario usare, per terminare e rimuovere dai recenti, finishAndRemoveTasks.
Lo uso e ottengo errore perché l'applicazione è indirizzata anche a versioni più antiche della 21.
Per far ciò, trovo il codice che adatta l'istruzione alla versione:
            case R.id.terminaSchermata:
                if(Build.VERSION.SDK_INT>=21){
                    finishAndRemoveTask();
                }else{
                    finish();
                }
E così funziona, rimuovendo completamente l'activity dai recenti una volta terminata.

Ripasso BroadcastReceiver nel tentativo di intercettare il tasto Home.

Provo a creare un broadcastReceiver.
Lo creo nello stesso modulo.

L'ho nidificato nella classe dell'Activity:
    public class MyReceiver extends BroadcastReceiver{

        @Override
        public void onReceive(Context context, Intent intent) {
            
        }
    }
Il metodo onReceive viene dato di default.

Per la registrazione, bisogna prima istanziare la classe creata.
Ci provo...

Scrivo intanto un'azione per l'evento onReceive:
    public class MyReceiver extends BroadcastReceiver{

        @Override
        public void onReceive(Context context, Intent intent) {
            System.out.println("ricevuto");
        }
    }


Ecco:
public class MainActivity extends AppCompatActivity {

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

        myReceiver=new MyReceiver();

    }
Ora devo definire un IntentFilter e registrare il Receiver insieme a questo IntentFilter in onResume, deregistrandolo in onPause
Provo a usare l'intentFilter che ho trovato nella costruzione dell'HomeWatcher:
        myReceiver=new MyReceiver();
        intentFilter=new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
Quindi registro in onResume e deregistro in onPause:
    @Override
    protected void onResume(){
        super.onResume();
        registerReceiver(myReceiver,intentFilter);
    }

    @Override
    protected void onPause(){
        super.onPause();
        unregisterReceiver(myReceiver);
    }
Funziona. Intercetta la pressione del tasto Home.
Ma non mi previene la chiusura dell'activity.

martedì 8 novembre 2016

Sequenza dei pulsanti nell'applicazione J-Ag.

Ottimizziamo il ciclo...

Quando appare la schermata principale, i tasti Conferma e Annulla sono disattivati.
Sistemo il pulsante Annulla.

Ecco: a inizio di schermata, NUOVO è attivo, mentre CONFERMA e ANNULLA sono inattivi.

Poi, una volta recepito il comando verbale, si attivano CONFERMA e ANNULLA per le due scelte.


E' necessario che non vi sia l'esibizione dell'agendina al termine di questa schermata, ma semplicemente un avvio e uno stop del timer.
Correggo.

r Fatto.
E ho trovato una successione per i tre pulsanti, NUOVO, CONFERMA e ANNULLA.
Quando appare la schermata, NUOVO è abilitato, mentre CONFERMA e ANNULLA sono disabilitati, in modo che si possa premere solo NUOVO.
        bttAnnulla=(Button) findViewById(R.id.bttAnnulla);
        bttAnnulla.setEnabled(false);
.....

        bttConferma=(Button)findViewById(R.id.bttConferma);
        bttConferma.setEnabled(false);
Quando schiaccio NUOVO, dopo la ricezione della voce, si va a onActivityResult e, SE IL RISULTATO E' RESULT_OK, ossia -1, si abilitano CONFERMA e ANNULLA, mentre si disabilita NUOVO:
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data){
        super.onActivityResult(requestCode, resultCode, data);
        if(resultCode==RESULT_OK){
            lista=data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
            tts.speak(lista.get(0),TextToSpeech.QUEUE_FLUSH,null);

            
            bottone.setEnabled(false);
            bttConferma.setEnabled(true);
            bttAnnulla.setEnabled(true);

        }else{r
            
            avviaTimer();
        }



    }
Se si sceglie di schiacciare CONFERMA, viene salvato il dato, quindi si disabilitano CONFERMA e ANNULLA mentre si riabilita NUOVO, e riparte il TIMER
        bttConferma.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {

                helper.save(lista.get(0));
                bttConferma.setEnabled(false);
                bttAnnulla.setEnabled(false);
                bottone.setEnabled(true);
                avviaTimer();
            }
        });
Se invece si sceglie di schiacciare ANNULLA, non viene salvato nessun dato, e ugualmente si disabilitano CONFERMA e ANNULLA, mentre si riabilita NUOVO, e riparte il TIMER.
        bttAnnulla.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                bttConferma.setEnabled(false);
                bttAnnulla.setEnabled(false);
                bottone.setEnabled(true);
                avviaTimer();
            }
        });


Se invece si evita di dare un input vocale e si schiaccia INDIETRO o si schiaccia al di fuori del prompt vocale, si agisce più a monte, e si dà un risultato sbagliato ad onActivityResult, per cui RESULT_OK è pari a zero invece che a -1: di conseguenza entra in gioco questo codice, che semplicemente riavvia il TIMER: dal momento che RESULT_OK è pari a zero, non si attivano CONFERMA e ANNULLA.
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data){
        super.onActivityResult(requestCode, resultCode, data);
        if(resultCode==RESULT_OK){
            lista=data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
            tts.speak(lista.get(0),TextToSpeech.QUEUE_FLUSH,null);


            bottone.setEnabled(false);
            bttConferma.setEnabled(true);
            bttAnnulla.setEnabled(true);

        }else{

            avviaTimer();
        }

    }

domenica 6 novembre 2016

Aggiunta di una suoneria e codice per l'impostazione di una suoneria personalizzata.

Riscriviamo un codice che suoni la suoneria di default.
        Uri ringtoneUri= RingtoneManager.getActualDefaultRingtoneUri(getApplicationContext(),RingtoneManager.TYPE_NOTIFICATION);
        Ringtone ringtone=RingtoneManager.getRingtone(getApplicationContext(),ringtoneUri);
        ringtone.play();
Questo suona la suoneria di tipo NOTIFICATION.

Esistono anche altri tipi:
        Uri ringtoneUri= RingtoneManager.getActualDefaultRingtoneUri(getApplicationContext(),RingtoneManager.TYPE_ALARM);
        Ringtone ringtone=RingtoneManager.getRingtone(getApplicationContext(),ringtoneUri);
        ringtone.play();
Questo invee suona la suoneria di tipo ALARM (che non la smette più!)

Andiamo a vedere l'altro tipo:

       Uri ringtoneUri= RingtoneManager.getActualDefaultRingtoneUri(getApplicationContext(),RingtoneManager.TYPE_RINGTONE);
        Ringtone ringtone=RingtoneManager.getRingtone(getApplicationContext(),ringtoneUri);
        ringtone.play();
Questo suona la suoneria telefonica.


C'è poi il capitolo della scelta della suoneria personalizzata per l'applicazione, che non ricordo.
Rinfreschiamo la memoria...

Ci si avvale di SharedPreferences.
Se non è stato salvato niente, ossia se la stringa ripescata da SharedPreferences è vuota, si usa la suoneria di default, altrimenti si Uri.parsa la stringa reperita in SharedPreferences.
Devo definire, però SharedPreferences.
    SharedPreferences SP;

.....

        SP=getApplicationContext().getSharedPreferences("suoneria",Context.MODE_PRIVATE);
Fatto questo, torno a modificare il codice che stabilisce la suoneria:
        SP=getApplicationContext().getSharedPreferences("suoneria",Context.MODE_PRIVATE);

        Uri ringtoneUri;
        Ringtone ringtone=null;
        String strSuoneria=SP.getString("suoneria","");
        if(TextUtils.isEmpty(strSuoneria)){
            ringtoneUri= RingtoneManager.getActualDefaultRingtoneUri(getApplicationContext(),RingtoneManager.TYPE_NOTIFICATION);
            
            
        }else{
            ringtoneUri=Uri.parse(strSuoneria);
        }
        ringtone=RingtoneManager.getRingtone(getApplicationContext(),ringtoneUri);
        ringtone.play();
Il discorso è semplice.

E funziona.
Ora devo provvedere una sezione per la scelta della suoneria.
Creo un'activity Settings per la regolazione di questa e altre funzioni, raggiungibile tramite menu.

Ecco l'XML del menu di MainActivity modificato:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/stopTimer"
        android:title="Stop Timer">

    </item>
    <item
        android:id="@+id/showSettings"
        android:title="Impostazioni" >
    </item>
</menu> 
Ed ecco il menu in MainActivity modificato:
    @Override
    public boolean onCreateOptionsMenu(Menu menu){
        super.onCreateOptionsMenu(menu);
        MenuInflater inflater=this.getMenuInflater();
        inflater.inflate(R.menu.miomenu,menu);
        return true;
    }



    @Override
    public boolean onOptionsItemSelected(MenuItem menuItem){
        switch(menuItem.getItemId()){
            case R.id.stopTimer:
                cancellaTimer();
                break;
            case R.id.showSettings:
                Intent i=new Intent(MainActivity.this,Settings.class);
                startActivity(i);
        }
        return true;
    }
E in Settings possiamo scrivere il codice per la scelta delle suonerie.
Prima l'intent:
        bttSceltaSuoneria.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                Intent intent=new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
                intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE,RingtoneManager.TYPE_NOTIFICATION);
                startActivityForResult(intent,0);

            }
        });
(subordinato a un Button)

Quindi onActivityResult:
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data){
        Uri uri=data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
        SharedPreferences.Editor editor=SP.edit();
        editor.putString("suoneria",uri.toString());
        editor.commit();
    }
E vediamo se combina... Comunque ho fatto una faticaccia a scrivere questo codice, completamente dimenticato.

Feedback vocale e conferma

Adesso inserisco un feedback.
Quando parlo, potrei non essere stato capito bene. E' bene che ci sia una risposta che mi permetta di verificare se il mio comando è stato ben capito.
Organizzo una risposta vocale...

Inizializzo il tts:
    TextToSpeech tts;

.....

        tts=new TextToSpeech(getApplicationContext(),new TextToSpeech.OnInitListener(){

            @Override
            public void onInit(int status) {
                if(status==TextToSpeech.SUCCESS){
                    tts.setLanguage(Locale.ITALIAN);
                }
            }
        });
Ora, quando ho parlato, arrivo a OnActivityResult.
E' qui che devo inserire il feedback.
Devo inserire un button "Annulla", però...

O meglio un button "Conferma".
Ecco tutto ciò che ho realizzato:


MainActivity:
public class MainActivity extends AppCompatActivity {

    Button bttConferma;
    TextToSpeech tts;
    Helper helper;
    Button bottone;
    ArrayList lista;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        bttConferma=(Button)findViewById(R.id.bttConferma);
        bttConferma.setEnabled(false);
        bttConferma.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {

                helper.save(lista.get(0));
                bttConferma.setEnabled(false);
                Intent i=new Intent(getApplicationContext(),Lista.class);
                startActivity(i);
            }
        });

        tts=new TextToSpeech(getApplicationContext(),new TextToSpeech.OnInitListener(){

            @Override
            public void onInit(int status) {
                if(status==TextToSpeech.SUCCESS){
                    tts.setLanguage(Locale.ITALIAN);
                }
            }
        });

        helper=new Helper(this);

        bottone=(Button)findViewById(R.id.button);
        bottone.setOnClickListener(new Button.OnClickListener(){

            @Override
            public void onClick(View v) {
                bttConferma.setEnabled(false);
                cancellaTimer();
                Intent intent=new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
                intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
                intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.ITALIAN);
                intent.putExtra(RecognizerIntent.EXTRA_PROMPT,"Parla ora");
                startActivityForResult(intent,0);

            }
        });

    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data){
        super.onActivityResult(requestCode, resultCode, data);
        if(resultCode==RESULT_OK){
            lista=data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
            tts.speak(lista.get(0),TextToSpeech.QUEUE_FLUSH,null);
            bttConferma.setEnabled(true);

        }else{
            Intent i=new Intent(getApplicationContext(),Lista.class);
            startActivity(i);
        }



    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu){
        super.onCreateOptionsMenu(menu);
        MenuInflater inflater=this.getMenuInflater();
        inflater.inflate(R.menu.miomenu,menu);
        return true;
    }



    @Override
    public boolean onOptionsItemSelected(MenuItem menuItem){
        switch(menuItem.getItemId()){
            case R.id.stopTimer:
                cancellaTimer();
                break;
        }
        return true;
    }

    private void cancellaTimer(){
        Intent intent=new Intent(getApplicationContext(),
                Lista.class);
        PendingIntent pendingIntent=PendingIntent.getActivity(getApplicationContext(),
                0,
                intent,
                0);
        AlarmManager alarmManager=(AlarmManager)getApplicationContext().getSystemService(Context.ALARM_SERVICE);
        alarmManager.cancel(pendingIntent);
        if(pendingIntent!=null)pendingIntent.cancel();
    }

    private void avviaTimer(int secondi){
        //stabilisce il ciclo
        Intent intent=new Intent(getApplicationContext(),Lista.class);
        PendingIntent pendingIntent=PendingIntent.getActivity(getApplicationContext(),0,intent,0);
        AlarmManager alarmManager=(AlarmManager) getApplicationContext().getSystemService(ALARM_SERVICE);
        alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime()+Const.secondi*1000,pendingIntent);

    }

}


Lista:
public class Lista extends AppCompatActivity {

    Button bttChiudi;
    Helper helper;
    MyCursorAdapter myCursorAdapter;
    ListView listView;

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

        bttChiudi=(Button)findViewById(R.id.bttChiudi);
        bttChiudi.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                finish();
            }
        });
        helper=new Helper(this);
        Cursor crs=helper.query();
        myCursorAdapter=new MyCursorAdapter(this,crs);
        listView=(ListView)findViewById(R.id.listView);
        listView.setAdapter(myCursorAdapter);





    }

    @Override
    protected void onDestroy(){
        super.onDestroy();
        Cursor crs=helper.query();
        if(crs.getCount()!=0) {

            Intent intent = new Intent(Lista.this, Lista.class);
            PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(), 0, intent, 0);
            AlarmManager alarmManager = (AlarmManager) getApplicationContext().getSystemService(ALARM_SERVICE);
            alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + Const.secondi * 1000, pendingIntent);
        }

    }

    class MyCursorAdapter extends CursorAdapter {

        Context context;
        public MyCursorAdapter(Context context, Cursor c) {
            super(context, c);
            this.context=context;
        }

        @Override
        public View newView(Context context, Cursor cursor, ViewGroup parent) {
            LayoutInflater layoutInflater=(LayoutInflater)context.getSystemService(LAYOUT_INFLATER_SERVICE);
            return layoutInflater.inflate(R.layout.row,null);
        }

        @Override
        public void bindView(View view, Context context, Cursor cursor) {

            TextView textView=(TextView)view.findViewById(R.id.textView);
            final TextView textView2=(TextView)view.findViewById(R.id.textView2);
            Button button=(Button)view.findViewById(R.id.button2);
            textView.setText(cursor.getString(cursor.getColumnIndexOrThrow("voce")));
            textView2.setText(""+cursor.getInt(cursor.getColumnIndexOrThrow("_id")));
            button.setOnClickListener(new View.OnClickListener(){

                @Override
                public void onClick(View v) {
                    helper.cancella(Integer.parseInt(textView2.getText().toString()));
                    myCursorAdapter.getCursor().requery();
                }
            });
        }
    }



}

HTML: Ripasso del layout a due colonne.

Partiamo subito sul sodo: mi serve un layout a due colonne.

Assodato che stabilire la larghezza della colonna laterale in termini percentuali sia sconveniente per la possibile comparsa di scrollbars orizzontali, questa va impostata in termini fissi.
Tutorial che sto seguendo

Ecco, sono riuscito a ottenere qualche risultatino decente, ossia una colonna di navigazione a sinistra: la regola fondamentale è usare il float per la barra navigation, e impostarne la larghezza (fissa), per poi impostare la proprietà margin-left del DIV "contenuto" allo stesso valore della larghezza della navigation.
<html>
<head>
<style>
#container:{
 width:300px;
 
}

#content{
 margin-left:200px;
 background-color:#AAFFAA; 
}

#navigation{
 float:left;
 width:200px;
 background-color:#FFCCAA;
}
 
</style>
</head>
<body>
<body>
    <div id="container">
        <div id="header"></div>
        <div id="navigation">
         Lunedi<br>
         Martedi<br>
         Mercoledi<br>
         Giovedi<br>
         Venerdi<br>
         Sabato<br>
        </div>
        <div id="content">
         Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc,
        </div>
        <div id="footer"></div>
    </div>
</body>
</body>
</html>

sabato 5 novembre 2016

Ripasso del delete di un record dal database, requery per refreshare la listView costruita con il CursorAdapter

Ripasso la cancellazione di un record di database.
Ho ripreso il codice di J-Communicator:
 public void cancella(String etichetta, String categoria){
  SQLiteDatabase db=this.getWritableDatabase();
  db.delete("tabella", "etichetta = '"+etichetta+"' and categoria='"+categoria+"'", null);
 }
Ecco: dopo la "rituale" apertura del database, si usa il metodo delete che prende come parametri la tabella e i criteri del record da cancellare, quindi il null che probabilmente è riferito a un array di strings.
Vediamo la documentazione...

int delete (String table, 
                String whereClause, 
                String[] whereArgs)
Sì, è la whereClause, ossia le clausole per il record da cancellare.


Ora provo ad aggiungere a bindView l'aggiunta di un listener al button.
Ho terminato tutto il lavoro su bindView, e mi devo concentrare un po' su di essa.
        @Override
        public void bindView(View view, Context context, Cursor cursor) {

            TextView textView=(TextView)view.findViewById(R.id.textView);
            final TextView textView2=(TextView)view.findViewById(R.id.textView2);
            Button button=(Button)view.findViewById(R.id.button2);
            textView.setText(cursor.getString(cursor.getColumnIndexOrThrow("voce")));
            textView2.setText(""+cursor.getInt(cursor.getColumnIndexOrThrow("_id")));
            button.setOnClickListener(new View.OnClickListener(){

                @Override
                public void onClick(View v) {
                    helper.cancella(Integer.parseInt(textView2.getText().toString()));
                    myCursorAdapter.getCursor().requery();
                }
            });
        }
L'aggiunta di un listener va per il meglio. Ho creato anche il codice che cancella un record in relazione all'id.
Ho avuto un po' di difficoltà per il refresh della listView, ma con il requery() ho risolto.

Ripasso del CursorAdapter per collegare una ListView a una tabella di database.

Il CursorAdapter avrebbe un metodo newView che non sarebbe altro che l'inflatazione del nuovo Layout, come ArrayAdapter.
La differenza potrebbe stare nel fatto che mentre nell'ArrayAdapter il metodo getView fa tutto lui, nel CursorAdapter la view viene inflatata da newView e poi in essa vengono messi i dati tramite bindView.
Vediamo un po' i parametri dei due metodi.

Sì, credo di essere in grado di provare...

Iniziamo estendendo CursorAdapter.
    class MyCursorAdapter extends CursorAdapter {

        Context context;
        public MyCursorAdapter(Context context, Cursor c) {
            super(context, c);
            this.context=context;
        }

        @Override
        public View newView(Context context, Cursor cursor, ViewGroup parent) {
            LayoutInflater layoutInflater=(LayoutInflater)context.getSystemService(LAYOUT_INFLATER_SERVICE);
            return layoutInflater.inflate(R.layout.row,null);
        }

        @Override
        public void bindView(View view, Context context, Cursor cursor) {

            TextView textView=(TextView)view.findViewById(R.id.textView);
            Button button=(Button)view.findViewById(R.id.button2);
            textView.setText(cursor.getString(cursor.getColumnIndexOrThrow("voce")));
        }
    }
E l'ho sperimentato: ecco tutto il codice:
public class Lista extends AppCompatActivity {

    Helper helper;
    MyCursorAdapter myCursorAdapter;
    ListView listView;

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

        helper=new Helper(this);
        Cursor crs=helper.query();
        myCursorAdapter=new MyCursorAdapter(this,crs);
        listView=(ListView)findViewById(R.id.listView);
        listView.setAdapter(myCursorAdapter);





    }

    @Override
    protected void onStop(){
        super.onStop();

        Intent intent=new Intent(Lista.this,Lista.class);
        PendingIntent pendingIntent=PendingIntent.getActivity(getApplicationContext(),0,intent,0);
        AlarmManager alarmManager=(AlarmManager) getApplicationContext().getSystemService(ALARM_SERVICE);
        alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime()+5*1000,pendingIntent);

    }

    class MyCursorAdapter extends CursorAdapter {

        Context context;
        public MyCursorAdapter(Context context, Cursor c) {
            super(context, c);
            this.context=context;
        }

        @Override
        public View newView(Context context, Cursor cursor, ViewGroup parent) {
            LayoutInflater layoutInflater=(LayoutInflater)context.getSystemService(LAYOUT_INFLATER_SERVICE);
            return layoutInflater.inflate(R.layout.row,null);
        }

        @Override
        public void bindView(View view, Context context, Cursor cursor) {

            TextView textView=(TextView)view.findViewById(R.id.textView);
            Button button=(Button)view.findViewById(R.id.button2);
            textView.setText(cursor.getString(cursor.getColumnIndexOrThrow("voce")));
        }
    }



}
Funziona perfettamente!

Aggiunta di eventi nel CustomAdapter

Ho modificato il CustomAdapter in modo da aggiungere un listener al button in ogni layout inflatato nella ListView.
    class CustomAdapter extends ArrayAdapter{

        Context context;
        int resource;
        ArrayList arrayList;
        public CustomAdapter(Context context, int resource, ArrayList objects) {
            super(context, resource, objects);
            this.context=context;
            this.resource=resource;
            this.arrayList=(ArrayList)objects;
        }

        @Override
        public View getView(int position,View convertView,ViewGroup group){
            LayoutInflater layoutInflater=(LayoutInflater)context.getSystemService(LAYOUT_INFLATER_SERVICE);
            convertView=layoutInflater.inflate(this.resource,null);
            final TextView textView=(TextView)convertView.findViewById(R.id.textView);
            Button button=(Button)convertView.findViewById(R.id.button2);
            textView.setText(arrayList.get(position).testoText);
            button.setText(arrayList.get(position).testoButton);
            button.setOnClickListener(new View.OnClickListener(){

                @Override
                public void onClick(View v) {
                    textView.setText("OKAY");
                }
            });
            return convertView;
        }
    }
Funziona.

Ripasso di CustomAdapter che estende ArrayAdapter

ArrayAdapter ha diversi tipi di costruttori. Mi sono rinfrescate le idee su cose viste, peraltro, anche abbastanza recentemente, ma che scordo con estrema facilità.
Ecco qui l'elenco delle firme dei costruttori:
Context, int
Context, int, int
Context, int, T[]
Context, int, int, T[]
Context, int, List
Context, int, int, List
In particolare, quello che ho usato prima è un costruttore a quattro parametri, precisamente l'ultimo in quanto ho usato una classe che eredita da List.
A quattro parametri posso usare anche il terzultimo, che invece di una classe di tipo List usa un semplice Array.
Poi si può usare a tre parametri, senza selezionare la view del layout.
La creazione di un CustomView estende ArrayAdapter e ne usa il costruttore a tre parametri context, layout e array, ossia il penultimo, Context, int, List
Provo a estendermi ArrayAdapter per crearmi un adapter personalizzato. Mi creo un tipo con due membri:
    class Tipo{
        public String testoText;
        public String testoButton;
    }
Di questo tipo creerò diverse istanze, che collocherò in un'arrayList.

Ora provo a estendere ArrayAdapter:
    class CustomAdapter extends ArrayAdapter{

        Context context;
        int resource;
        ArrayList<Tipo> arrayList;
        public CustomAdapter(Context context, int resource, ArrayList<Tipo> objects) {
            super(context, resource, objects);
            this.context=context;
            this.resource=resource;
            this.arrayList=(ArrayList)objects;
        }

        @Override
        public View getView(int position,View convertView,ViewGroup group){
            LayoutInflater layoutInflater=(LayoutInflater)context.getSystemService(LAYOUT_INFLATER_SERVICE);
            convertView=layoutInflater.inflate(this.resource,null);
            TextView textView=(TextView)convertView.findViewById(R.id.textView);
            Button button=(Button)convertView.findViewById(R.id.button2);
            textView.setText(arrayList.get(position).testoText);
            button.setText(arrayList.get(position).testoButton);
            return convertView;
        }
    } 
Creo diverse istanze di Tipo, le metto in un arrayList e do l'arrayList in pasto a una istanza di CustomAdapter.
        Tipo tipo=new Tipo();
        tipo.testoButton="Bottone";
        tipo.testoText="Testo";
        
        Tipo tipo1=new Tipo();
        tipo1.testoButton="Bottone secondo";
        tipo1.testoText="Testo secondo";
        
        Tipo tipo2=new Tipo();
        tipo2.testoButton="Altro bottone";
        tipo2.testoText="Altro testo";

        arrayList=new ArrayList<Tipo>();
        arrayList.add(tipo);
        arrayList.add(tipo1);
        arrayList.add(tipo2);

        customAdapter=new CustomAdapter(this,R.layout.row,arrayList);

        listView=(ListView) findViewById(R.id.listView);
        listView.setAdapter(customAdapter); 
Proviamo...



Sì, eccolo.

Nuovo ripasso dell'ArrayAdapter

Devo ripassare ancora ArrayAdapter.
Strano, ma non mi entra assolutamente in testa.

Risposta parziale:
Un ArrayAdapter usa un array di stringhe per porlo in una ListView.
Mi fabbrico un layout da inflatare con una TextView e un button.
Posso giocare a porre la stringa sia come testo della TextView sia come testo del button.
Vediamo se mi riesce...

A parte il context, l'adapter deve prendere come parametri il layout e la view specifica.
E da ultimo l'array da cui prendere gli elementi per metterli nella view specifica.
Ho realizzato un codice usando come view specifica sia la TextView sia il Button.
Con il primo codice:
public class Lista extends AppCompatActivity {

    ArrayAdapter arrayAdapter;
    ArrayList arrayList;
    ListView listView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_lista);

        arrayList=new ArrayList();
        arrayList.add("lunedi");
        arrayList.add("martedi");
        arrayList.add("mercoledi");

        arrayAdapter=new ArrayAdapter(this,R.layout.row,R.id.textView,arrayList);
        listView=(ListView) findViewById(R.id.listView);

        listView.setAdapter(arrayAdapter);
    }

...
Ho ottenuto questo:



Con questo codice, invece, che prende come view specifica il Button...
public class Lista extends AppCompatActivity {

    ArrayAdapter arrayAdapter;
    ArrayList arrayList;
    ListView listView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_lista);

        arrayList=new ArrayList();
        arrayList.add("lunedi");
        arrayList.add("martedi");
        arrayList.add("mercoledi");

        arrayAdapter=new ArrayAdapter(this,R.layout.row,R.id.button2,arrayList);
        listView=(ListView) findViewById(R.id.listView);

        listView.setAdapter(arrayAdapter);
    }

.....
ottengo questo:

mercoledì 2 novembre 2016

Prime esperienze di navigazione all'interno dell'emulatore con adb.

Un po' di adb e sintassi del Linux.
Vorrei strappare il database al cellulare non rootato, e forse mediante adb c'è modo di farlo.

Però devo imparare a usare adb.

Vediamo:
c:\Users\Antonello\AppData\Local\Android\sdk\platform-tools>adb devices
List of devices attached
emulator-5554   device


c:\Users\Antonello\AppData\Local\Android\sdk\platform-tools>
Ottengo un elenco dei dispositivi cui sono collegato. Al momento c'è solo l'emulatore. Ora attacco anche il cellulare.
c:\Users\Antonello\AppData\Local\Android\sdk\platform-tools>adb devices
List of devices attached
emulator-5554   device


c:\Users\Antonello\AppData\Local\Android\sdk\platform-tools>adb devices
List of devices attached
52036bacee7b833d        device
emulator-5554   device


c:\Users\Antonello\AppData\Local\Android\sdk\platform-tools>
Ecco prima e dopo aver attaccato il cellulare.
Vado sul cellulare
c:\Users\Antonello\AppData\Local\Android\sdk\platform-tools>adb -s 52036bacee7b833d shell
shell@a3xelte:/ $
Ora provo ad andare sull'emulatore:
c:\Users\Antonello\AppData\Local\Android\sdk\platform-tools>adb -s 52036bacee7b833d shell
shell@a3xelte:/ $ exit

c:\Users\Antonello\AppData\Local\Android\sdk\platform-tools>adb -s emulator-5554 shell
root@generic_x86_64:/ #
Sono uscito dal cellulare e sono entrato sull'emulatore.

Ora, dal momento che l'emulatore è rootato per definizione, posso aprirmi ADM (Android Device Monitor) e snavigazzare allegramente fra le directory dell'emulatore con i comandi Linux confrontando il mio percorso con il filesystem del dispositivo.

Tools->Android->Android Device Monitor

Eccolo.
Vediamo dove mi trovo...
Do il comando ls e ottengo:
127|root@generic_x86_64:/ # ls
acct
cache
charger
config
d
data
default.prop
dev
etc
file_contexts
fstab.goldfish
fstab.ranchu
init
init.environ.rc
init.goldfish.rc
init.ranchu.rc
init.rc
init.trace.rc
init.usb.rc
init.zygote32.rc
init.zygote64_32.rc
mnt
proc
property_contexts
root
sbin
sdcard
seapp_contexts
selinux_version
sepolicy
service_contexts
storage
sys
system
ueventd.goldfish.rc
ueventd.ranchu.rc
ueventd.rc
vendor
Confronto con un'immagine da ADM:



Ecco, praticamente sono qui.
Uso il comando pwd per vedere dove mi trovo:
root@generic_x86_64:/ # pwd
/
root@generic_x86_64:/ #
Sto praticamente nella directory radice. Prima avevo provato a fare qualcosa del genere con il cellulare e mi diceva che mi trovavo su sdcard... ma il cellulare non è rootato.

Ora voglio andare su sdcard. Come fare? Una mezza idea ce l'ho, vediamo se è giusta...
root@generic_x86_64:/ # cd ./sdcard/
root@generic_x86_64:/sdcard # ls
Alarms
Android
DCIM
Download
LOST.DIR
Movies
Music
Notifications
Pictures
Podcasts
Ringtones
root@generic_x86_64:/sdcard #
Confronto:



Ecco. C'è però un'anomalia.
Con ADM sdcard risulta vuota, e devo andare su storage/sdcard per ottenere l'accesso.
Lo desumo da questo:

...leggendo la dicitura a destra ->storage/sdcard.

E se provo ad andare su storage/sdcard con linux?
root@generic_x86_64:/ # cd ./storage/sdcard/
root@generic_x86_64:/storage/sdcard # ls
Alarms
Android
DCIM
Download
LOST.DIR
Movies
Music
Notifications
Pictures
Podcasts
Ringtones
root@generic_x86_64:/storage/sdcard
E' la stessa cosa.


Ora provo ad andare sulla directory del database di un'applicazione.
root@generic_x86_64:/ #
root@generic_x86_64:/ # ls
acct
cache
charger
config
d
data
default.prop
dev
etc
file_contexts
fstab.goldfish
fstab.ranchu
init
init.environ.rc
init.goldfish.rc
init.ranchu.rc
init.rc
init.trace.rc
init.usb.rc
init.zygote32.rc
init.zygote64_32.rc
mnt
proc
property_contexts
root
sbin
sdcard
seapp_contexts
selinux_version
sepolicy
service_contexts
storage
sys
system
ueventd.goldfish.rc
ueventd.ranchu.rc
ueventd.rc
vendor
root@generic_x86_64:/ #
Bene, vado in /data/:
root@generic_x86_64:/ # cd ./data/
root@generic_x86_64:/data # ls
adb
anr
app
app-asec
app-lib
app-private
backup
bugreports
dalvik-cache
data
dontpanic
drm
local
lost+found
media
mediadrm
misc
nativebenchmark
nativetest
nativetest64
property
resource-cache
security
system
tombstones
user
root@generic_x86_64:/data #
Vado nella seconda directory data e faccio la lista dei files contenuti:
root@generic_x86_64:/data # cd ./data/
127|root@generic_x86_64:/data/data # ls
com.android.backupconfirm
com.android.browser
com.android.calculator2
com.android.calendar
com.android.camera
com.android.captiveportallogin
com.android.certinstaller
com.android.contacts
com.android.customlocale2
com.android.defcontainer
com.android.deskclock
com.android.development
com.android.development_settings
com.android.dialer
com.android.documentsui
com.android.dreams.basic
com.android.email
com.android.emulator.smoketests
com.android.exchange
com.android.externalstorage
com.android.fallback
com.android.gallery
com.android.gesture.builder
com.android.htmlviewer
com.android.inputdevices
com.android.inputmethod.latin
com.android.keychain
com.android.location.fused
com.android.managedprovisioning
com.android.mms
com.android.mms.service
com.android.music
com.android.netspeed
com.android.packageinstaller
com.android.pacprocessor
com.android.phone
com.android.printspooler
com.android.protips
com.android.providers.calendar
com.android.providers.contacts
com.android.providers.downloads
com.android.providers.downloads.ui
com.android.providers.media
com.android.providers.settings
com.android.providers.telephony
com.android.providers.userdictionary
com.android.proxyhandler
com.android.sdksetup
com.android.server.telecom
com.android.settings
com.android.sharedstoragebackup
com.android.shell
com.android.smoketest
com.android.smoketest.tests
com.android.soundrecorder
com.android.speechrecorder
com.android.systemui
com.android.vending
com.android.vpndialogs
com.android.wallpaper.livepicker
com.android.webview
com.android.widgetpreview
com.example.android.apis
com.example.android.livecubes
com.example.android.softkeyboard
com.example.antonello.agendina
com.example.antonello.j_communicator
com.example.imagecom
com.google.android.apps.maps
com.google.android.gms
com.google.android.googlequicksearchbox
com.google.android.gsf
com.google.android.gsf.login
com.google.android.launcher
com.google.android.play.games
com.google.android.street
com.svox.pico
jp.co.omronsoft.openwnn
root@generic_x86_64:/data/data #
Praticamente vi sono elencati i packages.
Vado sul package che mi interessa:
root@generic_x86_64:/data/data # cd ./com.example.antonello.j_communicator/
root@generic_x86_64:/data/data/com.example.antonello.j_communicator # ls
cache
databases
lib
no_backup
shared_prefs
root@generic_x86_64:/data/data/com.example.antonello.j_communicator #
ecco la cartella dei database.
Ci vado.
1|root@generic_x86_64:/data/data/com.example.antonello.j_communicator #
cd ./databases/
root@generic_x86_64:/data/data/com.example.antonello.j_communicator/databases # ls
database.db
database.db-journal
root@generic_x86_64:/data/data/com.example.antonello.j_communicator/databases #
Ed ecco smascherato il mio file database.

E per il momento ci accontentiamo di questo.

martedì 1 novembre 2016

Gesture detection in previsione della nuova applicazione

E ora vediamo come si costruisce un GestureListener.
Vediamo dove posso recuperare la sintassi...

Quello che mi interessa è la classe GestureDetector.

Che costruttore ha la classe GestureDetector?
GestureDetector(Context context, GestureDetector.OnGestureListener listener)
Quindi si costruisce così:
private GestureDetector gestureDetector;

........

        gestureDetector = new GestureDetector(this, new GestureDetector.OnGestureListener(){

            @Override
            public boolean onDown(MotionEvent e) {
                return false;
            }

            @Override
            public void onShowPress(MotionEvent e) {

            }

            @Override
            public boolean onSingleTapUp(MotionEvent e) {
                return false;
            }

            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
                return false;
            }

            @Override
            public void onLongPress(MotionEvent e) {

            }

            @Override
            public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
                return false;
            }
        });
Adesso che ho istanziato GestureDetector, che faccio?
Vediamo se funziona, e come funziona.
Se do delle conseguenze a un gesto, risponderà?
Proviamo
            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
                System.out.println("ONSCROLL");
                return false;
            }
Ecco, risolto:
public class MainActivity extends AppCompatActivity {

    private ScrollView scrollView;

    private GestureDetector gestureDetector;

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

        gestureDetector = new GestureDetector(this, new GestureDetector.OnGestureListener(){

            @Override
            public boolean onDown(MotionEvent e) {
                return false;
            }

            @Override
            public void onShowPress(MotionEvent e) {

            }

            @Override
            public boolean onSingleTapUp(MotionEvent e) {
                return false;
            }

            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {

                return false;
            }

            @Override
            public void onLongPress(MotionEvent e) {

            }

            @Override
            public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
                System.out.println("ONFLING");
                scrollView.scrollBy(0,100);
                return true;
            }
        });

        scrollView=(ScrollView)findViewById(R.id.scrollView);


        scrollView.setOnTouchListener(new View.OnTouchListener(){

            @Override
            public boolean onTouch(View v, MotionEvent event) {

                if(gestureDetector.onTouchEvent(event)) return gestureDetector.onTouchEvent(event);
                switch(event.getAction()){
                    case MotionEvent.ACTION_DOWN:
                        System.out.println("DOWN");
                        break;
                    case MotionEvent.ACTION_MOVE:
                        System.out.println("MOVE");
                        break;
                    case MotionEvent.ACTION_UP:
                        System.out.println("UP");
                        break;
                }

                return true;
            }
        });


    }

}