/* * SaveMySugar - Exchange messages using the distance between phone calls * * Copyright (C) 2015 Antonio Ospite * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package it.ao2.savemysugar; import android.app.AlarmManager; import android.app.PendingIntent; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.support.v4.app.Fragment; import android.telephony.TelephonyManager; import android.text.method.ScrollingMovementMethod; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import java.lang.reflect.Method; import java.util.List; import java.util.Locale; import it.ao2.savemysugar.util.TimeLog; import it.ao2.util.morse.MorseDistanceModulator; import it.ao2.util.morse.MorseTranslator; public class SendFragment extends Fragment { private static final String TAG = "SaveMySugar"; private TextView mLogWindow; private String mDestinationNumber; private MorseDistanceModulator mModulator; private List mDistances; private long mEndCallTime; private Handler mSendSymbolHandler; public static class EndCallReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { endCall(context); } public void endCall(Context context) { TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); try { Class c = Class.forName(tm.getClass().getName()); Method m = c.getDeclaredMethod("getITelephony"); m.setAccessible(true); Object telephonyService = m.invoke(tm); c = Class.forName(telephonyService.getClass().getName()); m = c.getDeclaredMethod("endCall"); m.setAccessible(true); m.invoke(telephonyService); } catch (Exception e) { e.printStackTrace(); } } } @Override public void onAttach(Context context) { super.onAttach(context); mModulator = ((SaveMySugarApplication) getActivity().getApplication()).getModulator(); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_send, container, false); mLogWindow = (TextView) view.findViewById(R.id.send_log_window); mLogWindow.setMovementMethod(new ScrollingMovementMethod()); final Button sendMessageButton = (Button) view.findViewById(R.id.send_message); sendMessageButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { sendMessage(); } }); return view; } @Override public void onDestroy() { super.onDestroy(); cancelEndCallAlarm(); if (mSendSymbolHandler != null) { mSendSymbolHandler.removeCallbacksAndMessages(null); } } private void setEndCallAlarm(long timeout) { Intent intent = new Intent(getActivity(), EndCallReceiver.class); PendingIntent pi = PendingIntent.getBroadcast(getActivity().getApplicationContext(), 0, intent, 0); AlarmManager alarmManager = (AlarmManager) getActivity().getSystemService(Context.ALARM_SERVICE); alarmManager.set(AlarmManager.RTC_WAKEUP, timeout, pi); } private void cancelEndCallAlarm() { Intent intent = new Intent(getActivity(), EndCallReceiver.class); PendingIntent pi = PendingIntent.getBroadcast(getActivity().getApplicationContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); AlarmManager alarmManager = (AlarmManager) getActivity().getSystemService(Context.ALARM_SERVICE); alarmManager.cancel(pi); } // The function is called sendSymbol() for simplicity, because this is // what it looks like it does to a caller, but it actually just starts // the call, the symbol is represented by the distance _after_ the call // ends and this is handled in onActivityResult(). public void sendSymbol(String phoneNumber, int index) { if (!phoneNumber.equals("")) { double callSetupTime = mModulator.getPeriod(); double distance = mDistances.get(index); String symbol = (index == mDistances.size() - 1) ? "None" : mModulator.distanceToSymbol(callSetupTime + distance); TimeLog.log(TAG, mLogWindow, "sendSymbol Dial and wait " + String.format(Locale.US, "%.2f", (callSetupTime + distance)) + " = " + String.format(Locale.US, "%.2f", callSetupTime) + " + " + String.format(Locale.US, "%.2f", distance) + " seconds " + "(transmitting '" + symbol + "')\n"); try { Intent callIntent = new Intent(Intent.ACTION_CALL); callIntent.setData(Uri.parse("tel:" + phoneNumber)); // Set the alarm right before starting the ACTION_CALL // activity to minimize the risk of a premature hangup. // // If there were interleaving operations between setting the // alarm and starting the call, the call might be ended before // callSetupTime has passed from when the call is actually // placed. mEndCallTime = System.currentTimeMillis() + (long) (callSetupTime * 1000); setEndCallAlarm(mEndCallTime); startActivityForResult(callIntent, index); } catch (ActivityNotFoundException e) { Log.e(TAG, "Call failed", e); } } } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); // Here the requestCode value is "abused" to advance in the // transmission process. The logic used here assumes that the very // last distance, the one which terminates the transmission of // a message, can be skipped altogether; after all there's no call // after the last distance anyways, it's there just to make sure that // a final call is made, and sendSymbol() takes care of that. final int nextRequest = requestCode + 1; if (nextRequest < mDistances.size()) { // The timeout set in AlarmManager cannot be blindly trusted, it // may have passed more than callSetupTime seconds since // callIntent started. // // Account for possible delays in order to be as adherent as // possible to the nominal total symbol transmission distance. long delay = System.currentTimeMillis() - mEndCallTime; Log.d(TAG, "Delay " + (delay / 1000.)); long distance = (long) (mDistances.get(requestCode) * 1000) - delay; if (distance < 0) { distance = 0; } Log.d(TAG, "Should sleep " + mDistances.get(requestCode) + ". Will sleep " + (distance / 1000.)); // This is where the actual symbol transmission happens: in the // distance before the next symbol is sent. mSendSymbolHandler = new Handler(); mSendSymbolHandler.postDelayed(new Runnable() { @Override public void run() { sendSymbol(mDestinationNumber, nextRequest); mSendSymbolHandler = null; } }, distance); } } /** Called when the user clicks the Send button */ public void sendMessage() { Log.d(TAG, "sending Message"); View view = getView(); EditText editNumber = (EditText) view.findViewById(R.id.destination_number); mDestinationNumber = editNumber.getText().toString().trim(); EditText editMessage = (EditText) view.findViewById(R.id.message); String message = editMessage.getText().toString(); MorseTranslator translator = ((SaveMySugarApplication) getActivity().getApplication()).getTranslator(); String morse = translator.textToMorse(message); mDistances = mModulator.modulate(morse); mLogWindow.setText(morse); mLogWindow.append("\n"); if (mDistances.size() > 0) { sendSymbol(mDestinationNumber, 0); } } }