├── icon
├── playstore_feature.png
├── playstore_launcher.png
├── launcher.svg
└── playstore_feature.svg
├── res
├── drawable-hdpi
│ └── ic_launcher.png
├── drawable-ldpi
│ └── ic_launcher.png
├── drawable-mdpi
│ └── ic_launcher.png
├── drawable-xhdpi
│ └── ic_launcher.png
├── values-zh-rTW
│ └── strings.xml
├── values
│ └── strings.xml
├── values-cs
│ └── strings.xml
├── values-ru
│ └── strings.xml
├── values-de
│ └── strings.xml
├── values-es
│ └── strings.xml
├── values-fr
│ └── strings.xml
└── layout
│ └── main.xml
├── README.md
├── .gitignore
├── project.properties
├── ant.properties
├── proguard-project.txt
├── AndroidManifest.xml
├── src
└── com
│ └── gmail
│ └── jerickson314
│ └── sdscanner
│ ├── UIStringGenerator.java
│ ├── MainActivity.java
│ └── ScanFragment.java
└── LICENSE
/icon/playstore_feature.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jerickson314/sdscanner/HEAD/icon/playstore_feature.png
--------------------------------------------------------------------------------
/icon/playstore_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jerickson314/sdscanner/HEAD/icon/playstore_launcher.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jerickson314/sdscanner/HEAD/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/drawable-ldpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jerickson314/sdscanner/HEAD/res/drawable-ldpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jerickson314/sdscanner/HEAD/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jerickson314/sdscanner/HEAD/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | sdscanner
2 | =========
3 |
4 | SD Scanner: An app to force Android to rescan a storage medium. Uses a method compatible with 4.4.
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # built application files
2 | *.apk
3 | *.ap_
4 |
5 | # files for the dex VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # generated files
12 | bin/
13 | gen/
14 |
15 | # Local configuration file (sdk path, etc)
16 | local.properties
17 |
18 | # Eclipse project files
19 | .classpath
20 | .project
21 |
22 | # Proguard folder generated by Eclipse
23 | proguard/
24 |
25 | # Intellij project files
26 | *.iml
27 | *.ipr
28 | *.iws
29 | .idea/
30 |
--------------------------------------------------------------------------------
/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-19
15 |
--------------------------------------------------------------------------------
/ant.properties:
--------------------------------------------------------------------------------
1 | # This file is used to override default values used by the Ant build system.
2 | #
3 | # This file must be checked into Version Control Systems, as it is
4 | # integral to the build system of your project.
5 |
6 | # This file is only used by the Ant script.
7 |
8 | # You can use this to override default values such as
9 | # 'source.dir' for the location of your java source folder and
10 | # 'out.dir' for the location of your output folder.
11 |
12 | # You can also use it define how the release builds are signed by declaring
13 | # the following properties:
14 | # 'key.store' for the location of your keystore and
15 | # 'key.alias' for the name of the key to use.
16 | # The password will be asked during the build when you use the 'release' target.
17 | key.store=../my-release-key.keystore
18 | key.alias=jerickso
19 |
--------------------------------------------------------------------------------
/proguard-project.txt:
--------------------------------------------------------------------------------
1 | # To enable ProGuard in your project, edit project.properties
2 | # to define the proguard.config property as described in that file.
3 | #
4 | # Add project specific ProGuard rules here.
5 | # By default, the flags in this file are appended to flags specified
6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
7 | # You can edit the include path and order by changing the ProGuard
8 | # include property in project.properties.
9 | #
10 | # For more details, see
11 | # http://developer.android.com/guide/developing/tools/proguard.html
12 |
13 | # Add any project specific keep options here:
14 |
15 | # If your project uses WebView with JS, uncomment the following
16 | # and specify the fully qualified class name to the JavaScript interface
17 | # class:
18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
19 | # public *;
20 | #}
21 |
--------------------------------------------------------------------------------
/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/res/values-zh-rTW/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 重新掃描SD卡
4 | 重設
5 | 開始強制掃描
6 | 已檢驗
7 | 已經移除下列檔案的連結:
8 | 媒體資料庫也會被檢驗是否含有已更新或刪除之檔案。
9 | 讀取媒體資料庫時發生錯誤,可能會漏掉已更新或刪除之檔案。
10 | 讀取媒體資料庫時發生錯誤,但已經恢復正常。
11 | 讀取媒體資料庫時發生錯誤,一秒後將重新讀取...
12 | 已處理
13 | 需要重新掃描的主要路徑:
14 | 掃描完成!可以再掃描其他路徑。
15 | 掃描失敗:輸入的路徑無效。
16 | 正在建立初步的檔案清單...
17 | 正在查詢媒體資料庫...
18 | 目前尚未開始掃描。
19 | 忽略主要掃描路徑以外的所有已更新或刪除之檔案
20 | 遇到了一個錯誤,但已跳過
21 | 重新掃描SD卡
22 |
23 |
--------------------------------------------------------------------------------
/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | SD Scanner
4 | Start Rescan
5 | Examined
6 | Removed reference to
7 | Will also check existing media database for updated or deleted files.
8 | Encountered error reading media database, and might miss updated or deleted files or rescan up-to-date files.
9 | Encountered error reading media database, but recovered.
10 | Encountered error reading media database. Retrying in 1 second...
11 | Processed
12 | Path to check for new files:
13 | Completed, ready to start another scan.
14 | Scan failed: bad path specified for new file search.
15 | Preparing initial list of files...
16 | Querying database...
17 | Not yet started.
18 | Ignore updated and deleted files outside of the specified path.
19 | Encountered an error and skipping
20 | SD Scanner
21 |
22 |
--------------------------------------------------------------------------------
/res/values-cs/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | SD Scanner
4 | Spustit aktualizaci
5 | Prohledáno
6 | Odstraněn odkaz na
7 | Dojde také k ověření stávající databáze médií na aktualizované nebo smazané soubory.
8 | Zaznamenána chyba čtení databáze médií, může dojít k přeskočení aktualizovaných nebo smazaných souborů.
9 | Zaznamenána chyba čtení databáze médií, ale byla napravena.
10 | Zaznamenána chyba čtení databáze médií. Nový pokus za 1 sekundu...
11 | Zpracováno
12 | Cesta pro hledání nových souborů:
13 | Dokončeno, lze spustit další aktualizaci.
14 | Aktualizace selhala: nastavena chybná cesta pro hledání nových souborů.
15 | Příprava výchozího seznamu souborů...
16 | Dotazování databáze...
17 | Nebylo ještě spuštěno.
18 | Ignorovat aktualizované a smazané soubory vně skenovaného adresáře.
19 | Zaznamenána chyba a vynechání
20 | SD Scanner
21 |
22 |
--------------------------------------------------------------------------------
/res/values-ru/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | SD Scanner
4 | Начать сканирование
5 | Проверен
6 | Удалена ссылка на
7 | Существующая база медиаданных также будет проверена на наличие обновлённых или удалённых файлов.
8 | Ошибка чтения базы медиаданных; изменённые или удалённые файлы могут остаться незамеченными, а актуальные — проверены повторно.
9 | Ошибка чтения базы медиаданных; удалось восстановиться.
10 | Ошибка чтения базы медиаданных. Повторная попытка через 1 секунду...
11 | Обработан
12 | Путь для проверки новых файлов:
13 | Завершено, можно начать новое сканирование.
14 | Сканирование не удалось: указан некорректный путь для поиска новых файлов.
15 | Подготовка первоначального списка файлов...
16 | Запрос базы данных...
17 | Ещё не начиналось.
18 | Игнорировать обновлённые и удалённые файлы за пределами сканируемой папки.
19 | Ошибка обнаружена и пропущена
20 | SD Scanner
21 |
22 |
--------------------------------------------------------------------------------
/res/values-de/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | SD-Scanner
4 | Scan starten
5 | Untersucht:
6 | Es wird auch die existierende Mediendatenbank nach geänderten oder gelöschten Dateien durchsucht.
7 | Fehler beim Lesen der Mediendatenbank. Möglicherweise werden einige geänderte oder gelöschte Dateien ausgelassen, oder aktuelle Dateien neu gescannt.
8 | Fehler beim Lesen der Mediendatenbank, sie konnte aber wiederhergestellt werden.
9 | Fehler beim Lesen der Mediendatenbank. Erneuter Versuch in 1 Sekunde …
10 | Bearbeitet:
11 | Ort, an dem nach neuen Dateien gesucht werden soll:
12 | Scan beendet. Ein neuer Scan kann nun gestartet werden.
13 | Scan fehlgeschlagen: Ungültiger Ort für die Suche angegeben.
14 | Anfangsdateiliste wird erzeugt …
15 | Datenbank wird abgefragt …
16 | Scan noch nicht gestartet
17 | Geänderte und gelöschte Dateien außerhalb des angegebenen Ortes ignorieren
18 | Fehler aufgetreten, Ordner wird übersprungen:
19 | SD-Scanner
20 |
21 |
--------------------------------------------------------------------------------
/res/values-es/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | SD Scanner
4 | Escanear
5 | Examinado
6 | Eliminada referencia a
7 | También se comprobará si los archivos de la base de datos de medios han sido actualizados o eliminados.
8 | Se encontró un error leyendo la base de datos de medios, y se podría haber perdido información sobre archivos actualizados, eliminados o al día.
9 | Se encontró un error leyendo la base de datos de medios pero se ha recuperado.
10 | Se encontró un error leyendo la base de datos. Intentando de nuevo en 1 segundo...
11 | Procesado
12 | Ubicación para buscar archivos nuevos:
13 | Completado, listo para iniciar otro escaneo.
14 | El escaneo de archivos falló: error en la ubicación especificada para buscar archivos nuevos.
15 | Preparando lista inicial de archivos...
16 | Consultado la base de datos...
17 | Aun no iniciado.
18 | Ignorar archivos actualizados o eliminados fuera del directorio especificado.
19 | Se encontró un error y se ignora
20 | SD Scanner
21 |
22 |
--------------------------------------------------------------------------------
/res/values-fr/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | SD Scanner
4 | Démarrer un scan
5 | Examine
6 | Retirer du référencement
7 | Vérifiera également les bases de données multimédias existantes pour rechercher les fichiers mis à jour ou supprimés.
8 | Erreur lors de la lecture de la base de données multimédia, des fichiers mis à jour ou supprimés peuvent manquer ou rescanner les fichiers à jour.
9 | Erreur lors de la lecture de la base de données multimédia, mais elle a été corrigée.
10 | Erreur lors de la lecture de la base de données multimédia. Nouvel essai dans 1 seconde...
11 | Traîte
12 | Dossier où vérifier les nouveaux fichiers:
13 | Terminé, prêt à démarrer un nouveau scan.
14 | Le scan a échoué: dossier de recherche invalide.
15 | Préparation de la liste initiale des fichiers...
16 | Accès à la base de données...
17 | Pas encore démarré.
18 | Ignorer les fichiers mis à jour et supprimés en dehors du chemin spécifié.
19 | Passage au suivant suite à une erreur
20 | SD Scanner
21 |
22 |
--------------------------------------------------------------------------------
/src/com/gmail/jerickson314/sdscanner/UIStringGenerator.java:
--------------------------------------------------------------------------------
1 | /* SD Scanner - A manual implementation of the SD rescan process, compatible
2 | * with Android 4.4
3 | *
4 | * Copyright (C) 2013-2014 Jeremy Erickson
5 | *
6 | * This program is free software; you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation; either version 2 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | */
16 |
17 | package com.gmail.jerickson314.sdscanner;
18 |
19 | import android.app.Activity;
20 | import java.util.ArrayList;
21 | import java.util.Iterator;
22 |
23 | public class UIStringGenerator {
24 | private static interface SubGenerator {
25 | public String toString(Activity activity);
26 | }
27 |
28 | private static class ResourceSubGenerator implements SubGenerator {
29 | int mResId;
30 |
31 | public ResourceSubGenerator(int resId) {
32 | mResId = resId;
33 | }
34 |
35 | public String toString(Activity activity) {
36 | return activity.getString(mResId);
37 | }
38 | }
39 |
40 | private static class StringSubGenerator implements SubGenerator {
41 | String mString;
42 |
43 | public StringSubGenerator(String string) {
44 | mString = string;
45 | }
46 |
47 | public String toString(Activity activity) {
48 | return mString;
49 | }
50 | }
51 |
52 | ArrayList mSubGenerators = new ArrayList();
53 |
54 | public void addSubGenerator(int resId) {
55 | mSubGenerators.add(new ResourceSubGenerator(resId));
56 | }
57 |
58 | public void addSubGenerator(String string) {
59 | mSubGenerators.add(new StringSubGenerator(string));
60 | }
61 |
62 | public UIStringGenerator(int resId) {
63 | addSubGenerator(resId);
64 | }
65 |
66 | public UIStringGenerator(int resId, String string) {
67 | addSubGenerator(resId);
68 | addSubGenerator(string);
69 | }
70 |
71 | public UIStringGenerator(String string) {
72 | addSubGenerator(string);
73 | }
74 |
75 | public UIStringGenerator() {
76 | // Doing nothing results in empty string.
77 | }
78 |
79 | public String toString(Activity activity) {
80 | StringBuilder toReturn = new StringBuilder();
81 | Iterator iterator = mSubGenerators.iterator();
82 | while (iterator.hasNext()) {
83 | toReturn.append(iterator.next().toString(activity));
84 | }
85 | return toReturn.toString();
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/res/layout/main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
15 |
20 |
25 |
31 |
32 |
39 |
44 |
53 |
61 |
67 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/src/com/gmail/jerickson314/sdscanner/MainActivity.java:
--------------------------------------------------------------------------------
1 | /* SD Scanner - A manual implementation of the SD rescan process, compatible
2 | * with Android 4.4
3 | *
4 | * Copyright (C) 2013-2014 Jeremy Erickson
5 | *
6 | * This program is free software; you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation; either version 2 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | */
16 |
17 | package com.gmail.jerickson314.sdscanner;
18 |
19 | import android.app.Activity;
20 | import android.app.FragmentManager;
21 | import android.content.Context;
22 | import android.content.Intent;
23 | import android.content.SharedPreferences;
24 | import android.net.Uri;
25 | import android.provider.MediaStore;
26 | import android.os.Bundle;
27 | import android.os.Environment;
28 | import android.text.method.ScrollingMovementMethod;
29 | import android.view.View;
30 | import android.widget.Button;
31 | import android.widget.CheckBox;
32 | import android.widget.EditText;
33 | import android.widget.ProgressBar;
34 | import android.widget.TextView;
35 |
36 | import java.io.File;
37 | import java.io.IOException;
38 |
39 | public class MainActivity extends Activity
40 | implements ScanFragment.ScanProgressCallbacks
41 | {
42 | ScanFragment mScanFragment;
43 |
44 | @Override
45 | public void updateProgressNum(int progressNum) {
46 | ProgressBar progressBar = (ProgressBar)findViewById(R.id.progress_bar);
47 | progressBar.setProgress(progressNum);
48 | }
49 |
50 | @Override
51 | public void updateProgressText(UIStringGenerator progressText) {
52 | TextView progressLabel = (TextView)findViewById(R.id.progress_label);
53 | progressLabel.setText(progressText.toString(this));
54 | }
55 |
56 | @Override
57 | public void updateDebugMessages(UIStringGenerator debugMessages) {
58 | TextView debugLabel = (TextView)findViewById(R.id.debug_label);
59 | debugLabel.setText(debugMessages.toString(this));
60 | }
61 |
62 | @Override
63 | public void updatePath(String path) {
64 | EditText pathText = (EditText) findViewById(R.id.path_widget);
65 | pathText.setText(path);
66 | }
67 |
68 | @Override
69 | public void updateStartButtonEnabled(boolean startButtonEnabled) {
70 | Button startButton = (Button)findViewById(R.id.start_button);
71 | startButton.setEnabled(startButtonEnabled);
72 | }
73 |
74 | public void updateRestrictCheckboxChecked(boolean checked) {
75 | CheckBox restrictCheckbox = (CheckBox) findViewById(R.id.restrict_checkbox);
76 | restrictCheckbox.setChecked(checked);
77 | }
78 |
79 | @Override
80 | public void signalFinished() {
81 | if (getIntent().getAction().equals(Intent.ACTION_RUN)) {
82 | finish();
83 | }
84 | }
85 |
86 | /** Called when the activity is first created. */
87 | @Override
88 | public void onCreate(Bundle savedInstanceState)
89 | {
90 | super.onCreate(savedInstanceState);
91 | setContentView(R.layout.main);
92 |
93 | FragmentManager fm = getFragmentManager();
94 | mScanFragment = (ScanFragment) fm.findFragmentByTag("scan");
95 |
96 | if (mScanFragment == null) {
97 | mScanFragment = new ScanFragment();
98 | fm.beginTransaction().add(mScanFragment, "scan").commit();
99 | }
100 |
101 | // Setup with values from fragment.
102 | updateProgressNum(mScanFragment.getProgressNum());
103 | updateProgressText(mScanFragment.getProgressText());
104 | updateDebugMessages(mScanFragment.getDebugMessages());
105 | updateStartButtonEnabled(mScanFragment.getStartButtonEnabled());
106 |
107 | // Update path from preferences
108 | SharedPreferences preferences = getPreferences(Context.MODE_PRIVATE);
109 | try {
110 | updatePath(preferences.getString("path",
111 | Environment.getExternalStorageDirectory().getCanonicalPath()));
112 | updateRestrictCheckboxChecked(preferences.getBoolean(
113 | "restrict_db_scan", false));
114 | }
115 | catch (IOException Ex) {
116 | // Should never happen, but getCanonicalPath() declares the throw.
117 | updatePath("");
118 | updateRestrictCheckboxChecked(false);
119 | }
120 |
121 | // Make debug output scrollable.
122 | TextView debugLabel = (TextView)findViewById(R.id.debug_label);
123 | debugLabel.setMovementMethod(new ScrollingMovementMethod());
124 | }
125 |
126 | @Override
127 | public void onStart() {
128 | super.onStart();
129 |
130 | if (!mScanFragment.getHasStarted() &&
131 | getIntent().getAction().equals(Intent.ACTION_RUN)) {
132 | try {
133 | startScan();
134 | }
135 | catch (IOException ex) {
136 | // We currently do nothing.
137 | }
138 | }
139 | }
140 |
141 | @Override
142 | public void onStop() {
143 | super.onStop();
144 |
145 | // Write setting to preferences
146 | EditText pathText = (EditText) findViewById(R.id.path_widget);
147 | CheckBox restrictCheckbox = (CheckBox) findViewById(R.id.restrict_checkbox);
148 |
149 | SharedPreferences preferences = getPreferences(Context.MODE_PRIVATE);
150 | SharedPreferences.Editor editor = preferences.edit();
151 | editor.putString("path", pathText.getText().toString());
152 | editor.putBoolean("restrict_db_scan", restrictCheckbox.isChecked());
153 | editor.commit();
154 | }
155 |
156 | public void defaultButtonPressed(View view) throws IOException {
157 | updatePath(Environment.getExternalStorageDirectory().getCanonicalPath());
158 | }
159 |
160 | public void startButtonPressed(View view) throws IOException {
161 | startScan();
162 | }
163 |
164 | public void startScan() throws IOException {
165 | EditText pathText = (EditText) findViewById(R.id.path_widget);
166 | File path = new File(pathText.getText().toString());
167 | CheckBox restrictCheckbox = (CheckBox) findViewById(R.id.restrict_checkbox);
168 |
169 | mScanFragment.startScan(path.getCanonicalFile(), restrictCheckbox.isChecked());
170 | }
171 |
172 | }
173 |
--------------------------------------------------------------------------------
/icon/launcher.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
207 |
--------------------------------------------------------------------------------
/icon/playstore_feature.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
219 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 | {description}
294 | Copyright (C) {year} {fullname}
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License along
307 | with this program; if not, write to the Free Software Foundation, Inc.,
308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309 |
310 | Also add information on how to contact you by electronic and paper mail.
311 |
312 | If the program is interactive, make it output a short notice like this
313 | when it starts in an interactive mode:
314 |
315 | Gnomovision version 69, Copyright (C) year name of author
316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 | This is free software, and you are welcome to redistribute it
318 | under certain conditions; type `show c' for details.
319 |
320 | The hypothetical commands `show w' and `show c' should show the appropriate
321 | parts of the General Public License. Of course, the commands you use may
322 | be called something other than `show w' and `show c'; they could even be
323 | mouse-clicks or menu items--whatever suits your program.
324 |
325 | You should also get your employer (if you work as a programmer) or your
326 | school, if any, to sign a "copyright disclaimer" for the program, if
327 | necessary. Here is a sample; alter the names:
328 |
329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
331 |
332 | {signature of Ty Coon}, 1 April 1989
333 | Ty Coon, President of Vice
334 |
335 | This General Public License does not permit incorporating your program into
336 | proprietary programs. If your program is a subroutine library, you may
337 | consider it more useful to permit linking proprietary applications with the
338 | library. If this is what you want to do, use the GNU Lesser General
339 | Public License instead of this License.
340 |
--------------------------------------------------------------------------------
/src/com/gmail/jerickson314/sdscanner/ScanFragment.java:
--------------------------------------------------------------------------------
1 | /* SD Scanner - A manual implementation of the SD rescan process, compatible
2 | * with Android 4.4.
3 | *
4 | * This file contains the fragment that actually performs all scan activity
5 | * and retains state across configuration changes.
6 | *
7 | * Copyright (C) 2013-2014 Jeremy Erickson
8 | *
9 | * This program is free software; you can redistribute it and/or modify
10 | * it under the terms of the GNU General Public License as published by
11 | * the Free Software Foundation; either version 2 of the License, or
12 | * (at your option) any later version.
13 | *
14 | * This program is distributed in the hope that it will be useful,
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | * GNU General Public License for more details.
18 | */
19 |
20 | package com.gmail.jerickson314.sdscanner;
21 |
22 | import android.app.Activity;
23 | import android.app.Fragment;
24 | import android.content.ContentUris;
25 | import android.content.Context;
26 | import android.content.SharedPreferences;
27 | import android.database.Cursor;
28 | import android.database.DatabaseUtils;
29 | import android.media.MediaScannerConnection;
30 | import android.net.Uri;
31 | import android.os.AsyncTask;
32 | import android.os.Bundle;
33 | import android.os.Environment;
34 | import android.os.Handler;
35 | import android.os.SystemClock;
36 | import android.provider.MediaStore;
37 | import android.util.Log;
38 |
39 | import java.io.File;
40 | import java.io.IOException;
41 | import java.util.ArrayList;
42 | import java.util.Date;
43 | import java.util.HashSet;
44 | import java.util.Iterator;
45 | import java.util.TreeSet;
46 |
47 | public class ScanFragment extends Fragment {
48 |
49 | private static final String[] MEDIA_PROJECTION =
50 | {MediaStore.MediaColumns.DATA,
51 | MediaStore.MediaColumns.DATE_MODIFIED};
52 |
53 | private static final String[] STAR = {"*"};
54 |
55 | private static final int DB_RETRIES = 3;
56 |
57 | Context mApplicationContext;
58 |
59 | ArrayList mPathNames;
60 | TreeSet mFilesToProcess;
61 | int mLastGoodProcessedIndex;
62 |
63 | private Handler mHandler = new Handler();
64 |
65 | int mProgressNum;
66 | UIStringGenerator mProgressText =
67 | new UIStringGenerator(R.string.progress_unstarted_label);
68 | UIStringGenerator mDebugMessages = new UIStringGenerator();
69 | boolean mStartButtonEnabled;
70 | boolean mHasStarted = false;
71 |
72 | /**
73 | * Callback interface used by the fragment to update the Activity.
74 | */
75 | static interface ScanProgressCallbacks {
76 | void updateProgressNum(int progressNum);
77 | void updateProgressText(UIStringGenerator progressText);
78 | void updateDebugMessages(UIStringGenerator debugMessages);
79 | void updatePath(String path);
80 | void updateStartButtonEnabled(boolean startButtonEnabled);
81 | void signalFinished();
82 | }
83 |
84 | private ScanProgressCallbacks mCallbacks;
85 |
86 | private void updateProgressNum(int progressNum) {
87 | mProgressNum = progressNum;
88 | if (mCallbacks != null) {
89 | mCallbacks.updateProgressNum(mProgressNum);
90 | }
91 | }
92 |
93 | private void updateProgressText(int resId) {
94 | updateProgressText(new UIStringGenerator(resId));
95 | }
96 |
97 | private void updateProgressText(int resId, String string) {
98 | updateProgressText(new UIStringGenerator(resId, string));
99 | }
100 |
101 | private void updateProgressText(UIStringGenerator progressText) {
102 | mProgressText = progressText;
103 | if (mCallbacks != null) {
104 | mCallbacks.updateProgressText(mProgressText);
105 | }
106 | }
107 |
108 | private void addDebugMessage(int resId, String string) {
109 | mDebugMessages.addSubGenerator(resId);
110 | mDebugMessages.addSubGenerator(string + "\n");
111 | if (mCallbacks != null) {
112 | mCallbacks.updateDebugMessages(mDebugMessages);
113 | }
114 | }
115 |
116 | private void addDebugMessage(String debugMessage) {
117 | mDebugMessages.addSubGenerator(debugMessage + "\n");
118 | if (mCallbacks != null) {
119 | mCallbacks.updateDebugMessages(mDebugMessages);
120 | }
121 | }
122 |
123 | private void resetDebugMessages() {
124 | mDebugMessages = new UIStringGenerator();
125 | if (mCallbacks != null) {
126 | mCallbacks.updateDebugMessages(mDebugMessages);
127 | }
128 | }
129 |
130 | private void updateStartButtonEnabled(boolean startButtonEnabled) {
131 | mStartButtonEnabled = startButtonEnabled;
132 | if (mCallbacks != null) {
133 | mCallbacks.updateStartButtonEnabled(mStartButtonEnabled);
134 | }
135 | }
136 |
137 | private void signalFinished() {
138 | if (mCallbacks != null) {
139 | mCallbacks.signalFinished();
140 | }
141 | }
142 |
143 | public int getProgressNum() {
144 | return mProgressNum;
145 | }
146 |
147 | public UIStringGenerator getProgressText() {
148 | return mProgressText;
149 | }
150 |
151 | public UIStringGenerator getDebugMessages() {
152 | return mDebugMessages;
153 | }
154 |
155 | public boolean getStartButtonEnabled() {
156 | return mStartButtonEnabled;
157 | }
158 |
159 | public boolean getHasStarted() {
160 | return mHasStarted;
161 | }
162 |
163 | @Override
164 | public void onAttach(Activity activity) {
165 | super.onAttach(activity);
166 | mCallbacks = (ScanProgressCallbacks) activity;
167 | mApplicationContext = activity.getApplicationContext();
168 | }
169 |
170 | public ScanFragment() {
171 | super();
172 |
173 | // Set correct initial values.
174 | mProgressNum = 0;
175 | mStartButtonEnabled = true;
176 | }
177 |
178 | @Override
179 | public void onCreate(Bundle savedInstanceState) {
180 | super.onCreate(savedInstanceState);
181 |
182 | // Retain this fragment across configuration changes.
183 | setRetainInstance(true);
184 | }
185 |
186 | // Purely for debugging and not normally used, so does not translate
187 | // strings.
188 | public void listPathNamesOnDebug() {
189 | StringBuffer listString = new StringBuffer();
190 | listString.append("\n\nScanning paths:\n");
191 | Iterator iterator = mPathNames.iterator();
192 | while (iterator.hasNext()) {
193 | listString.append(iterator.next() + "\n");
194 | }
195 | addDebugMessage(listString.toString());
196 | }
197 |
198 | public void scannerEnded() {
199 | updateProgressNum(0);
200 | updateProgressText(R.string.progress_completed_label);
201 | updateStartButtonEnabled(true);
202 | signalFinished();
203 | }
204 |
205 | public void startMediaScanner(){
206 | //listPathNamesOnDebug();
207 | if (mPathNames.size() == 0) {
208 | scannerEnded();
209 | }
210 | else {
211 | MediaScannerConnection.scanFile(
212 | mApplicationContext,
213 | mPathNames.toArray(new String[mPathNames.size()]),
214 | null,
215 | new MediaScannerConnection.OnScanCompletedListener() {
216 | public void onScanCompleted(String path, Uri uri) {
217 | mHandler.post(new Updater(path));
218 | }
219 | });
220 | }
221 | }
222 |
223 | public void startScan(File path, boolean restrictDbUpdate) {
224 | mHasStarted = true;
225 | updateStartButtonEnabled(false);
226 | updateProgressText(R.string.progress_filelist_label);
227 | mFilesToProcess = new TreeSet();
228 | resetDebugMessages();
229 | if (path.exists()) {
230 | this.new PreprocessTask().execute(new ScanParameters(path, restrictDbUpdate));
231 | }
232 | else {
233 | updateProgressText(R.string.progress_error_bad_path_label);
234 | updateStartButtonEnabled(true);
235 | signalFinished();
236 | }
237 | }
238 |
239 | static class ProgressUpdate {
240 | public enum Type {
241 | DATABASE, STATE, DEBUG
242 | }
243 |
244 | Type mType;
245 |
246 | public Type getType() {
247 | return mType;
248 | }
249 |
250 | int mResId;
251 |
252 | public int getResId() {
253 | return mResId;
254 | }
255 |
256 | String mString;
257 |
258 | public String getString() {
259 | return mString;
260 | }
261 |
262 | int mProgress;
263 |
264 | public int getProgress() {
265 | return mProgress;
266 | }
267 |
268 | public ProgressUpdate(Type type, int resId, String string,
269 | int progress) {
270 | mType = type;
271 | mResId = resId;
272 | mString = string;
273 | mProgress = progress;
274 | }
275 | }
276 |
277 | static ProgressUpdate debugUpdate(int resId, String string) {
278 | return new ProgressUpdate(ProgressUpdate.Type.DEBUG, resId, string, 0);
279 | }
280 |
281 | static ProgressUpdate debugUpdate(int resId) {
282 | return debugUpdate(resId, "");
283 | }
284 |
285 | static ProgressUpdate databaseUpdate(String file, int progress) {
286 | return new ProgressUpdate(ProgressUpdate.Type.DATABASE, 0, file,
287 | progress);
288 | }
289 |
290 | static ProgressUpdate stateUpdate(int resId) {
291 | return new ProgressUpdate(ProgressUpdate.Type.STATE, resId, "", 0);
292 | }
293 |
294 | static class ScanParameters {
295 | File mPath;
296 | boolean mRestrictDbUpdate;
297 |
298 | public ScanParameters(File path, boolean restrictDbUpdate) {
299 | mPath = path;
300 | mRestrictDbUpdate = restrictDbUpdate;
301 | }
302 |
303 | public File getPath() {
304 | return mPath;
305 | }
306 |
307 | public boolean shouldScan(File file, boolean fromDb)
308 | throws IOException {
309 | // Empty directory check.
310 | if (file.isDirectory()) {
311 | File[] files = file.listFiles();
312 | if (files == null || files.length == 0) {
313 | Log.w("SDScanner", "Scan of empty directory " +
314 | file.getCanonicalPath() + " skipped to avoid bug.");
315 | return false;
316 | }
317 | }
318 | if (!mRestrictDbUpdate && fromDb) {
319 | return true;
320 | }
321 | while (file != null) {
322 | if (file.equals(mPath)) {
323 | return true;
324 | }
325 | file = file.getParentFile();
326 | }
327 | // If we fell through here, got up to root without encountering the
328 | // path to scan.
329 | if (!fromDb) {
330 | Log.w("SDScanner", "File " + file.getCanonicalPath() +
331 | " outside of scan directory skipped.");
332 | }
333 | return false;
334 | }
335 | }
336 |
337 | class PreprocessTask extends AsyncTask {
338 |
339 | private void recursiveAddFiles(File file, ScanParameters scanParameters)
340 | throws IOException {
341 | if (!scanParameters.shouldScan(file, false)) {
342 | // If we got here, there file was either outside the scan
343 | // directory, or was an empty directory.
344 | return;
345 | }
346 | if (!mFilesToProcess.add(file)) {
347 | // Avoid infinite recursion caused by symlinks.
348 | // If mFilesToProcess already contains this file, add() will
349 | // return false.
350 | return;
351 | }
352 | if (file.isDirectory()) {
353 | boolean nomedia = new File(file, ".nomedia").exists();
354 | // Only recurse downward if not blocked by nomedia.
355 | if (!nomedia) {
356 | File[] files = file.listFiles();
357 | if (files != null) {
358 | for (File nextFile : files) {
359 | recursiveAddFiles(nextFile.getCanonicalFile(),
360 | scanParameters);
361 | }
362 | }
363 | else {
364 | publishProgress(debugUpdate(
365 | R.string.skipping_folder_label,
366 | " " + file.getPath()));
367 | }
368 | }
369 | }
370 | }
371 |
372 | protected void dbOneTry(ScanParameters parameters) {
373 | Cursor cursor = mApplicationContext.getContentResolver().query(
374 | MediaStore.Files.getContentUri("external"),
375 | MEDIA_PROJECTION,
376 | //STAR,
377 | null,
378 | null,
379 | null);
380 | int data_column =
381 | cursor.getColumnIndex(MediaStore.MediaColumns.DATA);
382 | int modified_column =
383 | cursor.getColumnIndex(MediaStore.MediaColumns.DATE_MODIFIED);
384 | int totalSize = cursor.getCount();
385 | int currentItem = 0;
386 | int reportFreq = 0;
387 | // Used to calibrate reporting frequency
388 | long startTime = SystemClock.currentThreadTimeMillis();
389 | while (cursor.moveToNext()) {
390 | currentItem++;
391 | try {
392 | File file = new File(cursor.getString(data_column)).getCanonicalFile();
393 | if ((!file.exists() ||
394 | file.lastModified() / 1000L >
395 | cursor.getLong(modified_column))
396 | && parameters.shouldScan(file, true)) {
397 | // Media scanner handles these cases.
398 | // Is a set, so OK if already present.
399 | mFilesToProcess.add(file);
400 | }
401 | else {
402 | // Don't want to waste time scanning an up-to-date
403 | // file.
404 | mFilesToProcess.remove(file);
405 | }
406 | if (reportFreq == 0) {
407 | // Calibration phase
408 | if (SystemClock.currentThreadTimeMillis() - startTime > 25) {
409 | reportFreq = currentItem + 1;
410 | }
411 | }
412 | else if (currentItem % reportFreq == 0) {
413 | publishProgress(databaseUpdate(file.getPath(),
414 | (100 * currentItem) / totalSize));
415 | }
416 | }
417 | catch (IOException ex) {
418 | // Just ignore it for now.
419 | }
420 | }
421 | // Don't need the cursor any more.
422 | cursor.close();
423 | }
424 |
425 | @Override
426 | protected Void doInBackground(ScanParameters... parameters) {
427 | try {
428 | recursiveAddFiles(parameters[0].getPath(), parameters[0]);
429 | }
430 | catch (IOException Ex) {
431 | // Do nothing.
432 | }
433 | // Parse database
434 | publishProgress(stateUpdate(R.string.progress_database_label));
435 | boolean dbSuccess = false;
436 | int numRetries = 0;
437 | while (!dbSuccess && numRetries < DB_RETRIES) {
438 | dbSuccess = true;
439 | try {
440 | dbOneTry(parameters[0]);
441 | }
442 | catch (Exception Ex) {
443 | // For any of these errors, try again.
444 | numRetries++;
445 | dbSuccess = false;
446 | if (numRetries < DB_RETRIES) {
447 | publishProgress(stateUpdate(
448 | R.string.db_error_retrying));
449 | SystemClock.sleep(1000);
450 | }
451 | }
452 | }
453 | if (numRetries > 0) {
454 | if (dbSuccess) {
455 | publishProgress(debugUpdate(R.string.db_error_recovered));
456 | }
457 | else {
458 | publishProgress(debugUpdate(R.string.db_error_failure));
459 | }
460 | }
461 | // Prepare final path list for processing.
462 | mPathNames = new ArrayList(mFilesToProcess.size());
463 | Iterator iterator = mFilesToProcess.iterator();
464 | while (iterator.hasNext()) {
465 | mPathNames.add(iterator.next().getPath());
466 | }
467 | mLastGoodProcessedIndex = -1;
468 |
469 | return null;
470 | }
471 |
472 | @Override
473 | protected void onProgressUpdate(ProgressUpdate... progress) {
474 | switch (progress[0].getType()) {
475 | case DATABASE:
476 | updateProgressText(R.string.database_proc,
477 | " " + progress[0].getString());
478 | updateProgressNum(progress[0].getProgress());
479 | break;
480 | case STATE:
481 | updateProgressText(progress[0].getResId());
482 | updateProgressNum(0);
483 | break;
484 | case DEBUG:
485 | addDebugMessage(progress[0].getResId(), progress[0].getString());
486 | }
487 | }
488 |
489 | @Override
490 | protected void onPostExecute(Void result) {
491 | startMediaScanner();
492 | }
493 | }
494 |
495 | class Updater implements Runnable {
496 | String mPathScanned;
497 |
498 | public Updater(String path) {
499 | mPathScanned = path;
500 | }
501 |
502 | public void run() {
503 | if (mLastGoodProcessedIndex + 1 < mPathNames.size() &&
504 | mPathNames.get(mLastGoodProcessedIndex
505 | + 1).equals(mPathScanned)) {
506 | mLastGoodProcessedIndex++;
507 | }
508 | else {
509 | int newIndex = mPathNames.indexOf(mPathScanned);
510 | if (newIndex > -1) {
511 | mLastGoodProcessedIndex = newIndex;
512 | }
513 | }
514 | int progress = (100 * (mLastGoodProcessedIndex + 1))
515 | / mPathNames.size();
516 | if (progress == 100) {
517 | scannerEnded();
518 | }
519 | else {
520 | updateProgressNum(progress);
521 | updateProgressText(R.string.final_proc, " " + mPathScanned);
522 | }
523 | }
524 | }
525 | }
526 |
--------------------------------------------------------------------------------