/*
* Copyright (C) 2015 - Holy Lobster
*
* Nuntius 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 2 of the License, or
* (at your option) any later version.
*
* Nuntius 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 Nuntius. If not, see <http://www.gnu.org/licenses/>.
*/
package org.holylobster.nuntius.connection;
import android.annotation.TargetApi;
import android.app.Notification;
import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.Uri;
import android.os.Build;
import android.preference.PreferenceManager;
import android.provider.ContactsContract;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import org.holylobster.nuntius.bluetooth.BluetoothConnectionProvider;
import org.holylobster.nuntius.network.NetworkConnectionProvider;
import org.holylobster.nuntius.network.SslNetworkConnectionProvider;
import org.holylobster.nuntius.notifications.NotiHandler;
import org.holylobster.nuntius.notifications.IntentRequestCodes;
import org.holylobster.nuntius.notifications.NotificationListenerService;
import java.io.File;
import java.io.IOException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.HashSet;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;
import org.holylobster.nuntius.sms.SMessage;
import org.holylobster.nuntius.sms.SmsObservable;
import java.util.concurrent.CopyOnWriteArrayList;
public final class Server extends BroadcastReceiver implements SharedPreferences.OnSharedPreferenceChangeListener, ConnectionManager, Observer {
private static final String TAG = Server.class.getSimpleName();
public static final boolean BLUETOOTH_ENABLED = false;
private boolean ssl = true;
private final List<Connection> connections = new CopyOnWriteArrayList<>();
private BluetoothConnectionProvider bluetoothConnectionProvider;
private NetworkConnectionProvider networkConnectionProvider;
private Set<String> blacklistedApp;
private int minNotificationPriority = Notification.PRIORITY_DEFAULT;
private final NotificationListenerService context;
public Server(NotificationListenerService context) {
this.context = context;
SharedPreferences defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
blacklistedApp = defaultSharedPreferences.getStringSet("BlackList", new HashSet<String>());
Log.d(TAG, "server created");
SmsObservable.getInstance().addObserver(this);
}
public static Boolean bluetoothAvailable = null;
public static boolean bluetoothEnabled() {
return bluetoothAvailable() && BluetoothAdapter.getDefaultAdapter().isEnabled();
}
public static boolean bluetoothAvailable() {
if (bluetoothAvailable == null) {
bluetoothAvailable = BluetoothAdapter.getDefaultAdapter() != null;
}
return bluetoothAvailable;
}
public void onNotificationPosted(StatusBarNotification sbn) {
if (filter(sbn)) {
Message message = new Message("notificationPosted", sbn);
sendMessage(message);
}
}
public void onNotificationRemoved(StatusBarNotification sbn) {
if (filter(sbn)) {
Message message = new Message("notificationRemoved", sbn);
sendMessage(message);
}
}
private boolean filter(StatusBarNotification sbn) {
Notification notification = sbn.getNotification();
Log.d("blacklist", " " + blacklistedApp);
return
notification != null
// Filter low priority notifications
&& notification.priority >= minNotificationPriority
// Notification flags
&& !isOngoing(notification)
&& !isLocalOnly(notification)
&& !isBlacklisted(sbn);
}
private boolean isBlacklisted(StatusBarNotification sbn) {
return blacklistedApp.contains(sbn.getPackageName());
}
@TargetApi(Build.VERSION_CODES.KITKAT_WATCH)
private static boolean isLocalOnly(Notification notification) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT_WATCH) {
return false;
}
boolean local = (notification.flags & Notification.FLAG_LOCAL_ONLY) != 0;
Log.d(TAG, String.format("Notification is local: %1s", local));
return local;
}
private static boolean isOngoing(Notification notification) {
boolean ongoing = (notification.flags & Notification.FLAG_ONGOING_EVENT) != 0;
Log.d(TAG, String.format("Notification is ongoing: %1s", ongoing));
return ongoing;
}
private void sendMessage(Message message) {
//Log.d(TAG, message.toJSON(context));
for (Connection connection : connections) {
boolean queued = connection.enqueue(message);
if (!queued) {
Log.w(TAG, "Unable to enqueue message on connection " + connection);
}
}
}
public void start() {
Log.i(TAG, "Server starting...");
IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
context.registerReceiver(this, filter);
SharedPreferences defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
defaultSharedPreferences.registerOnSharedPreferenceChangeListener(this);
boolean mustRun = defaultSharedPreferences.getBoolean("main_enable_switch", true);
if (mustRun) {
startAll();
}
}
public void stop() {
Log.d(TAG, "Server stopping...");
context.unregisterReceiver(this);
PreferenceManager.getDefaultSharedPreferences(context).unregisterOnSharedPreferenceChangeListener(this);
stopAll();
}
void stopAll() {
stopBluetooth();
stopNetwork();
for (Connection connection : connections) {
connection.close();
}
connections.clear();
}
public String getStatusMessage() {
if (BLUETOOTH_ENABLED) {
if (bluetoothEnabled() && getNumberOfConnections() == 0 ) {
return "pair";
} else if (bluetoothConnectionProvider != null && bluetoothConnectionProvider.isAlive()) {
return "connection";
} else if (!NotificationListenerService.isNotificationAccessEnabled()) {
return "notification";
} else if (!bluetoothEnabled()) {
return "bluetooth";
} else {
return "...";
}
} else {
if (networkConnectionProvider != null && networkConnectionProvider.isAlive() && getNumberOfConnections() == 0) {
return "pair";
} else if (networkConnectionProvider != null && networkConnectionProvider.isAlive()) {
return "connection";
} else {
return "relaunch";
}
}
}
public int getNumberOfConnections() {
return connections.size();
}
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
switch (state) {
case BluetoothAdapter.STATE_OFF:
case BluetoothAdapter.STATE_TURNING_OFF:
case BluetoothAdapter.STATE_TURNING_ON:
stopBluetooth();
break;
case BluetoothAdapter.STATE_ON:
startBluetooth();
break;
}
}
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
Log.i(TAG, "Changes to preference " + key);
switch (key) {
case "main_enable_switch":
if (sharedPreferences.getBoolean("main_enable_switch", true)) {
startAll();
} else {
stopAll();
}
break;
case "pref_min_notification_priority":
minNotificationPriority = Integer.parseInt(sharedPreferences.getString("pref_min_notification_priority", String.valueOf(Notification.PRIORITY_DEFAULT)));
break;
case "BlackList":
blacklistedApp = sharedPreferences.getStringSet("BlackList", new HashSet<String>());
break;
default:
}
}
void startAll() {
if (BLUETOOTH_ENABLED) {
startBluetooth();
} else {
startNetwork();
}
}
private void startBluetooth() {
if (bluetoothEnabled()) {
bluetoothConnectionProvider = new BluetoothConnectionProvider(this);
bluetoothConnectionProvider.start();
} else {
Log.i(TAG, "Bluetooth not available or enabled. Cannot start Bluetooth server");
}
notifyListener(getStatusMessage());
}
private void startNetwork() {
if (networkAvailable()) {
try {
if (ssl) {
networkConnectionProvider = new SslNetworkConnectionProvider(this, new File(context.getFilesDir(), "custom.bks"));
} else {
networkConnectionProvider = new NetworkConnectionProvider(this);
}
networkConnectionProvider.start();
} catch (CertificateException | NoSuchAlgorithmException | KeyStoreException | IOException e) {
Log.e(TAG, "Error creating SSL server", e);
}
}
notifyListener(getStatusMessage());
}
private boolean networkAvailable() {
ConnectivityManager connManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
//NetworkInfo mWifi = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
//return mWifi.isConnected();
if (connManager.getActiveNetworkInfo() != null && connManager.getActiveNetworkInfo().isAvailable() && connManager.getActiveNetworkInfo().isConnected()) {
return true;
} else {
return false;
}
}
private void stopBluetooth() {
Log.i(TAG, "Stopping server thread.");
if (bluetoothConnectionProvider != null) {
bluetoothConnectionProvider.close();
Log.i(TAG, "Bluetooth Server thread stopped.");
} else {
Log.i(TAG, "Bluetooth Server thread already stopped.");
}
notifyListener(getStatusMessage());
}
private void stopNetwork() {
if (networkConnectionProvider != null) {
networkConnectionProvider.close();
Log.i(TAG, "Network Server thread stopped.");
} else {
Log.i(TAG, "Network Server thread already stopped.");
}
notifyListener(getStatusMessage());
}
private void notifyListener(String status) {
Intent intent = new Intent(IntentRequestCodes.INTENT_SERVER_STATUS_CHANGE);
intent.putExtra("status", status);
Log.d(TAG, "Sending server status change: " + status);
context.sendBroadcast(intent);
}
public void newConnection(Socket socket) {
NotiHandler notiHandler = new NotiHandler() {
@Override
public void onMessageReceived(IncomingMessage message) {
Log.d(TAG, "Message received: " + message);
try {
message.getEventType().manageEvent(context, message.getMsg());
} catch (IOException e) {
Log.e(TAG,"Error when parsing the message\n" + e.getMessage());
}
}
@Override
public void onConnectionClosed(Connection connection) {
connections.remove(connection);
Log.i(TAG, ">>Connection closed (" + connection.getDestination() + ")");
notifyListener(getStatusMessage());
}
};
connections.add(new Connection(context, socket, notiHandler));
notifyListener(getStatusMessage());
}
public String getContactName(String phoneNumber) {
ContentResolver cr = context.getContentResolver();
Uri uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(phoneNumber));
Cursor cursor = cr.query(uri, new String[]{ContactsContract.PhoneLookup.DISPLAY_NAME}, null, null, null);
if (cursor == null) {
return null;
}
String contactName = null;
if(cursor.moveToFirst()) {
contactName = cursor.getString(cursor.getColumnIndex(ContactsContract.PhoneLookup.DISPLAY_NAME));
}
if(!cursor.isClosed()) {
cursor.close();
}
return contactName;
}
@Override
public void update(Observable observable, Object data) {
if (data instanceof SMessage) {
SMessage sMessage = (SMessage) data;
sMessage.setSender(getContactName(sMessage.getSenderNum()));
sendMessage(new Message(sMessage));
}
}
}