blob: 756693ca1ffc4505db766d324cd5f17ed6a52b7d [file] [log] [blame]
// Copyright 2016 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package examples.baku.io.permissions;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Binder;
import android.os.IBinder;
import android.provider.Settings;
import android.util.Log;
import com.google.firebase.database.ChildEventListener;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseException;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import examples.baku.io.permissions.discovery.DeviceData;
import examples.baku.io.permissions.discovery.DevicePickerActivity;
import examples.baku.io.permissions.examples.ComposeActivity;
import examples.baku.io.permissions.examples.EmailActivity;
import examples.baku.io.permissions.messenger.Messenger;
import examples.baku.io.permissions.messenger.Message;
public class PermissionService extends Service {
private static final String TAG = PermissionService.class.getSimpleName();
static void l(String msg) {
Log.e(TAG, msg);
}
private static boolean mRunning;
public static boolean isRunning() {
return mRunning;
}
static final int FOREGROUND_NOTIFICATION_ID = 3278;
static final int FOCUS_NOTIFICATION = 43254;
static final String KEY_BLESSINGS = PermissionManager.KEY_BLESSINGS;
NotificationManager mNotificationManager;
FirebaseDatabase mFirebaseDB;
DatabaseReference mDevicesReference;
DatabaseReference mRequestsReference;
DatabaseReference mMessengerReference;
Messenger mMessenger;
DatabaseReference mPermissionsReference;
PermissionManager mPermissionManager;
Blessing mDeviceBlessing;
DatabaseReference mLocalDeviceReference;
private String mDeviceId;
private IBinder mBinder = new PermissionServiceBinder();
private String mFocus;
private Map<String, DeviceData> mDiscovered = new HashMap<>();
private Map<String, Integer> mDiscoveredNotifications = new HashMap<>();
private HashSet<DiscoveryListener> mDiscoveryListener = new HashSet<>();
public void revokeAll() {
for (Blessing blessing : mPermissionManager.mGrantedBlessings.values()) {
blessing.revoke(null);
}
}
public interface DiscoveryListener {
void onChange(Map<String, DeviceData> devices);
void onDisassociate(String deviceId);
}
public class PermissionServiceBinder extends Binder {
public PermissionService getInstance() {
return PermissionService.this;
}
}
public PermissionService() {
}
@Override
public void onCreate() {
super.onCreate();
mDeviceId = Settings.Secure.getString(getApplicationContext().getContentResolver(),
Settings.Secure.ANDROID_ID);
mFirebaseDB = FirebaseDatabase.getInstance();
mDevicesReference = mFirebaseDB.getReference("_devices");
mRequestsReference = mFirebaseDB.getReference("requests");
mPermissionsReference = mFirebaseDB.getReference("permissions");
mPermissionManager = new PermissionManager(mFirebaseDB.getReference(), mDeviceId);
mPermissionManager.addOnRequestListener(new PermissionManager.OnRequestListener() {
@Override
public boolean onRequest(PermissionRequest request) {
return true;
}
@Override
public void onRequestRemoved(PermissionRequest request) {
}
});
mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
initForegroundNotification();
registerDevice();
initDeviceBlessing();
initMessenger();
initDiscovery();
mRunning = true;
}
public Messenger getMessenger() {
return mMessenger;
}
public String getFocus() {
return mFocus;
}
public void setFocus(String dId) {
mFocus = dId;
if (!mDiscovered.containsKey(dId)) return;
DeviceData device = mDiscovered.get(dId);
String title = device.getName();
String subtitle = device.getId(); //default
if (device.getStatus() != null && device.getStatus().containsKey("description")) {
subtitle = device.getStatus().get("description");
}
int icon = R.drawable.ic_phone_android_black_24dp;
Intent dismissIntent = new Intent(this, PermissionService.class);
dismissIntent.putExtra("type", "dismiss");
dismissIntent.putExtra("deviceId", dId);
PendingIntent dismissPending = PendingIntent.getService(this, 0, dismissIntent, PendingIntent.FLAG_CANCEL_CURRENT);
Notification.Builder notificationBuilder = new Notification.Builder(this)
.setContentTitle(title)
.setContentText(subtitle)
.setSmallIcon(icon)
.setVibrate(new long[]{100})
.setPriority(Notification.PRIORITY_MAX)
.setDeleteIntent(dismissPending);
Map<String, String> status = device.getStatus();
if (status != null && status.containsKey(ComposeActivity.EXTRA_MESSAGE_PATH)) {
Intent pullIntent = new Intent(this, ComposeActivity.class);
pullIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
pullIntent.putExtra(ComposeActivity.EXTRA_MESSAGE_PATH, status.get(ComposeActivity.EXTRA_MESSAGE_PATH));
notificationBuilder.addAction(new Notification.Action.Builder(R.drawable.ic_cast_black_24dp, "Pull Message", PendingIntent.getActivity(this, 0, pullIntent, PendingIntent.FLAG_CANCEL_CURRENT)).build());
}
Notification notification = notificationBuilder.build();
mNotificationManager.notify(FOCUS_NOTIFICATION, notification);
}
public PermissionManager getPermissionManager() {
return mPermissionManager;
}
public String getDeviceId() {
return mDeviceId;
}
public Map<String, DeviceData> getDiscovered() {
return mDiscovered;
}
public void setStatus(String key, String value) {
mDevicesReference.child(mDeviceId).child("status").child(key).child(value);
}
public FirebaseDatabase getFirebaseDB() {
return mFirebaseDB;
}
@Override
public IBinder onBind(Intent intent) {
return new PermissionServiceBinder();
}
void initForegroundNotification() {
Intent contentIntent = new Intent(this, PermissionService.class);
PendingIntent contentPendingIntent = PendingIntent.getActivity(this, 0, contentIntent, PendingIntent.FLAG_CANCEL_CURRENT);
Intent discoverIntent = new Intent(getApplicationContext(), PermissionService.class);
discoverIntent.putExtra("type", "discover");
PendingIntent discoverPendingIntent = PendingIntent.getService(this, 1, discoverIntent, PendingIntent.FLAG_UPDATE_CURRENT);
Intent closeIntent = new Intent(getApplicationContext(), PermissionService.class);
closeIntent.putExtra("type", "close");
PendingIntent closePendingIntent = PendingIntent.getService(this, 2, closeIntent, PendingIntent.FLAG_UPDATE_CURRENT);
Notification notification = new Notification.Builder(this)
.setContentIntent(contentPendingIntent)
.setSmallIcon(R.drawable.ic_vpn_key_black_24dp)
.setContentTitle("Permission service running")
.addAction(new Notification.Action.Builder(R.drawable.ic_zoom_in_black_24dp, "Discover", discoverPendingIntent).build())
.addAction(new Notification.Action.Builder(R.drawable.ic_close_black_24dp, "Stop", closePendingIntent).build())
.build();
startForeground(FOREGROUND_NOTIFICATION_ID, notification);
}
void refreshForegroundNotification(Notification notification) {
mNotificationManager.notify(FOREGROUND_NOTIFICATION_ID, notification);
}
public void initDeviceBlessing() {
final DatabaseReference deviceBlessingRef = mFirebaseDB.getReference(KEY_BLESSINGS).child(mDeviceId);
deviceBlessingRef.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()) {
mDeviceBlessing = new Blessing(dataSnapshot);
} else {
mDeviceBlessing = new Blessing(mDeviceId, null, deviceBlessingRef);
}
//give device access its own document directory
mDeviceBlessing.setPermissions("documents/" + mDeviceId, PermissionManager.FLAG_WRITE | PermissionManager.FLAG_READ);
}
@Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
public Blessing getDeviceBlessing() {
return mDeviceBlessing;
}
public void initMessenger() {
mMessengerReference = mFirebaseDB.getReference("messages");
mMessenger = new Messenger(mDeviceId, mMessengerReference);
mMessenger.on("disassociate", new Messenger.Listener() {
@Override
public void call(String args, Messenger.Ack callback) {
}
});
mMessenger.on("cast", new Messenger.Listener() {
@Override
public void call(String args, Messenger.Ack callback) {
if (args != null) {
try {
JSONObject jsonArgs = new JSONObject(args);
if (jsonArgs.has("activity")) {
if (ComposeActivity.class.getSimpleName().equals(jsonArgs.getString("activity"))) {
String path = jsonArgs.getString(ComposeActivity.EXTRA_MESSAGE_PATH);
Intent emailIntent = new Intent(PermissionService.this, ComposeActivity.class);
emailIntent.putExtra(ComposeActivity.EXTRA_MESSAGE_PATH, path);
emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(emailIntent);
}
}
} catch (JSONException e) {
e.printStackTrace();
}
}
}
});
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null && intent.hasExtra("type")) {
String type = intent.getStringExtra("type");
l("start command " + type);
//TODO: move to a broadcast receiver. StartService intents are not ideal.
if ("sendRequest".equals(type)) {
if (intent.hasExtra("request")) {
Message request = intent.getParcelableExtra("request");
// sendRequest(request);
}
} else if ("discover".equals(type)) {
if (mDiscovered != null) {
Intent discoveryIntent = new Intent(this, DevicePickerActivity.class);
discoveryIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(discoveryIntent);
}
} else if ("dismiss".equals(type)) {
Message request = new Message("disassociate");
request.setTarget(mFocus);
// sendRequest(request);
setFocus(null);
} else if ("close".equals(type)) {
stopSelf();
} else if ("focus".equals(type)) {
if (intent.hasExtra("deviceId")) {
String dId = intent.getStringExtra("deviceId");
l("targetting " + dId);
if (mDiscovered.containsKey(dId)) {
mFocus = dId;
DeviceData target = mDiscovered.get(dId);
String title = "Targetting device: " + target.getName();
Intent contentIntent = new Intent(this, EmailActivity.class);
Intent discoverIntent = new Intent(this, PermissionService.class);
discoverIntent.putExtra("type", "discover");
PendingIntent discoverPendingIntent = PendingIntent.getService(this, 0, discoverIntent, PendingIntent.FLAG_CANCEL_CURRENT);
Intent castIntent = new Intent(this, PermissionService.class);
castIntent.putExtra("type", "sendRequest");
castIntent.putExtra("request", new Message("start"));
PendingIntent castPendingIntent = PendingIntent.getService(this, 0, castIntent, PendingIntent.FLAG_CANCEL_CURRENT);
Notification notification = new Notification.Builder(this)
.setPriority(Notification.PRIORITY_HIGH)
.setVibrate(new long[]{100})
.setContentIntent(PendingIntent.getActivity(this, 0, contentIntent, 0))
.setSmallIcon(R.drawable.ic_vpn_key_black_24dp)
.setContentTitle(title)
.addAction(new Notification.Action.Builder(R.drawable.ic_cast_black_24dp, "Cast", castPendingIntent).build())
.addAction(new Notification.Action.Builder(R.drawable.ic_zoom_in_black_24dp, "Discover", discoverPendingIntent).build())
.build();
refreshForegroundNotification(notification);
}
for (Iterator<Integer> iterator = mDiscoveredNotifications.values().iterator(); iterator.hasNext(); ) {
int notId = iterator.next();
mNotificationManager.cancel(notId);
}
mDiscoveredNotifications.clear();
}
}
}
return super.onStartCommand(intent, flags, startId);
}
void registerDevice() {
mLocalDeviceReference = mDevicesReference.child(mDeviceId);
mLocalDeviceReference.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (!dataSnapshot.exists()) {
resetLocalDevice();
} else {
try {
mLocalDevice = dataSnapshot.getValue(DeviceData.class);
} catch (DatabaseException e) {
e.printStackTrace();
}
}
}
@Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
private DeviceData mLocalDevice;
void resetLocalDevice() {
final String deviceName = android.os.Build.MODEL;
mLocalDevice = new DeviceData(mDeviceId, deviceName);
mLocalDevice.setActive(true);
mLocalDeviceReference.setValue(mLocalDevice);
}
void initDiscovery() {
mDevicesReference.addChildEventListener(new ChildEventListener() {
@Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
updateDevice(dataSnapshot);
}
@Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
updateDevice(dataSnapshot);
}
@Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
@Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
@Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
public void addDiscoveryListener(DiscoveryListener listener) {
mDiscoveryListener.add(listener);
}
private void updateDevice(DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()) {
String key = dataSnapshot.getKey();
if (!mDeviceId.equals(key)) {
try {
DeviceData device = dataSnapshot.getValue(DeviceData.class);
if (device != null) {
mDiscovered.put(key, device);
for (DiscoveryListener listener : mDiscoveryListener) {
listener.onChange(mDiscovered);
}
}
} catch (DatabaseException e) {
e.printStackTrace();
}
}
}
}
public static void start(Context context) {
context.startService(new Intent(context, PermissionService.class));
}
//convenience class for when context implements ServiceConnection
//throws cast exception
public static void bind(Context context) {
ServiceConnection connection = (ServiceConnection) context;
bind(context, connection);
}
public static void bind(Context context, ServiceConnection connection) {
context.bindService(new Intent(context, PermissionService.class), connection, BIND_AUTO_CREATE);
}
}