JavascriptProva

martedì 9 agosto 2016

Analisi del codice dell'Activity "Telefono".

Questo è il codice della schermata "Telefono":
public class Telefono extends Activity {

	AudioManager audioManager;
	Handler HndNonRisposta;
	Handler handler;
	String frase;
	Ringtone ringtone;
	TimerService mService;
	boolean mBound;
	Button bttRispondi, bttRifiuta;
	Helper helper;
	Timer timerNoRisposta;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_telefono);
		helper=new Helper(getApplicationContext());
		final AudioManager audioManager=(AudioManager)this.getSystemService(Context.AUDIO_SERVICE);
//--------------------------------------------------------------------------------
		//GESTIONE DELLE STRINGHE
		Cursor crs=helper.query();
		if(crs.getCount()!=0){
			Random rnd=new Random();
			int indice=rnd.nextInt(crs.getCount());
			crs.moveToPosition(indice);
			frase=crs.getString(0);
		}else{
			frase="Questa è una frase di prova";
		}
	
		
//--------------------------------------------------------------------------------
		
		
		
//-------------------------------------------------------------------------------
		//SUONERIA
//---------------------------------------------------------------------------------

		Uri ringtoneUri=RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_RINGTONE);
		ringtone=RingtoneManager.getRingtone(this, ringtoneUri);
		ringtone.play();

		bttRispondi=(Button)findViewById(R.id.button1);
		bttRifiuta=(Button)findViewById(R.id.button2);
		
		TimerTask noRisposta=new TimerTask(){

			@Override
			public void run() {
				ringtone.stop();
				mService.startTime();
				finish();
				
			}
			
		};
		timerNoRisposta=new Timer();
		timerNoRisposta.schedule(noRisposta, 10000);
		
//BOTTONE RISPOSTA

		bttRispondi.setOnClickListener(new View.OnClickListener() {
			
			
			@Override
			public void onClick(View v) {
				
				bttRispondi.setEnabled(false);
				bttRifiuta.setEnabled(false);
				timerNoRisposta.cancel();
				timerNoRisposta.purge();
				ringtone.stop();

				TimerTask tt=new TimerTask(){

					@Override
					public void run() {
						mService.tts.setOnUtteranceCompletedListener(new OnUtteranceCompletedListener(){

							@Override
							public void onUtteranceCompleted(String utteranceId) {
								audioManager.setSpeakerphoneOn(true);
								audioManager.setMode(AudioManager.MODE_NORMAL);
								mService.startTime();
								finish();
								
							}
							
						});
						HashMap map=new HashMap();
						map.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "value");
						audioManager.setMode(AudioManager.MODE_IN_CALL);
						audioManager.setSpeakerphoneOn(false);
						mService.tts.speak(frase, TextToSpeech.QUEUE_FLUSH, map);
						
					}
					
				};
				Timer Timer = new Timer();
				Timer.schedule(tt, 1500);

			}
		});

//BOTTONE RIFIUTA

		bttRifiuta.setOnClickListener(new View.OnClickListener() {
			
			@Override
			public void onClick(View v) {
				timerNoRisposta.cancel();
				timerNoRisposta.purge();
				ringtone.stop();
				mService.startTime();
				finish();
				
			}
		});
	}
//-------------------------------------------------------------------------
	
	@Override
	public void onWindowFocusChanged(boolean hasFocus){
		if(!hasFocus){
			ringtone.stop();

		}else{
			ringtone.play();
		}	
	}
	
	
	@Override
	public void onStart(){
		super.onStart();
		Intent intent=new Intent(this,TimerService.class);
		bindService(intent,mConnection,Service.BIND_AUTO_CREATE);
	}
	
	@Override
	public void onStop(){
		super.onStop();
		if(mBound){
			unbindService(mConnection);
			mBound=false;
		}
	}
	
	ServiceConnection mConnection=new ServiceConnection(){

		@Override
		public void onServiceConnected(ComponentName name, IBinder service) {
			LocalBinder bnd=(LocalBinder)service;
			mService=bnd.getService();
			mBound=true;
		}

		@Override
		public void onServiceDisconnected(ComponentName name) {
			mBound=false;
		}
	};
}
Ci sono diversi "blocchi".
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_telefono); helper=new Helper(getApplicationContext()); final AudioManager audioManager=(AudioManager)this.getSystemService(Context.AUDIO_SERVICE); Qui, subito dopo l'inizio di onCreate, si inizializzano Helper, per la gestione del database, e AudioManager, per la gestione delle telefonate.

C'è poi la gestione delle stringhe, che sono state salvate nel database da un'altra schermata:
		//GESTIONE DELLE STRINGHE
		Cursor crs=helper.query();
		if(crs.getCount()!=0){
			Random rnd=new Random();
			int indice=rnd.nextInt(crs.getCount());
			crs.moveToPosition(indice);
			frase=crs.getString(0);
		}else{
			frase="Questa è una frase di prova";
		}


Ora c'è la suoneria: questo codice viene eseguito sul momento e innesca la suoneria:
//-------------------------------------------------------------------------------
		//SUONERIA
//---------------------------------------------------------------------------------

		Uri ringtoneUri=RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_RINGTONE);
		ringtone=RingtoneManager.getRingtone(this, ringtoneUri);
		ringtone.play();


Ora, dopo l'istanziazione dei due buttons (che dovrei spostare prima) c'è la chiusura ritardata della suoneria nel caso in cui non vi sia risposta:

		bttRispondi=(Button)findViewById(R.id.button1);
		bttRifiuta=(Button)findViewById(R.id.button2);
		
		TimerTask noRisposta=new TimerTask(){

			@Override
			public void run() {
				ringtone.stop();
				mService.startTime();
				finish();
				
			}
			
		};
		timerNoRisposta=new Timer();
		timerNoRisposta.schedule(noRisposta, 10000);
Questo codice di interruzione della suoneria va a finire alla via finale comune: mService.startTime e finish().

Ed ecco quindi i codici dei pulsanti.
//BOTTONE RISPOSTA

		bttRispondi.setOnClickListener(new View.OnClickListener() {
			
			
			@Override
			public void onClick(View v) {
				
				bttRispondi.setEnabled(false);
				bttRifiuta.setEnabled(false);
				timerNoRisposta.cancel();
				timerNoRisposta.purge();
				ringtone.stop();

				TimerTask tt=new TimerTask(){

					@Override
					public void run() {
						mService.tts.setOnUtteranceCompletedListener(new OnUtteranceCompletedListener(){

							@Override
							public void onUtteranceCompleted(String utteranceId) {
								audioManager.setSpeakerphoneOn(true);
								audioManager.setMode(AudioManager.MODE_NORMAL);
								mService.startTime();
								finish();
								
							}
							
						});
						HashMap map=new HashMap();
						map.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "value");
						audioManager.setMode(AudioManager.MODE_IN_CALL);
						audioManager.setSpeakerphoneOn(false);
						mService.tts.speak(frase, TextToSpeech.QUEUE_FLUSH, map);
						
					}
					
				};
				Timer Timer = new Timer();
				Timer.schedule(tt, 1500);
Disabilita i pulsanti per evitarne pressioni accidentali con l'orecchio;
Elimina il task della non-risposta;
Blocca la suoneria;

Incapsula in un TimerTask tutto il codice per il parlato: dapprima il listener per il termine del parlato, quindi crea l'HashMap, imposta il microfono del cellulare e parla. Dopo il parlato OnUtteranceCompletedListener torna alla via finale comune mService.startTime e finish().


Quindi il pulsante "Rifiuta":
//BOTTONE RIFIUTA

		bttRifiuta.setOnClickListener(new View.OnClickListener() {
			
			@Override
			public void onClick(View v) {
				timerNoRisposta.cancel();
				timerNoRisposta.purge();
				ringtone.stop();
				mService.startTime();
				finish();
				
			}
		});
	}
Questo elimina il Task della "Non risposta", blocca la suoneria, e torna alla via finale comune mService.startTime e finish().

Mi sembra abbastanza stabile.
Quindi in pratica ogni evenienza esegue nell'ordine:
  1. Blocco della suoneria;
  2. mService.startTime;
  3. finish();
Fra i primi due si inserisce, nel caso di Risposta, il codice del parlato.

Si inizia a studiare i FAB.

Cerco di impadronirmi della scienza necessaria per costruire un FAB, che sarebbe una bellissima cosa.

Ricordare sempre di comportarsi come l'acqua, e mai come un sasso.

Intanto si definisce la classe, che estende View:
public class FloatingActionButton extends View {

 public FloatingActionButton(Context context) {
  super(context);
  // TODO Auto-generated constructor stub
 }

}
Questo è ciò che mi viene inizialmente, insieme al costruttore.
Ora dichiariamo, come di prassi, una variabile Context e vi poniamo il context preso come parametro del costruttore.
public class FloatingActionButton extends View {

 Context context;
 public FloatingActionButton(Context context) {
  super(context);
  this.context=context;
 }

}
Ecco che la cosa si va complicando.
Nel costruttore viene chiamata una funzione init(Color.WHITE).
Andando più avanti la scopro:
 public void init(int FloatingActionButtonColor) {
    setWillNotDraw(false);
    setLayerType(View.LAYER_TYPE_SOFTWARE, null);

    mButtonPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mButtonPaint.setColor(FloatingActionButtonColor);
    mButtonPaint.setStyle(Paint.Style.FILL);
    mButtonPaint.setShadowLayer(10.0f, 0.0f, 3.5f, Color.argb(100, 0, 0, 0));
    mDrawablePaint = new Paint(Paint.ANTI_ALIAS_FLAG);

...
Cosa sono queste? Funzioni e classi...
Dalla terza riga in poi è codice per il disegno, e quindi per il momento lo lascio stare. Mi interesso di setWillNotDraw e setLayerType, cercando se sono definite da qualche parte...

E invece sono metodi dell'oggetto View da cui questa classe eredita.
Per il momento lasciamoli stare, e vediamo oltre.
mButtonPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mi sembrano tutte istruzioni sulle quali posso avere le idee più chiare se vado a rivedermi Paint, Canvas onDraw eccetera... Vado a farlo.

domenica 7 agosto 2016

Studio della schermata Telefono non richiamabile dai Recent Tasks mediante l'apposito FLAG.

Attivo il programma.
Chiudo la MainActivity
Appare la schermata Telefono a intervalli.
Schiacciando Rispondi o Rifiuta funziona come deve e poi sparisce.

Ora provo, dopo che è sparita, a richiamarla in Recent Tasks.
Non vi rimane.
Per disattivare il programma devo riaprire dall'icona.

Ora provo a eliminare il flag:
   @Override
   public void run() {
    Intent intent =new Intent(getApplicationContext(),Telefono.class);
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
    startActivity(intent);
    
   }


Adesso appare e sta lì ferma, quando la richiamo riappare e ricomincia a suonare.
Rimetto il flag...

Il comportamento è come prima.
In Manifest non ci sono elementi che governino la persistenza delle activities nel Recent Tasks: la caratteristica è governata esclusivamente dal FLAG di cui sopra.

Revisione critica dei settings per gli intervalli nella mia CoachCall.

Ripercorriamo il percorso delle SharedPreferences nella mia app...

Si apre MainActivity.
In onStart si avvia e si binda il Service:
 public void onStart() {
  super.onStart();

  Intent intent = new Intent(this, TimerService.class);
  startService(intent);
  bindService(intent, mConnection, Service.BIND_AUTO_CREATE);
 }
Il Service, una volta attivo, crea SharedPreferences:
public class TimerService extends Service {

 SharedPreferences SP; 
 
 public TextToSpeech tts;
 
 OnAlarmChangeListener onAlarmChangeListener;
 boolean alarmOn;

 Handler handler=new Handler();
 IBinder mBinder=new LocalBinder();
 
 @Override
 public void onCreate(){

 
  Intent intent=new Intent(this,MainActivity.class);
  PendingIntent pi=PendingIntent.getActivity(this, 0, intent, 0);
  Notification notification=new NotificationCompat.Builder(this)
    .setSmallIcon(R.drawable.cornetta)
    .setContentTitle("CoachCall")
    .setContentText("Telefonate dal Coach")
    .setContentIntent(pi).build();
  startForeground(1,notification);
    
  
  SP=getApplicationContext().getSharedPreferences("settings", Context.MODE_PRIVATE);
  SharedPreferences.Editor editor= SP.edit();
  editor.putInt("intervalloMin", 10);
  editor.putInt("intervalloMax", 10);
  editor.commit();
Qui viene direttamente impostato intervalloMin e intervalloMax.
Quindi in startTime, ossia quando già onCreate è stato chiamato, si usano intervalloMin e intervalloMax:
 public void startTime(){
  int intervalloMin=SP.getInt("intervalloMin", 0);
  int intervalloMax=SP.getInt("intervalloMax", 0);
  int delta=intervalloMax-intervalloMin;
  Random rnd=new Random();
  int differenza=(intervalloMax==intervalloMin)? 0 : rnd.nextInt(delta);
  int intervallo=intervalloMin+differenza;
  Runnable runnable=new Runnable(){

   @Override
   public void run() {
    Intent intent =new Intent(getApplicationContext(),Telefono.class);
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
    startActivity(intent);
    
   }
   
  };
  handler.postDelayed(runnable, intervallo*1000);
  alarmOn=true;
  if(onAlarmChangeListener!=null)onAlarmChangeListener.AlarmChange();
 }

Quando si apre l'activity Impostazioni degli Intervalli, ecco il codice:
  SP=getApplicationContext().getSharedPreferences("settings",Context.MODE_PRIVATE);
  seekBar1=(SeekBar)findViewById(R.id.seekBar1);
  seekBar2=(SeekBar)findViewById(R.id.seekBar2);
  textView1=(TextView)findViewById(R.id.textView1);
  textView2=(TextView)findViewById(R.id.textView2);
  //codice che setta le seekBars in relazione ai valori letti nelle impostazioni
  
  seekBar1.setMax(230);
  intervalloMin=SP.getInt("intervalloMin", 10);
  setTextViewFromSeekBar(intervalloMin,textView1);
  seekBar1.setProgress(intervalloMin-10);
  
  seekBar2.setMax(230);
  intervalloMax=SP.getInt("intervalloMax", 10);
  setTextViewFromSeekBar(intervalloMax,textView2);
  seekBar2.setProgress(intervalloMax-10);
All'atto della regolazione degli intervalli, il valore che viene preso in caso di settings non inizializzati è 10.
In base a questo vengono compilati le TextView con i valori e le seekBars, la cui posizione viene settata a 10 in meno, in modo che per un valore di 10 vengano settate effettivamente a 0.
Il punto centrale è quello dell'utilizzo dei settings nella funzione setTime di TimerService.
Un codice come questo
  SharedPreferences.Editor editor= SP.edit();
  editor.putInt("intervalloMin", 10);
  editor.putInt("intervalloMax", 10);
  editor.commit();
seguito da questo che preleva i dati per l'utilizzo effettivo in startTime
 public void startTime(){
  int intervalloMin=SP.getInt("intervalloMin", 0);
  int intervalloMax=SP.getInt("intervalloMax", 0);
  int delta=intervalloMax-intervalloMin;
  Random rnd=new Random();
  int differenza=(intervalloMax==intervalloMin)? 0 : rnd.nextInt(delta);
  int intervallo=intervalloMin+differenza;
  Runnable runnable=new Runnable(){

   @Override
   public void run() {
    Intent intent =new Intent(getApplicationContext(),Telefono.class);
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
    startActivity(intent);
    
   }
   
  };
non ha senso: meglio togliere l'impostazione iniziale e prendere direttamente come 10 il valore in startTime se il setting non è inizializzato.
Ossia, il primo diventa:
  SP=getApplicationContext().getSharedPreferences("settings", Context.MODE_PRIVATE);
mentre in startTime:
 public void startTime(){
  int intervalloMin=SP.getInt("intervalloMin", 10);
  int intervalloMax=SP.getInt("intervalloMax", 10);
  int delta=intervalloMax-intervalloMin;
  Random rnd=new Random();
....
Quindi quando andremo a regolare gli intervalli, lasciando tutto inalterato...
  seekBar1.setMax(230);
  intervalloMin=SP.getInt("intervalloMin", 10);
  setTextViewFromSeekBar(intervalloMin,textView1);
  seekBar1.setProgress(intervalloMin-10);
  
  seekBar2.setMax(230);
  intervalloMax=SP.getInt("intervalloMax", 10);
  setTextViewFromSeekBar(intervalloMax,textView2);
  seekBar2.setProgress(intervalloMax-10);
in caso di non inizializzazione dei settings, l'intervallo preso per buono sarà 10, che andrà messo pari pari nelle TextView mentre seekBars saranno settate al valore -10, ossia a zero.
Una volta che invece con le seekBars sarà settato un valore diverso, questo sarà quello utilizzato in startTime del Service, e quello letto dai settings nel form per la regolazione degli intervalli, sempre settando le seekBars al valore sottratto di 10.

giovedì 4 agosto 2016

Immissione e feedback delle frasi immesse in un database.

Creare un database di frasi con possibilità di inserire nuovi record digitando in una EditText, mostrarli in una ScrollView e cancellarli mediante un pulsante.

Ho questo file di layout:
<RelativeLayout 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: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.palestrascrollview.MainActivity" >
 

</RelativeLayout> 
Devo aggiungere una ScrollView che contiene un Layout. Scelgo un LinearLayout.
<RelativeLayout 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: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.palestrascrollview.MainActivity" >
 <ScrollView
     android:id="@+id/scrollView"
     android:fillViewport="true"
     android:layout_width="400dp"
     android:layout_height="100dp"
     android:layout_alignParentTop="true"
     android:layout_alignParentLeft="true">
     <LinearLayout
                android:orientation="vertical"
         android:id="@+id/scrollLayout"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:background="#afc" />
 </ScrollView>

</RelativeLayout> 
Bene: ho ottenuto un quadrato di colore verdino-azzurro (quello da me voluto), che dovrebbe comportarsi come una scrollView.
Ma vorrei aggiungervi anche altre cosette.
Una EditText e un pulsante per l'immissione.

Sono giunto a una soluzione ragionevole, usando LinearLayout, che al momento preferisco rispetto ai RelativeLayout:
<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"
    tools:context="com.example.palestrascrollview.MainActivity" >
    
 <LinearLayout
     android:orientation="horizontal"
     android:layout_width="match_parent"
     android:layout_height="wrap_content" >
     
     <ScrollView
            android:id="@+id/scrollView"
            android:layout_width="400dp"
            android:layout_height="150dp"
            android:layout_alignParentLeft="true"
            android:layout_alignParentTop="true"
            android:fillViewport="true" >

            <LinearLayout
                android:id="@+id/scrollLayout"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="#afc"
                android:orientation="vertical" />
        </ScrollView>

 </LinearLayout>
 
 <LinearLayout
     android:orientation="horizontal"
     android:layout_width="match_parent"
     android:layout_height="wrap_content" >

     <EditText
         android:id="@+id/editText1"
         android:layout_marginTop="30dp"
         android:layout_width="400dp"
         android:layout_height="wrap_content"
         android:ems="10" >

     <requestFocus />
 </EditText>
 
 <Button
     android:id="@+id/button1"
     android:layout_marginTop="30dp"
     android:layout_marginLeft="30dp"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:text="Button" />    
 </LinearLayout>
 
</LinearLayout> 
Adesso occupiamoci del codice...

Ho creato un codice per una classe atta a generare il Database:
public class MainActivity extends Activity {

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

 }
 
 class Helper extends SQLiteOpenHelper{

  Context context;
  public Helper(Context context) {
   super(context, "database.db", null, 1);
   this.context=context;
  }
  @Override
  public void onCreate(SQLiteDatabase db) {
   db.execSQL("CREATE TABELLA(TESTO TEXT)");
   
  }
  
  public void save(String frase){
   SQLiteDatabase db=this.getWritableDatabase();
   ContentValues values=new ContentValues();
   values.put("TESTO", frase);
   db.insert("TABELLA", null, values);
  }
  
  public Cursor query(){
   SQLiteDatabase db=this.getWritableDatabase();
   Cursor crs=db.rawQuery("SELECT * FROM TABELLA", null);
   return crs;
   
   
  }
  @Override
  public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
   // TODO Auto-generated method stub
   
  }
  
 }


}
Ora devo lavorare per l'immissione dei dati nel database, usando EditText e il Button.
Al momento ho trovato questo:
public class MainActivity extends Activity {

 EditText editText;
 Button button;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  final Helper helper=new Helper(this);
  editText=(EditText)findViewById(R.id.editText1);
  button=(Button)findViewById(R.id.button1);
  
  button.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    helper.save(editText.getText().toString());

    
    
   }
  });

 }
I dati mi risultano regolarmente immessi nel database.
Ora devo pensare a un modo di visualizzarli immediatamente nella ScrollView.
Per il momento mi visualizzo la frase in LogCat per vedere che sia tutto a posto:
  button.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    helper.save(editText.getText().toString());
    frase=helper.query().getString(2);
    Log.d("FRASE", frase);
    
   }
  });
Ottengo un errore IndexOutOfBound, e mi viene in mente che ho dimenticato il MoveToFirst...
  public Cursor query(){
   SQLiteDatabase db=this.getWritableDatabase();
   Cursor crs=db.rawQuery("SELECT * FROM TABELLA", null);
   crs.moveToFirst();
   return crs;
   
   
  }
Un'altra correzione per l'errore del numero di colonna nel metodo getString dell'oggetto query():
   @Override
   public void onClick(View v) {
    helper.save(editText.getText().toString());
    frase=helper.query().getString(1);
    Log.d("FRASE", frase);
    
   }
e la cosa riesce!
Ora voglio estrarre, però, non solo il primo record del Cursor, ma tutti:
   @Override
   public void onClick(View v) {
    helper.save(editText.getText().toString());
    Cursor c=helper.query();
    do{
     Log.d("frase", c.getString(1));
    }while(c.moveToNext());
    
   }
Vediamo...

08-04 10:42:22.708: D/frase(8361): cvgfddghjjjjjjjhvvcfdsdhhjk
08-04 10:42:22.708: D/frase(8361): sddfffggg
08-04 10:42:22.708: D/frase(8361): ciao
08-04 10:42:22.708: D/frase(8361): ciccia
08-04 10:42:22.708: D/frase(8361): fgggggggggg
08-04 10:42:22.708: D/frase(8361): ciccio bello non scassare LA minchia
Perfetto!

mercoledì 3 agosto 2016

Uso di una ScrollView nel mio CoachCall.

Ho riusato una ScrollView.
Il modo migliore di usarla è questo:
<RelativeLayout 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: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.palestrascrollview.MainActivity" >
 
<ScrollView 
    android:layout_width="200dp"
    android:layout_height="200dp" 
    android:fillViewport="true"
    android:layout_alignParentLeft="true"
    android:layout_alignParentTop="true"
    android:layout_marginLeft="30dp"
    android:layout_marginTop="30dp"

    android:id="@+id/scrollView" >
     
     <LinearLayout
         android:orientation="vertical"
            android:id="@+id/mainLayout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#0cf" >
        </LinearLayout>
 </ScrollView>
</RelativeLayout> 
L'elemento cruciale è metterci dentro un LinearLayout (o anche un RelativeLayout o altro)
E ora ho una scrollView sulla quale aggiungere quello che voglio.

Voglio aggiungerci delle TextViews.
Voglio crearle però staccate le une dalle altre.
Ecco il codice:
public class MainActivity extends Activity {

 LinearLayout mainLayout;
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  mainLayout=(LinearLayout)findViewById(R.id.mainLayout);
  
  TextView textView=new TextView(this);
  textView.setBackgroundColor(Color.WHITE);
  textView.setText("Nel mezzo del cammin di nostra vita\r\n mi ritrovai per una selva oscura\r\n che la diritta via era smarrita");
  LinearLayout.LayoutParams lp=new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.WRAP_CONTENT);
  lp.setMargins(0, 20, 0, 20);
  mainLayout.addView(textView, lp);
  TextView textView1=new TextView(this);
  textView1.setBackgroundColor(Color.WHITE);
  textView1.setText("Nel mezzo del cammin di nostra vita\r\n mi ritrovai per una selva oscura\r\n che la diritta via era smarrita");
  mainLayout.addView(textView1,lp);
  textView.setHeight(200);
  textView1.setHeight(200);
 }


}
e funziona perfettamente: appaiono i testi scrollabili su distinte caselle di testo.

Aggiunta della suoneria e del meccanismo di cessazione attesa della risposta.

Ora, prima della voce sintetica va aggiunta la suoneria.
All'inizializzazione della schermata Telefono va innescata la suoneria, che poi va interrotta dal tasto Rispondi, il quale dà avvio alla voce.
Dunque lo metto in onCreate.
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_telefono);
  
  Uri ringtoneUri=RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_RINGTONE);
  ringtone=RingtoneManager.getRingtone(this, ringtoneUri);
  ringtone.play();
  bttRispondi=(Button)findViewById(R.id.button1);
  bttRispondi.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    ringtone.stop();
    mService.tts.setOnUtteranceCompletedListener(new OnUtteranceCompletedListener(){
     @Override
     public void onUtteranceCompleted(String utteranceId) {
      mService.startTime();
      finish(); 
     }
    });
La suoneria inizia a suonare e viene interrotta dal tasto Rispondi.
Proviamolo...

Sì, funziona.

Però sarebbe il caso di immettere un piccolo ritardo...
Proviamo:
    Handler handler=new Handler();
    handler.postDelayed(new Runnable(){

     @Override
     public void run() {
      HashMap hashMap=new HashMap();
      hashMap.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "id");
      
      mService.tts.speak("Ciao, scemo",TextToSpeech.QUEUE_FLUSH,hashMap);
      
     }
     
    },1000);
Proviamo...

Sì, funziona!

Prevedendo una non risposta da parte dell'utente, ossia né "accetta" né "rifiuta" ci dovrebbe essere l'abbandono della chiamata da parte del chiamante, come in tutte le normali telefonate.
Ho avuto delle difficoltà, però...

Vediamo di implementare questa funzione.
Ho "compattato" un po' il codice per la risposta: il listener per il termine del parlato viene inserito nel Runnable da lanciare con la latenza necessaria per portare il ricevitore all'orecchio:
  bttRispondi.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    ringtone.stop();
    handler=new Handler();
    handler.postDelayed(new Runnable(){

     @Override
     public void run() {
      mService.tts.setOnUtteranceCompletedListener(new OnUtteranceCompletedListener(){
       @Override
       public void onUtteranceCompleted(String utteranceId) {
        mService.startTime();
        finish(); 
       }
      });
      HashMap hashMap=new HashMap();
      hashMap.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "id");
      
      mService.tts.speak(frase,TextToSpeech.QUEUE_FLUSH,hashMap);
      handler.removeCallbacksAndMessages(null);
     }
    },1000);
   }
  });
Ho aggiunto anche il codice per la "ripulitura" dell'handler, anche se non credo ce ne sia proprio bisogno dal momento che con la distruzione dell'activity l'handler va distrutto... magari lo levo.

Metto ora un Runnable che viene lanciato dopo alcuni secondi dall'inizio della suoneria, con l'istruzione di interromperla e tornare al Service, distruggendo Telefono.
Vediamo se funziona...

  Uri ringtoneUri=RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_RINGTONE);
  ringtone=RingtoneManager.getRingtone(this, ringtoneUri);
  ringtone.play();
  HndNonRisposta=new Handler();
  HndNonRisposta.postDelayed(new Runnable(){

   @Override
   public void run() {
    ringtone.stop();
    mService.startTime();
    finish();
    
   }
   
  },10000);
Ora, se rispondo non ci sono problemi, mentre se rifiuto accade qualcosa di strano: si sovrappone un altro ciclo di schermate a ripetizione.
Questo perché il rifiuto fa tornare immediatamente il flusso del programma a startTime del Service, ma se resta attivo il ritardo per la chiusura spontanea questo torna a sua volta sempre a startTime del Service e tutto si impappa...

E' importante quindi aggiungere, qualora questo stop ritardato della suoneria venga anticipato dallo stop dovuto al rifiuto della chiamata, ma anche alla risposta (che non mi ha dato problemi forse per i tempi più lunghi...), un codice che "ripulisca" l'handler.
//BOTTONE RISPOSTA
  bttRispondi=(Button)findViewById(R.id.button1);
  bttRispondi.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    HndNonRisposta.removeCallbacksAndMessages(null);
    ringtone.stop();
    handler=new Handler();
    handler.postDelayed(new Runnable(){

     @Override
     public void run() {
      mService.tts.setOnUtteranceCompletedListener(new OnUtteranceCompletedListener(){
       @Override
       public void onUtteranceCompleted(String utteranceId) {
        mService.startTime();
        finish(); 
       }
      });
      HashMap hashMap=new HashMap();
      hashMap.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "id");
      
      mService.tts.speak(frase,TextToSpeech.QUEUE_FLUSH,hashMap);
      handler.removeCallbacksAndMessages(null);
     }
    },1000);
   }
  });

//BOTTONE RIFIUTA
  bttRifiuta=(Button)findViewById(R.id.button2);
  bttRifiuta.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    HndNonRisposta.removeCallbacksAndMessages(null);
    ringtone.stop();
    mService.startTime();
    finish();
    
   }
  });