File indexing completed on 2025-02-02 04:47:49

0001 /*
0002  * SPDX-FileCopyrightText: 2014 Albert Vaca Cintora <albertvaka@gmail.com>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 package org.kde.kdeconnect.Helpers;
0008 
0009 import android.content.Context;
0010 import android.net.Uri;
0011 import android.os.Environment;
0012 import android.provider.DocumentsContract;
0013 import android.util.Log;
0014 
0015 import androidx.annotation.NonNull;
0016 
0017 import org.apache.commons.io.IOUtils;
0018 import org.apache.commons.lang3.ArrayUtils;
0019 import org.apache.commons.lang3.StringUtils;
0020 
0021 import java.io.File;
0022 import java.io.FileReader;
0023 import java.util.ArrayList;
0024 import java.util.HashSet;
0025 import java.util.List;
0026 import java.util.Scanner;
0027 import java.util.StringTokenizer;
0028 import java.util.stream.Collectors;
0029 
0030 //Code from http://stackoverflow.com/questions/9340332/how-can-i-get-the-list-of-mounted-external-storage-of-android-device/19982338#19982338
0031 //modified to work on Lollipop and other devices
0032 public class StorageHelper {
0033 
0034     public static class StorageInfo {
0035 
0036         public final String path;
0037         public final boolean readonly;
0038         public final boolean removable;
0039         public final int number;
0040 
0041         public StorageInfo(String path, boolean readonly, boolean removable, int number) {
0042             this.path = path;
0043             this.readonly = readonly;
0044             this.removable = removable;
0045             this.number = number;
0046         }
0047 
0048     }
0049 
0050     /*
0051      * This function is bullshit because there is no proper way to do this in Android.
0052      * Patch after patch I'm making it even more horrible by trying to make it work for *more*
0053      * devices while trying no to break previously working ones.
0054      * If this function was a living being, it would be begging "please kill me".
0055      */
0056     public static List<StorageInfo> getStorageList() {
0057 
0058         List<StorageInfo> list = new ArrayList<>();
0059         String def_path = Environment.getExternalStorageDirectory().getPath();
0060         boolean def_path_removable = Environment.isExternalStorageRemovable();
0061         String def_path_state = Environment.getExternalStorageState();
0062         boolean def_path_available = def_path_state.equals(Environment.MEDIA_MOUNTED)
0063                 || def_path_state.equals(Environment.MEDIA_MOUNTED_READ_ONLY);
0064         boolean def_path_readonly = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED_READ_ONLY);
0065 
0066         HashSet<String> paths = new HashSet<>();
0067         int cur_removable_number = 1;
0068 
0069         if (def_path_available) {
0070             paths.add(def_path);
0071             list.add(0, new StorageInfo(def_path, def_path_readonly, def_path_removable, def_path_removable ? cur_removable_number++ : -1));
0072         }
0073 
0074         File storage = new File("/storage/");
0075         if (storage.exists() && storage.isDirectory() && storage.canRead()) {
0076             String mounts = null;
0077             try (Scanner scanner = new Scanner(new File("/proc/mounts"))) {
0078                 mounts = scanner.useDelimiter("\\A").next();
0079             } catch (Exception e) {
0080                 Log.e("StorageHelper", "Exception while getting storageList", e);
0081             }
0082 
0083             File[] dirs = storage.listFiles();
0084             for (File dir : dirs) {
0085                 //Log.e("getStorageList", "path: "+dir.getAbsolutePath());
0086                 if (dir.isDirectory() && dir.canRead() && dir.canExecute()) {
0087                     String path, path2;
0088                     path2 = dir.getAbsolutePath();
0089                     try {
0090                         //Log.e(dir.getAbsolutePath(), dir.getCanonicalPath());
0091                         path = dir.getCanonicalPath();
0092                     } catch (Exception e) {
0093                         path = path2;
0094                     }
0095                     if (!path.startsWith("/storage/emulated") || dirs.length == 1) {
0096                         if (!paths.contains(path) && !paths.contains(path2)) {
0097                             if (mounts == null || StringUtils.containsAny(mounts, path, path2)) {
0098                                 list.add(0, new StorageInfo(path, dir.canWrite(), true, cur_removable_number++));
0099                                 paths.add(path);
0100                             }
0101                         }
0102                     }
0103                 }
0104             }
0105         } else {
0106             //Legacy code for Android < 4.0 that still didn't have /storage
0107             List<String> entries = new ArrayList<>();
0108             try (FileReader fileReader = new FileReader("/proc/mounts")) {
0109                 // The reader is buffered internally, so buffering it separately is unnecessary.
0110                 final List<String> lines = IOUtils.readLines(fileReader).stream()
0111                         .filter(line -> StringUtils.containsAny(line, "vfat", "exfat", "ntfs", "/mnt"))
0112                         .collect(Collectors.toList());
0113                 for (String line : lines) {
0114                     if (line.contains("/storage/sdcard"))
0115                         entries.add(0, line);
0116                     else
0117                         entries.add(line);
0118                 }
0119             } catch (Exception e) {
0120                 Log.e("StorageHelper", "Exception", e);
0121             }
0122 
0123             for (String line : entries) {
0124                 StringTokenizer tokens = new StringTokenizer(line, " ");
0125                 tokens.nextToken(); //device
0126                 String mount_point = tokens.nextToken(); //mount point
0127                 if (paths.contains(mount_point)) {
0128                     continue;
0129                 }
0130                 tokens.nextToken(); //file system
0131                 String[] flags = tokens.nextToken().split(","); //flags
0132                 boolean readonly = ArrayUtils.contains(flags, "ro");
0133 
0134                 if (line.contains("/dev/block/vold") && !StringUtils.containsAny(line, "/mnt/secure",
0135                         "/mnt/asec", "/mnt/obb", "/dev/mapper", "tmpfs")) {
0136                     paths.add(mount_point);
0137                     list.add(new StorageInfo(mount_point, readonly, true, cur_removable_number++));
0138                 }
0139             }
0140         }
0141 
0142         return list;
0143     }
0144 
0145     /* treeUri                                                                       documentId
0146      * ==================================================================================================
0147      * content://com.android.providers.downloads.documents/tree/downloads         => downloads
0148      * content://com.android.externalstorage.documents/tree/1715-1D1F:            => 1715-1D1F:
0149      * content://com.android.externalstorage.documents/tree/1715-1D1F:My%20Photos => 1715-1D1F:My Photos
0150      * content://com.android.externalstorage.documents/tree/primary:              => primary:
0151      * content://com.android.externalstorage.documents/tree/primary:DCIM          => primary:DCIM
0152      * content://com.android.externalstorage.documents/tree/primary:Download/bla  => primary:Download/bla
0153      */
0154     public static String getDisplayName(@NonNull Context context, @NonNull Uri treeUri) {
0155         List<String> pathSegments = treeUri.getPathSegments();
0156 
0157         if (!pathSegments.get(0).equals("tree")) {
0158             throw new IllegalArgumentException("treeUri is not valid");
0159         }
0160 
0161         String documentId = DocumentsContract.getTreeDocumentId(treeUri);
0162 
0163         int colonIdx = pathSegments.get(1).indexOf(':');
0164 
0165         if (colonIdx >= 0) {
0166             String tree = pathSegments.get(1).substring(0, colonIdx + 1);
0167 
0168             if (!documentId.equals(tree)) {
0169                 return documentId.substring(tree.length());
0170             } else {
0171                 return documentId.substring(0, colonIdx);
0172             }
0173         }
0174 
0175         return documentId;
0176     }
0177 }