+package ro.ieval.fonbot;
+
+import static ro.ieval.fonbot.R.string.*;
+import static ro.ieval.fonbot.Utils.toNonNull;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.nio.channels.FileChannel;
+import java.nio.channels.SocketChannel;
+import java.util.ArrayList;
+import java.util.Date;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+import ro.ieval.fonbot.Utils.Command;
+import ro.ieval.fonbot.Utils.MessageType;
+import ro.ieval.fonbot.Utils.OngoingEvent;
+import ro.ieval.fonbot.Utils.RingerMode;
+import ro.ieval.fonbot.Utils.WipeType;
+import android.annotation.SuppressLint;
+import android.app.AlarmManager;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.admin.DevicePolicyManager;
+import android.bluetooth.BluetoothAdapter;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.Cursor;
+import android.graphics.ImageFormat;
+import android.hardware.Camera;
+import android.hardware.Camera.PictureCallback;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.location.LocationProvider;
+import android.media.AudioManager;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.net.ConnectivityManager;
+import android.net.Uri;
+import android.net.wifi.WifiManager;
+import android.os.AsyncTask;
+import android.os.BatteryManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.os.Vibrator;
+import android.preference.PreferenceManager;
+import android.provider.BaseColumns;
+import android.provider.CallLog.Calls;
+import android.provider.ContactsContract.CommonDataKinds;
+import android.provider.ContactsContract.CommonDataKinds.BaseTypes;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.Settings.Secure;
+import android.speech.tts.TextToSpeech;
+import android.speech.tts.TextToSpeech.OnInitListener;
+import android.support.v4.app.NotificationCompat;
+import android.telephony.SmsManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.view.SurfaceView;
+import android.widget.Toast;
+
+import com.android.internal.telephony.ITelephony;
+
+/*
+ * Copyright © 2013 Marius Gavrilescu
+ *
+ * This file is part of FonBot.
+ *
+ * FonBot 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.
+ *
+ * FonBot 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 FonBot. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Implementation of all FonBot commands. The methods of this class do not do argument checking.
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+final class Heavy {
+ /**
+ * LocationListener that sends notifications to the user.
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+ private static final class FonBotLocationListener implements LocationListener {
+ /** Context instance */
+ private final Context context;
+ /** Destination address for notifications */
+ private final Address replyTo;
+
+ /**
+ * Construct a FonBotLocationListener.
+ *
+ * @param context Context instance
+ * @param replyTo the reply address
+ */
+ FonBotLocationListener(final Context context, final Address replyTo) {
+ this.context=context;
+ this.replyTo=replyTo;
+ }
+
+ @Override
+ public void onLocationChanged(@Nullable final Location loc) {
+ if(loc==null)
+ return;
+ final StringBuilder sb=new StringBuilder(toNonNull(context.getString(location)));
+ sb.append(": ");
+ sb.append(toNonNull(context.getString(latitude)));
+ sb.append(": ");
+ sb.append(loc.getLatitude());
+ sb.append(", ");
+ sb.append(toNonNull(context.getString(longitude)));
+ sb.append(": ");
+ sb.append(loc.getLongitude());
+
+ if(loc.hasAccuracy()){
+ sb.append(", ");
+ sb.append(toNonNull(context.getString(accuracy)));
+ sb.append(": ");
+ sb.append(loc.getAccuracy());
+ }
+
+ if(loc.hasAltitude()){
+ sb.append(", ");
+ sb.append(toNonNull(context.getString(altitude)));
+ sb.append(": ");
+ sb.append(loc.getAltitude());
+ }
+
+ if(loc.hasBearing()){
+ sb.append(", ");
+ sb.append(toNonNull(context.getString(bearing)));
+ sb.append(": ");
+ sb.append(loc.getBearing());
+ }
+
+ if(loc.hasSpeed()){
+ sb.append(", ");
+ sb.append(toNonNull(context.getString(speed)));
+ sb.append(": ");
+ sb.append(loc.getSpeed());
+ }
+
+ final Date locationDate=new Date(loc.getTime());
+ sb.append(" ");
+ sb.append(toNonNull(context.getString(at)));
+ sb.append(locationDate.toString());
+ Utils.sendMessage(toNonNull(context), toNonNull(replyTo), toNonNull(sb.toString()));
+ }
+
+ @Override
+ public void onProviderDisabled(@Nullable final String provider) {
+ Utils.sendMessage(toNonNull(context), toNonNull(replyTo), location_provider_disabled, provider);
+ }
+
+ @Override
+ public void onProviderEnabled(@Nullable final String provider) {
+ Utils.sendMessage(toNonNull(context), toNonNull(replyTo), location_provider_enabled, provider);
+ }
+
+ @Override
+ public void onStatusChanged(@Nullable final String provider, final int status, @Nullable final Bundle extras) {
+ final int state;
+ switch(status){
+ case LocationProvider.AVAILABLE:
+ state=location_provider_available;
+ break;
+ case LocationProvider.TEMPORARILY_UNAVAILABLE:
+ state=location_provider_temporary_unavailable;
+ break;
+ case LocationProvider.OUT_OF_SERVICE:
+ state=location_provider_out_of_service;
+ break;
+ default:
+ state=location_provider_unknown_state;
+ }
+ Utils.sendMessage(toNonNull(context), toNonNull(replyTo), state, provider);
+ }
+ }
+ /**
+ * Currently active FonBotLocationListener
+ */
+ private static FonBotLocationListener locationListener = null;
+
+ /**
+ * AsyncTask that sends a byte[] to a server
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ *
+ */
+ private static class SendDataAsyncTask extends AsyncTask<Void, Void, Void>{
+ /**
+ * Context instance used by this class
+ */
+ private final Context context;
+ /**
+ * Server hostname
+ */
+ private final String hostname;
+ /**
+ * Server port
+ */
+ private final int port;
+ /**
+ * Data to send
+ */
+ private final byte[] data;
+ /**
+ * Address for sending back errors and success messages
+ */
+ private final Address replyTo;
+
+ /**
+ * Constructs a SendDataAsyncTasks from its parameters
+ *
+ * @param context the context
+ * @param replyTo the reply Address
+ * @param hostname the server hostname
+ * @param port the server port
+ * @param data the data to send
+ */
+ public SendDataAsyncTask(final Context context,final Address replyTo, final String hostname, final int port, final byte[] data) {//NOPMD array is immutable
+ super();
+ this.context=context;
+ this.hostname=hostname;
+ this.port=port;
+ this.data=data;
+ this.replyTo=replyTo;
+ }
+
+ @Override
+ protected @Nullable Void doInBackground(@Nullable final Void... params) {
+ final Socket sock;
+ try {
+ sock = new Socket(hostname, port);
+ try {
+ sock.getOutputStream().write(data);
+ } catch (IOException e) {
+ Utils.sendMessage(toNonNull(context), toNonNull(replyTo), error_writing_to_socket, e.getMessage());
+ return null;
+ }
+
+ try {
+ sock.close();
+ } catch (IOException e) {
+ Utils.sendMessage(toNonNull(context), toNonNull(replyTo), cannot_close_socket, e.getMessage());
+ return null;
+ }
+ } catch (UnknownHostException e) {
+ Utils.sendMessage(toNonNull(context), toNonNull(replyTo), unknown_host, hostname);
+ return null;
+ } catch (IOException e) {
+ Utils.sendMessage(toNonNull(context), toNonNull(replyTo), cannot_connect_to_host_on_port, hostname, Integer.valueOf(port));
+ return null;
+ }
+ Utils.sendMessage(toNonNull(context), toNonNull(replyTo), photo_sent);
+ return null;
+ }
+ }
+
+ /**
+ * PictureCallback that sends the picture to a server.
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+ private static final class FonBotPictureCallback implements PictureCallback{
+ /** Server hostname */
+ private final String hostname;
+ /** Server port */
+ private final int port;
+ /** Context instance */
+ private final Context context;
+ /** Reply address */
+ private final Address replyTo;
+
+ /**
+ * Construct a FonBotPictureCallback.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param hostname server hostname
+ * @param port server port
+ */
+ FonBotPictureCallback(final Context context, final Address replyTo, final String hostname, final int port) {
+ this.hostname=hostname;
+ this.port=port;
+ this.context=context;
+ this.replyTo=replyTo;
+ }
+
+ @Override
+ @SuppressWarnings("hiding")
+ public void onPictureTaken(final @Nullable byte[] data, final @Nullable Camera camera) {
+ if(camera==null || data==null)
+ return;
+ camera.stopPreview();
+ stopCamera();
+ Utils.sendMessage(toNonNull(context), toNonNull(replyTo), sending_photo);
+ new SendDataAsyncTask(toNonNull(context), toNonNull(replyTo), toNonNull(hostname), port, data).execute();
+ }
+ }
+
+ /**
+ * Get help for a particular command
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param command command to get help for
+ */
+ public static void help(final Context context, final Address replyTo, final Command command){//NOPMD method is a big switch statement. Nothing confusing.
+ switch(command){
+ case ANSWER:
+ Utils.sendMessage(context, replyTo, answer_help);
+ break;
+ case BATT:
+ Utils.sendMessage(context, replyTo, batt_help);
+ break;
+ case BLUETOOTH:
+ Utils.sendMessage(context, replyTo, bluetooth_help);
+ break;
+ case CALLLOG:
+ Utils.sendMessage(context, replyTo, calllog_help);
+ break;
+ case CONTACTS:
+ Utils.sendMessage(context, replyTo, contacts_help);
+ break;
+ case DATA:
+ Utils.sendMessage(context, replyTo, data_help);
+ break;
+ case DELNOTIFICATION:
+ Utils.sendMessage(context, replyTo, delnotification_help, Utils.join(", ", toNonNull(MessageType.values())));
+ break;
+ case DIAL:
+ Utils.sendMessage(context, replyTo, dial_help);
+ break;
+ case DIALOG:
+ Utils.sendMessage(context, replyTo, dialog_help);
+ break;
+ case DISABLE:
+ Utils.sendMessage(context, replyTo, disable_help, Utils.join(", ", toNonNull(Command.values())));
+ break;
+ case ECHO:
+ Utils.sendMessage(context, replyTo, echo_help);
+ break;
+ case ENABLE:
+ Utils.sendMessage(context, replyTo, enable_help, Utils.join(", ", toNonNull(Command.values())));
+ break;
+ case FLASH:
+ Utils.sendMessage(context, replyTo, flash_help);
+ break;
+ case GLOCATION:
+ Utils.sendMessage(context, replyTo, glocation_help);
+ break;
+ case GPS:
+ Utils.sendMessage(context, replyTo, gps_help);
+ break;
+ case HANGUP:
+ Utils.sendMessage(context, replyTo, hangup_help);
+ break;
+ case HELP:
+ Utils.sendMessage(context, replyTo, help_help, Utils.join(", ",toNonNull(Command.values())));
+ break;
+ case LAUNCH:
+ Utils.sendMessage(context, replyTo, launch_help);
+ break;
+ case LOCATION:
+ Utils.sendMessage(context, replyTo, location_help, Utils.join(", ",toNonNull(Utils.LocationProvider.values())));
+ break;
+ case LOCK:
+ Utils.sendMessage(context, replyTo, lock_help);
+ break;
+ case LS:
+ Utils.sendMessage(context, replyTo, ls_help);
+ break;
+ case NCFILE:
+ Utils.sendMessage(context, replyTo, ncfile_help);
+ break;
+ case NEXT:
+ Utils.sendMessage(context, replyTo, next_help);
+ break;
+ case NOLOCATION:
+ Utils.sendMessage(context, replyTo, nolocation_help);
+ break;
+ case PAUSE:
+ Utils.sendMessage(context, replyTo, pause_help);
+ break;
+ case PHOTO:
+ Utils.sendMessage(context, replyTo, photo_help);
+ break;
+ case PLAY:
+ Utils.sendMessage(context, replyTo, play_help);
+ break;
+ case POLL:
+ Utils.sendMessage(context, replyTo, poll_help);
+ break;
+ case PREV:
+ Utils.sendMessage(context, replyTo, prev_help);
+ break;
+ case RING:
+ Utils.sendMessage(context, replyTo, ring_help);
+ break;
+ case RINGER:
+ Utils.sendMessage(context, replyTo, ringer_help, Utils.join(", ", toNonNull(RingerMode.values())));
+ break;
+ case RM:
+ Utils.sendMessage(context, replyTo, rm_help);
+ break;
+ case SETNOTIFICATION:
+ Utils.sendMessage(context, replyTo, setnotification_help, Utils.join(", ", toNonNull(MessageType.values())));
+ break;
+ case SETPASSWORD:
+ Utils.sendMessage(context, replyTo, setpassword_help);
+ break;
+ case SMS:
+ Utils.sendMessage(context, replyTo, sms_help);
+ break;
+ case SMSLOG:
+ Utils.sendMessage(context, replyTo, smslog_help);
+ break;
+ case SPEAK:
+ Utils.sendMessage(context, replyTo, speak_help);
+ break;
+ case TOAST:
+ Utils.sendMessage(context, replyTo, toast_help);
+ break;
+ case VIBRATE:
+ Utils.sendMessage(context, replyTo, vibrate_help);
+ break;
+ case VIEW:
+ Utils.sendMessage(context, replyTo, view_help);
+ break;
+ case WIFI:
+ Utils.sendMessage(context, replyTo, wifi_help);
+ break;
+ case WIPE:
+ Utils.sendMessage(context, replyTo, wipe_help, Utils.WIPE_CONFIRM_STRING);
+ break;
+ case REBOOT:
+ Utils.sendMessage(context, replyTo, reboot_help);
+ break;
+ case SHUTDOWN:
+ Utils.sendMessage(context, replyTo, shutdown_help);
+ break;
+ case NOTIFY:
+ Utils.sendMessage(context, replyTo, notify_help);
+ }
+ }
+
+ /**
+ * Camera instance.
+ *
+ * @see #startCamera(Context, Address)
+ * @see #stopCamera()
+ */
+ private static Camera camera;
+ /**
+ * Ringtone used by the {@link Utils.Command#RING RING} command.
+ *
+ * @see #setupRingtone(Context)
+ */
+ private static Ringtone ringtone;
+ /**
+ * Saved ringer volume.
+ *
+ * @see #startAlarm(Context, Address)
+ * @see #stopAlarm(Context, Address)
+ */
+ private static int savedRingVolume;
+ /**
+ * Saved ringer mode.
+ *
+ * @see #startAlarm(Context, Address)
+ * @see #stopAlarm(Context, Address)
+ */
+ private static int savedRingerMode;
+
+ /** Private constructor */
+ private Heavy(){
+ //do nothing
+ }
+
+ /**
+ * Convert a phone number type to a string
+ *
+ * @param context Context instance
+ * @param type phone number type
+ * @param label name of a custom phone type
+ * @return the phone number type
+ */
+ private static @Nullable String phoneNumberType(final Context context, final int type, final @Nullable String label) {
+ switch(type){
+ case BaseTypes.TYPE_CUSTOM:
+ return label;
+ case Phone.TYPE_ASSISTANT:
+ return context.getString(phone_numer_type_assistant);
+ case Phone.TYPE_CALLBACK:
+ return context.getString(phone_number_type_callback);
+ case Phone.TYPE_CAR:
+ return context.getString(phone_number_type_car);
+ case Phone.TYPE_COMPANY_MAIN:
+ return context.getString(phone_number_type_company_main);
+ case Phone.TYPE_FAX_HOME:
+ return context.getString(phone_number_type_home_fax);
+ case Phone.TYPE_FAX_WORK:
+ return context.getString(phone_number_type_work_fax);
+ case Phone.TYPE_HOME:
+ return context.getString(phone_number_type_home);
+ case Phone.TYPE_ISDN:
+ return context.getString(phone_number_type_isdn);
+ case Phone.TYPE_MAIN:
+ return context.getString(phone_number_type_main);
+ case Phone.TYPE_MMS:
+ return context.getString(phone_number_type_mms);
+ case Phone.TYPE_MOBILE:
+ return context.getString(phone_number_type_mobile);
+ case Phone.TYPE_OTHER:
+ return context.getString(phone_number_type_other);
+ case Phone.TYPE_OTHER_FAX:
+ return context.getString(phone_number_type_other_fax);
+ case Phone.TYPE_PAGER:
+ return context.getString(phone_number_type_pager);
+ case Phone.TYPE_RADIO:
+ return context.getString(phone_number_type_radio);
+ case Phone.TYPE_TELEX:
+ return context.getString(phone_number_type_telex);
+ case Phone.TYPE_TTY_TDD:
+ return context.getString(phone_number_type_textphone);
+ case Phone.TYPE_WORK:
+ return context.getString(phone_number_type_work);
+ case Phone.TYPE_WORK_MOBILE:
+ return context.getString(phone_number_type_work_mobile);
+ case Phone.TYPE_WORK_PAGER:
+ return context.getString(phone_number_type_work_pager);
+ }
+
+ return context.getString(phone_number_type_unknown, Integer.valueOf(type));
+ }
+
+ /**
+ * Setup the ringtone used by the {@link Utils.Command#RING RING} command
+ *
+ * @param context Context
+ */
+ private static void setupRingtone(final Context context){
+ if(ringtone==null){//NOPMD not supposed to be thread-safe
+ final Uri alert=RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
+ ringtone=RingtoneManager.getRingtone(context, alert);
+ }
+ }
+
+ /**
+ * Make the phone start ringing. Turns up the volume and sets the ringer mode to NORMAL
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ */
+ private static void startAlarm(final Context context, final Address replyTo){
+ Utils.registerOngoing(context, toNonNull(OngoingEvent.RING));
+ final AudioManager man=(AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ savedRingerMode=man.getRingerMode();
+ man.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+ savedRingVolume=man.getStreamVolume(AudioManager.STREAM_RING);
+ man.setStreamVolume(AudioManager.STREAM_RING, man.getStreamMaxVolume(AudioManager.STREAM_RING), 0);
+ Utils.sendMessage(context, replyTo, ringing);
+ ringtone.play();
+ }
+
+ /**
+ * Get a camera instance.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ */
+ private static void startCamera(final Context context, final Address replyTo){
+ if(camera!=null)
+ return;
+ try{
+ camera=Camera.open();
+ } catch (Exception e){
+ Utils.sendMessage(context, replyTo, cannot_grab_camera);
+ }
+ }
+
+ /**
+ * Make the phone stop ringing. Restores the volume and ringer mode.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ */
+ private static void stopAlarm(final Context context, final Address replyTo){
+ Utils.unregisterOngoing(context, toNonNull(OngoingEvent.RING));
+ final AudioManager man=(AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ Utils.sendMessage(context, replyTo, no_longer_ringing);
+ ringtone.stop();
+ man.setStreamVolume(AudioManager.STREAM_RING, savedRingVolume, 0);
+ man.setRingerMode(savedRingerMode);
+ }
+
+ /**
+ * Release the previously grabbed camera instance
+ *
+ * @see #startCamera(Context, Address)
+ */
+ private static void stopCamera(){
+ if(camera==null)
+ return;
+ camera.release();
+ camera=null;
+ }
+
+ /**
+ * Send battery status information to an Address
+ *
+ * @param context Context instance
+ * @param replyTo destination Address
+ *
+ * @see #describeBatteryLevel(Context, Address, MessageType)
+ */
+ public static void batt(final Context context, final Address replyTo){
+ describeBatteryLevel(context, replyTo, null);
+ }
+
+ /**
+ * Show the bluetooth radio status.
+ *
+ * @param context Context instance
+ * @param replyTo destination Address
+ */
+ public static void bluetooth(final Context context, final Address replyTo) {
+ final BluetoothAdapter adapter=BluetoothAdapter.getDefaultAdapter();
+ if(adapter==null){
+ Utils.sendMessage(context, replyTo, no_bluetooth_adapter);
+ return;
+ }
+
+ if(adapter.isEnabled())
+ Utils.sendMessage(context, replyTo, bluetooth_on);
+ else
+ Utils.sendMessage(context, replyTo, bluetooth_off);
+ }
+
+ /**
+ * Set the bluetooth radio status.
+ *
+ * @param context Context instance
+ * @param replyTo destination Address
+ * @param on the requested radio status
+ */
+ public static void bluetooth(final Context context, final Address replyTo, final boolean on){
+ final BluetoothAdapter adapter=BluetoothAdapter.getDefaultAdapter();
+ if(adapter==null){
+ Utils.sendMessage(context, replyTo, no_bluetooth_adapter);
+ return;
+ }
+
+ if(on) {
+ adapter.enable();
+ Utils.sendMessage(context, replyTo, enabling_bluetooth);
+ }
+ else {
+ adapter.disable();
+ Utils.sendMessage(context, replyTo, disabling_bluetooth);
+ }
+ }
+
+ /**
+ * Cancel an ongoing event.
+ *
+ * @param context Context instance
+ * @param event the event to cancel
+ */
+ public static void cancelOngoing(final Context context, final OngoingEvent event){
+ switch(event){
+ case LOCATION:
+ nolocation(context, toNonNull(Address.BLACKHOLE));
+ break;
+ case POLL:
+ poll(context, toNonNull(Address.BLACKHOLE), 0);
+ break;
+ case RING:
+ ring(context, toNonNull(Address.BLACKHOLE), false);
+ break;
+ }
+ }
+
+ /**
+ * Send the last calls to an Address.
+ *
+ * @param context Context instance
+ * @param replyTo destination Address
+ * @param numCalls how many calls to send
+ */
+ public static void calllog(final Context context, final Address replyTo, final int numCalls) {
+ final String[] fields = {
+ Calls.TYPE, Calls.NUMBER, Calls.CACHED_NAME, Calls.DURATION, Calls.DATE
+ };
+
+ final Cursor cursor = context.getContentResolver().query(
+ Calls.CONTENT_URI,
+ fields,
+ null,
+ null,
+ Calls.DATE + " DESC"
+ );
+
+ if (cursor.moveToFirst()) {
+ do {
+ final StringBuilder sb=new StringBuilder(50);//NOPMD different strings
+ final int type=cursor.getInt(0);
+ final String from=cursor.getString(1);
+
+ switch(type){
+ case Calls.INCOMING_TYPE:
+ sb.append(context.getString(incoming_call_from, from));
+ break;
+ case Calls.MISSED_TYPE:
+ sb.append(context.getString(missed_call_from, from));
+ break;
+ case Calls.OUTGOING_TYPE:
+ sb.append(context.getString(outgoing_call_to, from));
+ break;
+ }
+
+ if (cursor.getString(2) != null)
+ sb.append('(').append(cursor.getString(2)).append(") ");
+
+ sb.append(context.getString(duration_seconds_starting_at,
+ Long.valueOf(cursor.getLong(3)),
+ new Date(cursor.getLong(4))));
+
+ Utils.sendMessage(context, replyTo, toNonNull(sb.toString()));
+ } while (cursor.moveToNext() && cursor.getPosition() < numCalls);
+ }
+
+ cursor.close();
+ }
+
+ /**
+ * Search for contacts by name/nickname and send matching entries to an Address.
+ *
+ * @param context Context instance
+ * @param replyTo destination Address
+ * @param name name/nickname part to search for
+ */
+ @SuppressLint("StringFormatMatches")
+ public static void contacts(final Context context, final Address replyTo, final String name){
+ final Cursor cursor=context.getContentResolver().query(Uri.withAppendedPath(
+ Contacts.CONTENT_FILTER_URI, name),
+ new String[]{Contacts.DISPLAY_NAME, BaseColumns._ID, Contacts.LOOKUP_KEY},
+ null, null, Contacts.DISPLAY_NAME);
+
+ if(cursor.getCount()==0)
+ Utils.sendMessage(context, replyTo, no_matching_contacts_found);
+
+ while(cursor.moveToNext()){
+ final String[] fields = {
+ CommonDataKinds.Phone.NUMBER,
+ CommonDataKinds.Phone.TYPE,
+ CommonDataKinds.Phone.LABEL,
+ };
+
+ final Cursor inCursor=context.getContentResolver().query(Data.CONTENT_URI,
+ fields,
+ Data.CONTACT_ID+" = ? AND "+Data.MIMETYPE+ " = ?",
+ new String[]{Long.toString(cursor.getLong(1)), CommonDataKinds.Phone.CONTENT_ITEM_TYPE},
+ CommonDataKinds.Phone.LABEL);
+
+ while(inCursor.moveToNext())
+ Utils.sendMessage(context, replyTo, toNonNull(context.getString(contact_info,
+ cursor.getString(0),
+ inCursor.getString(0),
+ phoneNumberType(context, inCursor.getInt(1), inCursor.getString(2)))));
+
+ inCursor.close();
+ }
+
+ cursor.close();
+ }
+
+ /**
+ * Send battery status information to an Address or as a notification
+ *
+ * @param context Context instance
+ * @param replyTo Address to send the information to, if sending to a direct address. Null otherwise.
+ * @param type Notification type, if sending as a notification. Null otherwise.
+ */
+ public static void describeBatteryLevel(final Context context, final @Nullable Address replyTo, final @Nullable MessageType type) {
+ if(replyTo==null&&type==null)
+ return;
+ final Intent intent=context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+ if(intent==null)
+ return;
+ final double level=intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
+ final int scale=intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100);
+ final int plugged=intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
+ final int status=intent.getIntExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN);
+ final int temp=intent.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, 0);
+ final int volt=intent.getIntExtra(BatteryManager.EXTRA_VOLTAGE, 0);
+
+ final StringBuilder sb=new StringBuilder(100);
+ sb.append(context.getString(battery_level, Double.valueOf(level*100/scale)));
+
+ switch(plugged){
+ case 0:
+ sb.append(context.getString(not_plugged_in));
+ break;
+ case BatteryManager.BATTERY_PLUGGED_AC:
+ sb.append(context.getString(plugged_in_ac));
+ break;
+ case BatteryManager.BATTERY_PLUGGED_USB:
+ sb.append(context.getString(plugged_in_usb));
+ break;
+ case BatteryManager.BATTERY_PLUGGED_WIRELESS:
+ sb.append(context.getString(plugged_in_wireless));
+ break;
+ }
+
+ switch(status){
+ case BatteryManager.BATTERY_STATUS_CHARGING:
+ sb.append(context.getString(status_charging));
+ break;
+ case BatteryManager.BATTERY_STATUS_DISCHARGING:
+ sb.append(context.getString(status_discharging));
+ break;
+ case BatteryManager.BATTERY_STATUS_FULL:
+ sb.append(context.getString(status_full));
+ break;
+ case BatteryManager.BATTERY_STATUS_NOT_CHARGING:
+ sb.append(context.getString(status_not_charging));
+ break;
+ case BatteryManager.BATTERY_STATUS_UNKNOWN:
+ sb.append(context.getString(status_unknown));
+ break;
+ }
+
+ sb.append(context.getString(temperature, Integer.valueOf(temp)));
+
+ sb.append(context.getString(voltage, Integer.valueOf(volt)));
+ if(type==null)
+ Utils.sendMessage(context, toNonNull(replyTo), toNonNull(sb.toString()));
+ else
+ Utils.sendMessage(context, type, toNonNull(sb.toString()));
+ }
+
+ /**
+ * Dial a phone number.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param nr phone number to dial
+ */
+ public static void dial(final Context context, final Address replyTo, final String nr){
+ final Intent intent=new Intent(Intent.ACTION_CALL,Uri.parse("tel:"+nr));
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ final String name=Utils.callerId(context, nr);
+ if(name==null)
+ Utils.sendMessage(context, replyTo, dialing, nr);
+ else
+ Utils.sendMessage(context, replyTo, dialing, nr+" ("+name+")");
+ context.startActivity(intent);
+ }
+
+ /**
+ * Show a dialog with a message and a list of buttons.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param message dialog message
+ * @param buttons dialog buttons
+ */
+ public static void dialog(final Context context, final Address replyTo, final String message, final String[] buttons){
+ final Intent intent=new Intent(context, DialogActivity.class);
+ intent.putExtra(DialogActivity.EXTRA_MESSAGE, message);
+ intent.putExtra(DialogActivity.EXTRA_BUTTONS, buttons);
+ intent.putExtra(DialogActivity.EXTRA_REPLYTO, replyTo.toString());
+ intent.addFlags(
+ Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS|
+ Intent.FLAG_ACTIVITY_NEW_TASK|
+ Intent.FLAG_ACTIVITY_NO_USER_ACTION|
+ Intent.FLAG_FROM_BACKGROUND);
+ Utils.sendMessage(context, toNonNull(replyTo), showing_dialog);
+ context.startActivity(intent);
+ }
+
+ /**
+ * Turns the flashlight on or off.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param on requested flashlight state
+ */
+ public static void flash(final Context context, final Address replyTo, final boolean on){
+ startCamera(context, replyTo);
+ if(camera==null)
+ return;
+ final Camera.Parameters parms=camera.getParameters();
+ if(on){
+ parms.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
+ camera.setParameters(parms);
+ } else {
+ parms.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);
+ camera.setParameters(parms);
+ stopCamera();
+ }
+ }
+
+ /**
+ * Start sending location updates to an Address.
+ *
+ * @param context Context instance
+ * @param replyTo destination Address
+ * @param provider LocationProvider
+ * @param minTime minimum time between two consecutive updates (in ms)
+ * @param minDistance minimum distance between two consecutive updates (in meters)
+ *
+ * @see LocationManager#requestLocationUpdates(String, long, float, LocationListener)
+ */
+ public static void location(final Context context, final Address replyTo, final String provider,final long minTime,final float minDistance){
+ final LocationManager man=(LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
+ if(locationListener!=null)
+ nolocation(context, toNonNull(Address.BLACKHOLE));
+ Utils.registerOngoing(context, toNonNull(OngoingEvent.LOCATION));
+ locationListener=new FonBotLocationListener(context, replyTo);
+ man.removeUpdates(locationListener);
+ final Location lastKnownLocation=man.getLastKnownLocation(provider);
+ if(lastKnownLocation!=null){
+ Utils.sendMessage(context, replyTo, last_known_location);
+ locationListener.onLocationChanged(lastKnownLocation);
+ }
+ Utils.sendMessage(context, replyTo, listening_for_location_updates);
+ man.requestLocationUpdates(provider, minTime, minDistance, locationListener);
+ }
+
+ /**
+ * Lock the phone.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ */
+ public static void lock(final Context context, final Address replyTo) {
+ final DevicePolicyManager dpm=(DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ dpm.lockNow();
+ Utils.sendMessage(context, replyTo, device_locked);
+ }
+
+ /**
+ * Send a command to a running instance of the music player
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param command command to send
+ */
+ public static void musicPlayerCommand(final Context context, final Address replyTo, final String command) {
+ final Intent intent=new Intent("com.android.music.musicservicecommand");
+ intent.putExtra("command", command);
+ context.sendBroadcast(intent);
+ Utils.sendMessage(context, replyTo, command_sent);
+ }
+
+ /**
+ * Send a file to a server.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param filename file to send
+ * @param hostname server hostname
+ * @param port server port
+ */
+ public static void ncfile(final Context context, final Address replyTo, final String filename,final String hostname,final int port){
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected @Nullable Void doInBackground(@Nullable final Void... params) {
+ final FileChannel in;
+ try{
+ in=new FileInputStream(filename).getChannel();
+ } catch (final FileNotFoundException e){
+ Utils.sendMessage(context, replyTo, file_not_found, filename);
+ return null;
+ }
+ final SocketChannel sock;
+ try{
+ sock = SocketChannel.open(new InetSocketAddress(hostname, port));
+ } catch (final IOException e){
+ Utils.sendMessage(context, replyTo, toNonNull(context.getString(
+ cannot_connect_to_host_on_port, hostname, Integer.valueOf(port))));
+ try {
+ in.close();
+ } catch (IOException ex) {
+ //ignored
+ }
+ return null;
+ }
+
+ try{
+ in.transferTo(0, in.size(), sock);
+ } catch (final IOException e){
+ Utils.sendMessage(context, replyTo, toNonNull(context.getString(
+ io_error, e.getMessage())));
+ } finally {
+ try{
+ in.close();
+ } catch (IOException e){
+ //ignored
+ }
+ try{
+ sock.close();
+ } catch(IOException e){
+ //ignored
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(@Nullable final Void result) {
+ Utils.sendMessage(context, replyTo, file_sent);
+ }
+ }.execute();
+ }
+
+ /**
+ * Stop sending location updates.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ */
+ public static void nolocation(final Context context, final Address replyTo){
+ Utils.unregisterOngoing(context, toNonNull(OngoingEvent.LOCATION));
+ final LocationManager man=(LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
+ man.removeUpdates(locationListener);
+ locationListener=null;
+ Utils.sendMessage(context, replyTo, no_longer_listening_for_location_updates);
+ }
+
+ /**
+ * Take a photo and send it to a server.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param hostname server hostname
+ * @param port server port
+ */
+ public static void photo(final Context context, final Address replyTo, final String hostname, final int port){
+ startCamera(context, replyTo);
+ if(camera==null)
+ return;
+ final Camera.Parameters parms=camera.getParameters();
+ parms.setJpegQuality(70);
+ parms.setPictureFormat(ImageFormat.JPEG);
+ camera.setParameters(parms);
+
+ final SurfaceView fakeView=new SurfaceView(context);
+ try {
+ camera.setPreviewDisplay(fakeView.getHolder());
+ } catch (IOException e) {
+ Utils.sendMessage(context, replyTo, error_setting_preview_display);
+ return;
+ }
+ camera.startPreview();
+ final Handler handler=new Handler();
+
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(2000);
+ } catch (InterruptedException e) {
+ //ignored
+ }
+
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ camera.takePicture(null, null, new FonBotPictureCallback(context, replyTo, hostname, port));
+ }
+ });
+ }
+ }).start();
+ }
+
+ /**
+ * Send a directory listing to an Address
+ *
+ * @param context Context instance
+ * @param replyTo destination Address
+ * @param directory directory to list
+ */
+ public static void ls(final Context context, final Address replyTo, final String directory) {
+ final File[] files=new File(directory).listFiles();
+ if(files==null){
+ Utils.sendMessage(context, replyTo, string_is_not_a_directory, directory);
+ return;
+ }
+
+ final StringBuilder sb=new StringBuilder(context.getString(files_in_directory,directory));
+ for(final File file : files){
+ sb.append(file.getName());
+ if(file.isDirectory())
+ sb.append('/');
+ sb.append(" ");
+ }
+
+ Utils.sendMessage(context, replyTo, toNonNull(sb.toString()));
+ }
+
+ /**
+ * Make the phone start ringing if it is not ringing or stop ringing if it is.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ */
+ public static void ring(final Context context, final Address replyTo){
+ setupRingtone(context);
+ if(ringtone==null){
+ Utils.sendMessage(context, replyTo, no_ringtone_found);
+ return;
+ }
+ if(ringtone.isPlaying())
+ stopAlarm(context, replyTo);
+ else
+ startAlarm(context, replyTo);
+ }
+
+ /**
+ * Make the phone start/stop ringing.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param on true if the phone should start ringing, false otherwise
+ */
+ public static void ring(final Context context, final Address replyTo, final boolean on){
+ setupRingtone(context);
+ if(ringtone==null){
+ Utils.sendMessage(context, replyTo, no_ringtone_found);
+ return;
+ }
+ if(on&&!ringtone.isPlaying())
+ startAlarm(context, replyTo);
+ else if(ringtone.isPlaying()&&!on)
+ stopAlarm(context, replyTo);
+ }
+
+ /**
+ * Send the current ringer mode to an Address
+ *
+ * @param context Context instance
+ * @param replyTo destination Address
+ */
+ public static void ringer(final Context context, final Address replyTo){
+ final AudioManager man=(AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ switch(man.getRingerMode()){
+ case AudioManager.RINGER_MODE_NORMAL:
+ Utils.sendMessage(context, replyTo, ringer_mode_normal);
+ break;
+ case AudioManager.RINGER_MODE_VIBRATE:
+ Utils.sendMessage(context, replyTo, ringer_mode_vibrate);
+ break;
+ case AudioManager.RINGER_MODE_SILENT:
+ Utils.sendMessage(context, replyTo, ringer_mode_silent);
+ break;
+ default:
+ Utils.sendMessage(context, replyTo, unknown_ringer_mode);
+ }
+ }
+
+ /**
+ * Set the ringer mode.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param ringerMode requested ringer mode
+ *
+ * @see Utils.RingerMode
+ */
+ public static void ringer(final Context context, final Address replyTo, final int ringerMode){
+ final AudioManager man=(AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ man.setRingerMode(ringerMode);
+ ringer(context, replyTo);
+ }
+
+ /**
+ * Remove a file or empty directory.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param filename file/empty directory to delete
+ */
+ public static void rm(final Context context, final Address replyTo, final String filename){
+ if(new File(filename).delete())
+ Utils.sendMessage(context, replyTo, file_deleted);
+ else
+ Utils.sendMessage(context, replyTo, error_while_deleting_file);
+ }
+
+ /**
+ * Clear the keyguard password.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @throws SecurityException if FonBot does not have device administration permissions
+ */
+ public static void setPassword(final Context context, final Address replyTo) throws SecurityException{
+ final DevicePolicyManager dpm=(DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+
+ dpm.resetPassword("", 0);
+ Utils.sendMessage(context, replyTo, password_cleared);
+ }
+
+ /**
+ * Change the keyguard password.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param password new password
+ * @throws SecurityException if FonBot does not have device administration permissions
+ */
+ public static void setPassword(final Context context, final Address replyTo, final String password) throws SecurityException{
+ final DevicePolicyManager dpm=(DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+
+ dpm.resetPassword(password, 0);
+ Utils.sendMessage(context, replyTo, password_set);
+ }
+
+ /**
+ * Send a text message.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param destination destination phone number
+ * @param text text message contents
+ */
+ public static void sms(final Context context, final Address replyTo, final String destination, final String text){
+ final SmsManager manager=SmsManager.getDefault();
+ final ArrayList<String> messages=manager.divideMessage(text);
+ if(messages.size() > 1)
+ Utils.sendMessage(context, replyTo, message_was_split_into_parts, Integer.valueOf(messages.size()));
+
+ final ArrayList<PendingIntent> sents=new ArrayList<PendingIntent>(messages.size());
+ final ArrayList<PendingIntent> delivereds=new ArrayList<PendingIntent>(messages.size());
+
+ final String name=Utils.callerId(context, destination);
+ final String fullDestination;
+ if(name==null)
+ fullDestination=destination;
+ else
+ fullDestination=destination+" ("+name+")";
+
+ for(int i=0;i<messages.size();i++){
+ final Intent sent=new Intent(context,SmsStatusReceiver.class);
+ sent.putExtra(SmsStatusReceiver.EXTRA_DESTINATION, fullDestination);
+ sent.putExtra(SmsStatusReceiver.EXTRA_PART, i+1);
+ sent.putExtra(SmsStatusReceiver.EXTRA_TOTAL, messages.size());
+ sent.putExtra(SmsStatusReceiver.EXTRA_REPLY_TO, replyTo.toString());
+ sent.setAction(SmsStatusReceiver.SENT_ACTION+i);//actions must be unique
+ sents.add(PendingIntent.getBroadcast(context, 0, sent, PendingIntent.FLAG_UPDATE_CURRENT));
+
+ final Intent delivered=new Intent(context, SmsStatusReceiver.class);
+ delivered.putExtra(SmsStatusReceiver.EXTRA_DESTINATION, fullDestination);
+ delivered.putExtra(SmsStatusReceiver.EXTRA_PART, i+1);
+ delivered.putExtra(SmsStatusReceiver.EXTRA_TOTAL, messages.size());
+ delivered.putExtra(SmsStatusReceiver.EXTRA_REPLY_TO, replyTo.toString());
+ delivered.setAction(SmsStatusReceiver.DELIVERED_ACTION+i);//actions must be unique
+ delivereds.add(PendingIntent.getBroadcast(context, 0, delivered, PendingIntent.FLAG_UPDATE_CURRENT));
+ }
+
+ Log.d(Heavy.class.getName(), "Sending sms to "+destination);
+ manager.sendMultipartTextMessage(destination, null, messages, sents, delivereds);
+ }
+
+ /**
+ * Send the last SMSes to an Address.
+ *
+ * @param context Context instance
+ * @param replyTo destination Address
+ * @param numSms how many SMSes to send
+ */
+ public static void smslog(final Context context, final Address replyTo, final int numSms) {
+ final String[] fields = {"type","address", "body", "date"};
+
+ final Cursor cursor = context.getContentResolver().query (
+ Uri.parse("content://sms"),
+ fields,
+ null,
+ null,
+ "date DESC"
+ );
+
+ if (cursor.moveToFirst()) {
+ do {
+ final String fromNumber=cursor.getString(1);
+ final String from;
+ final String name=Utils.callerId(context, Utils.toNonNull(fromNumber));
+ if(name==null)
+ from=fromNumber;
+ else
+ from=fromNumber+" ("+name+')';
+ final String message=cursor.getString(2).replace("\n", "\n ");
+ final Date date=new Date(cursor.getLong(3));
+
+ if(cursor.getInt(0)==1)
+ Utils.sendMessage(context, replyTo, incoming_message, from, message, date);
+ else
+ Utils.sendMessage(context, replyTo, outgoing_message, from, message, date);
+ } while (cursor.moveToNext() && cursor.getPosition() < numSms);
+ }
+
+ cursor.close();
+ }
+
+ /** TTS instance, only used by {@link #speak(Context, Address, String)} */
+ private static TextToSpeech tts;
+
+ /**
+ * Speak a String using the text-to-speech engine.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param text text to speak
+ */
+ public static void speak(final Context context, final Address replyTo, final String text){
+ tts=new TextToSpeech(context, new OnInitListener() {
+ @Override
+ public void onInit(final int status) {
+ if(status==TextToSpeech.SUCCESS){
+ Utils.sendMessage(context, replyTo, speaking);
+ tts.speak(text, TextToSpeech.QUEUE_ADD, null);
+ } else
+ Utils.sendMessage(context, replyTo, tts_engine_not_available);
+ }
+ });
+ }
+
+ /**
+ * Show a toast notification with the default duration.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param text toast text
+ */
+ public static void toast(final Context context, final Address replyTo, final String text){
+ toast(context, replyTo, text, Toast.LENGTH_SHORT);
+ }
+
+ /**
+ * Show a toast notification.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param text toast text
+ * @param duration toast duration
+ */
+ public static void toast(final Context context, final Address replyTo, final String text, final int duration){
+ Toast.makeText(context,text,duration).show();
+ Utils.sendMessage(context, replyTo, toast_shown);
+ }
+
+ /**
+ * Make the phone vibrate for a number of milliseconds.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param ms vibrate duration, in milliseconds
+ */
+ public static void vibrate(final Context context, final Address replyTo, final long ms){
+ final Vibrator v=(Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
+ Utils.sendMessage(context, replyTo, vibrating);
+ v.vibrate(ms);
+ }
+
+ /**
+ * View an URI in an appropriate activity.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param uri URI to view
+ */
+ public static void view(final Context context, final Address replyTo, final Uri uri) {
+ try{
+ final Intent intent=new Intent(Intent.ACTION_VIEW);
+ intent.setData(uri);
+ intent.setFlags(Intent.FLAG_FROM_BACKGROUND|Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(intent);
+ Utils.sendMessage(context, replyTo, url_opened);
+ } catch(ActivityNotFoundException e){
+ Utils.sendMessage(context, replyTo, no_activity_found_for_this_url);
+ } catch(Exception e){
+ Utils.sendMessage(context, replyTo, invalid_url);
+ }
+ }
+
+ /**
+ * Get the current WiFi state.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ */
+ public static void wifi(final Context context, final Address replyTo){
+ final WifiManager man=(WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ if(man.isWifiEnabled())
+ Utils.sendMessage(context, replyTo, wifi_on);
+ else
+ Utils.sendMessage(context, replyTo, wifi_off);
+ }
+
+ /**
+ * Set the WiFi state.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param on the requested WiFi state
+ */
+ public static void wifi(final Context context, final Address replyTo, final boolean on){
+ final WifiManager man=(WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ man.setWifiEnabled(on);
+ if(on)
+ Utils.sendMessage(context, replyTo, enabling_wifi);
+ else
+ Utils.sendMessage(context, replyTo, disabling_wifi);
+ }
+
+ /**
+ * Factory reset the phone, optionally deleting the SD card too.
+ *
+ * @param context Context instance
+ * @param type {@link Utils.WipeType} instance
+ * @throws SecurityException if FonBot does not have device administration permissions
+ */
+ @SuppressLint("InlinedApi")
+ public static void wipe(final Context context, final WipeType type) throws SecurityException{
+ final DevicePolicyManager dpm=(DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+
+ switch(type){
+ case DATA:
+ dpm.wipeData(0);
+ break;
+ case FULL:
+ dpm.wipeData(DevicePolicyManager.WIPE_EXTERNAL_STORAGE);
+ break;
+ }
+ }
+
+ /**
+ * Disable a Command. The command cannot be used until enabled again with the {@link Utils.Command#ENABLE ENABLE} command.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param command Command to disable
+ */
+ public static void disable(final Context context, final Address replyTo, final Command command){
+ PreferenceManager.getDefaultSharedPreferences(context).edit()
+ .putBoolean(command+"disabled", true).commit();
+ Utils.sendMessage(context, replyTo, command_disabled, command);
+ }
+
+ /**
+ * Re-enable a disabled Command.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param command Command to re-enable
+ */
+ public static void enable(final Context context, final Address replyTo, final Command command){
+ PreferenceManager.getDefaultSharedPreferences(context).edit()
+ .remove(command+"disabled").commit();
+ Utils.sendMessage(context, replyTo, command_enabled, command);
+
+ }
+
+ /**
+ * Check whether a Command is disabled.
+ *
+ * @param context Context instance
+ * @param command Command to check
+ * @return true if the Command is disabled, false otherwise
+ */
+ public static boolean isCommandDisabled(final Context context, final Command command){
+ return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(command+"disabled", false);
+ }
+
+ /**
+ * Poll the server for pending commands.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ */
+ public static void poll(final Context context, final Address replyTo) {
+ Utils.sendMessage(context, replyTo, polling_server);
+ new PollServerAsyncTask().execute();
+ }
+
+ /**
+ * Change the server poll interval.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param ms server poll interval in milliseconds. If 0, server poll is disabled
+ */
+ public static void poll(final Context context, final Address replyTo, final long ms){
+ final AlarmManager man=(AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ final Intent pollAlarm=new Intent(context, LocalBroadcastReceiver.class);
+ pollAlarm.setAction(LocalBroadcastReceiver.ACTION_POLL_ALARM);
+ final PendingIntent intent=PendingIntent.getBroadcast(context, 0, pollAlarm, 0);
+ if(ms==0){
+ Utils.unregisterOngoing(context, toNonNull(OngoingEvent.POLL));
+ man.cancel(intent);
+ Utils.sendMessage(context, replyTo, polling_stopped);
+ } else {
+ Utils.registerOngoing(context, toNonNull(OngoingEvent.POLL));
+ man.setRepeating(AlarmManager.RTC_WAKEUP, 0, ms, intent);
+ Utils.sendMessage(context, replyTo, polling_every_milliseconds, Long.valueOf(ms));
+ }
+ }
+
+ /**
+ * Get an instance of {@link ITelephony}
+ *
+ * @param context Context instance
+ * @return an instance of {@link ITelephony}
+ * @throws NoSuchMethodException thrown by reflection
+ * @throws IllegalArgumentException thrown by reflection
+ * @throws IllegalAccessException thrown by reflection
+ * @throws InvocationTargetException thrown by reflection
+ */
+ private static ITelephony getITelephony(final Context context) throws NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException{
+ final TelephonyManager man=(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ final Method m=TelephonyManager.class.getDeclaredMethod("getITelephony");
+ m.setAccessible(true);
+ return toNonNull((ITelephony) m.invoke(man));
+ }
+
+ /**
+ * Hang up the phone.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ */
+ public static void hangup(final Context context, final Address replyTo){
+ try{
+ getITelephony(context).endCall();
+ } catch(Exception e){
+ Utils.sendMessage(context, replyTo, exception_while_hanging_up_call,
+ e.getClass().getName(), e.getMessage());
+ }
+ }
+
+ /**
+ * Answer the phone if it is ringing.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ */
+ public static void answer(final Context context, final Address replyTo){
+ try{
+ getITelephony(context).answerRingingCall();
+ } catch(Exception e){
+ Utils.sendMessage(context, replyTo, exception_while_answering_call,
+ e.getClass().getName(), e.getMessage());
+ }
+ }
+
+ /**
+ * Launch a package.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param pkg name of the package to launch
+ */
+ public static void launch(final Context context, final Address replyTo, final String pkg){
+ final Intent intent=context.getPackageManager().getLaunchIntentForPackage(pkg);
+ if(intent==null){
+ Utils.sendMessage(context, replyTo, no_such_package);
+ return;
+ }
+ context.startActivity(intent);
+ Utils.sendMessage(context, replyTo, app_launched);
+ }
+
+ /**
+ * Get the mobile data enabled status.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ */
+ public static void data(final Context context, final Address replyTo){
+ try{
+ final ConnectivityManager man=(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ final Method m=ConnectivityManager.class.getDeclaredMethod("getMobileDataEnabled");
+ m.setAccessible(true);
+ if(((Boolean)m.invoke(man)).booleanValue())
+ Utils.sendMessage(context, replyTo, data_on);
+ else
+ Utils.sendMessage(context, replyTo, data_off);
+ } catch(Exception e){
+ Utils.sendMessage(context, replyTo, exception_while_determining_data_state,
+ e.getClass().getName(), e.getMessage());
+ }
+ }
+
+ /**
+ * Set the mobile data enabled status.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param enable whether to enable mobile data
+ */
+ public static void data(final Context context, final Address replyTo, final boolean enable) {
+ try{
+ if(enable){
+ getITelephony(context).enableDataConnectivity();
+ Utils.sendMessage(context, replyTo, enabling_data);
+ } else {
+ getITelephony(context).disableDataConnectivity();
+ Utils.sendMessage(context, replyTo, disabling_data);
+ }
+ } catch(Exception e){
+ Utils.sendMessage(context, replyTo, exception_while_getting_itelephony,
+ e.getClass().getName(), e.getMessage());
+ }
+ }
+
+ /**
+ * Get the GPS status.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ */
+ public static void gps(final Context context, final Address replyTo){
+ if(Secure.isLocationProviderEnabled(context.getContentResolver(), LocationManager.GPS_PROVIDER))
+ Utils.sendMessage(context, replyTo, gps_on);
+ else
+ Utils.sendMessage(context, replyTo, gps_off);
+ }
+
+ /**
+ * Set the GPS status.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param enabled requested GPS status
+ */
+ public static void gps(final Context context, final Address replyTo, final boolean enabled) {
+ Secure.setLocationProviderEnabled(context.getContentResolver(), LocationManager.GPS_PROVIDER, enabled);
+ if(enabled)
+ Utils.sendMessage(context, replyTo, enabling_gps);
+ else
+ Utils.sendMessage(context, replyTo, disabling_gps);
+ }
+
+ /**
+ * Get the Google location (aka network location) state.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ */
+ public static void glocation(final Context context, final Address replyTo){
+ if(Secure.isLocationProviderEnabled(context.getContentResolver(), LocationManager.NETWORK_PROVIDER))
+ Utils.sendMessage(context, replyTo, network_location_on);
+ else
+ Utils.sendMessage(context, replyTo, network_location_off);
+ }
+
+ /**
+ * Set the Google location (aka network location) state.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param enabled requested Google location state
+ */
+ public static void glocation(final Context context, final Address replyTo, final boolean enabled) {
+ Secure.setLocationProviderEnabled(context.getContentResolver(), LocationManager.NETWORK_PROVIDER, enabled);
+ if(enabled)
+ Utils.sendMessage(context, replyTo, enabling_network_location);
+ else
+ Utils.sendMessage(context, replyTo, disabling_network_location);
+ }
+
+ /**
+ * Reboot the phone.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param reason reboot reason
+ *
+ * @see PowerManager#reboot(String)
+ */
+ public static void reboot(final Context context, final Address replyTo, final @Nullable String reason) {
+ final PowerManager pm=(PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ Utils.sendMessage(context, replyTo, rebooting);
+ pm.reboot(reason);
+ }
+
+ /**
+ * Cancel a notification.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param id notification ID
+ */
+ public static void notify(final Context context, final Address replyTo, final int id) {
+ final NotificationManager man=(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ man.cancel(id);
+ Utils.sendMessage(context, replyTo, notification_canceled);
+ }
+
+ /**
+ * Show a notification.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param id notification ID
+ * @param title notificationO title
+ * @param text notification text
+ */
+ public static void notify(final Context context, final Address replyTo, final int id, final String title, final String text) {
+ final NotificationManager man=(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ man.notify(id, new NotificationCompat.Builder(context).
+ setContentTitle(title).
+ setContentText(text).
+ setSmallIcon(android.R.drawable.stat_notify_sync_noanim).
+ build());
+ Utils.sendMessage(context, replyTo, notification_shown);
+ }
+}