fix merge conflict
|
@ -14,12 +14,14 @@ android {
|
|||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
|
||||
debug {
|
||||
multiDexEnabled true
|
||||
debuggable true
|
||||
|
@ -33,6 +35,7 @@ android {
|
|||
// but continue the build even when errors are found:
|
||||
abortOnError false
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
|
|
Po Szerokość: | Wysokość: | Rozmiar: 9.6 KiB |
|
@ -1,5 +1,6 @@
|
|||
package org.schabi.newpipe;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Application;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
|
@ -65,6 +66,7 @@ import io.reactivex.plugins.RxJavaPlugins;
|
|||
public class App extends Application {
|
||||
protected static final String TAG = App.class.toString();
|
||||
private RefWatcher refWatcher;
|
||||
private static App app;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static final Class<? extends ReportSenderFactory>[]
|
||||
|
@ -88,6 +90,8 @@ public class App extends Application {
|
|||
}
|
||||
refWatcher = installLeakCanary();
|
||||
|
||||
app = this;
|
||||
|
||||
// Initialize settings first because others inits can use its values
|
||||
SettingsActivity.initSettings(this);
|
||||
|
||||
|
@ -100,6 +104,9 @@ public class App extends Application {
|
|||
ImageLoader.getInstance().init(getImageLoaderConfigurations(10, 50));
|
||||
|
||||
configureRxJavaErrorHandler();
|
||||
|
||||
// Check for new version
|
||||
new CheckForNewAppVersionTask().execute();
|
||||
}
|
||||
|
||||
protected Downloader getDownloader() {
|
||||
|
@ -211,6 +218,31 @@ public class App extends Application {
|
|||
NotificationManager mNotificationManager =
|
||||
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
mNotificationManager.createNotificationChannel(mChannel);
|
||||
|
||||
setUpUpdateNotificationChannel(importance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up notification channel for app update.
|
||||
* @param importance
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.O)
|
||||
private void setUpUpdateNotificationChannel(int importance) {
|
||||
|
||||
final String appUpdateId
|
||||
= getString(R.string.app_update_notification_channel_id);
|
||||
final CharSequence appUpdateName
|
||||
= getString(R.string.app_update_notification_channel_name);
|
||||
final String appUpdateDescription
|
||||
= getString(R.string.app_update_notification_channel_description);
|
||||
|
||||
NotificationChannel appUpdateChannel
|
||||
= new NotificationChannel(appUpdateId, appUpdateName, importance);
|
||||
appUpdateChannel.setDescription(appUpdateDescription);
|
||||
|
||||
NotificationManager appUpdateNotificationManager
|
||||
= (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
appUpdateNotificationManager.createNotificationChannel(appUpdateChannel);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
@ -226,4 +258,8 @@ public class App extends Application {
|
|||
protected boolean isDisposedRxExceptionsReported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static App getApp() {
|
||||
return app;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,230 @@
|
|||
package org.schabi.newpipe;
|
||||
|
||||
import android.app.Application;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.Signature;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.NotificationManagerCompat;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
|
||||
/**
|
||||
* AsyncTask to check if there is a newer version of the NewPipe github apk available or not.
|
||||
* If there is a newer version we show a notification, informing the user. On tapping
|
||||
* the notification, the user will be directed to the download link.
|
||||
*/
|
||||
public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
|
||||
|
||||
private static final Application app = App.getApp();
|
||||
private static final String GITHUB_APK_SHA1 = "B0:2E:90:7C:1C:D6:FC:57:C3:35:F0:88:D0:8F:50:5F:94:E4:D2:15";
|
||||
private static final String newPipeApiUrl = "https://newpipe.schabi.org/api/data.json";
|
||||
private static final int timeoutPeriod = 30;
|
||||
|
||||
private SharedPreferences mPrefs;
|
||||
private OkHttpClient client;
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
|
||||
mPrefs = PreferenceManager.getDefaultSharedPreferences(app);
|
||||
|
||||
// Check if user has enabled/ disabled update checking
|
||||
// and if the current apk is a github one or not.
|
||||
if (!mPrefs.getBoolean(app.getString(R.string.update_app_key), true)
|
||||
|| !isGithubApk()) {
|
||||
this.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String doInBackground(Void... voids) {
|
||||
|
||||
// Make a network request to get latest NewPipe data.
|
||||
if (client == null) {
|
||||
|
||||
client = new OkHttpClient
|
||||
.Builder()
|
||||
.readTimeout(timeoutPeriod, TimeUnit.SECONDS)
|
||||
.build();
|
||||
}
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(newPipeApiUrl)
|
||||
.build();
|
||||
|
||||
try {
|
||||
Response response = client.newCall(request).execute();
|
||||
return response.body().string();
|
||||
} catch (IOException ex) {
|
||||
ErrorActivity.reportError(app, ex, null, null,
|
||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||
"app update API fail", R.string.app_ui_crash));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(String response) {
|
||||
|
||||
// Parse the json from the response.
|
||||
if (response != null) {
|
||||
|
||||
try {
|
||||
JSONObject mainObject = new JSONObject(response);
|
||||
JSONObject flavoursObject = mainObject.getJSONObject("flavors");
|
||||
JSONObject githubObject = flavoursObject.getJSONObject("github");
|
||||
JSONObject githubStableObject = githubObject.getJSONObject("stable");
|
||||
|
||||
String versionName = githubStableObject.getString("version");
|
||||
String versionCode = githubStableObject.getString("version_code");
|
||||
String apkLocationUrl = githubStableObject.getString("apk");
|
||||
|
||||
compareAppVersionAndShowNotification(versionName, apkLocationUrl, versionCode);
|
||||
|
||||
} catch (JSONException ex) {
|
||||
ErrorActivity.reportError(app, ex, null, null,
|
||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||
"could not parse app update JSON data", R.string.app_ui_crash));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to compare the current and latest available app version.
|
||||
* If a newer version is available, we show the update notification.
|
||||
* @param versionName
|
||||
* @param apkLocationUrl
|
||||
*/
|
||||
private void compareAppVersionAndShowNotification(String versionName,
|
||||
String apkLocationUrl,
|
||||
String versionCode) {
|
||||
|
||||
int NOTIFICATION_ID = 2000;
|
||||
|
||||
if (BuildConfig.VERSION_CODE < Integer.valueOf(versionCode)) {
|
||||
|
||||
// A pending intent to open the apk location url in the browser.
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(apkLocationUrl));
|
||||
PendingIntent pendingIntent
|
||||
= PendingIntent.getActivity(app, 0, intent, 0);
|
||||
|
||||
NotificationCompat.Builder notificationBuilder = new NotificationCompat
|
||||
.Builder(app, app.getString(R.string.app_update_notification_channel_id))
|
||||
.setSmallIcon(R.drawable.ic_newpipe_update)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setAutoCancel(true)
|
||||
.setContentTitle(app.getString(R.string.app_update_notification_content_title))
|
||||
.setContentText(app.getString(R.string.app_update_notification_content_text)
|
||||
+ " " + versionName);
|
||||
|
||||
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(app);
|
||||
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get the apk's SHA1 key.
|
||||
* https://stackoverflow.com/questions/9293019/get-certificate-fingerprint-from-android-app#22506133
|
||||
*/
|
||||
private static String getCertificateSHA1Fingerprint() {
|
||||
|
||||
PackageManager pm = app.getPackageManager();
|
||||
String packageName = app.getPackageName();
|
||||
int flags = PackageManager.GET_SIGNATURES;
|
||||
PackageInfo packageInfo = null;
|
||||
|
||||
try {
|
||||
packageInfo = pm.getPackageInfo(packageName, flags);
|
||||
} catch (PackageManager.NameNotFoundException ex) {
|
||||
ErrorActivity.reportError(app, ex, null, null,
|
||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||
"Could not find package info", R.string.app_ui_crash));
|
||||
}
|
||||
|
||||
Signature[] signatures = packageInfo.signatures;
|
||||
byte[] cert = signatures[0].toByteArray();
|
||||
InputStream input = new ByteArrayInputStream(cert);
|
||||
|
||||
CertificateFactory cf = null;
|
||||
X509Certificate c = null;
|
||||
|
||||
try {
|
||||
cf = CertificateFactory.getInstance("X509");
|
||||
c = (X509Certificate) cf.generateCertificate(input);
|
||||
} catch (CertificateException ex) {
|
||||
ErrorActivity.reportError(app, ex, null, null,
|
||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||
"Certificate error", R.string.app_ui_crash));
|
||||
}
|
||||
|
||||
String hexString = null;
|
||||
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA1");
|
||||
byte[] publicKey = md.digest(c.getEncoded());
|
||||
hexString = byte2HexFormatted(publicKey);
|
||||
} catch (NoSuchAlgorithmException ex1) {
|
||||
ErrorActivity.reportError(app, ex1, null, null,
|
||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||
"Could not retrieve SHA1 key", R.string.app_ui_crash));
|
||||
} catch (CertificateEncodingException ex2) {
|
||||
ErrorActivity.reportError(app, ex2, null, null,
|
||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||
"Could not retrieve SHA1 key", R.string.app_ui_crash));
|
||||
}
|
||||
|
||||
return hexString;
|
||||
}
|
||||
|
||||
private static String byte2HexFormatted(byte[] arr) {
|
||||
|
||||
StringBuilder str = new StringBuilder(arr.length * 2);
|
||||
|
||||
for (int i = 0; i < arr.length; i++) {
|
||||
String h = Integer.toHexString(arr[i]);
|
||||
int l = h.length();
|
||||
if (l == 1) h = "0" + h;
|
||||
if (l > 2) h = h.substring(l - 2, l);
|
||||
str.append(h.toUpperCase());
|
||||
if (i < (arr.length - 1)) str.append(':');
|
||||
}
|
||||
return str.toString();
|
||||
}
|
||||
|
||||
public static boolean isGithubApk() {
|
||||
|
||||
return getCertificateSHA1Fingerprint().equals(GITHUB_APK_SHA1);
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ import android.os.Bundle;
|
|||
import android.support.v7.preference.Preference;
|
||||
|
||||
import org.schabi.newpipe.BuildConfig;
|
||||
import org.schabi.newpipe.CheckForNewAppVersionTask;
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
public class MainSettingsFragment extends BasePreferenceFragment {
|
||||
|
@ -13,6 +14,13 @@ public class MainSettingsFragment extends BasePreferenceFragment {
|
|||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
addPreferencesFromResource(R.xml.main_settings);
|
||||
|
||||
if (!CheckForNewAppVersionTask.isGithubApk()) {
|
||||
final Preference update = findPreference(getString(R.string.update_pref_screen_key));
|
||||
getPreferenceScreen().removePreference(update);
|
||||
|
||||
defaultPreferences.edit().putBoolean(getString(R.string.update_app_key), false).apply();
|
||||
}
|
||||
|
||||
if (!DEBUG) {
|
||||
final Preference debug = findPreference(getString(R.string.debug_pref_screen_key));
|
||||
getPreferenceScreen().removePreference(debug);
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
package org.schabi.newpipe.settings;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.preference.Preference;
|
||||
|
||||
import org.schabi.newpipe.CheckForNewAppVersionTask;
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
public class UpdateSettingsFragment extends BasePreferenceFragment {
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
String updateToggleKey = getString(R.string.update_app_key);
|
||||
findPreference(updateToggleKey).setOnPreferenceChangeListener(updatePreferenceChange);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
addPreferencesFromResource(R.xml.update_settings);
|
||||
}
|
||||
|
||||
private Preference.OnPreferenceChangeListener updatePreferenceChange
|
||||
= (preference, newValue) -> {
|
||||
|
||||
defaultPreferences.edit().putBoolean(getString(R.string.update_app_key),
|
||||
(boolean) newValue).apply();
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
Po Szerokość: | Wysokość: | Rozmiar: 680 B |
Po Szerokość: | Wysokość: | Rozmiar: 720 B |
Po Szerokość: | Wysokość: | Rozmiar: 720 B |
Po Szerokość: | Wysokość: | Rozmiar: 512 B |
Po Szerokość: | Wysokość: | Rozmiar: 401 B |
Po Szerokość: | Wysokość: | Rozmiar: 379 B |
Po Szerokość: | Wysokość: | Rozmiar: 562 B |
Po Szerokość: | Wysokość: | Rozmiar: 734 B |
Po Szerokość: | Wysokość: | Rozmiar: 954 B |
Po Szerokość: | Wysokość: | Rozmiar: 1010 B |
Po Szerokość: | Wysokość: | Rozmiar: 1.3 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 1.3 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 1.4 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 2.1 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 1.7 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 2.0 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 431 B |
Po Szerokość: | Wysokość: | Rozmiar: 379 B |
Po Szerokość: | Wysokość: | Rozmiar: 710 B |
Po Szerokość: | Wysokość: | Rozmiar: 1.1 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 2.0 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 431 B |
Po Szerokość: | Wysokość: | Rozmiar: 379 B |
Po Szerokość: | Wysokość: | Rozmiar: 710 B |
Po Szerokość: | Wysokość: | Rozmiar: 1.1 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 2.0 KiB |
|
@ -41,6 +41,7 @@
|
|||
<attr name="ic_list" format="reference"/>
|
||||
<attr name="ic_grid" format="reference"/>
|
||||
<attr name="ic_delete" format="reference"/>
|
||||
<attr name="ic_settings_update" format="reference"/>
|
||||
|
||||
<!-- Can't refer to colors directly in drawable's xml-->
|
||||
<attr name="toolbar_shadow_drawable" format="reference"/>
|
||||
|
|
|
@ -222,6 +222,10 @@
|
|||
<item>@string/always_ask_open_action_key</item>
|
||||
</string-array>
|
||||
|
||||
<!-- Updates -->
|
||||
<string name="update_app_key" translatable="false">update_app_key</string>
|
||||
<string name="update_pref_screen_key" translatable="false">update_pref_screen_key</string>
|
||||
|
||||
<!-- alternatively, load these from some local android data store -->
|
||||
<string-array name="language_codes" translatable="false">
|
||||
<item>af</item>
|
||||
|
|
|
@ -112,6 +112,7 @@
|
|||
<string name="settings_category_appearance_title">Appearance</string>
|
||||
<string name="settings_category_other_title">Other</string>
|
||||
<string name="settings_category_debug_title">Debug</string>
|
||||
<string name="settings_category_updates_title">Updates</string>
|
||||
<string name="background_player_playing_toast">Playing in background</string>
|
||||
<string name="popup_playing_toast">Playing in popup mode</string>
|
||||
<string name="background_player_append">Queued on background player</string>
|
||||
|
@ -153,6 +154,10 @@
|
|||
<string name="notification_channel_name">NewPipe Notification</string>
|
||||
<string name="notification_channel_description">Notifications for NewPipe background and popup players</string>
|
||||
|
||||
<string name="app_update_notification_channel_id" translatable="false">newpipeAppUpdate</string>
|
||||
<string name="app_update_notification_channel_name">App Update Notification</string>
|
||||
<string name="app_update_notification_channel_description">Notifications for new NewPipe version</string>
|
||||
|
||||
<string name="unknown_content">[Unknown]</string>
|
||||
|
||||
<string name="toggle_orientation">Toggle Orientation</string>
|
||||
|
@ -513,6 +518,10 @@
|
|||
<item>144p</item>
|
||||
</string-array>
|
||||
|
||||
<!-- Updates Settings -->
|
||||
<string name="updates_setting_title">Updates</string>
|
||||
<string name="updates_setting_description">Show a notification to prompt app update when a new version is available</string>
|
||||
|
||||
<!-- Minimize to exit action -->
|
||||
<string name="minimize_on_exit_title">Minimize on app switch</string>
|
||||
<string name="minimize_on_exit_summary">Action when switching to other app from main video player — %s</string>
|
||||
|
@ -525,16 +534,21 @@
|
|||
<string name="auto">Auto</string>
|
||||
<string name="switch_view">Switch View</string>
|
||||
|
||||
<!-- App update notification -->
|
||||
<string name="app_update_notification_content_title">NewPipe Update Available!</string>
|
||||
<string name="app_update_notification_content_text">Tap to download</string>
|
||||
|
||||
|
||||
|
||||
<string name="missions_header_finished">Finished</string>
|
||||
<string name="missions_header_pending">In queue</string>
|
||||
|
||||
|
||||
<string name="paused">paused</string>
|
||||
<string name="queued">queued</string>
|
||||
<string name="post_processing">post-processing</string>
|
||||
|
||||
|
||||
<string name="enqueue">Queue</string>
|
||||
|
||||
|
||||
<string name="permission_denied">Action denied by the system</string>
|
||||
|
||||
<!-- download notifications -->
|
||||
|
|
|
@ -56,6 +56,7 @@
|
|||
<item name="ic_list">@drawable/ic_list_black_24dp</item>
|
||||
<item name="ic_grid">@drawable/ic_grid_black_24dp</item>
|
||||
<item name="ic_delete">@drawable/ic_delete_black_24dp</item>
|
||||
<item name="ic_settings_update">@drawable/ic_settings_update_black</item>
|
||||
|
||||
<item name="separator_color">@color/light_separator_color</item>
|
||||
<item name="contrast_background_color">@color/light_contrast_background_color</item>
|
||||
|
@ -118,6 +119,7 @@
|
|||
<item name="ic_list">@drawable/ic_list_white_24dp</item>
|
||||
<item name="ic_grid">@drawable/ic_grid_white_24dp</item>
|
||||
<item name="ic_delete">@drawable/ic_delete_white_24dp</item>
|
||||
<item name="ic_settings_update">@drawable/ic_settings_update_white</item>
|
||||
|
||||
<item name="separator_color">@color/dark_separator_color</item>
|
||||
<item name="contrast_background_color">@color/dark_contrast_background_color</item>
|
||||
|
|
|
@ -29,6 +29,12 @@
|
|||
android:icon="?attr/language"
|
||||
android:title="@string/content"/>
|
||||
|
||||
<PreferenceScreen
|
||||
android:fragment="org.schabi.newpipe.settings.UpdateSettingsFragment"
|
||||
android:icon="?attr/ic_settings_update"
|
||||
android:title="@string/settings_category_updates_title"
|
||||
android:key="update_pref_screen_key"/>
|
||||
|
||||
<PreferenceScreen
|
||||
android:fragment="org.schabi.newpipe.settings.DebugSettingsFragment"
|
||||
android:icon="?attr/bug"
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:key="general_preferences"
|
||||
android:title="@string/settings_category_updates_title">
|
||||
|
||||
<SwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:key="@string/update_app_key"
|
||||
android:title="@string/updates_setting_title"
|
||||
android:summary="@string/updates_setting_description"/>
|
||||
|
||||
</PreferenceScreen>
|