code: /*
   * Copyright 2016 Google Inc. All rights reserved.
   *
   * 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 org.physical_web.physicalweb;
  
  import android.app.Activity;
  import android.app.ProgressDialog;
  import android.bluetooth.BluetoothAdapter;
  import android.bluetooth.BluetoothGatt;
  import android.bluetooth.BluetoothGattCallback;
  import android.bluetooth.BluetoothGattCharacteristic;
  import android.bluetooth.BluetoothProfile;
  import android.content.DialogInterface;
  import android.content.DialogInterface.OnCancelListener;
  import android.content.Intent;
  import android.net.Uri;
  import android.os.Build.VERSION;
  import android.os.Build.VERSION_CODES;
  import android.support.v4.content.FileProvider;
  import android.widget.Toast;
  
  import java.io.File;
  import java.io.FileNotFoundException;
  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.util.UUID;
  
  /**
   * This class is used to connect to a ble gatt service and download a web page.
   */
  public class BluetoothSite extends BluetoothGattCallback {
  
    private static final String TAG = BluetoothSite.class.getSimpleName();
    private static final UUID SERVICE_UUID = UUID.fromString("ae5946d4-e587-4ba8-b6a5-a97cca6affd3");
    private static final UUID CHARACTERISTIC_WEBPAGE_UUID = UUID.fromString(
        "d1a517f0-2499-46ca-9ccc-809bc1c966fa");
    // This is BluetoothGatt.CONNECTION_PRIORITY_HIGH, from API level 21
    private static final int CONNECTION_PRIORITY_HIGH = 1;
    private FileOutputStream mHtml;
    private Activity activity;
    private BluetoothGatt mBluetoothGatt;
    private BluetoothGattCharacteristic characteristic;
    private ConnectionListener mCallback;
    private ProgressDialog progress;
    private int transferRate = 20;
    private boolean running = false;
  
    public BluetoothSite(Activity activity) {
      this.activity = activity;
    }
  
  
    public Boolean isRunning() {
      return running;
    }
  
    /**
     * Connects to the Gatt service of the device to download a web page and displays a progress bar
     * for the title.
     * @param deviceAddress The mac address of the bar
     * @param title The title of the web page being downloaded
     */
    public void connect(String deviceAddress, String title) {
      running = true;
      String progressTitle = activity.getString(R.string.page_loading_title) + " " + title;
      progress = new ProgressDialog(activity);
      progress.setCancelable(true);
      progress.setOnCancelListener(new OnCancelListener() {
        @Override
        public void onCancel(DialogInterface dialogInterface) {
          Log.i(TAG, "Dialog box canceled");
          close();
        }
      });
      progress.setTitle(progressTitle);
      progress.setMessage(activity.getString(R.string.page_loading_message));
      progress.show();
      activity.runOnUiThread(new Runnable() {
        @Override
        public void run() {
          mBluetoothGatt = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(deviceAddress)
              .connectGatt(activity, false, BluetoothSite.this);
        }
      });
    }
  
    /**
     * Connects to the Gatt service of the device to download a web page and displays a progress bar
     * for the title.
     * @param deviceAddress The mac address of the bar
     * @param title The title of the web page being downloaded
     * @param callback The callback for when the connection is complete.
     */
    public void connect(String deviceAddress, String title, ConnectionListener callback) {
      mCallback = callback;
      connect(deviceAddress, title);
    }
  
    @Override
    public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic,
        int status) {
      // Make sure the site is running.  It can stop if the dialog is dismissed very quickly.
      if (!isRunning()) {
        close();
        return;
      }
  
      // Make sure the read was successful.
      if (status != BluetoothGatt.GATT_SUCCESS) {
        Log.i(TAG, "onCharacteristicRead unsuccessful: " + status);
        close();
        Toast.makeText(activity, R.string.ble_download_error_message, Toast.LENGTH_SHORT).show();
        return;
      }
  
      // Record the data.
      Log.i(TAG, "onCharacteristicRead successful");
      try {
        mHtml.write(characteristic.getValue());
      } catch (IOException e) {
        Log.e(TAG, "Could not write to buffer", e);
        close();
        return;
      }
  
      // Request a new read if we are not done.
      if (characteristic.getValue().length == transferRate) {
        gatt.readCharacteristic(this.characteristic);
        return;
      }
  
      // At this point we are done.  Show the file.
      Log.i(TAG, "transfer is complete");
      close();
      openInChrome(getHtmlFile());
    }
  
    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
      if (newState == BluetoothProfile.STATE_CONNECTED && status == gatt.GATT_SUCCESS) {
        Log.i(TAG, "Connected to GATT server");
        mBluetoothGatt = gatt;
        if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
          gatt.requestConnectionPriority(CONNECTION_PRIORITY_HIGH);
          gatt.requestMtu(505);
        } else {
          gatt.discoverServices();
        }
      } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
        Log.i(TAG, "Disconnected to GATT server");
        // ensure progress dialog is removed and running is set false
        close();
      } else if (status != gatt.GATT_SUCCESS) {
        Log.i(TAG, "Status is " + status);
        close();
      }
    }
  
  
    @Override
    public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
      Log.i(TAG, "MTU changed to " + mtu);
      transferRate = mtu - 5;
      gatt.discoverServices();
    }
  
    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
      Log.i(TAG, "Services Discovered");
      if (status != BluetoothGatt.GATT_SUCCESS) {
        Log.e(TAG, "Service discovery failed!");
        return;
      }
  
      try {
        mHtml = new FileOutputStream(getHtmlFile());
      } catch (FileNotFoundException e) {
        Log.e(TAG, "File not found", e);
        return;
      }
  
      characteristic = gatt.getService(SERVICE_UUID).getCharacteristic(CHARACTERISTIC_WEBPAGE_UUID);
      gatt.readCharacteristic(characteristic);
    }
  
    private void close() {
      if (mCallback != null) {
        mCallback.onConnectionFinished();
      }
      if (progress != null) {
        progress.dismiss();
      }
  
      if (mHtml != null) {
        try {
          mHtml.close();
        } catch (IOException e) {
          Log.e(TAG, "Failed to close file", e);
          return;
        }
        mHtml = null;
      }
  
      running = false;
      if (mBluetoothGatt == null) {
        return;
      }
      mBluetoothGatt.close();
      mBluetoothGatt = null;
    }
  
    private File getHtmlFile() {
      File websiteDir = new File(activity.getFilesDir(), "Websites");
      websiteDir.mkdir();
      return new File(websiteDir, "website.html");
    }
  
    private File getTempFile() {
      File websiteDir = new File(activity.getFilesDir(), "Websites");
      websiteDir.mkdir();
      return new File(websiteDir, "temp.html");
    }
  
    private void openInChrome(File file) {
      if(Utils.isGzippedFile(file)) {
        file = Utils.gunzip(file, getTempFile());
      }
      Intent intent = new Intent(Intent.ACTION_VIEW);
      Uri contentUri = new FileProvider()
          .getUriForFile(activity, "org.physical_web.fileprovider", file);
      activity.grantUriPermission("com.android.chrome", contentUri,
          Intent.FLAG_GRANT_READ_URI_PERMISSION);
      intent.setDataAndType(contentUri, "text/html");
      intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
      activity.startActivity(intent);
    }
  }
  