JavascriptProva

giovedì 30 giugno 2016

Analisi di un codice da StackOverflow

Analizziamo...

Un'activity A ha un codice per chiamare un Service.
    Intent intent = new Intent(this, response.class);

    // Add extras to the bundle
    intent.putExtra("foo", "bar");
    // Start the service
    // startService(intent);
Abbiamo dunque un Service che si chiama response, e lo startService relativo è "commentizzato" perché, dato che si deve creare un PendingIntent, non serve.
Andiamo avanti...

alarm = (AlarmManager) getSystemService(Context.ALARM_SERVICE); int i; i = 15; alarm.setRepeating(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), i * 1000, pintent); Ecco infatti che viene creato il PendingIntent:
pintent = PendingIntent.getService(this, 0, intent, 0);
E ora si crea l'AlarmManager:
    alarm = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
    int i
    i = 15;
    alarm.setRepeating(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(),
            i * 1000, pintent);
Non capisco perché c'è subito anche
startService(intent);
Forse è per far partire subito una volta l'intent prima che ne prenda il controllo l'AlarmManager...

A parte l'int i che viene reso 15, abbiamo capito che è tutto un codice per chiamare ripetutamente un Service chiamato response.

Quando questo Service ottiene una certa risposta da un server, chiama l'activity B: questa deve sopprimere l'AlarmManager dell'activity A, ed è stato provato questo codice:
      Intent sintent = new Intent(this, response.class);
     pintent = PendingIntent.getService(this, 0, sintent, 0);
     alarm = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
     stopService(sintent);
     alarm.cancel(pintent);
che ci analizziamo ora...

Viene creato un Intent che chiama il Service, e viene incapsulato in un PendingIntent. Quindi viene creato un nuovo AlarmManager.
Per stoppare il Service, si usa, giustamente, stopService(sIntent), mentre per stoppare AlarmManager si cancella il PendingIntent che chiama il Service. Ma pare che la cosa non riesca...

La risposta è che bisogna creare l'Intent con il context dell'Activity A.
Come si fa?

Il suggerimento è di mettere in A questo:
private static Context context;

public static Context getAppContext(){
    return ActivityA.context;
}
Viene dichiarata una variabile statica privata context di tipo Context.
Quindi viene creata una funzione pubblica get-, nel miglior stile dell'incapsulamento, che restituisce la variabile precedente.
Ovviamente questa va inizializzata:
context=getApplicationContext();
E quindi l'intent da "togliere" all'istanza di AlarmManager creata nell'Activity B viene costruito così:
Intent intent = new Intent(ActivityA.getAppContext(), ServiceClass.class);
intent.putExtra("fsasf", "bar");
PendingIntent pintent = PendingIntent.getService(this, 0, intent, 0);
AlarmManager alarm = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
stopService(intent);
alarm.cancel(pintent);
contro il precedente:
      Intent sintent = new Intent(this, response.class);
     pintent = PendingIntent.getService(this, 0, sintent, 0);
     alarm = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
     stopService(sintent);
     alarm.cancel(pintent);

martedì 28 giugno 2016

Modificare un Drawable programmaticamente

Voglio disegnare un cerchio potendo cambiandone il colore da Java.
Inizio a creare una Shape:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval"> 
    <solid android:color="#AFF" />
    <stroke
        android:width="5dp"
        android:color="#faa" />
    

</shape>
E funziona.
Il problema è adesso modificare questo.
Dal momento che il codice xml per impostarlo come sfondo è:
 <View
     android:layout_width="100dp"
     android:layout_height="100dp"
     android:background="@drawable/provashape"/>
come mi regolo per trovare un tipo compatibile con questa shape?

Pur senza aver capito ancora la differenza che c'è fra GradientDrawable, ShapeDrawable, ColorDrawable eccetera, ho capito che questa shape è un tipo GradientDrawable.

Quindi posso scrivere questo codice, che mi permette di modificare il colore, lo stroke eccetera del mio Drawable.
public class MainActivity extends Activity 
 
 Button button;
 View view;
 GradientDrawable drawable;
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  view=(View)findViewById(R.id.view1);
  drawable=(GradientDrawable)view.getBackground();
  
  
  
  button=(Button)findViewById(R.id.button1);
  
  
  button.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
    drawable.setStroke(20, Color.BLACK);
    
   }
  });
  

 }
}

Studio dello Stop per la mia applicazione per mezzo delle variabili globali

Shared preferences.
Mi servono per la mia applicazione.
Ma prima cerchiamo di renderla con le variabili globali...

Ecco, ho ripristinato il meccanismo di reciproca chiamata fra un Service e un'Activity.

Service di nome Timer:
 @Override
 public void onDestroy(){
  Log.d("TIMER","ONDESTROY");
  Intent intent=new Intent(this,Form.class);
  intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  startActivity(intent);
  Log.d("TIMER","/ ONDESTROY");
  super.onDestroy();
 }


Activity di nome Form, alla pressione di un pulsante si ha l'istruzione finish() che rimanda a onDestroy().
 public void onDestroy(){
  startService(new Intent(this, Timer.class));
  super.onDestroy();
 }
Quest'ultima non è marcata. La marco e procedo allo studio dell'interruzione del ciclo.
 public void onDestroy(){
  Log.d("FORM","ONDESTROY");
  startService(new Intent(this, Timer.class));
  Log.d("FORM","/ ONDESTROY");
  super.onDestroy();
 }

Per il momento, c'è il richiamo immediato di Form da Timer, mentre il richiamo inverso è ritardato solo in quanto subordinato alla pressione del pulsante.
Ora inserisco un AlarmManager in Timer:
 @Override
 public void onDestroy(){
  Log.d("TIMER","ONDESTROY");
  Intent intent=new Intent(this,Form.class);
  intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  pendingIntent=PendingIntent.getActivity(this, 0, intent, 0);
  alarmManager=(AlarmManager)this.getSystemService(Context.ALARM_SERVICE);
  alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime()+5*1000, pendingIntent);
  Log.d("TIMER","/ ONDESTROY");
  super.onDestroy();
 }
L'ho impostato a 5 secondi.
Lo sperimento.

Ho dovuto fare qualche aggiustamento per correggere errori di distrazione e dovuti al fatto che non ricordavo bene la procedura...

Comunque alla fine funziona: l'Act Form appare dopo 5 secondi dalla prima pressione del pulsante Start e quindi si ripete sempre dopo 5 secondi dal suo tasto Invia.

Ora devo gestire lo stop, con le variabili globali.
Aggiungo questa riga:
 @Override
 public void onDestroy(){
  Log.d("TIMER","ONDESTROY");
  Intent intent=new Intent(this,Form.class);
  intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  pendingIntent=PendingIntent.getActivity(this, 0, intent, 0);
  alarmManager=(AlarmManager)this.getSystemService(Context.ALARM_SERVICE);
  alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime()+5*1000, pendingIntent);
  if(Globals.Stop==true)alarmManager.cancel(pendingIntent);
  Log.d("TIMER","/ ONDESTROY");
  super.onDestroy();
 }
e il codice per il pulsante che renda true la variabile globale Stop.
  bttStop.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    Globals.Stop=true;
    
   }
  });
Vediamo...

Per far ripartire il ciclo ci metto pure la variabile che ridiventa false con il pulsante Start:
  bttStart.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    Globals.Stop=false;
    startService(new Intent(getApplicationContext(),Timer.class));
    
   }
  });


Sperimento la cosa. Quando sono tornato sulla schermata Main, quella con i due pulsanti Start e Stop, se schiaccio Stop il ciclo successivo si ripete per una volta e quindi si stoppa definitivamente.
Evidentemente, una volta che alarmManager è "partito"; anche se viene cancellato, questo vale per il ciclo successivo.

Faccio un'altra prova: metto il Cancel direttamente dove sta senza condizioni...

 @Override
 public void onDestroy(){
  Log.d("TIMER","ONDESTROY");
  Intent intent=new Intent(this,Form.class);
  intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  pendingIntent=PendingIntent.getActivity(this, 0, intent, 0);
  alarmManager=(AlarmManager)this.getSystemService(Context.ALARM_SERVICE);
  alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime()+3*1000, pendingIntent);
  /*if(Globals.Stop==true)*/alarmManager.cancel(pendingIntent);
  Log.d("TIMER","/ ONDESTROY");
  super.onDestroy();
 }
Ecco: così facendo, il ciclo non parte affatto perché alarmManager viene cancellato "sul nascere".
Dunque il problema non è che il ciclo era già stato avviato quando viene richiesto il valore della variabile Stop, ma forse il fatto che la variabile Stop è ancora false fino a quando non si sia avviato Form.
Questo codice:
 @Override
 public void onDestroy(){
  Log.d("TIMER","ONDESTROY");
  Intent intent=new Intent(this,Form.class);
  intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  pendingIntent=PendingIntent.getActivity(this, 0, intent, 0);
  alarmManager=(AlarmManager)this.getSystemService(Context.ALARM_SERVICE);
  alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime()+3*1000, pendingIntent);
  if(Globals.Stop==true)alarmManager.cancel(pendingIntent);
  Log.d("STOP",""+Globals.Stop);
  Log.d("TIMER","/ ONDESTROY");
  super.onDestroy();
 }
...me lo prova:
06-28 14:13:39.367: D/TIMER(4762): ONSTARTCOMMAND
06-28 14:13:39.367: D/TIMER(4762): / ONSTARTCOMMAND
06-28 14:13:39.367: D/TIMER(4762): ONDESTROY
06-28 14:13:39.377: D/STOP(4762): false
06-28 14:13:39.377: D/TIMER(4762): / ONDESTROY
Ossia quando viene premuto il pulsante, Globals.Stop è ancora false.
Ma proseguendo ancora oltre...
06-28 14:13:39.367: D/TIMER(4762): ONSTARTCOMMAND
06-28 14:13:39.367: D/TIMER(4762): / ONSTARTCOMMAND
06-28 14:13:39.367: D/TIMER(4762): ONDESTROY
06-28 14:13:39.377: D/STOP(4762): false
06-28 14:13:39.377: D/TIMER(4762): / ONDESTROY
06-28 14:13:50.018: D/FORM(4762): ONDESTROY
06-28 14:13:50.018: D/FORM(4762): / ONDESTROY
06-28 14:13:50.038: D/TIMER(4762): ONSTARTCOMMAND
06-28 14:13:50.038: D/TIMER(4762): / ONSTARTCOMMAND
06-28 14:13:50.038: D/TIMER(4762): ONDESTROY
06-28 14:13:50.048: D/STOP(4762): true
06-28 14:13:50.048: D/TIMER(4762): / ONDESTROY
...si vede che al ciclo successivo la variabile è true e quindi il ciclo si ferma.

Dunque voglio vedere quando diventa true: mi marco a tale scopo anche Form...

public class Form extends Activity {

 Button button1, button2, button3, button4, button5;
 ImageView imageView;
 DBHelper dbHelper;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_form);
  Log.d("FORM","ONCREATE");
  Log.d("STOP",""+Globals.Stop);
  
  button1=(Button)findViewById(R.id.button1);
  button2=(Button)findViewById(R.id.button2);
  button3=(Button)findViewById(R.id.button3);
  button4=(Button)findViewById(R.id.button4);
  button5=(Button)findViewById(R.id.bttInvia);
  imageView=(ImageView)findViewById(R.id.imageView1);
  
  OnClickListener onClickListener=new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    if(v==button1) imageView.setImageResource(R.drawable.nonurgentenonimportante);
    if(v==button2) imageView.setImageResource(R.drawable.urgentenonimportante);
    if(v==button3) imageView.setImageResource(R.drawable.urgenteimportante);
    if(v==button4) imageView.setImageResource(R.drawable.nonurgenteimportante);
    button5.setVisibility(View.VISIBLE);
    
    
   }
  };
  
  button5.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    finish(); 
    
   }
  });
  button1.setOnClickListener(onClickListener);
  button2.setOnClickListener(onClickListener);
  button3.setOnClickListener(onClickListener);
  button4.setOnClickListener(onClickListener);

  Log.d("FORM","/ ONCREATE");

 }
 
 public void onDestroy(){
  Log.d("FORM","ONDESTROY");
  Log.d("STOP",""+Globals.Stop);
  startService(new Intent(this, Timer.class));
  Log.d("FORM","/ ONDESTROY");
  super.onDestroy();
 }


}
E riproviamo: interrogo il valore della variabile all'inizio di FORM ONCREATE e all'inizio di FORM ONDESTROY.

06-28 14:26:35.604: D/TIMER(9225): ONDESTROY
06-28 14:26:35.604: D/STOP(9225): false
06-28 14:26:35.604: D/TIMER(9225): / ONDESTROY

06-28 14:26:38.677: D/FORM(9225): ONCREATE
06-28 14:26:38.677: D/STOP(9225): false
06-28 14:26:38.687: D/FORM(9225): / ONCREATE
06-28 14:26:41.280: D/FORM(9225): ONDESTROY
06-28 14:26:41.280: D/STOP(9225): false
06-28 14:26:41.280: D/FORM(9225): / ONDESTROY

06-28 14:26:41.300: D/TIMER(9225): ONSTARTCOMMAND
06-28 14:26:41.300: D/TIMER(9225): / ONSTARTCOMMAND
06-28 14:26:41.300: D/TIMER(9225): ONDESTROY
06-28 14:26:41.300: D/STOP(9225): false
06-28 14:26:41.300: D/TIMER(9225): / ONDESTROY

06-28 14:26:44.353: D/FORM(9225): ONCREATE
06-28 14:26:44.353: D/STOP(9225): true
06-28 14:26:44.353: D/FORM(9225): / ONCREATE
06-28 14:26:46.345: D/FORM(9225): ONDESTROY
06-28 14:26:46.345: D/STOP(9225): true
06-28 14:26:46.355: D/FORM(9225): / ONDESTROY

06-28 14:26:46.365: D/TIMER(9225): ONSTARTCOMMAND
06-28 14:26:46.375: D/TIMER(9225): / ONSTARTCOMMAND
06-28 14:26:46.375: D/TIMER(9225): ONDESTROY
06-28 14:26:46.385: D/STOP(9225): true
06-28 14:26:46.385: D/TIMER(9225): / ONDESTROY
Sì... pare che quello che conta sia il valore all'inizio del metodo.
Quindi la soluzione più pratica che mi venga in mente è bloccare la comparsa del Form se la variabile è pari a true, perché al momento in cui è cessato il metodo TIMER ONDESTROY il valore della variabile globale true sarà finalmente palese.

Provo a trovare una soluzione...

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  if(Globals.Stop)finish();
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_form);
  Log.d("FORM","ONCREATE");
  Log.d("STOP",""+Globals.Stop);

.....

Questa sembra funzionare.
Ma gettiamo un occhio a come vengono terminate tutte le componenti...
06-28 14:43:13.188: D/TIMER(13654): ONDESTROY
06-28 14:43:13.188: D/STOP(13654): false
06-28 14:43:13.188: D/TIMER(13654): / ONDESTROY

06-28 14:43:16.231: D/FORM(13654): ONCREATE
06-28 14:43:16.231: D/STOP(13654): true
06-28 14:43:16.231: D/FORM(13654): / ONCREATE
06-28 14:43:16.281: D/FORM(13654): ONDESTROY
06-28 14:43:16.281: D/STOP(13654): true
06-28 14:43:16.281: D/FORM(13654): / ONDESTROY

06-28 14:43:16.281: D/TIMER(13654): ONSTARTCOMMAND
06-28 14:43:16.281: D/TIMER(13654): / ONSTARTCOMMAND
06-28 14:43:16.281: D/TIMER(13654): ONDESTROY
06-28 14:43:16.291: D/STOP(13654): true
06-28 14:43:16.291: D/TIMER(13654): / ONDESTROY
Ecco: viene chiamato FORM, questo viene cessato e alla sua onDestroy viene chiamato Timer, il quale vede disattivato AlarmManager e non può più richiamare FORM.
Si potrebbe evitare il richiamo a vuoto di TIMER semplicemente ponendo il suo richiamo da FORM ONDESTROY come subordinato al valore false di Globals.Stop.

 public void onDestroy(){
  Log.d("FORM","ONDESTROY");
  Log.d("STOP",""+Globals.Stop);
  if(Globals.Stop==false) startService(new Intent(this, Timer.class));
  Log.d("FORM","/ ONDESTROY");
  super.onDestroy();
 }
E seguiamo...
06-28 14:47:13.302: D/TIMER(16187): ONDESTROY
06-28 14:47:13.302: D/STOP(16187): false
06-28 14:47:13.302: D/TIMER(16187): / ONDESTROY

06-28 14:47:16.355: D/FORM(16187): ONCREATE
06-28 14:47:16.355: D/STOP(16187): true
06-28 14:47:16.355: D/FORM(16187): / ONCREATE
06-28 14:47:16.415: D/FORM(16187): ONDESTROY
06-28 14:47:16.415: D/STOP(16187): true
06-28 14:47:16.415: D/FORM(16187): / ONDESTROY
Come previsto!
Forse a questo punto si potrebbe pure evitare il blocco di AlarmManager: una volta che questo abbia chiamato Form, questo si distrugge, e dal suo ONDESTROY non viene richiamato TIMER.

Ma lasciamocelo, come mezzo di sicurezza in più!

lunedì 27 giugno 2016

I menu in Android.

Per giungere all'interfaccia per le regolazioni mi conviene usare un menu.
Non ricordo assolutamente nulla dei menu.
E' dunque il caso di ripassare un po'...

Il menu è una risorsa, e quindi va salvato, in formato XML, nelle risorse.
Nella sottocartella res/menu:
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context="com.example.timemanager.MainActivity" >

    <item
        android:id="@+id/action_settings"
        android:orderInCategory="100"
        android:showAsAction="never"
        android:title="@string/action_settings"/>

</menu> 
Che significano le caratteristiche dell'item?
Vediamo...
Sul tutorial ci sono due attributi di un item: id e title.
Quell'orderInCategory e showAsAction sono assenti.
Fammi creare una Palestra ad hoc...

Ecco.
Il codice che gestisce la creazione del menu nell'Activity è piuttosto banale: praticamente si riassume nel codice che permette a un menuInflater di inflatare il menu contenuto nelle risorse.
 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
  // Inflate the menu; this adds items to the action bar if it is present.
  getMenuInflater().inflate(R.menu.main, menu);
  return true;
Quindi c'è il codice che gestisce il menu, che mi sembra un po' più complicato.
Con il codice visto finora, il menu appare ma non fa assolutamente niente.

Vediamo il codice per la gestione.
Praticamente è un evento onOptionItemSelected, che ha per parametro Menuitem, il quale confronta il menuItem selezionato con l'id di un elemento del menu.

Facciamo esercitazione. Mi annoto qui il codice prima di cancellarlo per riscriverlo.
 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
  // Inflate the menu; this adds items to the action bar if it is present.
  getMenuInflater().inflate(R.menu.main, menu);
  return true;
 }

 @Override
 public boolean onOptionsItemSelected(MenuItem item) {
  // Handle action bar item clicks here. The action bar will
  // automatically handle clicks on the Home/Up button, so long
  // as you specify a parent activity in AndroidManifest.xml.
  int id = item.getItemId();
  if (id == R.id.action_settings) {
   return true;
  }
  return super.onOptionsItemSelected(item);
 }
Sì, credo di aver memorizzato a sufficienza.

Interfaccia per la regolazione degli intervalli di campionamento

Sistemazione di una schermata con spinners per la selezione di intervalli di tempo.
Lo scheletro è questo:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.palestraxml3.Terza" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Intervallo minimo di campionamento" />
    
    <LinearLayout 
        android:id="@+id/frameTimer"
        android:orientation="horizontal"
        android:layout_height="100dp"
        android:layout_width="match_parent"
        android:background="@drawable/shap" >

        <LinearLayout 
            android:orientation="vertical"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content">
        <TextView    
            android:id="@+id/ore"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="ORE" />
        
        <Spinner 
            android:id="@+id/spinnerora1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        
        </LinearLayout>
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:orientation="vertical" >

            <TextView
                android:id="@+id/ore3"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Ciao mondo" />

            <TextView
                android:id="@+id/ore4"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Ciao ciccio" />
        </LinearLayout>

    </LinearLayout>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Intervallo minimo di campionamento" />
    <LinearLayout 
        android:id="@+id/frameTimer2"
        android:orientation="horizontal"
        android:layout_height="100dp"
        android:layout_width="match_parent"
        android:background="#FAA" >
       
        <LinearLayout 
            android:orientation="vertical"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content">
        <TextView    
           
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Ciao mondo" />
        
        <TextView 
            
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Ciao ciccio" />
        
        </LinearLayout>
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:orientation="vertical" >

            <TextView
              
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Ciao mondo" />

            <TextView
            
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Ciao ciccio" />
        </LinearLayout>

    </LinearLayout>
    
 

  
</LinearLayout> 
Ora ci costruisco un'interfaccia con gli spinners.

Inizio con la costruzione dell'intervallo minimo.
Questo è il LinearLayout orizzontale superiore:
    <LinearLayout 
        android:id="@+id/frameTimer"
        android:orientation="horizontal"
        android:layout_height="100dp"
        android:layout_width="match_parent"
        android:background="@drawable/shap" >

        <LinearLayout 
            android:orientation="vertical"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content">
        <TextView    
            android:id="@+id/ore"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="ORE" />
        
        <Spinner 
            android:id="@+id/spinnerora1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        
        </LinearLayout>
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:orientation="vertical" >

       <TextView
           android:id="@+id/ore3"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="Ciao mondo" />

        <Spinner 
            android:id="@+id/spinnerminuti1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        </LinearLayout>

    </LinearLayout> 
Ho messo due spinner.
Devo cambiare le etichette che sono ancora quelle della prova.
Ora compilo il codice Java.

Vengo a incontrare il problema di popolare una ArrayList con gli elementi di una matrice di String.

Vediamo...

Creo un'arrayList e una matrice di String, e vediamo cosa accade.

  String[] words={"uno","due","tre","quattro"};
  List lista=new ArrayList();
  Collections.addAll(lista,words);
  for(String e : lista){
   Log.d("ELEMENTO",e);
  }
Con Collection.addAll(lista di destinazione, lista di origine) si può ottenere la copia:
06-27 21:06:46.652: D/ELEMENTO(7376): uno
06-27 21:06:46.652: D/ELEMENTO(7376): due
06-27 21:06:46.652: D/ELEMENTO(7376): tre
06-27 21:06:46.652: D/ELEMENTO(7376): quattro
Dunque passiamo all'applicazione.


Ecco il codice totale:
public class Interfaccia extends Activity {

 Spinner spinnerOre1,spinnerMinuti1;
 Spinner spinnerOre2,spinnerMinuti2;
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_interfaccia);
  spinnerOre1=(Spinner)findViewById(R.id.spinnerore1);
  spinnerMinuti1=(Spinner)findViewById(R.id.spinnerminuti1);
  spinnerOre2=(Spinner)findViewById(R.id.spinnerore2);
  spinnerMinuti2=(Spinner)findViewById(R.id.spinnerminuti2);
  
  
  Integer[] ore={0,1,2,3,4,5,6,7,8,9,10,11,12};
  List listaOre=new ArrayList();
  Collections.addAll(listaOre, ore);
  ArrayAdapter adapter=new ArrayAdapter(this,R.layout.testo,listaOre);
  spinnerOre1.setAdapter(adapter);
  spinnerOre2.setAdapter(adapter);
  
  Integer[] minuti={0,5,10,15,20,25,30,35,40,45,50,55};
  List listaMinuti=new ArrayList();
  Collections.addAll(listaMinuti, minuti);
  ArrayAdapter adapter2=new ArrayAdapter(this,R.layout.testo,listaMinuti);
  spinnerMinuti1.setAdapter(adapter2);
  spinnerMinuti2.setAdapter(adapter2);
  

 }
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.palestraxml3.Terza" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Intervallo minimo di campionamento" />
    
    
<!-- Il LinearLayout orizzontale "intervallo minimo di campionamento -->
    <LinearLayout 
        android:id="@+id/frameTimer"
        android:orientation="horizontal"
        android:layout_height="wrap_content"
        android:layout_width="match_parent"
        android:gravity="center"
        android:background="@drawable/shap" >
        
        
    <!-- primo LinearLayout veticale (ore) -->
        <LinearLayout 
            android:orientation="vertical"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content">
        <TextView    
            android:id="@+id/txtore1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="ORE" />
        
        <Spinner 
            android:id="@+id/spinnerore1"
            android:layout_width="100dp"
            android:layout_height="wrap_content" />
        
        </LinearLayout>
        
   <!-- Secondo linearLayout verticale(minuti) -->
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:orientation="vertical" >
        
            <TextView
                android:id="@+id/txtminuti1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="MINUTI" />
            
            <Spinner
                android:id="@+id/spinnerminuti1"
                android:layout_width="100dp"
                android:layout_height="wrap_content" />
        </LinearLayout>
    <!-- fine secondo linearlayoue verticale -->

    </LinearLayout>
<!-- fine primo linearlayout orizzontale (intervallo minimo) -->
    
    
    <TextView
        android:layout_marginTop="10dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Intervallo massimo di campionamento" />
<!-- Secondo layout orizzontale (intervallo massimo di campionamento -->
    <LinearLayout 
        android:id="@+id/frameTimer2"
        android:orientation="horizontal"
        android:layout_height="wrap_content"
        android:layout_width="match_parent"
        android:gravity="center"
        android:background="@drawable/shap" > 
       
        <LinearLayout 
            android:orientation="vertical"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content">
            
        <TextView    
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="ORE" />
        
        <Spinner
            android:id="@+id/spinnerore2"
            android:layout_width="100dp"
            android:layout_height="wrap_content" />
        
        </LinearLayout>
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:orientation="vertical" >

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="MINUTI" />

            <Spinner
                android:id="@+id/spinnerminuti2"
                android:layout_width="100dp"
                android:layout_height="wrap_content" />
        </LinearLayout>

    </LinearLayout>
</LinearLayout>
Abbiamo poi i files XML contenuti nelle varie cartelle delle risorse: un file testo.xml che serve da elemento degli Spinner, un file shap.xml che serve per la forma arrotondata dei LinearLayout.

Ora devo gestirlo da un punto di vista matematico.

Gestione dello spinner per la selezione degli intervalli di tempo nella mia App

Ho incontrato più ostacoli io oggi di un saltatore a ostacoli: caterve di argomenti che devo ancora imparare, e che vorrei cominciare a digerire appena possibile.

Per il momento, cerchiamo di agire per il meglio con i mezzi che conosco.
Lo Spinner.
Non mi è assolutamente riuscito di popolarlo per mezzo di files XML, e lo faccio da codice java, fanculo!
Ci tornerò, sicuramente.
Ricordare che devo imparare come l'acqua, non come un mattone.
La cosa è fondamentale!

Ripasso di ArrayList.
   List lista=new ArrayList();
   lista.add(1);
   lista.add(2);
   lista.add(3);
E adesso devo popolare lo Spinner:
Ecco tutto il codice. Lo riscrivo per esercitarmi...

public class MainActivity extends Activity {

 Spinner spinner;
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  spinner=(Spinner)findViewById(R.id.spinner);
  List lista=new ArrayList();
  lista.add(1);
  lista.add(2);
  lista.add(3);
  lista.add(1);
  lista.add(2);
  lista.add(3);
  lista.add(1);
  lista.add(2);
  lista.add(3);
  ArrayAdapter adapter=new ArrayAdapter(this,android.R.layout.simple_spinner_dropdown_item,lista);
  spinner.setAdapter(adapter);
  

 }
}
E funziona.
Ho trovato un modo di modificare le caratteristiche dello spinner.
Vediamo se funziona.
Devo creare un file XML a parte per l'item.
<?xml version="1.0" encoding="utf-8"?>
<TextView  
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" 
    android:layout_height="wrap_content"
    android:textSize="10sp"
    android:gravity="left"  
    android:textColor="#0000FF"         
    android:padding="5dip"
    /> 
E l'adapter lo modifico in questo modo:
  ArrayAdapter adapter=new ArrayAdapter(this,R.layout.testo,lista);
  spinner.setAdapter(adapter);


Ora sperimento un formato per la mia applicazione...

Fatto.
Poi ho scritto anche il codice per ottenere il dato dell'elemento dello Spinner che ho cliccato:
public class MainActivity extends Activity {

 Spinner spinner;
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  spinner=(Spinner)findViewById(R.id.spinner);
  List lista=new ArrayList();
  lista.add(1);
  lista.add(2);
  lista.add(3);
  lista.add(1);
  lista.add(2);
  lista.add(3);
  lista.add(1);
  lista.add(2);
  lista.add(3);
  ArrayAdapter adapter=new ArrayAdapter(this,R.layout.testo,lista);
  spinner.setAdapter(adapter);
  
  

  spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {

   @Override
   public void onItemSelected(AdapterView parent, View view,
     int position, long id) {
    int numero=(Integer) spinner.getSelectedItem();
    Log.d("SPINNER",""+numero);
    
   }

   @Override
   public void onNothingSelected(AdapterView parent) {
    // TODO Auto-generated method stub
    
   }
  });

 }
}
E funziona.

sabato 25 giugno 2016

Disegnare forme con il Canvas e il Paint

Per cercare di rifare l'immagine di un bersaglio sulla mia App, devo vedere come sia possibile disegnare forme sul display e come eventualmente far sì che siano associate ad eventi.
Vediamo un po' come si fa, al solito modo estremamente pratico.

Si crea una classe che estende View. Provo a crearla come classe nidificata nell'Act.
 public class CustomView extends View{

  public CustomView(Context context) {
   super(context);
  }
  
 }


Per prima cosa, dobbiamo creare un Paint, dichiarato come variabile privata:
 public class CustomView extends View{

  private Paint paint;
  public CustomView(Context context) {
   super(context);
   paint=new Paint();
  }
  
 }
Quindi al Paint si attribuisce un colore:
 public class CustomView extends View{

  private Paint paint;
  public CustomView(Context context) {
   super(context);
   paint=new Paint();
   paint.setColor(Color.GREEN);
  }
  
 }
Non ho risolto nulla, finora, però.
Vediamo il resto...

C'è lo "sfondo", adesso: Paint è solo il pennello, mentre in onDraw si definisce lo sfondo.
onDraw ha per parametro questo sfondo, che è il Canvas:
  @Override
  protected void onDraw(Canvas canvas){
   super.onDraw(canvas);
  }
Praticamente questo sarebbe l'evento mediante il quale la View viene disegnata, e ha come parametro la tela su cui si esegue il disegno.
Viene disegnato lo sfondo tramite un colore, mentre per ciò che sta sullo sfondo si usa il pennello paint.
Questa descrizione...pittoresca dovrebbe essere abbastanza adeguata, ammesso che abbia capito bene.
  @Override
  protected void onDraw(Canvas canvas){
   super.onDraw(canvas);
   canvas.drawColor(Color.RED);
   canvas.drawCircle(200, 200, 100, paint);
  }
Ecco, adesso monto la view dandole delle dimsneioni precise:
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  RelativeLayout mainLayout=(RelativeLayout)findViewById(R.id.mainLayout);
  View v=new CustomView(this);
  mainLayout.addView(v);
  v.getLayoutParams().width=300;
  v.getLayoutParams().height=300;

  

 }
Ed ecco:



Non difficile, in fondo.
Ora elimino il colore di sfondo e vediamo se ottengo solo un cerchio verde:
  @Override
  protected void onDraw(Canvas canvas){
   super.onDraw(canvas);
   
   canvas.drawCircle(100, 100, 100, paint);
  }




Perfetto!

giovedì 23 giugno 2016

Conversione misure di data e ora.

Mettiamo su il database.
I campi saranno:
  1. Data
  2. Ora
  3. Tipo di attività
  4. Appropriata o no
Proviamo...

Innanzitutto ci vuoie un'esercitazione sui campi di database tipo Data...
Creare un database con un campo Data.

A quanto ho capito, non esiste un formato specifico per le date.
Bisogna memorizzare le date come TEXT.
Dunque ci vuole una conversione.

Ecco:
  Date date=new Date();
  
  Long tempo=date.getTime();
  Log.v("TEMPO",""+tempo);
  
  Date data= new Date(tempo);
  
  DateFormat df=new SimpleDateFormat("dd/MM/yyyy hh:mm:ss");
  
  Log.v("DATA",""+df.format(data));
06-24 12:36:17.422: V/TEMPO(27860): 1466764577430
06-24 12:36:17.432: V/DATA(27860): 24/06/2016 12:36:17
Sopra abbiamo il numero di secondi trascorsi dalla mezzanotte del 1/1/1970, mentre sotto abbiamo data e ora attuali.

I triggers di AOK: studio razionale.

Anche questa è "programmazione".
I trigger di AOK.

Cominciamo ad analizzarli un po'...
Iniziamo con la condizione Bring Object To Area
Questo è semplice: si imposta un oggetto e si imposta un'area, senza complicazioni.
Pongo come oggetto un esploratore a cavallo e come area una zona vicina al Centro Città.
Pongo come effetto, per testare la validità del trigger, un semplice "Declare victory".





Ha funzionato!
Ora, per confronto, vediamo la condizione Objects in Area.
Questa ha una serie di comandi che non mi è molto chiara:



Riassumendo:
Source Player (su questo non ci sono dubbi: è il giocatore di riferimento)
Object Group
Object List
Object List Type
Object Type
Poi c'è l'area e il numero di oggetti, che dovrebbero essere chiari.
Provo a non impostare quei quattro che non capisco, ossia Type, List, List Type e Group, ma imposto solo l'Area e il numero, e vediamo che succede: secondo me così facendo basta un oggetto qualunque del giocatore 1 in una determinata area per soddisfare la condizione, ma vediamo se è vero...

Sì, è così!
Ecco il trigger, in cui ho impostato solo il giocatore, il numero di oggetti (1) e l'area:



Ora lo sperimento: dapprima con l'esploratore a cavallo:



quindi con il villager:



quindi con il monaco:



Funziona in tutti i casi.
Ora selezione sull'elenco a cascata Object Type, che ha le opzioni <None>, Military, Civilian, Buildings e Other, l'opzione Military e mi aspetto che il trigger adesso valga solo per le unità militari. Vediamo se è vero.
Lo sperimento con il villager, con il monaco e con l'esploratore a cavallo...

Villager:


Monaco:


Esploratore a cavallo:


Bene. Dunque selezionare dalla casella Object Type serve a specificare il tipo di oggetto fra militari, civili, edifici e la categoria "altro".

Devo ancora esplorare altre caratteristiche di questa condizione che è piuttosto complicata...

mercoledì 22 giugno 2016

Salvataggio dei settings di un'applicazione Android

E' piuttosto facile, ma va memorizzato:
public class MainActivity extends Activity {

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  
  SharedPreferences sharedPreferences=this.getPreferences(Context.MODE_PRIVATE);
  
  //SCRITTURA
  SharedPreferences.Editor editor=sharedPreferences.edit();
  editor.putString("Preferenza1", "Bella");
  editor.putString("Preferenza", "Buona");
  editor.commit();
  
  //LETTURA
  String defaultValue="Ciccia";
  String risultato=sharedPreferences.getString("Preferenza", defaultValue);
  Log.d("RISULTATO",risultato);
  
  
 }


}
In questo modo si possono salvare le opzioni per l'esecuzione di un'App. Ad esempio il parlato o il muto, o il valore medio dell'intervallo di tempo entro il quale creare intervalli casuali.

Ripasso di alcune caratteristiche dei database in Android

Voglio ripassare i Database.


Scrivere il codice completo per un database con un campo _id e due campi text, quindi estrarre il secondo record e scrivere in LogCat i valori dei due campi text.
Tutto il codice è in MainActivity:
public class MainActivity extends Activity {

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  Helper helper=new Helper(this);
  helper.save("Mario", "Rossi");
  helper.save("Giuseppe", "Verdi");
  helper.save("Ludwig", "van Beethoven");
  
  String valore=helper.query(2).getString(1);
  Log.v("VALORE",valore);

 }
 
 class Helper extends SQLiteOpenHelper{

  public Helper(Context context) {
   super(context, "Database.db", null, 1);
   
  }

  @Override
  public void onCreate(SQLiteDatabase db) {
   db.execSQL("create table TABELLA(_id integer primary key,nome text,cognome text)");
  }
  
  public Cursor query(int numero){
   SQLiteDatabase db=this.getWritableDatabase();
   Cursor crs=db.rawQuery("select * from TABELLA where _id="+numero, null);
   crs.moveToFirst();
   return crs;
  }
  
  public void save(String nome, String cognome){
   SQLiteDatabase db=this.getWritableDatabase();
   ContentValues values=new ContentValues();
   values.put("nome", nome);
   values.put("cognome", cognome);
   db.insert("TABELLA", null, values);
  }

  @Override
  public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
   // TODO Auto-generated method stub
   
  }
  
 }

}


Ora dobbiamo studiare i constraint.

Creiamo un database sul tipo di quello di cui sopra, con il nome come campo unico.

Apporto delle modifiche al metodo onCreate della classe Helper.
  public void onCreate(SQLiteDatabase db) {
   db.execSQL("create table TABELLA(_id integer primary key,nome text,cognome text, unique(nome) on conflict fail)");
  }
Vediamo cosa accade.

Ecco, coi nomi di cui sopra, tutti diversi, ovvio che non accade niente anche se il constraint funziona.
Metto due nomi uguali:
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  Helper helper=new Helper(this);
  helper.save("Mario", "Rossi");
  helper.save("Giuseppe", "Verdi");
  helper.save("Giuseppe","Mazzini");
  
 }
Ecco: Nel database mi vengono inseriti solo i primi valori, Mario Rossi e Giuseppe Verdi, mentre il terzo viene ignorato.

Il programma non crasha, ma ottengo la segnalazione di errore in LogCat:
06-22 23:20:47.497: E/SQLiteLog(22909): (2067) abort at 12 in [INSERT INTO TABELLA(nome,cognome) VALUES (?,?)]: UNIQUE constraint failed: TABELLA.nome
06-22 23:20:47.498: E/SQLiteDatabase(22909): Error inserting nome=Giuseppe cognome=Mazzini

> Ora facciamo un constraint impostando come unique nome e cognome.
Poi metto un Giuseppe Mazzini, un Giuseppe Garibaldi, un'Anita Garibaldi e un altro Giuseppe Mazzini. Voglio vedere se mi viene bloccata solo la combinazione di nome e cognome anziché i nomi e cognomi unici.
  @Override
  public void onCreate(SQLiteDatabase db) {
   db.execSQL("create table TABELLA(_id integer primary key,nome text,cognome text, unique(nome,cognome) on conflict fail)");
  }
E creiamo i nomi...
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  Helper helper=new Helper(this);
  helper.save("Mario", "Rossi");
  helper.save("Giuseppe", "Mazzini");
  helper.save("Giuseppe", "Garibaldi");
  helper.save("Anita", "Garibaldi");
  helper.save("Giuseppe","Mazzini");

 }
Ottimo:
ottengo:
Mario Rossi
Giuseppe Mazzini
Giuseppe Garibaldi
Anita Garibaldi
e non il secondo Giuseppe Mazzini.
Il messaggio di errore (senza che peraltro il programma crashi) è

06-22 23:27:53.420: E/SQLiteLog(26154): (2067) abort at 13 in [INSERT INTO TABELLA(nome,cognome) VALUES (?,?)]: UNIQUE constraint failed: TABELLA.nome, TABELLA.cognome
06-22 23:27:53.421: E/SQLiteDatabase(26154): Error inserting nome=Giuseppe cognome=Mazzini
06-22 23:27:53.421: E/SQLiteDatabase(26154): android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed: TABELLA.nome, TABELLA.cognome (code 2067)

Sì: è solo la combinazione di nome e cognome ripetuta che crea l'eccezione.

Per gestire l'eccezione:
  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("TABELLA", null, values);
   }catch(SQLiteConstraintException e){
    
    Toast.makeText(getApplicationContext(), "Errore unique", Toast.LENGTH_LONG).show();
   }
  }
Ottengo ugualmente un messaggio di errore in LogCat, ma posso almeno gestirlo.

Adesso voglio ripassare le condizioni della query SELECT: anziché scrivere così
  public Cursor query(int numero){
   SQLiteDatabase db=this.getWritableDatabase();
   Cursor crs=db.rawQuery("select * from TABELLA where _id="+numero, null);
   crs.moveToFirst();
   return crs;
  }
posso scrivere usando un array:
  public Cursor query(int numero){
   SQLiteDatabase db=this.getWritableDatabase();
   Cursor crs=db.rawQuery("select * from TABELLA where _id=?", new String[]{numero+""});
   crs.moveToFirst();
   return crs;
  }
Questo array può portare solo Stringhe, per cui devo convertire il numero in stringa sommandolo a una stringa vuota.

Activity a intervalli regolari.

Esercizio: un'activity appare a intervalli casuali.

Ho ottenuto agevolmente, con il mio schema originale creato da me, un "rimbalzo" fra il Service Alfa e l'Activity Seconda.
Adesso ci metto un timer.
Ecco i codici:

MainActivity:
public class MainActivity extends Activity {

 
 Button bttStart;
 Button bttStop;
 Intent intent;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

 
  bttStart=(Button)findViewById(R.id.button1);
  bttStop=(Button)findViewById(R.id.button2);

  
  bttStart.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    Globals.stop=false;
    startService(new Intent(getApplicationContext(),Alfa.class));
   }
  });
  
  bttStop.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    Globals.stop=true;
    Globals.alarmManager.cancel(Globals.pendingIntent);
    Log.d("STOP","TRUE");
    
   }
  });

  
 }
}


Alfa:
public class Alfa extends Service{
 Intent intent;
 
 
 @Override
 public int onStartCommand(Intent intent, int flags, int startUI){
  Log.v("ALFA","ONSTARTCOMMAND");
  stopSelf();
  Log.v("ALFA","/ ONSTARTCOMMAND");
  return Service.START_NOT_STICKY;
 }
 
 @Override
 public void onDestroy(){
  Log.v("ALFA","ONDESTROY");
  if(Globals.stop==false){
   intent=new Intent(this,Seconda.class);
   intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
   Globals.pendingIntent=PendingIntent.getActivity(this, 0, intent, 0);
   Globals.alarmManager=(AlarmManager)this.getSystemService(Context.ALARM_SERVICE);
   Globals.alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime()+5*1000, Globals.pendingIntent);
  }

  Log.v("ALFA","/ ONDESTROY");
  super.onDestroy();
 }

 @Override
 public IBinder onBind(Intent intent) {
  // TODO Auto-generated method stub
  return null;
 }
 
}


Seconda:
public class Seconda extends Activity {

 Button button;
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_seconda);
  button=(Button)findViewById(R.id.button1);
  
  button.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    finish();
    
   }
  });
 }
 
 @Override
 public void onDestroy(){
  Log.d("SECONDA", "ONDESTROY");
  if(Globals.stop==false)startService(new Intent(this,Alfa.class));
  super.onDestroy();
  Log.d("SECONDA", "/ ONDESTROY");
 }

}


E' il caso di vedere dove sono posizionati gli "stop", ossia le condizioni per l'interruzione del ciclo.
Essi sono posizionati esclusivamente nei metodi onDestroy delle due classi.
Ho aggiunto AlarmManager come variabile globale in modo che possa essere direttamente azionata da MainActivity.

Chiamare un'Activity da un Service

Adesso provo a chiamare da un Service un'altra Activity.
Creo un'altra Activity e la chiamo Seconda.
Per distinguerne l'aspetto dalla prima Activity la coloro di azzurrino e al posto di Hello World scrivo "CIAO, DEFICIENTE".

Bene, e adesso sostituisco in Alfa il codice per chiamare un Service con quello per chiamare un'Activity:

Vecchio codice:
 @Override
 public void onDestroy(){
  Log.v("ALFA","ONDESTROY");
  if(Globals.stop==false)startService(new Intent(this,Speaker.class));
  Log.v("ALFA","/ ONDESTROY");
  super.onDestroy();
 }


Nuovo codice:
 @Override
 public void onDestroy(){
  Log.v("ALFA","ONDESTROY");
  if(Globals.stop==false)startActivity(new Intent(this,Seconda.class));
  Log.v("ALFA","/ ONDESTROY");
  super.onDestroy();
 }
Vediamo se parte.

Ci sono dei problemi, per chiamare un'Activity da un Service: è necessario un ulteriore codice:
 @Override
 public void onDestroy(){
  Log.v("ALFA","ONDESTROY");
  if(Globals.stop==false){
   Intent intent=new Intent(this,Seconda.class);
   intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
   startActivity(intent);
  }
  Log.v("ALFA","/ ONDESTROY");
  super.onDestroy();
 }
Ora sì, che funziona!

lunedì 20 giugno 2016

Esercizi con OnUtteranceCompletedListener per intercettare la fine del "discorso" di TTS.

Mettiamo a posto il TextToSpeech...

Fin qua credo di saper scrivere abbastanza il codice:
 public void onCreate(){
  TTS=new TextToSpeech(this,new TextToSpeech.OnInitListener() {
   
   @Override
   public void onInit(int status) {
    if(status==TextToSpeech.SUCCESS){
     int result=TTS.setLanguage(Locale.ITALIAN);
     TTS.setOnUtteranceCompletedListener(listener);
     TTS.speak("Ciao, ciccio bello", TextToSpeech.QUEUE_FLUSH, null);
    }
    
   }
  });
Che è stato scritto nel contesto di un Service.
Questo parla.
Lo riscrivo da capo e verifico che parli una volta chiamato il Service.
  TTS=new TextToSpeech(this,new TextToSpeech.OnInitListener() {
   
   @Override
   public void onInit(int status) {
    if(status==TextToSpeech.SUCCESS){
     TTS.setLanguage(Locale.ITALIAN);
     TTS.speak("Io sono il codice che dovrebbe parlare",TextToSpeech.QUEUE_FLUSH,null);
     
    }
    
   }
  });
E funziona!
Adesso voglio una conseguenza quando ha terminato di parlare.
Provo a usare onUtteranceCompletedListener.
Ma per fare questo devo usare Hashmap.
Vediamo...

Sono due righe.
Creiamo innanzitutto la HashMap, subito dopo il setLanguage(Locale.ITALIAN).
HashMap map=new HashMap();
Quindi ci devo inserire un valore, che è TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID.

Proviamo...
map.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "key");
E adesso imposto map come ultimo parametro del metodo speak.
Rifacciamo tutto:
  TTS=new TextToSpeech(this,new TextToSpeech.OnInitListener() {
   
   @Override
   public void onInit(int status) {
    if(status==TextToSpeech.SUCCESS){
     TTS.setLanguage(Locale.ITALIAN);
     HashMap map=new HashMap();
     map.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID,"key");
     TTS.speak("Io sono il codice che dovrebbe parlare",TextToSpeech.QUEUE_FLUSH,map);
     
    }
    
   }
  });
E mettiamo un'azione da eseguire in onUtteranceCompletedListener. Devo però impostare il listener a TTS:
   @Override
   public void onInit(int status) {
    if(status==TextToSpeech.SUCCESS){
     TTS.setLanguage(Locale.ITALIAN);
     TTS.setOnUtteranceCompletedListener(listener);
     HashMap map=new HashMap();
     map.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID,"key");
     TTS.speak("Io sono il codice che dovrebbe parlare",TextToSpeech.QUEUE_FLUSH,map);
     
    }
    
   }
  });
Fatto questo creo il listener:
  OnUtteranceCompletedListener listener=new TextToSpeech.OnUtteranceCompletedListener() {
   
   @Override
   public void onUtteranceCompleted(String utteranceId) {
    // TODO Auto-generated method stub
    
   }
  };
E ci metto una segnalazione da fare in LogCat:
  OnUtteranceCompletedListener listener=new TextToSpeech.OnUtteranceCompletedListener() {
   
   @Override
   public void onUtteranceCompleted(String utteranceId) {
    Log.v("SPEAKER","UTTERANCE COMPLETED LISTENED");
    
   }
  };
E vediamo se appena terminato di parlare mi dà questo segnale in LogCat...

06-21 07:39:58.249: V/SPEAKER(9142): UTTERANCE COMPLETED LISTENED
Ottimo!

Un modo di arrestare il loop dei services che si chiamano a vicenda.

Ora devo vedere come stoppare i due Services che si rincorrono.

Se distruggo tutti e due nello stesso tempo cosa accads?

MainActivity:
  button.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    stopService(new Intent(getApplicationContext(),Alfa.class));
    stopService(new Intent(getApplicationContext(),Beta.class));
   }
  });
Non funziona. Ho sempre quel succedersi indiavolato di giri viziosi fra l'uno e l'altro Service.



Ho risolto in questo modo: ho una variabile globale booleana chiamata stop dichiarata in una classe chiamata Globals:
public class Globals {
 public static boolean stop=false;
}
e pongo la condizione a ognuno dei due Services, per chiamare l'altro, che la variabile sia false:

Alfa:
public class Alfa extends Service{
 
 @Override
 public int onStartCommand(Intent intent, int flags, int startUI){
  Log.v("ALFA","ONSTARTCOMMAND");
  stopSelf();
  Log.v("ALFA","/ ONSTARTCOMMAND");
  return Service.START_NOT_STICKY;
 }
 
 @Override
 public void onDestroy(){
  Log.v("ALFA","ONDESTROY");
  if(Globals.stop==false)startService(new Intent(this,Beta.class));
  Log.v("ALFA","/ ONDESTROY");
  super.onDestroy();
 }

 @Override
 public IBinder onBind(Intent intent) {
  // TODO Auto-generated method stub
  return null;
 }
 
}


Beta:
public class Beta extends Service{

 @Override
 public int onStartCommand(Intent intent, int flags, int startUI){
  Log.v("BETA","ONSTARTCOMMAND");
  Log.e("BETA","Esecuzione del compito stabilito");
  stopSelf();
  Log.v("BETA","/ ONSTARTCOMMAND");
  return Service.START_NOT_STICKY; 
 }
 
 @Override
 public void onDestroy(){
  Log.v("BETA","ONDESTROY");
  if(Globals.stop==false)startService(new Intent(this,Alfa.class));
  Log.v("BETA","/ ONDESTROY");
  super.onDestroy();
 }
 
 @Override
 public IBinder onBind(Intent intent) {
  // TODO Auto-generated method stub
  return null;
 }
 
}
Così facendo, pare funzioni egregiamente, ponendo che in MainActivity il pulsante "Stop" dia alla variabile il valore true
  button.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    Globals.stop=true;
    Log.d("STOP","TRUE");
   }
  });
...ovviamente, facendo in modo che il pulsante Start reimposti la variabile a false:
  bttStart.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    Globals.stop=false;
    intent=new Intent(getApplicationContext(),Alfa.class); 
    startService(intent);
    
   }
  });
E cavoli, funziona!
Metto un "marker" che mi dia il valore della variabile nel momento in cui viene impostata a true e mi analizzo un po' la cosa in LogCat:
06-20 18:57:45.648: V/ALFA(1873): ONDESTROY
06-20 18:57:45.658: V/ALFA(1873): / ONDESTROY
06-20 18:57:45.658: V/BETA(1873): ONSTARTCOMMAND
06-20 18:57:45.658: E/BETA(1873): Esecuzione del compito stabilito
06-20 18:57:45.658: V/BETA(1873): / ONSTARTCOMMAND
06-20 18:57:45.658: D/STOP(1873): TRUE
06-20 18:57:45.658: V/BETA(1873): ONDESTROY
06-20 18:57:45.658: V/BETA(1873): / ONDESTROY
Qui la variabile è stata impostata a TRUE dopo che Beta ha eseguito onStartCommand e ha chiamato onDestroy: arrivato a onDestroy il codice trova la variabile TRUE e non chiama Alfa, così il ciclo si interrompe.

06-20 19:00:08.708: V/BETA(1873): / ONSTARTCOMMAND
06-20 19:00:08.708: V/BETA(1873): ONDESTROY
06-20 19:00:08.718: V/BETA(1873): / ONDESTROY
06-20 19:00:08.718: V/ALFA(1873): ONSTARTCOMMAND
06-20 19:00:08.718: V/ALFA(1873): / ONSTARTCOMMAND
06-20 19:00:08.718: D/STOP(1873): TRUE
06-20 19:00:08.728: V/ALFA(1873): ONDESTROY
06-20 19:00:08.728: V/ALFA(1873): / ONDESTROY
Alfa ha eseguito onStartCommand chiamando onDestroy, ma nel frattempo la variabile globale è diventata TRUE, e così il codice di onDestroy non chiama più Beta e il ciclo si interrompe.

06-20 19:01:24.222: V/ALFA(1873): ONDESTROY
06-20 19:01:24.222: V/ALFA(1873): / ONDESTROY
06-20 19:01:24.232: D/STOP(1873): TRUE
06-20 19:01:24.232: V/BETA(1873): ONSTARTCOMMAND
06-20 19:01:24.232: E/BETA(1873): Esecuzione del compito stabilito
06-20 19:01:24.232: V/BETA(1873): / ONSTARTCOMMAND
06-20 19:01:24.242: V/BETA(1873): ONDESTROY
06-20 19:01:24.242: V/BETA(1873): / ONDESTROY
Ecco, questa è più interessante: Alfa ha appena chiamato Beta, e nel frattempo la variabile diventa TRUE: dato che la chiamata è già partita, il compito viene comunque eseguito, quindi si va a onDestroy e Beta non chiama più Alfa dato che incontra un TRUE.
Se io voglio interrompere l'esecuzione del programma nel preciso momento in cui schiaccio il pulsante Stop, ossia nel preciso momento in cui la variabile diventa TRUE, questo non va più bene. In tempi rapidi, la differenza non si nota, ma se io devo inserire un Timer in modo da far diventare questi tempi minuti od ore, allora il problema emerge, perché potrei avere la cessazione dell'attività del programma anche molte ore dopo averlo interrotto con il pulsante Stop

Studiamo una soluzione...

Non mi piace molto perché mi sembrerebbe più elegante una soluzione con una sola verifica di Globals.stop, ma dovrebbe funzionare:

Beta:
 public int onStartCommand(Intent intent, int flags, int startUI){
  Log.v("BETA","ONSTARTCOMMAND");
  if(Globals.stop==false)Log.e("BETA","Esecuzione del compito stabilito");
  stopSelf();
  Log.v("BETA","/ ONSTARTCOMMAND");
  return Service.START_NOT_STICKY; 
 }
Ecco, dopo vari tentativi, TRUE è ricapitato nel posto che mi interessa analizzare:
06-20 19:10:09.614: V/ALFA(5123): ONSTARTCOMMAND
06-20 19:10:09.614: V/ALFA(5123): / ONSTARTCOMMAND
06-20 19:10:09.614: V/ALFA(5123): ONDESTROY
06-20 19:10:09.624: V/ALFA(5123): / ONDESTROY
06-20 19:10:09.654: D/STOP(5123): TRUE
06-20 19:10:09.654: D/dalvikvm(5123): GC_EXPLICIT freed 501K, 47% free 6875K/12928K, paused 2ms+4ms, total 32ms
06-20 19:10:09.674: V/BETA(5123): ONSTARTCOMMAND
06-20 19:10:09.674: V/BETA(5123): / ONSTARTCOMMAND
06-20 19:10:09.674: V/BETA(5123): ONDESTROY
06-20 19:10:09.674: V/BETA(5123): / ONDESTROY
Così facendo, viene eseguito ONSTARTCOMMAND ma senza che venga eseguito il compito specifico.
Compare quel codice che non riesco a capire...

06-20 19:12:37.989: V/ALFA(5123): ONDESTROY
06-20 19:12:37.999: V/ALFA(5123): / ONDESTROY
06-20 19:12:37.999: D/STOP(5123): TRUE
06-20 19:12:37.999: V/BETA(5123): ONSTARTCOMMAND
06-20 19:12:37.999: V/BETA(5123): / ONSTARTCOMMAND
06-20 19:12:38.009: V/BETA(5123): ONDESTROY
06-20 19:12:38.009: V/BETA(5123): / ONDESTROY
Adesso è ricapitato nello stesso punto, ma non appare nulla... Non so. Forse sarò in condizione di capirlo in tempi futuri...

Comunque, questo sistema FUNZIONA!

domenica 19 giugno 2016

Studio dei due Service che si rincorrono

Mi devo esercitare meglio per perfezionare il meccanismo.

Con Services semplici.

Riscriviamo Alfa e Beta.


Alfa:
public class Alfa extends Service{

 @Override
 public IBinder onBind(Intent intent) {
  // TODO Auto-generated method stub
  return null;
 }
 
}

Beta:
public class Beta extends Service{

 @Override
 public IBinder onBind(Intent intent) {
  // TODO Auto-generated method stub
  return null;
 }
 
}
Aggiungo ad ambedue onStartCommand e onDestroy:

ALfa:
public class Alfa extends Service{
 
 @Override
 public int onStartCommand(Intent intent, int flags, int startUI){
  return Service.START_NOT_STICKY;
 }
 
 @Override
 public void onDestroy(){
  super.onDestroy();
 }

 @Override
 public IBinder onBind(Intent intent) {
  // TODO Auto-generated method stub
  return null;
 }
 
}

Beta:
public class Beta extends Service{

 @Override
 public int onStartCommand(Intent intent, int flags, int startUI){
  return Service.START_NOT_STICKY; 
 }
 
 @Override
 public void onDestroy(){
  super.onDestroy();
 }
 
 @Override
 public IBinder onBind(Intent intent) {
  // TODO Auto-generated method stub
  return null;
 }
 
}


Ora faccio eseguire a Beta un compito, come scrivere qualcosa in LogCat:
 @Override
 public int onStartCommand(Intent intent, int flags, int startUI){
  Log.v("BETA","COMPITO ESEGUITO");
  stopSelf();
  return Service.START_NOT_STICKY; 
 }
Alfa può evocare Beta e poi distruggersi.
Anziché farlo in onStartCommand, per concentrare tutto, può farlo in onDestroy, se in onStartCommand inserisco stopSelf.
public class Alfa extends Service{
 
 @Override
 public int onStartCommand(Intent intent, int flags, int startUI){
  stopSelf()
  return Service.START_NOT_STICKY;
 }
 
 @Override
 public void onDestroy(){
  startService(new Intent(this,Beta.class));
  super.onDestroy();
 }

 @Override
 public IBinder onBind(Intent intent) {
  // TODO Auto-generated method stub
  return null;
 }
 
}
Mettiamo gli indici ai metodi e proviamo...
public class Alfa extends Service{
 
 @Override
 public int onStartCommand(Intent intent, int flags, int startUI){
  Log.v("ALFA","ONSTARTCOMMAND");
  stopSelf();
  return Service.START_NOT_STICKY;
 }
 
 @Override
 public void onDestroy(){
  Log.v("ALFA","ONDESTROY");
  startService(new Intent(this,Beta.class));
  super.onDestroy();
 }

 @Override
 public IBinder onBind(Intent intent) {
  // TODO Auto-generated method stub
  return null;
 }
 
}
Proviamo... Bene.
Mettiamo tutti i markers e riproviamo.
public class Alfa extends Service{
 
 @Override
 public int onStartCommand(Intent intent, int flags, int startUI){
  Log.v("ALFA","ONSTARTCOMMAND");
  stopSelf();
  Log.v("ALFA","/ ONSTARTCOMMAND");
  return Service.START_NOT_STICKY;
 }
 
 @Override
 public void onDestroy(){
  Log.v("ALFA","ONDESTROY");
  startService(new Intent(this,Beta.class));
  Log.v("ALFA","/ ONDESTROY");
  super.onDestroy();
 }

 @Override
 public IBinder onBind(Intent intent) {
  // TODO Auto-generated method stub
  return null;
 }
 
}
public class Beta extends Service{

 @Override
 public int onStartCommand(Intent intent, int flags, int startUI){
  Log.v("BETA","ONSTARTCOMMAND");
  Log.e("BETA","Esecuzione del compito stabilito");
  stopSelf();
  Log.v("BETA","/ ONSTARTCOMMAND");
  return Service.START_NOT_STICKY; 
 }
 
 @Override
 public void onDestroy(){
  Log.v("BETA","ONDESTROY");
  Log.v("BETA","/ ONDESTROY");
  super.onDestroy();
 }
 
 @Override
 public IBinder onBind(Intent intent) {
  // TODO Auto-generated method stub
  return null;
 }
 
}
Ed ecco la sequenza:
06-19 18:51:02.222: V/ALFA(29067): ONSTARTCOMMAND
06-19 18:51:02.222: V/ALFA(29067): / ONSTARTCOMMAND
06-19 18:51:02.242: V/ALFA(29067): ONDESTROY
06-19 18:51:02.252: V/ALFA(29067): / ONDESTROY
06-19 18:51:02.252: V/BETA(29067): ONSTARTCOMMAND
06-19 18:51:02.252: E/BETA(29067): Esecuzione del compito stabilito
06-19 18:51:02.262: V/BETA(29067): / ONSTARTCOMMAND
06-19 18:51:02.262: V/BETA(29067): ONDESTROY
06-19 18:51:02.262: V/BETA(29067): / ONDESTROY
Ora, se in onDestroy di Beta metto un richiamo ad Alfa creo un circolo vizioso:
 public void onDestroy(){
  Log.v("BETA","ONDESTROY");
  startService(new Intent(this,Alfa.class));
  Log.v("BETA","/ ONDESTROY");
  super.onDestroy();
 }
Dovrò riavviare il cellulare...


Qual è il modo migliore per interrompere il circolo vizioso infinito?

Promemoria codice dei due processi che si mordono la coda

Dunque lo scheletro dell'applicazione a due Services alternantisi è essenzialmente che in onStartCommand ogni Service apre l'altro e chiude se stesso.
Facile.
Provo ad applicarlo al Service che parla...

Qui conviene aspettare che si arrivi a onUtteranceCompletedListener per aprire l'altro e chiudere se stesso, in modo da dare il tempo al Service di parlare.
Ci provo.
Questo service (ne ho creato una copia denominata Voce) non overrida onStartCommand, ma onCreate, che chiama onInit del TextToSpeech, implementata dal service stesso.
Mi conviene piuttosto attendere che abbia finito di parlare per richiamare Alfa, quindi lo start con relativo stop di se stesso lo piazzo in onUtteranceCompleted.
 @Override
 public void onUtteranceCompleted(String utteranceId) {
  Intent intent=new Intent(this,Alfa.class);
  startService(intent);
  stopSelf();
  Log.v("SEGNALE","ONUTTERANCE");
  
 }
Alfa dovrebbe comportarsi come al solito, richiamando Voce con ritardo secondo AlarmManager.
Proviamo...

Dimenticavo la condizione relativa alla variabile globale:
 @Override
 public void onUtteranceCompleted(String utteranceId) {
  if(Globals.stop==false){
   Intent intent=new Intent(this,Alfa.class);
   startService(intent);
   stopSelf();
  }
  Log.v("SEGNALE","ONUTTERANCE");
  
 }
e andiamo dopo aver cambiato, in Alfa, Beta con Voce...

Sì, funziona!


Ora devo stabilire i tempi casuali.

L'ho fatto...
Non riesco a cancellare subito l'AlarmManager... Intanto mi annoto il codice:
public class MainActivity extends Activity {

 Button button;
 Button bttStart;
 Intent intent;
 AlarmManager alarmManager;
 PendingIntent pendingIntent;
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  

  
  button=(Button)findViewById(R.id.button1);
  bttStart=(Button)findViewById(R.id.button2);
  
  button.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    Globals.stop=true;
   }
  });
  
  bttStart.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    Globals.stop=false;
    intent=new Intent(getApplicationContext(),Alfa.class); 
    startService(intent);
    
   }
  });

  
 }
}


Alfa;
public class Alfa extends Service{

 AlarmManager alarmManager;
 PendingIntent pendingIntent;
 @Override
 public IBinder onBind(Intent intent) {
  // TODO Auto-generated method stub
  return null;
 }
 
 @Override
 public int onStartCommand(Intent intent,int flags,int startUI){
  Log.v("ALFA","ONSTARTCOMMAND");
  if (Globals.stop==false){
   Intent i=new Intent(this,Voce.class);
   pendingIntent=PendingIntent.getService(this,0,i,0);
   Log.d("MSECS",""+Globals.mSecs);
   alarmManager=(AlarmManager)this.getSystemService(Context.ALARM_SERVICE);
   alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime()+Globals.mSecs, pendingIntent);
   stopSelf();
  }
  else{
   alarmManager.cancel(pendingIntent);
   stopSelf();
   stopService(new Intent(getApplicationContext(),Voce.class));
  }
  return Service.START_NOT_STICKY;
  
 }
 @Override
 public void onDestroy(){
  Log.v("ALFA","ONDESTROY");
  super.onDestroy();
  
 }

}


Voce:
public class Voce extends Service implements TextToSpeech.OnInitListener,TextToSpeech.OnUtteranceCompletedListener{
 TextToSpeech tts;

 @Override
 public void onCreate(){
  tts=new TextToSpeech(this,this);
  Log.v("SEGNALE", "ONCREATE");
 }
 @Override
 public IBinder onBind(Intent intent) {
  // TODO Auto-generated method stub
  return null;
 }

 @Override
 public void onInit(int status) {
  if(status==TextToSpeech.SUCCESS){
   int result=tts.setLanguage(Locale.ITALIAN);
   tts.setOnUtteranceCompletedListener(this);
   HashMap hashMap=new HashMap();
   hashMap.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "frase");
   tts.setPitch(.5f);
   int tempo=Globals.mSecs/1000; //tempo in secondi
   int minuti=tempo/60;
   int secondi=tempo%60;
   tts.speak("Sono passati "+minuti+" minuti e"+secondi+"secondi", TextToSpeech.QUEUE_FLUSH, hashMap);
  }
  Log.v("SEGNALE","ONINIT");
  
 }

 @Override
 public void onUtteranceCompleted(String utteranceId) {
  Log.v("VARIABILE GLOBALE",""+Globals.mSecs);
  if(Globals.stop==false){
   Globals.mSecs=(int) (Math.random()*3*60*1000);
   Intent intent=new Intent(this,Alfa.class);
   startService(intent);
   stopSelf();
  }
  Log.v("SEGNALE","ONUTTERANCE");
  
 }
 
 @Override
 public void onDestroy(){
  tts.stop();
  tts.shutdown();
  super.onDestroy();
  Log.v("SEGNALE","ONDESTROY");
 }
 
}


sabato 18 giugno 2016

Due Services che si mordono la coda (con il dovuto ritardo)

Sto facendo tentativi per ottenere ogni volta un intervallo casuale fra le ripetizioni di un Service.

Sto pensando, in proposito, a un sistema di due Services che si chiamino l'uno con l'altro.
Uno chiama l'altro che esegue il compito e si distrugge, quindi questo esegue il compito, si distrugge e in quel momento richiama il primo.
Proviamo con due Services nuovi...

Sì, ci sono riuscito.
Ecco i codici:

MainActivity:
public class MainActivity extends Activity {

 Button button;
 Button bttStart;
 Intent intent;
 AlarmManager alarmManager;
 PendingIntent pendingIntent;
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  

  
  button=(Button)findViewById(R.id.button1);
  bttStart=(Button)findViewById(R.id.button2);
  
  button.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    Globals.stop=true;
   }
  });
  
  bttStart.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    Globals.stop=false;
    intent=new Intent(getApplicationContext(),Alfa.class); 
    startService(intent);
    
   }
  });

  
 }
}


Alfa:
public class Alfa extends Service{

 @Override
 public IBinder onBind(Intent intent) {
  // TODO Auto-generated method stub
  return null;
 }
 
 @Override
 public int onStartCommand(Intent intent,int flags,int startUI){
  Log.v("ALFA","ONSTARTCOMMAND");
  if (Globals.stop==false){
   Intent i=new Intent(this,Beta.class);
   PendingIntent pendingIntent=PendingIntent.getService(this,0,i,0);
   AlarmManager alarmManager=(AlarmManager)this.getSystemService(Context.ALARM_SERVICE);
   alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime()+10*1000, pendingIntent);
   stopSelf();
  }
  return Service.START_NOT_STICKY;
  
 }
        @Override
 public void onDestroy(){
  Log.v("ALFA","ONDESTROY");
  super.onDestroy();
  
 }

}


Beta:
public class Beta extends Service{

 @Override
 public IBinder onBind(Intent intent) {
  // TODO Auto-generated method stub
  return null;
 }

 @Override
 public int onStartCommand(Intent intent, int flags,int startUI){
  Log.v("BETA","ONSTARTCOMMAND");
  if(Globals.stop==false){
   Intent i=new Intent(this,Alfa.class);
   startService(i);
   stopSelf();
  }
  return Service.START_NOT_STICKY;
  
 }
 @Override
 public void onDestroy(){
  Log.v("BETA","ONDESTROY");
  super.onDestroy();
  
 }

}
In più è dichiarata una variabile globale:
public class Globals {
 public static boolean stop=false;
}



Quali sono i princìpi:
MainActivity alla pressione di un bottone chiama Alfa:
  bttStart.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    Globals.stop=false;
    intent=new Intent(getApplicationContext(),Alfa.class); 
    startService(intent);
    
   }
  })


Alfa all'avvio, ossia in onStartCommand, chiama Beta e stoppa se stesso con un ritardo stabilito da AlarmManager:
 @Override
 public int onStartCommand(Intent intent,int flags,int startUI){
  Log.v("ALFA","ONSTARTCOMMAND");
  if (Globals.stop==false){
   Intent i=new Intent(this,Beta.class);
   PendingIntent pendingIntent=PendingIntent.getService(this,0,i,0);
   AlarmManager alarmManager=(AlarmManager)this.getSystemService(Context.ALARM_SERVICE);
   alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime()+10*1000, pendingIntent);
   stopSelf();
  }
  return Service.START_NOT_STICKY;
  
 }


Beta all'avvio, ossia in onStartCommand, chiama Alfa e stoppa se stesso:
@Override
 public int onStartCommand(Intent intent, int flags,int startUI){
  Log.v("BETA","ONSTARTCOMMAND");
  if(Globals.stop==false){
   Intent i=new Intent(this,Alfa.class);
   startService(i);
   stopSelf();
  }
  return Service.START_NOT_STICKY;
  
 }
Alfa, attivato, richiama Beta (sempre con il ritardo di AlarmManager) e stoppa se stesso, e così via in un loop senza fine.

Per stoppare il loop abbiamo una variabile globale booleana Globals.Variabile che inizialmente è false e appunto per andare avanti ad aprirsi a vicenda fra Alfa e Beta è impostata la condizione che questa variabile sia false: nel bottone 2 di MainActivity c'è il codice per impostare questa variabile a true:
  button.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    Globals.stop=true;
   }
  });
e questo, facendo venire meno la condizione per l'apertura reciproca dei due Services, interrompe efficacamente il processo.

L'ho idato io: ingegnoso!

AlarmManager gestito da un Service intermedio.

Creare un AlarmManager nel contesto di un Service in modo da chiamare un altro Service a intervalli.

Ecco fatto:

MainActivity:
public class MainActivity extends Activity {

 boolean flag=true;
 Button button;
 Button bttStart;
 Intent intent;
 AlarmManager alarmManager;
 PendingIntent pendingIntent;
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  

  
  button=(Button)findViewById(R.id.button1);
  bttStart=(Button)findViewById(R.id.button2);
  
  button.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    stopService(new Intent(getApplicationContext(),BaseAlarm.class));
   }
  });
  
  bttStart.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    intent=new Intent(getApplicationContext(),BaseAlarm.class); 
    startService(intent);
    
   }
  });

  
 }
}


BaseAlarm (service):
public class BaseAlarm extends Service{
 Intent intent;
 PendingIntent pendingIntent;
 AlarmManager alarmManager;
 int mSecs=5*1000;
 @Override
 public IBinder onBind(Intent intent) {
  // TODO Auto-generated method stub
  return null;
 }
 
 @Override
 public int onStartCommand(Intent intent, int flags, int startUI){
  Log.v("BASEALARM","ONSTARTCOMMAND");
  intent=new Intent(this,Servizio.class);
  pendingIntent=PendingIntent.getService(this, 0, intent, 0);
  alarmManager=(AlarmManager)this.getSystemService(Context.ALARM_SERVICE);
  alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, 
          0, 
          mSecs, 
          pendingIntent);
  return Service.START_NOT_STICKY;
  
 }
 public void onDestroy(){
  alarmManager.cancel(pendingIntent);
  
   stopService(new Intent(getApplicationContext(),Servizio.class));
  
  
  super.onDestroy();
  Log.v("BASEALARM","ONDESTROY");
  
 }

}


Servizio (service):
public class Servizio extends Service implements TextToSpeech.OnInitListener,TextToSpeech.OnUtteranceCompletedListener{
 TextToSpeech tts;

 @Override
 public void onCreate(){
  tts=new TextToSpeech(this,this);
  Log.v("SEGNALE", "ONCREATE");
 }
 @Override
 public IBinder onBind(Intent intent) {
  // TODO Auto-generated method stub
  return null;
 }

 @Override
 public void onInit(int status) {
  if(status==TextToSpeech.SUCCESS){
   int result=tts.setLanguage(Locale.ITALIAN);
   tts.setOnUtteranceCompletedListener(this);
   HashMap hashMap=new HashMap();
   hashMap.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "frase");

   tts.speak("Ciao, birichino.", TextToSpeech.QUEUE_FLUSH, hashMap);
  }
  Log.v("SEGNALE","ONINIT");
  
 }

 @Override
 public void onUtteranceCompleted(String utteranceId) {
  try{
   tts.stop();
   tts.shutdown();
   stopSelf();
  }catch(Exception e){}
  Log.v("SEGNALE","ONUTTERANCE");
  
 }
 
 @Override
 public void onDestroy(){
  super.onDestroy();
  Log.v("SEGNALE","ONDESTROY");
 }
 
}
Ho dovuto aggiungere un try...catch all'evento onDestroy di Servizio perché certamente la chiusura del TTS impiega del tempo per cui lo stopSelf veniva eseguito probabilmente in anticipo.

Azione ripetitiva con Alarm.setInexactRepeating e cancellazione di un Alarm

Vediamo sulla documentazione setInexactRepeating di AlarmManager.
Il secondo parametro si chiama triggerAtMillis e il secondo intervalMillis.
Che significano?

Ecco: il primo dovrebbe essere il tempo intercorso prima della prima ripetizione, e l'altro il tempo fra le ripetizioni.
Facciamo pochi secondi...

alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, 1000*20, 1000*5, pendingIntent);
Vediamo... No... appare immediatamente per poi andare a intervalli.

Ma se la proprietà AlarmManager.INTERVAL_HALF_HOUR comprendesse SystemClock.elapsedRealtime sommata di mezz'ora?

Riproviamo con questa sintassi:
  alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, 
           SystemClock.elapsedRealtime()+1000*40, 
           1000*5, 
           pendingIntent);
Sì, adesso funziona.

Solo che adesso il mio Service va mettendo toast a tempo indeterminato.
Come lo stoppiamo?
Ci aggiungo questo nel codice di MainActivity:
 public void onDestroy(){
  super.onDestroy();
  stopService(new Intent(this,Servizio2.class));
 }
...che non funziona! Provo a metterlo in un onClickListener del button, ma non funziona lo stesso.
Dopo un po' di tribolazioni ho avuto l'intuizione che devo stoppare l'AlarmManager prima di stoppare il Service!

Proviamo:
  button.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    alarmManager.cancel(pendingIntent);
    
   }
  });
Vediamo se la smette di lampeggiare!

E' riuscito!
Per completezza devo chiudere anche il Service:
  button.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    alarmManager.cancel(pendingIntent);
    stopService(new Intent(getApplicationContext(),Servizio2.class));
   }
  });
e ne voglio una conferma mettendo un segnale nell'evento onDestroy del Service:
 @Override
 public void onDestroy(){
  super.onDestroy();
  Log.v("SEGNALE","ONDESTROY");
 }
Ecco:
06-18 18:07:02.445: V/SEGNALE(14864): ONDESTROY

venerdì 17 giugno 2016

Chiamare un Service dopo un intervallo prestabilito di tempo, una sola volta

Adesso mi esercito con AlarmManager.
Ovviamente, prima di farlo con il TTS, meglio usare dei semplici Toast o dei Log in LogCat.


Creiamo un'app che mostri un Toast ogni 30 secondi.

Provo a creare un AlarmManager secondo quel poco che ricordo, certamente sbagliando...
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		AlarmManager alarmManager=(AlarmManager)this.getSystemService(Context.ALARM_SERVICE);
		alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,)
...che è incompleto: il parametro successivo presente sul tutorial è SystemClock.elapsedRealtime() sommato a qualcosa...
Voglio vedere, criticamente, cosa è questo SystemClock.elapsedRealtime().

Dunque "commentizzo" il codice incompleto di cui sopra e me lo metto in LogCat.
		Log.v("SystemClock.elapsedRealtime",""+SystemClock.elapsedRealtime());
		
		/*AlarmManager alarmManager=(AlarmManager)this.getSystemService(Context.ALARM_SERVICE);
		alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,)*/
E vediamo...

Ho ottenuto questo valore:
06-18 01:08:21.163: V/SystemClock.elapsedRealtime(19020): 8482000
Sospetto che si tratti di un valore in millisecondi: lo converto in ore...
8482 secondi,
141 minuti e 22 secondi
2 ore, 21 minuti e 22 secondi.

Dovrebbe essere il tempo passato dall'accensione del cellulare...

Ora per fare una prova lo spengo e lo riaccendo, e vediamo...

06-18 01:34:22.074: V/SystemClock.elapsedRealtime(9256): 661888
conversione: 11 min, 1 sec, 888 msec.
Ci sta!

Ora, mentre si esegue l'App viene stabilito un tempo passato dall'accensione del cellulare e viene stabilito che dopo un tot di millisecondi si deve verificare un dato evento.
Riprendiamo il codice...

Eccolo, completato con intent e pendingIntent:
public class MainActivity extends Activity {

	Button button;
	Intent intent;
	PendingIntent pendingIntent;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
	
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		intent=new Intent(this,Servizio2.class);
		pendingIntent=PendingIntent.getService(this, 0, intent, 0);
		AlarmManager alarmManager=(AlarmManager)this.getSystemService(Context.ALARM_SERVICE);
		alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,SystemClock.elapsedRealtime()+1000*60,pendingIntent);
		
	}
}
Vediamo se funziona: dovrebbe scattare il Toast evocato dal Service dopo 60 secondi.

E non scatta!
Ovviamente, mi pareva strano che non ci fosse un errore...
non ho registrato il Service sul Manifest!.
Non funziona nemmeno con il semplice startService(intent).
Rimedio...
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <service android:name="Servizio"
            android:exported="true" />
        <service android:name="Servizio2" />
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" > 
Adesso vediamo...

Ora il Toast appare, ma appare subito, non dopo il tempo voluto.
Provo con il LogCat...

Appare subito e dopo un tempo congruente con i 60 secondi. Perché?

Perché sono cretino io!. Avevo lasciato startService(intent), e quindi appariva subito grazie a questo!

Ora rifaccio tutto (dopo soli 10 secondi perché sembra che il tempo non passi mai). Sì! Funziona!

Creazione di un Service con tts (definitiva)

Ho ancora bisogno di esercitarmi sul ciclo vitale dei Services. Costruisco un Service...

Dopo la prima costruzione guidata da Eclipse ho questo:
public class Servizio extends Service{

	@Override
	public IBinder onBind(Intent intent) {
		// TODO Auto-generated method stub
		return null;
	}
	
}
Gli eventi che voglio esplorare sono onCreate, onStartCommand e onDestroy.
Li inserisco.
public class Servizio extends Service{
	
	@Override
	public void onCreate(){
		super.onCreate();
		Log.v("SEGNALE", "ONCREATE");
	}
	
	@Override
	public int onStartCommand(Intent intent, int flags, int startUI){
		Log.v("SEGNALE", "ONSTARTCOMMAND");
		return startUI;
		
	}
	
	@Override
	public void onDestroy(){
		super.onDestroy();
		Log.v("SEGNALE", "ONDESTROY");
	}

	@Override
	public IBinder onBind(Intent intent) {
		// TODO Auto-generated method stub
		return null;
	}
	
}
E ora chiamo questo Service da MainActivity:
public class MainActivity extends Activity {

	Button button;
	Intent intent;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		button=(Button)findViewById(R.id.button1);
		button.setOnClickListener(new View.OnClickListener() {
			
			@Override
			public void onClick(View v) {
				intent=new Intent(getApplicationContext(),Servizio.class);
				startService(intent);
				
			}
		});

	
		}
}
... chiamo con la pressione di un pulsante.
E vado.

E schiaccio ripetutamente il Button.
Ecco:
06-16 19:26:01.314: V/SEGNALE(2305): ONCREATE
06-16 19:26:01.314: V/SEGNALE(2305): ONSTARTCOMMAND
06-16 19:26:06.289: V/SEGNALE(2305): ONSTARTCOMMAND
06-16 19:26:08.170: V/SEGNALE(2305): ONSTARTCOMMAND
06-16 19:26:09.662: V/SEGNALE(2305): ONSTARTCOMMAND
06-16 19:26:11.203: V/SEGNALE(2305): ONSTARTCOMMAND
ONCREATE viene chiamato soltanto alla prima pressione.
Ci riprovo ancora, reinstallando sul cellulare da Eclipse dopo aver chiuso l'App sul cellulare:
06-16 19:28:28.678: V/SEGNALE(4387): ONSTARTCOMMAND
06-16 19:28:30.099: V/SEGNALE(4387): ONSTARTCOMMAND
06-16 19:28:31.340: V/SEGNALE(4387): ONSTARTCOMMAND
06-16 19:28:32.401: V/SEGNALE(4387): ONSTARTCOMMAND
Ecco: ONCREATE non viene più evocato.
Dopo un po' ottengo un messaggio di errore "L'applicazione Esercizio6 non risponde. Chiudere?".
Digito "SI" e vedo in LogCat a vedere di capirci qualcosa...

Nessuna segnalazione.
Dunque, il Service viene creato una sola volta, mentre le volte successive è già creato e quindi viene soltanto eseguito startCommand.

Passato un po' di tempo, riapro l'App e compare di nuovo ONCREATE.
E mi segnala poi di nuovo l'errore di sopra, senza alcuna segnalazione in LogCat.


Provo a chiudere l'App e a riaprirla, e ridigito.
06-16 19:36:25.543: V/SEGNALE(5494): ONCREATE
06-16 19:36:25.543: V/SEGNALE(5494): ONSTARTCOMMAND
06-16 19:36:25.553: V/SEGNALE(5494): ONSTARTCOMMAND
06-16 19:36:26.844: V/SEGNALE(5494): ONSTARTCOMMAND
06-16 19:36:27.555: V/SEGNALE(5494): ONSTARTCOMMAND
06-16 19:36:28.145: V/SEGNALE(5494): ONSTARTCOMMAND
Ma se faccio tutto, chiusura e riapertura dell'App, in tempi brevi, ONCREATE non viene richiamata.
Se lo faccio in tempi più lunghi viene richiamata, e comunque una singola pressione sul bottone mi causa ripetute chiamate di ONSTARTCOMMAND.

Comunque, ONCREATE, trovo sulla documentazione, viene eseguito se è necessario.
Resta l'enigma di quel messaggio di errore...
Che non sia perché onStartCommand ha necessariamente bisogno di un return STICKY o qualche altra cosa?
Ora ci metto Service.START_STICKY e vediamo se lo fa di nuovo...

Ecco, sembra che con lo START_STICKY non venga fuori quel problema.
Dunque bisogna necessariamente dare un valore al return di onStartCommand.

Con lo START_STICKY sembra che sia veramente "colloso": dopo diversi minuti non si ricrea ONCREATE, segno che il Service è ancora "vivo" nel cellulare.
Ora provo con un diverso valore...
	@Override
	public int onStartCommand(Intent intent, int flags, int startUI){
		Log.v("SEGNALE", "ONSTARTCOMMAND");
		return Service.START_NOT_STICKY;
		
	}
06-16 20:31:21.959: V/SEGNALE(10437): ONCREATE
06-16 20:31:21.959: V/SEGNALE(10437): ONSTARTCOMMAND
06-16 20:31:23.891: V/SEGNALE(10437): ONSTARTCOMMAND
06-16 20:31:25.062: V/SEGNALE(10437): ONSTARTCOMMAND
06-16 20:31:25.662: V/SEGNALE(10437): ONSTARTCOMMAND
Chiudo l'App e la riapro, e ogni volta vedo questo:
06-16 20:31:21.959: V/SEGNALE(10437): ONCREATE
06-16 20:31:21.959: V/SEGNALE(10437): ONSTARTCOMMAND
06-16 20:31:23.891: V/SEGNALE(10437): ONSTARTCOMMAND
06-16 20:31:25.062: V/SEGNALE(10437): ONSTARTCOMMAND
06-16 20:31:25.662: V/SEGNALE(10437): ONSTARTCOMMAND
Attendo più tempo...
06-16 20:33:10.655: V/SEGNALE(11963): ONCREATE
06-16 20:33:10.665: V/SEGNALE(11963): ONSTARTCOMMAND
06-16 20:33:11.676: V/SEGNALE(11963): ONSTARTCOMMAND
06-16 20:33:12.306: V/SEGNALE(11963): ONSTARTCOMMAND


Controprova: ritorno con lo START_STICKY:
	@Override
	public int onStartCommand(Intent intent, int flags, int startUI){
		Log.v("SEGNALE", "ONSTARTCOMMAND");
		return Service.START_STICKY;
		
	}
06-16 20:34:35.368: V/SEGNALE(12274): ONCREATE
06-16 20:34:35.368: V/SEGNALE(12274): ONSTARTCOMMAND
06-16 20:34:36.389: V/SEGNALE(12274): ONSTARTCOMMAND
06-16 20:34:36.919: V/SEGNALE(12274): ONSTARTCOMMAND
06-16 20:34:37.490: V/SEGNALE(12274): ONSTARTCOMMAND
06-16 20:34:39.171: V/SEGNALE(12274): ONSTARTCOMMAND
Chiudo e riapro...
06-16 20:36:15.355: V/SEGNALE(14166): ONSTARTCOMMAND
06-16 20:36:16.376: V/SEGNALE(14166): ONSTARTCOMMAND
06-16 20:36:17.057: V/SEGNALE(14166): ONSTARTCOMMAND
06-16 20:36:17.667: V/SEGNALE(14166): ONSTARTCOMMAND
Ma vedo anche:
06-16 20:36:14.344: V/SEGNALE(14166): ONCREATE
06-16 20:36:14.344: V/SEGNALE(14166): ONSTARTCOMMAND
sia pure non ogni volta come con lo STICKY.

Ho riprovato con NOT_STICKY e ONCREATE mi appare ogni volta.
Sì, credo che la differenza sia questa: con STICKY il Service viene creato solo se necessario, mentre con NOT_STICKY viene regolarmente creato ogni volta.
Non vedo riferimenti di ONDESTROY, però.

Ecco, se io metto uno stopSelf() arriva il DESTROY, con la necessità di ricreare ogni volta il Service:
	@Override
	public int onStartCommand(Intent intent, int flags, int startUI){
		Log.v("SEGNALE", "ONSTARTCOMMAND");
		stopSelf();
		return Service.START_NOT_STICKY;
	}
06-16 20:52:43.849: V/SEGNALE(16417): ONCREATE
06-16 20:52:43.849: V/SEGNALE(16417): ONSTARTCOMMAND
06-16 20:52:43.849: V/SEGNALE(16417): ONDESTROY
06-16 20:52:45.151: V/SEGNALE(16417): ONCREATE
06-16 20:52:45.151: V/SEGNALE(16417): ONSTARTCOMMAND
06-16 20:52:45.151: V/SEGNALE(16417): ONDESTROY
06-16 20:52:48.004: V/SEGNALE(16417): ONCREATE
06-16 20:52:48.004: V/SEGNALE(16417): ONSTARTCOMMAND
06-16 20:52:48.014: V/SEGNALE(16417): ONDESTROY

Ecco perché nel mio Service parlante è assurdo mettere il codice con lo speak() nel contesto di onCreate: in tutti i casi in cui onCreate non viene creato, il Service non parlerà!
Provo ancora:
public class Servizio extends Service{
	TextToSpeech tts;
	@Override
	public void onCreate(){
		super.onCreate();
		Log.v("SEGNALE", "ONCREATE");
		tts=new TextToSpeech(this,new TextToSpeech.OnInitListener() {
			
			@Override
			public void onInit(int status) {
				// TODO Auto-generated method stub
				if(status==TextToSpeech.SUCCESS){
					tts.setLanguage(Locale.ITALIAN);
				}
			}
		});
		
	}
	
	@Override
	public int onStartCommand(Intent intent, int flags, int startUI){
		Log.v("SEGNALE", "ONSTARTCOMMAND");
		tts.speak("Vieni avanti, cretino", TextToSpeech.QUEUE_FLUSH, null);
		stopSelf();
		return Service.START_NOT_STICKY;
	}
	
	@Override
	public void onDestroy(){
		super.onDestroy();
		Log.v("SEGNALE", "ONDESTROY");
	}

	@Override
	public IBinder onBind(Intent intent) {
		// TODO Auto-generated method stub
		return null;
	}
	
}
Ecco, in questo caso ottengo un errore:
06-16 21:11:07.356: V/SEGNALE(19270): ONCREATE
06-16 21:11:07.536: W/ApplicationPackageManager(19270): getCSCPackageItemText()
06-16 21:11:07.596: I/TextToSpeech(19270): Sucessfully bound to com.google.android.tts
06-16 21:11:07.596: V/SEGNALE(19270): ONSTARTCOMMAND
06-16 21:11:07.616: W/TextToSpeech(19270): speak failed: not bound to TTS engine
06-16 21:11:07.696: V/SEGNALE(19270): ONDESTROY
06-16 21:11:07.726: E/ActivityThread(19270): Service com.example.esercizio6.Servizio has leaked ServiceConnection android.speech.tts.TextToSpeech$Connection@41e1a728 that was originally bound here
che è dovuto al fatto che non c'è connessione con il tts engine.
Troppo presto, probabilmente.
Provo a mettere lo speak() nel contesto di onCreate:
public class Servizio extends Service{
	TextToSpeech tts;
	@Override
	public void onCreate(){
		super.onCreate();
		Log.v("SEGNALE", "ONCREATE");
		tts=new TextToSpeech(this,new TextToSpeech.OnInitListener() {
			
			@Override
			public void onInit(int status) {
				// TODO Auto-generated method stub
				if(status==TextToSpeech.SUCCESS){
					tts.setLanguage(Locale.ITALIAN);
					tts.speak("Vieni avanti, cretino", TextToSpeech.QUEUE_FLUSH, null);
					stopSelf();
				}
			}
		});
		
	}
	
	@Override
	public int onStartCommand(Intent intent, int flags, int startUI){
		Log.v("SEGNALE", "ONSTARTCOMMAND");
		return Service.START_NOT_STICKY;
	}
	
	@Override
	public void onDestroy(){
		super.onDestroy();
		Log.v("SEGNALE", "ONDESTROY");
	}

	@Override
	public IBinder onBind(Intent intent) {
		// TODO Auto-generated method stub
		return null;
	}
	
}
Ottengo una risposta molto ritardata, incostante e incompleta.
Perché?
Forse è quello stopSelf() che dà fastidio... Lo levo.
	public void onCreate(){
		super.onCreate();
		Log.v("SEGNALE", "ONCREATE");
		tts=new TextToSpeech(this,new TextToSpeech.OnInitListener() {
			
			@Override
			public void onInit(int status) {
				// TODO Auto-generated method stub
				if(status==TextToSpeech.SUCCESS){
					tts.setLanguage(Locale.ITALIAN);
					tts.speak("Vieni avanti, cretino", TextToSpeech.QUEUE_FLUSH, null);
					
				}
			}
		});
		
	}
E vediamo:
Sì, adesso funziona, sia pure con notevole ritardo.
Alla seconda non funziona più, semplicemente perché ONCREATE non viene più eseguita!

E' un bel problema, come gestire il tts in un service.
L'inizializzazione del tts può anche essere messa in onCreate, ma lo speak() no, altrimenti non viene eseguito in tutte quelle situazioni in cui onCreate non viene eseguito.
Peraltro lo speak() rischia di non essere eseguito nemmeno in onStartCommand, se l'inizializzazione del tts in onCreate prende troppo tempo e nel frattempo viene eseguito onStartCommand senza che la connessione al tts sia completa.
Vediamo di trovare esempi in rete...

Ho trovato questo.
Ci provo.

public class Servizio extends Service implements TextToSpeech.OnInitListener,OnUtteranceCompletedListener{

	TextToSpeech tts;
	@Override
	public IBinder onBind(Intent intent) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void onUtteranceCompleted(String utteranceId) {
		// TODO Auto-generated method stub
	}

	@Override
	public void onInit(int status) {
		// TODO Auto-generated method stub
		
	}
	
}
Ecco lo scheletro che ho ottenuto estendendo Service e implementando le interfacce di TextToSpeech.
Ora in onInit piazzo tutto il codice per la creazione di un motore tts, mentre l'istanziazione la piazzo in onCreate.
public class Servizio extends Service implements TextToSpeech.OnInitListener,OnUtteranceCompletedListener{

	TextToSpeech tts;
	@Override
	public void onCreate(){
		tts=new TextToSpeech(this,this);
	}
	@Override
	public IBinder onBind(Intent intent) {
		// TODO Auto-generated method stub
		return null;
	}
	@Override
	public void onUtteranceCompleted(String utteranceId) {
		
		
	}
	@Override
	public void onInit(int status) {
		if(status==TextToSpeech.SUCCESS){
			int result=tts.setLanguage(Locale.ITALIAN);
			tts.speak("Ciao, ciccio", TextToSpeech.QUEUE_FLUSH, null);
			
		}	
	}
	
}
Ho piazzato l'istanziazione in onCreate e il codice per l'inizializzazione e lo speak() in onInit, ovviamente.
Vediamo se funziona...

Funziona solo la prima volta. Successivamente si blocca.
Ci metto anche onStartCommand, per metterci lo STICKY o meno...

Ecco, adesso mi è più chiaro.
06-17 01:26:09.764: V/SEGNALE(19408): ONCREATE
06-17 01:26:09.764: V/SEGNALE(19408): ONSTARTCOMMAND
06-17 01:26:12.156: I/TextToSpeech(19408): Connected to ComponentInfo{com.google.android.tts/com.google.android.tts.service.GoogleTTSService}
06-17 01:26:12.196: I/TextToSpeech(19408): Set up connection to ComponentInfo{com.google.android.tts/com.google.android.tts.service.GoogleTTSService}
06-17 01:26:12.216: V/SEGNALE(19408): ONINIT
Viene chiamato ONCREATE, quindi ONSTARTCOMMAND e da ultimo ONINIT: tts viene istanziato in onCreate, da dove chiama onInit per l'inizializzazione e quindi lo speak.
onStartCommand viene chiamato direttamente dal richiamo del Service, credo, ma onInit viene chiamato da onCreate nel momento in cui in onCreate tts viene istanziato.

Alla successiva pressione del button, invece, si realizza solo onStartCommand, e per questo il programma non emette alcun suono.

Ora, dunque, bisognerebbe fare in modo da chiudere il Service ogni volta, in modo che si possa riaprire onCreate.
A questo proposito cerchiamo di usare onUtteranceCompletedListener.

E se mettessi tutto in onStartCommand?
Proviamo...

public class Servizio extends Service{

	TextToSpeech tts;
	@Override
	public void onCreate(){
	
		Log.v("SEGNALE", "ONCREATE");
	}
		
	@Override
	public int onStartCommand(Intent intent, int flags, int startUI){
		tts=new TextToSpeech(this,new TextToSpeech.OnInitListener() {
			
			@Override
			public void onInit(int status) {
				if(status==TextToSpeech.SUCCESS){
					int result=tts.setLanguage(Locale.ITALIAN);
					tts.speak("Vieni avanti, cretino", TextToSpeech.QUEUE_FLUSH, null);
				}
			}
		});
		Log.v("SEGNALE", "ONSTARTCOMMAND");
		return Service.START_NOT_STICKY;
	}
	
	@Override
	public void onDestroy(){
		super.onDestroy();
		Log.v("SEGNALE", "ONDESTROY");
	}
	
	@Override
	public IBinder onBind(Intent intent) {
		// TODO Auto-generated method stub
		return null;
	}

}
Sembra funzionare in modo molto più efficiente di prima.

Potrei anche stoppare il Service, comunque.
Questa è la sequenza su LogCat:
06-17 11:39:04.933: V/SEGNALE(12422): ONCREATE
06-17 11:39:04.933: W/ApplicationPackageManager(12422): getCSCPackageItemText()
06-17 11:39:04.943: I/TextToSpeech(12422): Sucessfully bound to com.google.android.tts
06-17 11:39:04.943: V/SEGNALE(12422): ONSTARTCOMMAND
06-17 11:39:05.053: I/TextToSpeech(12422): Connected to ComponentInfo{com.google.android.tts/com.google.android.tts.service.GoogleTTSService}
06-17 11:39:05.063: I/TextToSpeech(12422): Set up connection to ComponentInfo{com.google.android.tts/com.google.android.tts.service.GoogleTTSService}
06-17 11:39:20.208: W/ApplicationPackageManager(12422): getCSCPackageItemText()
06-17 11:39:20.218: I/TextToSpeech(12422): Sucessfully bound to com.google.android.tts
06-17 11:39:20.218: V/SEGNALE(12422): ONSTARTCOMMAND
06-17 11:39:20.218: I/TextToSpeech(12422): Connected to ComponentInfo{com.google.android.tts/com.google.android.tts.service.GoogleTTSService}
06-17 11:39:20.228: I/TextToSpeech(12422): Set up connection to ComponentInfo{com.google.android.tts/com.google.android.tts.service.GoogleTTSService}
La prima volta il sintetizzatore vocale impiega più tempo, per partire, mentre le volte successive parte immediatamente.
Forse non mi conviene che il Service si chiuda...

Il ritardo è dovuto al tempo necessario per la creazione ex novo del Service.

Però, così facendo, se il service rimane attivo, a ogni interruzione del service seguita da ri-creazione, mi "parte" la voce...

Dal momento che non mi interessa l'esecuzione immediata, posso permettermi qualche secondo perché venga creato il Service ex novo, e quindi è meglio interromperlo a ogni esecuzione.
Come fare?

Riscrivo...

public class Servizio extends Service implements TextToSpeech.OnInitListener,TextToSpeech.OnUtteranceCompletedListener{
	TextToSpeech tts;

	@Override
	public IBinder onBind(Intent intent) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void onInit(int status) {
		if(status==TextToSpeech.SUCCESS){
			int result=tts.setLanguage(Locale.ITALIAN);
			tts.speak("Ciao, bello", TextToSpeech.QUEUE_FLUSH, null);
		}
		
	}

	@Override
	public void onUtteranceCompleted(String utteranceId) {
		// TODO Auto-generated method stub
		
	}
	
}
Questo dovrebbe essere sufficiente per parlare...
Vediamo...

No: ho dimenticato di istanziare tts nell'ambito di onCreate.
public class Servizio extends Service implements TextToSpeech.OnInitListener,TextToSpeech.OnUtteranceCompletedListener{
	TextToSpeech tts;

	@Override
	public void onCreate(){
		tts=new TextToSpeech(this,this);
	}
	@Override
	public IBinder onBind(Intent intent) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void onInit(int status) {
		if(status==TextToSpeech.SUCCESS){
			int result=tts.setLanguage(Locale.ITALIAN);
			tts.speak("Ciao, bello", TextToSpeech.QUEUE_FLUSH, null);
		}
		
	}

	@Override
	public void onUtteranceCompleted(String utteranceId) {
		// TODO Auto-generated method stub
		
	}
	
}
E infatti parla. Con i soliti alcuni secondi di ritardo, ma parla.

Ora voglio che il Service si chiuda per riaprirsi all'occorrenza.
Per prima cosa, mi marco con il Log tutti i metodi
public class Servizio extends Service implements TextToSpeech.OnInitListener,TextToSpeech.OnUtteranceCompletedListener{
	TextToSpeech tts;

	@Override
	public void onCreate(){
		tts=new TextToSpeech(this,this);
		Log.v("SEGNALE", "ONCREATE");
	}
	@Override
	public IBinder onBind(Intent intent) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void onInit(int status) {
		if(status==TextToSpeech.SUCCESS){
			int result=tts.setLanguage(Locale.ITALIAN);
			tts.speak("Ciao, bello", TextToSpeech.QUEUE_FLUSH, null);
		}
		Log.v("SEGNALE","ONINIT");
		
	}

	@Override
	public void onUtteranceCompleted(String utteranceId) {
		// TODO Auto-generated method stub
		
	}
	
}
Ecco:
06-17 13:20:56.402: I/TextToSpeech(27081): Sucessfully bound to com.google.android.tts
06-17 13:20:56.412: V/SEGNALE(27081): ONCREATE
06-17 13:20:58.454: I/TextToSpeech(27081): Connected to ComponentInfo{com.google.android.tts/com.google.android.tts.service.GoogleTTSService}
06-17 13:20:58.494: I/TextToSpeech(27081): Set up connection to ComponentInfo{com.google.android.tts/com.google.android.tts.service.GoogleTTSService}
06-17 13:20:58.564: D/SEGNALE(27081): ONINIT
E adesso andiamo a studiare onUtterance... eccetera...

Per prima cosa mi marco anche quello:
	@Override
	public void onUtteranceCompleted(String utteranceId) {
		Log.v("SEGNALE","ONUTTERANCE");
		
	}
E scopro che non viene eseguito.
Aggiungo anche onDestroy:
	@Override
	public void onDestroy(){
		super.onDestroy();
		Log.v("SEGNALE","ONDESTROY");
	}
E so già che non viene eseguito, comunque me lo riprovo ugualmente...

Infatti non viene eseguito.

Andiamo finalmente a studiare onUtteranceCompleted!
E' deprecato, consigliata la classe onUtteranceProgressListener, ma in entrembi i casi è necessario fornire un utteranceId
Che accidenti è un utteranceId?



Soluzione trovata. Ecco il Service:
public class Servizio extends Service implements TextToSpeech.OnInitListener,TextToSpeech.OnUtteranceCompletedListener{
	TextToSpeech tts;

	@Override
	public void onCreate(){
		tts=new TextToSpeech(this,this);
		Log.v("SEGNALE", "ONCREATE");
	}
	@Override
	public IBinder onBind(Intent intent) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void onInit(int status) {
		if(status==TextToSpeech.SUCCESS){
			int result=tts.setLanguage(Locale.ITALIAN);
			tts.setOnUtteranceCompletedListener(this);
			HashMap hashMap=new HashMap();
			hashMap.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "frase");
			tts.setPitch(0.1f);
			tts.setSpeechRate(0.1f);
			tts.speak("Verrò a ucciderti stanotte", TextToSpeech.QUEUE_FLUSH, hashMap);
		}
		Log.v("SEGNALE","ONINIT");
		
	}

	@Override
	public void onUtteranceCompleted(String utteranceId) {
		tts.stop();
		tts.shutdown();
		stopSelf();
		Log.v("SEGNALE","ONUTTERANCE");
		
	}
	
	@Override
	public void onDestroy(){
		super.onDestroy();
		Log.v("SEGNALE","ONDESTROY");
	}
	
}
Funziona.
Dopo aver detto la frase, il Service viene stoppato e si distrugge (non senza aver chiuso il tts).