Compare commits
18 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
0ba6299990 | ||
![]() |
bc6c4fbc80 | ||
![]() |
377b57f985 | ||
![]() |
aab60aec4e | ||
![]() |
4eb37b0d33 | ||
![]() |
6b26eb661f | ||
![]() |
46fc6dbbfd | ||
![]() |
4f420c55d0 | ||
![]() |
9ded5eb974 | ||
![]() |
2a7a8ac38b | ||
![]() |
f5065c93a8 | ||
![]() |
7b802eaf2f | ||
![]() |
4090132d84 | ||
![]() |
242cdb5dd4 | ||
![]() |
1fdb573774 | ||
![]() |
88ee9849f7 | ||
![]() |
94403d1d7e | ||
![]() |
ff6a60ae26 |
@ -1,4 +1,4 @@
|
||||
image: fdroid/ci:client-20161023
|
||||
image: registry.gitlab.com/fdroid/ci-images:client
|
||||
|
||||
cache:
|
||||
paths:
|
||||
@ -7,13 +7,21 @@ cache:
|
||||
|
||||
before_script:
|
||||
- export GRADLE_USER_HOME=$PWD/.gradle
|
||||
- export ANDROID_COMPILE_SDK=`sed -n 's,.*compileSdkVersion\s*\([0-9][0-9]*\).*,\1,p' app/build.gradle`
|
||||
- echo y | android --silent update sdk --no-ui --filter android-${ANDROID_COMPILE_SDK}
|
||||
|
||||
test:
|
||||
script:
|
||||
- cd app
|
||||
- ./tools/langs-list-check.py
|
||||
- ./tools/check-string-format.py
|
||||
- cd ..
|
||||
- ./gradlew assemble -PdisablePreDex
|
||||
# always report on lint errors to the build log
|
||||
- sed -i -e 's,textReport .*,textReport true,' app/build.gradle
|
||||
- ./gradlew lint -PdisablePreDex
|
||||
- ./gradlew pmd -PdisablePreDex
|
||||
- ./gradlew checkstyle -PdisablePreDex
|
||||
- ./gradlew test -PdisablePreDex || {
|
||||
for log in app/build/reports/*ests/*/*ml; do
|
||||
echo "read $log here:";
|
||||
@ -26,6 +34,7 @@ connected10:
|
||||
variables:
|
||||
AVD_SDK: "10"
|
||||
script:
|
||||
- ./gradlew assembleDebug -PdisablePreDex
|
||||
- emulator64-arm -avd fcl-test-$AVD_SDK -no-skin -no-audio -no-window &
|
||||
- ./tools/wait-for-emulator
|
||||
- adb shell input keyevent 82 &
|
||||
@ -47,7 +56,8 @@ connected24:
|
||||
variables:
|
||||
AVD_SDK: "24"
|
||||
script:
|
||||
- emulator64-x86 -avd fcl-test-$AVD_SDK -no-skin -no-audio -no-window &
|
||||
- ./gradlew assembleDebug -PdisablePreDex
|
||||
- emulator64-arm -avd fcl-test-$AVD_SDK -no-audio -no-window &
|
||||
- ./tools/wait-for-emulator
|
||||
- adb shell input keyevent 82 &
|
||||
- export EXITVALUE=0
|
||||
@ -64,24 +74,6 @@ connected24:
|
||||
done
|
||||
- exit $EXITVALUE
|
||||
|
||||
pmd:
|
||||
script:
|
||||
- ./gradlew pmd -PdisablePreDex
|
||||
|
||||
checkstyle:
|
||||
script:
|
||||
- ./gradlew checkstyle -PdisablePreDex
|
||||
|
||||
tools:
|
||||
before_script:
|
||||
- echo "ignored, no gradle needed"
|
||||
script:
|
||||
- cd app
|
||||
- ./tools/langs-list-check.py
|
||||
- ./tools/check-string-format.py
|
||||
after_script:
|
||||
- echo "ignored, no gradle needed"
|
||||
|
||||
after_script:
|
||||
# this file changes every time but should not be cached
|
||||
- rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock
|
||||
|
@ -170,7 +170,7 @@ android {
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
versionCode 102050
|
||||
versionCode 102350
|
||||
versionName getVersionName()
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
@ -0,0 +1,100 @@
|
||||
/*
|
||||
* *
|
||||
* * This file is part of QuickLyric
|
||||
* * Created by geecko
|
||||
* *
|
||||
* * QuickLyric is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * QuickLyric is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with QuickLyric. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.geecko.QuickLyric.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.app.AppCompatDialog;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class AppCompatListPreference extends ListPreference {
|
||||
|
||||
private AppCompatDialog mDialog;
|
||||
|
||||
public AppCompatListPreference(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public AppCompatListPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AppCompatDialog getDialog() {
|
||||
return mDialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void showDialog(Bundle state) {
|
||||
if (getEntries() == null || getEntryValues() == null) {
|
||||
throw new IllegalStateException(
|
||||
"ListPreference requires an entries array and an entryValues array.");
|
||||
}
|
||||
|
||||
int preselect = findIndexOfValue(getValue());
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getContext())
|
||||
.setTitle(getDialogTitle())
|
||||
.setIcon(getDialogIcon())
|
||||
.setSingleChoiceItems(getEntries(), preselect, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (which >= 0 && getEntryValues() != null) {
|
||||
String value = getEntryValues()[which].toString();
|
||||
if (callChangeListener(value) && isPersistent()) {
|
||||
setValue(value);
|
||||
}
|
||||
}
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
PreferenceManager pm = getPreferenceManager();
|
||||
try {
|
||||
Method method = pm.getClass().getDeclaredMethod(
|
||||
"registerOnActivityDestroyListener",
|
||||
PreferenceManager.OnActivityDestroyListener.class);
|
||||
method.setAccessible(true);
|
||||
method.invoke(pm, this);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
mDialog = builder.create();
|
||||
if (state != null) {
|
||||
mDialog.onRestoreInstanceState(state);
|
||||
}
|
||||
mDialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityDestroy() {
|
||||
super.onActivityDestroy();
|
||||
if (mDialog != null && mDialog.isShowing() &&
|
||||
mDialog.getWindow() != null && mDialog.getWindow().getWindowManager() != null) {
|
||||
mDialog.dismiss();
|
||||
}
|
||||
}
|
||||
}
|
@ -432,6 +432,15 @@ public class AppDetails extends AppCompatActivity {
|
||||
myAppObserver);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
App newApp = AppProvider.Helper.findHighestPriorityMetadata(getContentResolver(), app.packageName);
|
||||
if (newApp.isInstalled() != app.isInstalled()) {
|
||||
setApp(newApp);
|
||||
}
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResumeFragments() {
|
||||
// Must be called before super.onResumeFragments(), as the fragments depend on the active
|
||||
@ -993,7 +1002,7 @@ public class AppDetails extends AppCompatActivity {
|
||||
return apk;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
throw new IllegalStateException("Couldn't find app while installing");
|
||||
throw new IllegalStateException("Couldn't find installed apk for " + app.packageName, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,13 +22,13 @@ import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import org.fdroid.fdroid.views.fragments.PreferencesFragment;
|
||||
|
||||
public class PreferencesActivity extends ActionBarActivity {
|
||||
public class PreferencesActivity extends AppCompatActivity {
|
||||
|
||||
public static final int RESULT_RESTART = 4;
|
||||
|
||||
|
@ -408,13 +408,29 @@ public final class Utils {
|
||||
|
||||
byte[] mdbytes = md.digest();
|
||||
return toHexString(mdbytes).toLowerCase(Locale.ENGLISH);
|
||||
} catch (IOException | NoSuchAlgorithmException e) {
|
||||
} catch (IOException e) {
|
||||
// The annoyance (potentially) caused by miscellaneous filesystem corruption results in
|
||||
// F-Droid constantly popping up crash reports when F-Droid isn't even open. As such this
|
||||
// exception-message-parsing-and-throwing-a-new-ignorable-exception-hackery is probably
|
||||
// warranted. See https://www.gitlab.com/fdroid/fdroidclient/issues/855 for more detail.
|
||||
if (e.getMessage().contains("read failed: EIO (I/O error)")) {
|
||||
throw new PotentialFilesystemCorruptionException(e);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(e);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
} finally {
|
||||
closeQuietly(fis);
|
||||
}
|
||||
}
|
||||
|
||||
public static class PotentialFilesystemCorruptionException extends IllegalArgumentException {
|
||||
public PotentialFilesystemCorruptionException(IOException e) {
|
||||
super(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the base 16 representation of the byte array argument.
|
||||
*
|
||||
|
@ -1,10 +1,12 @@
|
||||
package org.fdroid.fdroid.compat;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import org.fdroid.fdroid.installer.PrivilegedInstaller;
|
||||
import org.fdroid.fdroid.Utils;
|
||||
|
||||
public class PackageManagerCompat {
|
||||
@ -12,10 +14,20 @@ public class PackageManagerCompat {
|
||||
private static final String TAG = "PackageManagerCompat";
|
||||
|
||||
@TargetApi(11)
|
||||
public static void setInstaller(PackageManager mPm, String packageName) {
|
||||
public static void setInstaller(Context context, PackageManager mPm, String packageName) {
|
||||
if (Build.VERSION.SDK_INT < 11) return;
|
||||
try {
|
||||
mPm.setInstallerPackageName(packageName, "org.fdroid.fdroid");
|
||||
/*
|
||||
* Starting with 7.0 (API 24), we're using PackageInstaller APIs
|
||||
* to install and uninstall apps via the privileged extension.
|
||||
* That enforces the uninstaller being the same as the installer,
|
||||
* so set the package name to that.
|
||||
*/
|
||||
if (Build.VERSION.SDK_INT >= 24 && PrivilegedInstaller.isDefault(context)) {
|
||||
mPm.setInstallerPackageName(packageName, "org.fdroid.fdroid.privileged");
|
||||
} else {
|
||||
mPm.setInstallerPackageName(packageName, "org.fdroid.fdroid");
|
||||
}
|
||||
Utils.debugLog(TAG, "Installer package name for " + packageName + " set successfully");
|
||||
} catch (Exception e) {
|
||||
// Many problems can occur:
|
||||
|
@ -8,13 +8,19 @@ import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.Signature;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Process;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
import org.acra.ACRA;
|
||||
import org.fdroid.fdroid.Hasher;
|
||||
import org.fdroid.fdroid.Utils;
|
||||
import org.fdroid.fdroid.data.Schema.InstalledAppTable;
|
||||
import rx.functions.Action1;
|
||||
import rx.schedulers.Schedulers;
|
||||
import rx.subjects.PublishSubject;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FilenameFilter;
|
||||
@ -23,10 +29,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import rx.functions.Action1;
|
||||
import rx.schedulers.Schedulers;
|
||||
import rx.subjects.PublishSubject;
|
||||
|
||||
/**
|
||||
* Handles all updates to {@link InstalledAppProvider}, whether checking the contents
|
||||
* versus what Android says is installed, or processing {@link Intent}s that come
|
||||
@ -124,13 +126,21 @@ public class InstalledAppProviderService extends IntentService {
|
||||
* is in sync with what the {@link PackageManager} tells us is installed. Once
|
||||
* completed, the relevant {@link android.content.ContentProvider}s will be
|
||||
* notified of any changes to installed statuses.
|
||||
* <p/>
|
||||
* <p>
|
||||
* The installed app cache could get out of sync, e.g. if F-Droid crashed/ or
|
||||
* ran out of battery half way through responding to {@link Intent#ACTION_PACKAGE_ADDED}.
|
||||
* This method returns immediately, and will continue to work in an
|
||||
* {@link IntentService}. It doesn't really matter where we put this in the
|
||||
* bootstrap process, because it runs in its own thread, at the lowest priority:
|
||||
* {@link Process#THREAD_PRIORITY_LOWEST}.
|
||||
* <p>
|
||||
* APKs installed in {@code /system} will often have zeroed out timestamps, like
|
||||
* 2008-01-01 (ziptime) or 2009-01-01. So instead anything older than 2010 every
|
||||
* time since we have no way to know whether an APK wasn't changed as part of an
|
||||
* OTA update. An OTA update could change the APK without changing the
|
||||
* {@link PackageInfo#versionCode} or {@link PackageInfo#lastUpdateTime}.
|
||||
*
|
||||
* @see <a href="https://gitlab.com/fdroid/fdroidclient/issues/819>issue #819</a>
|
||||
*/
|
||||
public static void compareToPackageManager(Context context) {
|
||||
Map<String, Long> cachedInfo = InstalledAppProvider.Helper.all(context);
|
||||
@ -139,7 +149,8 @@ public class InstalledAppProviderService extends IntentService {
|
||||
.getInstalledPackages(PackageManager.GET_SIGNATURES);
|
||||
for (PackageInfo packageInfo : packageInfoList) {
|
||||
if (cachedInfo.containsKey(packageInfo.packageName)) {
|
||||
if (packageInfo.lastUpdateTime > cachedInfo.get(packageInfo.packageName)) {
|
||||
if (packageInfo.lastUpdateTime < 1262300400000L // 2010-01-01 00:00
|
||||
|| packageInfo.lastUpdateTime > cachedInfo.get(packageInfo.packageName)) {
|
||||
insert(context, packageInfo);
|
||||
}
|
||||
cachedInfo.remove(packageInfo.packageName);
|
||||
@ -188,6 +199,22 @@ public class InstalledAppProviderService extends IntentService {
|
||||
String hashType = "sha256";
|
||||
String hash = Utils.getBinaryHash(apk, hashType);
|
||||
insertAppIntoDb(this, packageInfo, hashType, hash);
|
||||
} catch (final Utils.PotentialFilesystemCorruptionException e) {
|
||||
Log.e(TAG, "Encountered potential filesystem corruption, or other unknown " +
|
||||
"problem when calculating hash of " + apk.getAbsolutePath() + ". " +
|
||||
"It is unlikely F-Droid can do anything about this, and this " +
|
||||
"likely happened in the background. As such, we will continue without " +
|
||||
"interrupting the user by asking them to send a crash report.");
|
||||
Handler handler = new Handler(Looper.getMainLooper());
|
||||
handler.post(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Context context = getApplicationContext();
|
||||
Toast.makeText(context, e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
return;
|
||||
} catch (IllegalArgumentException e) {
|
||||
Utils.debugLog(TAG, e.getMessage());
|
||||
ACRA.getErrorReporter().handleException(e, false);
|
||||
@ -207,7 +234,7 @@ public class InstalledAppProviderService extends IntentService {
|
||||
* broadcast. In the first case, it will already have a {@link PackageInfo} for us. However if
|
||||
* it is from the later case, we'll need to query the {@link PackageManager} ourselves to get
|
||||
* this info.
|
||||
*
|
||||
* <p>
|
||||
* Can still return null, as there is potentially race conditions to do with uninstalling apps
|
||||
* such that querying the {@link PackageManager} for a given package may throw an exception.
|
||||
*/
|
||||
|
@ -20,6 +20,7 @@
|
||||
package org.fdroid.fdroid.installer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.support.v4.content.FileProvider;
|
||||
|
||||
@ -59,8 +60,9 @@ public class ApkFileProvider extends FileProvider {
|
||||
|
||||
if (useContentUri) {
|
||||
// return a content Uri using support libs FileProvider
|
||||
|
||||
return getUriForFile(context, AUTHORITY, sanitizedApkFile);
|
||||
Uri apkUri = getUriForFile(context, AUTHORITY, sanitizedApkFile);
|
||||
context.grantUriPermission("org.fdroid.fdroid.privileged", apkUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
return apkUri;
|
||||
}
|
||||
|
||||
// Need the apk to be world readable, so that the installer is able to read it.
|
||||
|
@ -334,7 +334,10 @@ public class InstallManagerService extends Service {
|
||||
case Installer.ACTION_INSTALL_COMPLETE:
|
||||
Apk apkComplete = removeFromActive(downloadUrl);
|
||||
|
||||
PackageManagerCompat.setInstaller(getPackageManager(), apkComplete.packageName);
|
||||
PackageManagerCompat.setInstaller(context, getPackageManager(), apkComplete.packageName);
|
||||
if (PrivilegedInstaller.isDefault(context)) {
|
||||
cancelNotification(downloadUrl);
|
||||
}
|
||||
|
||||
localBroadcastManager.unregisterReceiver(this);
|
||||
break;
|
||||
|
@ -45,12 +45,12 @@ public class InstallerFactory {
|
||||
}
|
||||
|
||||
Installer installer;
|
||||
if (apk.packageName.equals(PrivilegedInstaller.PRIVILEGED_EXTENSION_PACKAGE_NAME)) {
|
||||
// special case for installing "Privileged Extension" with root
|
||||
installer = new ExtensionInstaller(context, apk);
|
||||
} else if (PrivilegedInstaller.isDefault(context)) {
|
||||
if (PrivilegedInstaller.isDefault(context)) {
|
||||
Utils.debugLog(TAG, "privileged extension correctly installed -> PrivilegedInstaller");
|
||||
installer = new PrivilegedInstaller(context, apk);
|
||||
} else if (apk.packageName.equals(PrivilegedInstaller.PRIVILEGED_EXTENSION_PACKAGE_NAME)) {
|
||||
// special case for installing "Privileged Extension" with root
|
||||
installer = new ExtensionInstaller(context, apk);
|
||||
} else {
|
||||
installer = new DefaultInstaller(context, apk);
|
||||
}
|
||||
|
@ -27,12 +27,14 @@ import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import org.fdroid.fdroid.Preferences;
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.compat.PackageManagerCompat;
|
||||
import org.fdroid.fdroid.data.Apk;
|
||||
import org.fdroid.fdroid.privileged.IPrivilegedCallback;
|
||||
import org.fdroid.fdroid.privileged.IPrivilegedService;
|
||||
@ -393,6 +395,10 @@ public class PrivilegedInstaller extends Installer {
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Set installer to the privileged extension
|
||||
*/
|
||||
PackageManagerCompat.setInstaller(context, context.getPackageManager(), apk.packageName);
|
||||
Intent serviceIntent = new Intent(PRIVILEGED_EXTENSION_SERVICE_INTENT);
|
||||
serviceIntent.setPackage(PRIVILEGED_EXTENSION_PACKAGE_NAME);
|
||||
context.getApplicationContext().bindService(serviceIntent, mServiceConnection,
|
||||
@ -406,7 +412,6 @@ public class PrivilegedInstaller extends Installer {
|
||||
|
||||
@Override
|
||||
protected boolean supportsContentUri() {
|
||||
// TODO: correct?
|
||||
return false;
|
||||
return Build.VERSION.SDK_INT >= 24;
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<PreferenceCategory android:title="@string/updates">
|
||||
<ListPreference android:title="@string/update_interval"
|
||||
<com.geecko.QuickLyric.view.AppCompatListPreference android:title="@string/update_interval"
|
||||
android:key="updateInterval"
|
||||
android:defaultValue="24"
|
||||
android:entries="@array/updateIntervalNames"
|
||||
@ -24,12 +24,12 @@
|
||||
android:title="@string/update_history" />
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory android:title="@string/display">
|
||||
<ListPreference android:title="@string/pref_language"
|
||||
<com.geecko.QuickLyric.view.AppCompatListPreference android:title="@string/pref_language"
|
||||
android:key="language"
|
||||
android:defaultValue=""
|
||||
android:entries="@array/languageNames"
|
||||
android:entryValues="@array/languageValues" />
|
||||
<ListPreference android:title="@string/theme"
|
||||
<com.geecko.QuickLyric.view.AppCompatListPreference android:title="@string/theme"
|
||||
android:key="theme"
|
||||
android:defaultValue="light"
|
||||
android:entries="@array/themeNames"
|
||||
@ -77,7 +77,7 @@
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory android:title="@string/other"
|
||||
android:key="pref_category_other">
|
||||
<ListPreference android:title="@string/cache_downloaded"
|
||||
<com.geecko.QuickLyric.view.AppCompatListPreference android:title="@string/cache_downloaded"
|
||||
android:key="keepCacheFor"
|
||||
android:defaultValue="86400000"
|
||||
android:entries="@array/keepCacheNames"
|
||||
|
Loading…
x
Reference in New Issue
Block a user