Java tutorial
/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.marekscholtz.bluetify.utilities; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothServerSocket; import android.bluetooth.BluetoothSocket; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.IBinder; import android.os.Vibrator; import android.preference.PreferenceManager; import android.provider.Settings; import android.support.annotation.Nullable; import android.support.v4.app.NotificationCompat; import android.support.v4.app.RemoteInput; import android.support.v4.content.ContextCompat; import android.util.Base64; import com.marekscholtz.bluetify.R; import com.marekscholtz.bluetify.activities.ChatActivity; import com.marekscholtz.bluetify.entities.ChatMessage; import com.marekscholtz.bluetify.entities.Friend; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.UUID; import io.realm.Realm; import io.realm.RealmConfiguration; public class BluetoothChatService extends Service { private static final String NAME_SECURE = "BluetoothChatSecure"; private static final UUID UUID_SECURE = UUID.fromString("fa87c0d0-afac-11de-8a39-0800200c9a66"); private BluetoothAdapter mBluetoothAdapter; private Handler mHandler; private AcceptThread mAcceptThread; private ConnectThread mConnectThread; private ConnectedThread mConnectedThread; private int mState; private static SharedPreferences mSharedPreferences; private final BluetoothChatServiceBinder mBluetoothChatServiceBinder = new BluetoothChatServiceBinder(); public static final int STATE_NONE = 0; public static final int STATE_LISTEN = 1; public static final int STATE_CONNECTING = 2; public static final int STATE_CONNECTED = 3; public BluetoothChatService() { mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); mState = STATE_NONE; } public class BluetoothChatServiceBinder extends Binder { public BluetoothChatService getService() { return BluetoothChatService.this; } } @Nullable @Override public IBinder onBind(Intent intent) { return mBluetoothChatServiceBinder; } @Override public int onStartCommand(Intent intent, int flags, int startId) { accept(); return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { if (mConnectThread != null) { mConnectThread.cancel(); mConnectThread = null; } if (mConnectedThread != null) { mConnectedThread.cancel(); mConnectedThread = null; } if (mAcceptThread != null) { mAcceptThread.cancel(); mAcceptThread = null; } super.onDestroy(); } public void setHandler(Handler handler) { mHandler = handler; } public void unsetHandler() { mHandler = null; } public int getState() { return mState; } public void accept() { if (mAcceptThread != null) { mAcceptThread.cancel(); mAcceptThread = null; } if (mConnectThread != null) { mConnectThread.cancel(); mConnectThread = null; } if (mConnectedThread != null) { mConnectedThread.cancel(); mConnectedThread = null; } mAcceptThread = new AcceptThread(); mAcceptThread.start(); } public synchronized void connect(String macAddress) { if (mAcceptThread != null) { mAcceptThread.cancel(); mAcceptThread = null; } if (mConnectThread != null) { mConnectThread.cancel(); mConnectThread = null; } if (mConnectedThread != null) { mConnectedThread.cancel(); mConnectedThread = null; } mConnectThread = new ConnectThread(mBluetoothAdapter.getRemoteDevice(macAddress)); mConnectThread.start(); } private synchronized void connected(BluetoothSocket socket) { if (mAcceptThread != null) { mAcceptThread.cancel(); mAcceptThread = null; } if (mConnectThread != null) { mConnectThread.cancel(); mConnectThread = null; } if (mConnectedThread != null) { mConnectedThread.cancel(); mConnectedThread = null; } mConnectedThread = new ConnectedThread(socket); mConnectedThread.start(); BluetoothAdapter bluetoothAdapter = ((BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE)) .getAdapter(); if (mSharedPreferences == null) { mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); } byte[] usernameBytes = ("&u" + mSharedPreferences.getString("username", bluetoothAdapter.getName()) + "&p") .getBytes(); if (mSharedPreferences.getString("profile_photo", null) == null) { write(usernameBytes); } else { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); try { outputStream.write(usernameBytes); outputStream .write(Base64.decode(mSharedPreferences.getString("profile_photo", null), Base64.DEFAULT)); } catch (IOException e) { e.printStackTrace(); } write(outputStream.toByteArray()); } } public void connectionError() { mState = STATE_NONE; if (mHandler != null) { mHandler.obtainMessage(Constants.MESSAGE_STATE_CHANGE, mState, -1).sendToTarget(); } BluetoothChatService.this.accept(); } public void write(byte[] out) { ConnectedThread connectedThread; synchronized (this) { if (mState != STATE_CONNECTED) return; connectedThread = mConnectedThread; } connectedThread.write(out); } private class AcceptThread extends Thread { private final BluetoothServerSocket mmServerSocket; private String mSocketType; private AcceptThread() { BluetoothServerSocket tmp = null; mSocketType = "Secure"; try { tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME_SECURE, UUID_SECURE); } catch (IOException e) { e.printStackTrace(); } mmServerSocket = tmp; mState = STATE_LISTEN; if (mHandler != null) { mHandler.obtainMessage(Constants.MESSAGE_STATE_CHANGE, mState, -1).sendToTarget(); } } public void run() { setName("AcceptThread" + mSocketType); BluetoothSocket socket; while (mState != STATE_CONNECTED) { try { socket = mmServerSocket.accept(); } catch (IOException e) { break; } if (socket != null) { synchronized (BluetoothChatService.this) { switch (mState) { case STATE_LISTEN: case STATE_CONNECTING: connected(socket); break; case STATE_NONE: case STATE_CONNECTED: try { socket.close(); } catch (IOException e) { e.printStackTrace(); } break; } } } } } private void cancel() { try { mmServerSocket.close(); } catch (IOException e) { e.printStackTrace(); } } } private class ConnectThread extends Thread { private final BluetoothSocket mmSocket; private String mSocketType; private ConnectThread(BluetoothDevice device) { BluetoothSocket tmp = null; mSocketType = "Secure"; try { tmp = device.createRfcommSocketToServiceRecord(UUID_SECURE); } catch (IOException e) { e.printStackTrace(); } mmSocket = tmp; mState = STATE_CONNECTING; if (mHandler != null) { mHandler.obtainMessage(Constants.MESSAGE_STATE_CHANGE, mState, -1).sendToTarget(); } } public void run() { setName("ConnectThread" + mSocketType); mBluetoothAdapter.cancelDiscovery(); try { mmSocket.connect(); } catch (IOException e) { try { mmSocket.close(); } catch (IOException e2) { e.printStackTrace(); } connectionError(); return; } synchronized (BluetoothChatService.this) { mConnectThread = null; } connected(mmSocket); } private void cancel() { try { mmSocket.close(); } catch (IOException e) { e.printStackTrace(); } } } private class ConnectedThread extends Thread { private final BluetoothSocket mmSocket; private final InputStream mmInStream; private final OutputStream mmOutStream; private String mmPreviousCommand; private ConnectedThread(BluetoothSocket socket) { mmSocket = socket; InputStream tmpIn = null; OutputStream tmpOut = null; try { tmpIn = socket.getInputStream(); tmpOut = socket.getOutputStream(); } catch (IOException e) { e.printStackTrace(); } mmInStream = tmpIn; mmOutStream = tmpOut; mState = STATE_CONNECTED; if (mHandler != null) { mHandler.obtainMessage(Constants.MESSAGE_STATE_CHANGE, mState, -1).sendToTarget(); } } public void run() { byte[] messageBytes = new byte[1048576]; int bytes; while (mState == STATE_CONNECTED) { try { bytes = mmInStream.read(messageBytes); Realm.init(getApplicationContext()); RealmConfiguration config = new RealmConfiguration.Builder().deleteRealmIfMigrationNeeded() .build(); Realm realm = Realm.getInstance(config); Friend friend = realm.where(Friend.class) .equalTo("mMacAddress", mmSocket.getRemoteDevice().getAddress()).findFirst(); String command = new String(Arrays.copyOfRange(messageBytes, 0, 2)); realm.beginTransaction(); if ("&u".equals(command)) { mmPreviousCommand = "&u"; friend.setName(new String( Arrays.copyOfRange(messageBytes, 2, new String(messageBytes).indexOf("&p")))); if (new String(messageBytes).indexOf("&p") + 2 < bytes) { friend.setProfilePhoto(Arrays.copyOfRange(messageBytes, new String(messageBytes).indexOf("&p") + 2, bytes)); } else { friend.setProfilePhoto(null); } if (mHandler != null) { mHandler.obtainMessage(Constants.USERNAME_CHANGED, friend.getName()).sendToTarget(); } } else if ("&t".equals(command)) { mmPreviousCommand = command; ChatMessage chatMessage = realm.createObject(ChatMessage.class, getNextPrimaryKey(realm)); chatMessage.setMacAddress(mmSocket.getRemoteDevice().getAddress()); chatMessage.setPicture(false); if (!"t&".equals(new String(Arrays.copyOfRange(messageBytes, bytes - 2, bytes)))) { chatMessage.setContent(Arrays.copyOfRange(messageBytes, 2, bytes)); } else { chatMessage.setContent(Arrays.copyOfRange(messageBytes, 2, bytes - 2)); if (mSharedPreferences.getBoolean("notification_enabled", false)) { showNotification(friend, chatMessage); } } chatMessage.setSent(false); chatMessage.setDate(new Date()); } else if ("&i".equals(command)) { mmPreviousCommand = command; ChatMessage chatMessage = realm.createObject(ChatMessage.class, getNextPrimaryKey(realm)); chatMessage.setMacAddress(mmSocket.getRemoteDevice().getAddress()); chatMessage.setPicture(true); if (!"i&".equals(new String(Arrays.copyOfRange(messageBytes, bytes - 2, bytes)))) { chatMessage.setContent(Arrays.copyOfRange(messageBytes, 2, bytes)); } else { byte[] bitmapBytes = Arrays.copyOfRange(messageBytes, 2, bytes - 2); Bitmap bitmap = BitmapFactory.decodeByteArray(bitmapBytes, 0, bitmapBytes.length); Calendar calendar = Calendar.getInstance(); calendar.setTime(chatMessage.getDate()); String bitmapFileName = String.valueOf(calendar.get(Calendar.YEAR)) + "-" + calendar.get(Calendar.MONTH) + "-" + calendar.get(Calendar.DAY_OF_MONTH) + " " + calendar.get(Calendar.HOUR_OF_DAY) + "." + calendar.get(Calendar.MINUTE) + "." + calendar.get(Calendar.SECOND) + " " + friend.getName() + ".png"; File bitmapPathFile = new File( Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), bitmapFileName); FileOutputStream fileOutputStream = new FileOutputStream(bitmapPathFile); bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream); chatMessage.setContent(bitmapPathFile.toURI().toString().getBytes()); if (mSharedPreferences.getBoolean("notification_enabled", false)) { showNotification(friend, chatMessage); } } chatMessage.setSent(false); chatMessage.setDate(new Date()); } else if ("&u".equals(mmPreviousCommand)) { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); try { byteArrayOutputStream.write(friend.getProfilePhoto()); byteArrayOutputStream.write(Arrays.copyOfRange(messageBytes, 0, bytes)); } catch (IOException e) { e.printStackTrace(); } friend.setProfilePhoto(byteArrayOutputStream.toByteArray()); } else if ("&t".equals(mmPreviousCommand)) { ChatMessage chatMessage = realm.where(ChatMessage.class).findAll().last(); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); byteArrayOutputStream.write(chatMessage.getContent()); if (!"t&".equals(new String(Arrays.copyOfRange(messageBytes, bytes - 2, bytes)))) { try { byteArrayOutputStream.write(Arrays.copyOfRange(messageBytes, 0, bytes)); } catch (IOException e) { e.printStackTrace(); } chatMessage.setContent(byteArrayOutputStream.toByteArray()); } else { try { byteArrayOutputStream.write(Arrays.copyOfRange(messageBytes, 0, bytes - 2)); } catch (IOException e) { e.printStackTrace(); } chatMessage.setContent(byteArrayOutputStream.toByteArray()); if (mSharedPreferences.getBoolean("notification_enabled", false)) { showNotification(friend, chatMessage); } } } else if ("&i".equals(mmPreviousCommand)) { ChatMessage chatMessage = realm.where(ChatMessage.class).findAll().last(); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); byteArrayOutputStream.write(chatMessage.getContent()); if (!"i&".equals(new String(Arrays.copyOfRange(messageBytes, bytes - 2, bytes)))) { try { byteArrayOutputStream.write(Arrays.copyOfRange(messageBytes, 0, bytes)); } catch (IOException e) { e.printStackTrace(); } chatMessage.setContent(byteArrayOutputStream.toByteArray()); } else { try { byteArrayOutputStream.write(Arrays.copyOfRange(messageBytes, 0, bytes - 2)); } catch (IOException e) { e.printStackTrace(); } Bitmap bitmap = BitmapFactory.decodeByteArray(byteArrayOutputStream.toByteArray(), 0, byteArrayOutputStream.size()); Calendar calendar = Calendar.getInstance(); calendar.setTime(chatMessage.getDate()); String bitmapFileName = String.valueOf(calendar.get(Calendar.YEAR)) + "-" + calendar.get(Calendar.MONTH) + "-" + calendar.get(Calendar.DAY_OF_MONTH) + " " + calendar.get(Calendar.HOUR_OF_DAY) + "." + calendar.get(Calendar.MINUTE) + "." + calendar.get(Calendar.SECOND) + " " + friend.getName() + ".png"; File bitmapPathFile = new File( Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), bitmapFileName); FileOutputStream fileOutputStream = new FileOutputStream(bitmapPathFile); bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream); chatMessage.setContent(bitmapPathFile.toURI().toString().getBytes()); if (mSharedPreferences.getBoolean("notification_enabled", false)) { showNotification(friend, chatMessage); } } } realm.commitTransaction(); } catch (IOException e) { connectionError(); break; } } } private void write(byte[] buffer) { try { mmOutStream.write(buffer); } catch (IOException e) { e.printStackTrace(); } } private void cancel() { try { mmSocket.close(); } catch (IOException e) { e.printStackTrace(); } } } public String getConnectedMacAddress() { if (mConnectedThread != null) { return mConnectedThread.mmSocket.getRemoteDevice().getAddress(); } else { return null; } } public Long getNextPrimaryKey(Realm realm) { try { return realm.where(ChatMessage.class).max("mId").longValue() + 1; } catch (NullPointerException | ArrayIndexOutOfBoundsException e) { return 1L; } } private void showNotification(Friend friend, ChatMessage chatMessage) { if (mHandler == null) { Intent clickIntent = new Intent(getApplicationContext(), ChatActivity.class); clickIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); clickIntent.putExtra("mac_address", friend.getMacAddress()); PendingIntent clickPendingIntent = PendingIntent.getActivity(getApplicationContext(), 0, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT); NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(getApplicationContext()) .setSmallIcon(R.drawable.ic_logo_white_24dp).setPriority(Notification.PRIORITY_HIGH) .setColor(ContextCompat.getColor(getApplicationContext(), R.color.colorPrimary)) .setContentTitle(friend.getName()).setContentIntent(clickPendingIntent).setAutoCancel(true); if (mSharedPreferences.getBoolean("notification_sound", false)) { notificationBuilder.setSound(Settings.System.DEFAULT_NOTIFICATION_URI); } if (mSharedPreferences.getBoolean("notification_vibration", false)) { notificationBuilder.setVibrate(new long[] { 0, 250, 250, 250 }); } if (mSharedPreferences.getBoolean("notification_light", false)) { notificationBuilder.setLights(0xff0082fc, 1000, 1000); } if (!chatMessage.hasPicture()) { notificationBuilder.setContentText(new String(chatMessage.getContent())); } else { if (chatMessage.isSent()) { notificationBuilder.setContentText( getApplicationContext().getResources().getString(R.string.you_sent_an_image) + "."); } else { notificationBuilder.setContentText(friend.getName() + " " + getApplicationContext().getResources().getString(R.string.sent_you_an_image) + "."); } } if (friend.getProfilePhoto() != null) { byte[] profilePhotoBytes = friend.getProfilePhoto(); Bitmap profilePhotoBitmap = BitmapFactory.decodeByteArray(friend.getProfilePhoto(), 0, profilePhotoBytes.length); notificationBuilder.setLargeIcon(profilePhotoBitmap); } //TODO reply in notification // if (android.os.Build.VERSION_CODES.N <= android.os.Build.VERSION.SDK_INT) { // PendingIntent replyPendingIntent = // PendingIntent.getActivity( // getApplicationContext(), // 0, // new Intent("REPLY"), // PendingIntent.FLAG_UPDATE_CURRENT // ); // RemoteInput remoteInput = new RemoteInput.Builder("message_text") // .setLabel(getString(R.string.write_a_message)) // .build(); // // NotificationCompat.Action action = new NotificationCompat.Action.Builder(R.drawable.ic_send_white_24dp, // getString(R.string.reply), replyPendingIntent) // .addRemoteInput(remoteInput) // .build(); // // getApplicationContext().registerReceiver(new NotificationBroadcastReceiver(), new IntentFilter("REPLY")); // notificationBuilder.addAction(action); // } NotificationManager mNotificationManager = (NotificationManager) getApplicationContext() .getSystemService(Context.NOTIFICATION_SERVICE); mNotificationManager.notify(0, notificationBuilder.build()); } else { Vibrator vibrator = (Vibrator) getApplicationContext().getSystemService(Context.VIBRATOR_SERVICE); vibrator.vibrate(125); } } public static class BluetoothBroadcastReceiver extends BroadcastReceiver { private Realm mRealm; @Override public void onReceive(Context context, Intent intent) { if (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1) == BluetoothAdapter.STATE_ON) { context.startService(new Intent(context, BluetoothChatService.class)); mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); } else if (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1) == BluetoothAdapter.STATE_TURNING_OFF) { Realm.init(context); RealmConfiguration config = new RealmConfiguration.Builder().deleteRealmIfMigrationNeeded().build(); mRealm = Realm.getInstance(config); mRealm.beginTransaction(); for (Friend friend : mRealm.where(Friend.class).findAll()) { friend.setOnline(false); } mRealm.commitTransaction(); context.stopService(new Intent(context, BluetoothChatService.class)); } } } public class NotificationBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Bundle remoteInput = RemoteInput.getResultsFromIntent(intent); if (remoteInput != null) { BluetoothChatService.this.write(("&t" + remoteInput.getString("message_text") + "&t").getBytes()); unregisterReceiver(this); } } } }