summaryrefslogtreecommitdiff
path: root/android/source/src/java/org/libreoffice/storage/external/ExtsdDocumentsProvider.java
blob: 4ce77f0e1916e9cbefb33c83e8b48a43e3889062 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
package org.libreoffice.storage.external;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.preference.PreferenceManager;
import android.support.v4.provider.DocumentFile;

import org.libreoffice.R;
import org.libreoffice.storage.DocumentProviderSettingsActivity;
import org.libreoffice.storage.IFile;

import java.io.File;
import java.net.URI;

/**
 * Implementation of IDocumentProvider for the external file system, for android 4.4+
 *
 * The DocumentFile class is required when accessing files in external storage
 * for Android 4.4+. The ExternalFile class is used to handle this.
 *
 * Android 4.4 & 5+ use different types of root directory paths,
 * 5 using a DirectoryTree Uri and 4.4 using a normal File path.
 * As such, different methods are required to obtain the rootDirectory IFile.
 * 4.4 has to guess the location of the rootDirectory as well.
 */
public class ExtsdDocumentsProvider implements IExternalDocumentProvider,
        OnSharedPreferenceChangeListener{
    private static final String LOGTAG = ExtsdDocumentsProvider.class.getSimpleName();

    private int id;
    private File cacheDir;
    private Context context;
    private String rootPathURI;

    public ExtsdDocumentsProvider(int id, Context context) {
        this.id = id;
        this.context = context;
        setupRootPathUri();
        setupCache();
    }

    private void setupRootPathUri() {
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
        String rootURIGuess = guessRootURI();
        rootPathURI = preferences.getString(
                DocumentProviderSettingsActivity.KEY_PREF_EXTERNAL_SD_PATH_URI, rootURIGuess);
    }

    //Android 4.4 specific
    @TargetApi(Build.VERSION_CODES.KITKAT)
    public String guessRootURI() {
        File[] options = context.getExternalFilesDirs(null);
        File internalSD = Environment.getExternalStorageDirectory();
        String internalSDPath = internalSD.getAbsolutePath();

        for (File option: options) {
            // Returned paths may be null if a storage device is unavailable.
            if (null == option) { continue; }

            String optionPath = option.getAbsolutePath();

            if(optionPath.contains(internalSDPath))
                return option.toURI().toString();

        }

        return "";
    }

    private void setupCache() {
        // TODO: probably we should do smarter cache management
        cacheDir = new File(context.getExternalCacheDir(), "externalFiles");
        if (cacheDir.exists()) {
            deleteRecursive(cacheDir);
        }
        cacheDir.mkdirs();
    }

    private static void deleteRecursive(File file) {
        if (file.isDirectory()) {
            for (File child : file.listFiles())
                deleteRecursive(child);
        }
        file.delete();
    }

    public File getCacheDir() {
        return cacheDir;
    }

    @Override
    public IFile getRootDirectory() {
        if(android.os.Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
            return android4RootDirectory();
        } else {
            return android5RootDirectory();
        }
    }

    private ExternalFile android4RootDirectory() {
        try{
            File f = new File(new URI(rootPathURI));
            return new ExternalFile(this, DocumentFile.fromFile(f), context);
        } catch (Exception e) {
            //invalid rootPathURI
            throw buildRuntimeExceptionForInvalidFileURI();
        }
    }

    private ExternalFile android5RootDirectory() {
        try {
            return new ExternalFile(this,
                                    DocumentFile.fromTreeUri(context, Uri.parse(rootPathURI)),
                                    context);
        } catch (Exception e) {
            //invalid rootPathURI
            throw buildRuntimeExceptionForInvalidFileURI();
        }
    }

    private RuntimeException buildRuntimeExceptionForInvalidFileURI() {
        return new RuntimeException(context.getString(R.string.ext_document_provider_error));
    }

    @Override
    public IFile createFromUri(URI javaURI) {
        //TODO: refactor when new DocumentFile API exist
        //uri must be of a DocumentFile file, not directory.
        Uri androidUri = Uri.parse(javaURI.toString());
        return new ExternalFile(this,
                                DocumentFile.fromSingleUri(context, androidUri),
                                context);
    }

    @Override
    public int getNameResource() {
        return R.string.external_sd_file_system;
    }

    @Override
    public int getId() {
        return id;
    }

    @Override
    public boolean checkProviderAvailability() {
        return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) && Environment.isExternalStorageRemovable();
    }

    @Override
    public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
        if (key.equals(DocumentProviderSettingsActivity.KEY_PREF_EXTERNAL_SD_PATH_URI)) {
            rootPathURI = preferences.getString(key, "");
        }
    }

}