From 51db9450dbdf2d2502da48d1925d2bcc629d6d5c Mon Sep 17 00:00:00 2001 From: Emmanuel M Date: Fri, 8 Jun 2018 09:46:57 -0300 Subject: [PATCH] Fixed USB OTG URI forgetting when app pauses --- .../filemanager/activities/MainActivity.java | 88 ++++++++++++------- .../filesystem/SingletonUsbOtg.java | 38 -------- .../filesystem/usb/SingletonUsbOtg.java | 47 ++++++++++ .../filesystem/usb/UsbOtgRepresentation.java | 46 ++++++++++ .../filemanager/ui/views/drawer/Drawer.java | 5 +- .../com/amaze/filemanager/utils/OTGUtil.java | 35 ++++++-- 6 files changed, 177 insertions(+), 82 deletions(-) delete mode 100644 app/src/main/java/com/amaze/filemanager/filesystem/SingletonUsbOtg.java create mode 100644 app/src/main/java/com/amaze/filemanager/filesystem/usb/SingletonUsbOtg.java create mode 100644 app/src/main/java/com/amaze/filemanager/filesystem/usb/UsbOtgRepresentation.java diff --git a/app/src/main/java/com/amaze/filemanager/activities/MainActivity.java b/app/src/main/java/com/amaze/filemanager/activities/MainActivity.java index 4d987cdce..577ff2571 100644 --- a/app/src/main/java/com/amaze/filemanager/activities/MainActivity.java +++ b/app/src/main/java/com/amaze/filemanager/activities/MainActivity.java @@ -48,6 +48,7 @@ import android.service.quicksettings.TileService; import android.support.annotation.ColorInt; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.RequiresApi; import android.support.design.widget.AppBarLayout; import android.support.design.widget.Snackbar; import android.support.v4.app.Fragment; @@ -91,9 +92,10 @@ import com.amaze.filemanager.filesystem.HybridFile; import com.amaze.filemanager.filesystem.HybridFileParcelable; import com.amaze.filemanager.filesystem.PasteHelper; import com.amaze.filemanager.filesystem.RootHelper; -import com.amaze.filemanager.filesystem.SingletonUsbOtg; +import com.amaze.filemanager.filesystem.usb.SingletonUsbOtg; import com.amaze.filemanager.filesystem.ssh.CustomSshJConfig; import com.amaze.filemanager.filesystem.ssh.SshConnectionPool; +import com.amaze.filemanager.filesystem.usb.UsbOtgRepresentation; import com.amaze.filemanager.fragments.AppsListFragment; import com.amaze.filemanager.fragments.CloudSheetFragment; import com.amaze.filemanager.fragments.CloudSheetFragment.CloudConnectionCallbacks; @@ -686,32 +688,13 @@ public class MainActivity extends PermissionsActivity implements SmbConnectionLi if (usb != null && !rv.contains(usb.getPath())) rv.add(usb.getPath()); if (SDK_INT >= Build.VERSION_CODES.KITKAT) { - if (isUsbDeviceConnected()) rv.add(OTGUtil.PREFIX_OTG + "/"); + if (SingletonUsbOtg.getInstance().isDeviceConnected()) { + rv.add(OTGUtil.PREFIX_OTG + "/"); + } } return rv; } - /** - * Method finds whether a USB device is connected or not - * @return true if device is connected - */ - private boolean isUsbDeviceConnected() { - if (OTGUtil.isMassStorageDeviceConnected(this)) { - if(!SingletonUsbOtg.getInstance().hasRootBeenRequested()) { - SingletonUsbOtg.getInstance().setHasRootBeenRequested(false); - // we need to set this every time as there is no way to know that whether USB device was - // disconnected after closing the app and another one was connected in that case - // the URI will obviously change otherwise we could persist the URI even after - // reopening the app by not writing this preference when it's not null - SingletonUsbOtg.getInstance().setUsbOtgRoot(null); - } - return true; - } else { - SingletonUsbOtg.getInstance().setUsbOtgRoot(null); - return false; - } - } - @Override public void onBackPressed() { if (!drawer.isLocked() && drawer.isOpen()) { @@ -1135,28 +1118,65 @@ public class MainActivity extends PermissionsActivity implements SmbConnectionLi registerReceiver(receiver2, new IntentFilter(TAG_INTENT_FILTER_GENERAL)); if (SDK_INT >= Build.VERSION_CODES.KITKAT) { - // Registering intent filter for OTG - IntentFilter otgFilter = new IntentFilter(); - otgFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); - otgFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); - registerReceiver(mOtgReceiver, otgFilter); + updateUsbInformation(); } } + /** + * Updates everything related to USB devices + * MUST ALWAYS be called after onResume() + */ + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + private void updateUsbInformation() { + boolean isInformationUpdated = false; + List connectedDevices = OTGUtil.getMassStorageDevicesConnected(this); + + if(!connectedDevices.isEmpty()) { + if (SingletonUsbOtg.getInstance().getUsbOtgRoot() != null && OTGUtil.isUsbUriAccessible(this)) { + for (UsbOtgRepresentation device : connectedDevices) { + if (SingletonUsbOtg.getInstance().checkIfRootIsFromDevice(device)) { + isInformationUpdated = true; + break; + } + } + + if(!isInformationUpdated) { + SingletonUsbOtg.getInstance().resetUsbOtgRoot(); + } + } + + if(!isInformationUpdated) { + SingletonUsbOtg.getInstance().setConnectedDevice(connectedDevices.get(0)); + isInformationUpdated = true; + } + } + + if(!isInformationUpdated) { + SingletonUsbOtg.getInstance().resetUsbOtgRoot(); + drawer.refreshDrawer(); + goToMain(null); + } + + // Registering intent filter for OTG + IntentFilter otgFilter = new IntentFilter(); + otgFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); + otgFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); + registerReceiver(mOtgReceiver, otgFilter); + } + /** * Receiver to check if a USB device is connected at the runtime of application - * If device is not connected at runtime (i.e. it was connected when the app was closed) - * then {@link #isUsbDeviceConnected()} method handles the connection through - * {@link #getStorageDirectories()} */ BroadcastReceiver mOtgReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) { - SingletonUsbOtg.getInstance().setUsbOtgRoot(null); + SingletonUsbOtg.getInstance().resetUsbOtgRoot(); + List connectedDevices = OTGUtil.getMassStorageDevicesConnected(MainActivity.this); + SingletonUsbOtg.getInstance().setConnectedDevice(connectedDevices.get(0)); drawer.refreshDrawer(); } else if (intent.getAction().equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) { - SingletonUsbOtg.getInstance().setUsbOtgRoot(null); + SingletonUsbOtg.getInstance().resetUsbOtgRoot(); drawer.refreshDrawer(); goToMain(null); } @@ -1594,7 +1614,7 @@ public class MainActivity extends PermissionsActivity implements SmbConnectionLi if (SDK_INT >= Build.VERSION_CODES.KITKAT) { if (intent.getAction().equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) { - SingletonUsbOtg.getInstance().setUsbOtgRoot(null); + SingletonUsbOtg.getInstance().resetUsbOtgRoot(); drawer.refreshDrawer(); } } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/SingletonUsbOtg.java b/app/src/main/java/com/amaze/filemanager/filesystem/SingletonUsbOtg.java deleted file mode 100644 index e90d708e0..000000000 --- a/app/src/main/java/com/amaze/filemanager/filesystem/SingletonUsbOtg.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.amaze.filemanager.filesystem; - -import android.net.Uri; -import android.support.annotation.Nullable; - -public class SingletonUsbOtg { - private static SingletonUsbOtg instance = null; - - public static SingletonUsbOtg getInstance() { - if(instance == null) instance = new SingletonUsbOtg(); - return instance; - } - - private Uri usbOtgRoot = null; - /** - * Indicates whether last app exit was for setting {@link #usbOtgRoot} or not - */ - private boolean hasRootBeenRequested = false; - - private SingletonUsbOtg() { } - - public void setUsbOtgRoot(@Nullable Uri root) { - usbOtgRoot = root; - } - - public @Nullable Uri getUsbOtgRoot() { - return usbOtgRoot; - } - - public void setHasRootBeenRequested(boolean hasRootBeenRequested) { - this.hasRootBeenRequested = hasRootBeenRequested; - } - - public boolean hasRootBeenRequested() { - return hasRootBeenRequested; - } - -} diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/usb/SingletonUsbOtg.java b/app/src/main/java/com/amaze/filemanager/filesystem/usb/SingletonUsbOtg.java new file mode 100644 index 000000000..4a5eb2161 --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/filesystem/usb/SingletonUsbOtg.java @@ -0,0 +1,47 @@ +package com.amaze.filemanager.filesystem.usb; + +import android.bluetooth.BluetoothClass; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +public class SingletonUsbOtg { + private static SingletonUsbOtg instance = null; + + public static SingletonUsbOtg getInstance() { + if(instance == null) instance = new SingletonUsbOtg(); + return instance; + } + + private UsbOtgRepresentation connectedDevice = null; + private @Nullable Uri usbOtgRoot; + + private SingletonUsbOtg() { } + + public void setConnectedDevice(UsbOtgRepresentation connectedDevice) { + this.connectedDevice = connectedDevice; + } + + public boolean isDeviceConnected() { + return connectedDevice != null; + } + + public void setUsbOtgRoot(@Nullable Uri root) { + if(connectedDevice == null) throw new IllegalStateException("No device connected!"); + usbOtgRoot = root; + } + + public void resetUsbOtgRoot() { + connectedDevice = null; + usbOtgRoot = null; + } + + public @Nullable Uri getUsbOtgRoot() { + return usbOtgRoot; + } + + public boolean checkIfRootIsFromDevice(@NonNull UsbOtgRepresentation device) { + return usbOtgRoot != null && connectedDevice.hashCode() == device.hashCode(); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/usb/UsbOtgRepresentation.java b/app/src/main/java/com/amaze/filemanager/filesystem/usb/UsbOtgRepresentation.java new file mode 100644 index 000000000..486eb219d --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/filesystem/usb/UsbOtgRepresentation.java @@ -0,0 +1,46 @@ +package com.amaze.filemanager.filesystem.usb; + +import android.hardware.usb.UsbInterface; +import android.support.annotation.Nullable; + +import com.amaze.filemanager.utils.Utils; + +/** + * This class replesents a usb device. + * + * @see UsbOtgRepresentation#equals(Object) + */ +public class UsbOtgRepresentation { + + public final int productID, vendorID; + public final @Nullable String serialNumber; + + public UsbOtgRepresentation(int productID, int vendorID, @Nullable String serialNumber) { + this.productID = productID; + this.vendorID = vendorID; + this.serialNumber = serialNumber; + } + + /** + * This does not ensure a device is equal to another! + * This tests parameters to know to a certain degree of certanty that a device is "similar enough" + * to another one to be the same one. + */ + @Override + public boolean equals(Object obj) { + if(!(obj instanceof UsbOtgRepresentation)) return false; + + UsbOtgRepresentation other = (UsbOtgRepresentation) obj; + return productID == other.productID && vendorID == other.vendorID + && ((serialNumber == null && other.serialNumber == null) || serialNumber.equals(other.serialNumber)); + } + + @Override + public int hashCode() { + int result = productID; + result = 37 * result + vendorID; + result = 37 * result + (serialNumber != null? serialNumber.hashCode():0); + return result; + } + +} diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java b/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java index b219a96e0..b6ad2bde5 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java @@ -56,7 +56,7 @@ import com.amaze.filemanager.activities.PreferencesActivity; import com.amaze.filemanager.database.CloudHandler; import com.amaze.filemanager.filesystem.HybridFile; import com.amaze.filemanager.filesystem.RootHelper; -import com.amaze.filemanager.filesystem.SingletonUsbOtg; +import com.amaze.filemanager.filesystem.usb.SingletonUsbOtg; import com.amaze.filemanager.fragments.AppsListFragment; import com.amaze.filemanager.fragments.CloudSheetFragment; import com.amaze.filemanager.fragments.FtpServerFragment; @@ -582,13 +582,10 @@ public class Drawer implements NavigationView.OnNavigationItemSelectedListener { && SingletonUsbOtg.getInstance().getUsbOtgRoot() == null) { MaterialDialog dialog = GeneralDialogCreation.showOtgSafExplanationDialog(mainActivity); dialog.getActionButton(DialogAction.POSITIVE).setOnClickListener((v) -> { - SingletonUsbOtg.getInstance().setHasRootBeenRequested(true); Intent safIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); mainActivity.startActivityForResult(safIntent, MainActivity.REQUEST_CODE_SAF); dialog.dismiss(); }); - - dialog.show(); } else { pendingPath = meta.path; closeIfNotLocked(); diff --git a/app/src/main/java/com/amaze/filemanager/utils/OTGUtil.java b/app/src/main/java/com/amaze/filemanager/utils/OTGUtil.java index 2fd73fae2..f68b1de69 100644 --- a/app/src/main/java/com/amaze/filemanager/utils/OTGUtil.java +++ b/app/src/main/java/com/amaze/filemanager/utils/OTGUtil.java @@ -3,18 +3,26 @@ package com.amaze.filemanager.utils; import android.content.Context; import android.hardware.usb.UsbConstants; import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbInterface; import android.hardware.usb.UsbManager; import android.net.Uri; +import android.os.Build; +import android.provider.DocumentsContract; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.RequiresApi; import android.support.v4.provider.DocumentFile; import android.util.Log; import com.amaze.filemanager.filesystem.HybridFileParcelable; import com.amaze.filemanager.filesystem.RootHelper; -import com.amaze.filemanager.filesystem.SingletonUsbOtg; +import com.amaze.filemanager.filesystem.usb.SingletonUsbOtg; +import com.amaze.filemanager.filesystem.usb.UsbOtgRepresentation; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import static android.content.Context.USB_SERVICE; @@ -108,26 +116,41 @@ public class OTGUtil { return rootUri; } + /** + * Check if the usb uri is still accessible + */ + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + public static boolean isUsbUriAccessible(Context context) { + Uri rootUriString = SingletonUsbOtg.getInstance().getUsbOtgRoot(); + return DocumentsContract.isDocumentUri(context, rootUriString); + } + /** * Checks if there is at least one USB device connected with class MASS STORAGE. */ - public static boolean isMassStorageDeviceConnected(@NonNull final Context context) { + @NonNull + public static List getMassStorageDevicesConnected(@NonNull final Context context) { UsbManager usbManager = (UsbManager) context.getSystemService(USB_SERVICE); - if(usbManager == null) return false; + if(usbManager == null) return Collections.emptyList(); HashMap devices = usbManager.getDeviceList(); + ArrayList usbOtgRepresentations = new ArrayList<>(); for (String deviceName : devices.keySet()) { UsbDevice device = devices.get(deviceName); for (int i = 0; i < device.getInterfaceCount(); i++){ - if (device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_MASS_STORAGE){ - return true; + if (device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_MASS_STORAGE) { + final @Nullable String serial = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP? device.getSerialNumber():null; + + UsbOtgRepresentation usb = new UsbOtgRepresentation(device.getProductId(), device.getVendorId(), serial); + usbOtgRepresentations.add(usb); } } } - return false; + return usbOtgRepresentations; } }