From 05be26d507f25a4f95b498d003bf17d01651b936 Mon Sep 17 00:00:00 2001 From: "Amir Hossein Goodarzi (Numb)" Date: Wed, 10 Aug 2016 18:31:48 +0430 Subject: [PATCH] fix names and color --- .../java/org/fdroid/fdroid/#RepoUpdater.java# | 397 ++++++++++++++++++ .../org/fdroid/fdroid/#RepoXMLHandler.java# | 307 ++++++++++++++ app/src/main/res/values-fa/strings.xml | 50 +-- app/src/main/res/values/colors.xml | 2 +- app/src/main/res/values/strings.xml | 50 +-- 5 files changed, 755 insertions(+), 51 deletions(-) create mode 100644 app/src/main/java/org/fdroid/fdroid/#RepoUpdater.java# create mode 100644 app/src/main/java/org/fdroid/fdroid/#RepoXMLHandler.java# diff --git a/app/src/main/java/org/fdroid/fdroid/#RepoUpdater.java# b/app/src/main/java/org/fdroid/fdroid/#RepoUpdater.java# new file mode 100644 index 000000000..293387593 --- /dev/null +++ b/app/src/main/java/org/fdroid/fdroid/#RepoUpdater.java# @@ -0,0 +1,397 @@ + + +package org.fdroid.fdroid; + +import android.content.ContentValues; +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.TextUtils; +import android.util.Log; + +import org.fdroid.fdroid.data.Apk; +import org.fdroid.fdroid.data.App; +import org.fdroid.fdroid.data.Repo; +import org.fdroid.fdroid.data.RepoPersister; +import org.fdroid.fdroid.data.RepoProvider; +import org.fdroid.fdroid.data.Schema.RepoTable; +import org.fdroid.fdroid.net.Downloader; +import org.fdroid.fdroid.net.DownloaderFactory; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.CodeSigner; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.Date; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +/** + * Responsible for updating an individual repository. This will: + * * Download the index.jar + * * Verify that it is signed correctly and by the correct certificate + * * Parse the index.xml from the .jar file + * * Save the resulting repo, apps, and apks to the database. + * + * WARNING: this class is the central piece of the entire security model of + * FDroid! Avoid modifying it when possible, if you absolutely must, be very, + * very careful with the changes that you are making! + */ +public class RepoUpdater { + + private static final String TAG = "RepoUpdater"; + + private final String indexUrl; + + @NonNull + private final Context context; + @NonNull + private final Repo repo; + private boolean hasChanged; + + @Nullable + private ProgressListener downloadProgressListener; + private ProgressListener committingProgressListener; + private ProgressListener processXmlProgressListener; + private String cacheTag; + private X509Certificate signingCertFromJar; + + @NonNull private final RepoPersister persister; + + /** + * Updates an app repo as read out of the database into a {@link Repo} instance. + * + * @param repo A {@link Repo} read out of the local database + */ + public RepoUpdater(@NonNull Context context, @NonNull Repo repo) { + this.context = context; + this.repo = repo; + this.persister = new RepoPersister(context, repo); + + String url = repo.address + "/index.jar"; + String versionName = Utils.getVersionName(context); + if (versionName != null) { + url += "?client_version=" + versionName; + } + this.indexUrl = url; + } + + public void setDownloadProgressListener(ProgressListener progressListener) { + this.downloadProgressListener = progressListener; + } + + public void setProcessXmlProgressListener(ProgressListener progressListener) { + this.processXmlProgressListener = progressListener; + } + + public void setCommittingProgressListener(ProgressListener progressListener) { + this.committingProgressListener = progressListener; + } + + public boolean hasChanged() { + return hasChanged; + } + + private Downloader downloadIndex() throws UpdateException { + Downloader downloader = null; + try { + downloader = DownloaderFactory.create(context, indexUrl); + downloader.setCacheTag(repo.lastetag); + downloader.setListener(downloadProgressListener); + downloader.download(); + + if (downloader.isCached()) { + // The index is unchanged since we last read it. We just mark + // everything that came from this repo as being updated. + Utils.debugLog(TAG, "Repo index for " + indexUrl + " is up to date (by etag)"); + } + + } catch (IOException e) { + if (downloader != null && downloader.outputFile != null) { + if (!downloader.outputFile.delete()) { + Log.w(TAG, "Couldn't delete file: " + downloader.outputFile.getAbsolutePath()); + } + } + + throw new UpdateException(repo, "Error getting index file", e); + } catch (InterruptedException e) { + // ignored if canceled, the local database just won't be updated + e.printStackTrace(); + } + return downloader; + } + + /** + * All repos are represented by a signed jar file, {@code index.jar}, which contains + * a single file, {@code index.xml}. This takes the {@code index.jar}, verifies the + * signature, then returns the unzipped {@code index.xml}. + * + * @throws UpdateException All error states will come from here. + */ + public void update() throws UpdateException { + + final Downloader downloader = downloadIndex(); + hasChanged = downloader.hasChanged(); + + if (hasChanged) { + // Don't worry about checking the status code for 200. If it was a + // successful download, then we will have a file ready to use: + cacheTag = downloader.getCacheTag(); + processDownloadedFile(downloader.outputFile); + } + } + + private ContentValues repoDetailsToSave; + private String signingCertFromIndexXml; + + private RepoXMLHandler.IndexReceiver createIndexReceiver() { + return new RepoXMLHandler.IndexReceiver() { + @Override + public void receiveRepo(String name, String description, String signingCert, int maxAge, int version, long timestamp) { + signingCertFromIndexXml = signingCert; + repoDetailsToSave = prepareRepoDetailsForSaving(name, description, maxAge, version, timestamp); + } + + @Override + public void receiveApp(App app, List packages) { + try { + persister.saveToDb(app, packages); + } catch (UpdateException e) { + throw new RuntimeException("Error while saving repo details to database.", e); + } + } + }; + } + + public void processDownloadedFile(File downloadedFile) throws UpdateException { + InputStream indexInputStream = null; + try { + if (downloadedFile == null || !downloadedFile.exists()) { + throw new UpdateException(repo, downloadedFile + " does not exist!"); + } + + // Due to a bug in Android 5.0 Lollipop, the inclusion of spongycastle causes + // breakage when verifying the signature of the downloaded .jar. For more + // details, check out https://gitlab.com/fdroid/fdroidclient/issues/111. + FDroidApp.disableSpongyCastleOnLollipop(); + + JarFile jarFile = new JarFile(downloadedFile, true); + JarEntry indexEntry = (JarEntry) jarFile.getEntry("index.xml"); + indexInputStream = new ProgressBufferedInputStream(jarFile.getInputStream(indexEntry), + processXmlProgressListener, new URL(repo.address), (int) indexEntry.getSize()); + + // Process the index... + SAXParserFactory factory = SAXParserFactory.newInstance(); + factory.setNamespaceAware(true); + final SAXParser parser = factory.newSAXParser(); + final XMLReader reader = parser.getXMLReader(); + final RepoXMLHandler repoXMLHandler = new RepoXMLHandler(repo, createIndexReceiver()); + reader.setContentHandler(repoXMLHandler); + reader.parse(new InputSource(indexInputStream)); + + long timestamp = repoDetailsToSave.getAsLong(RepoTable.Cols.TIMESTAMP); + if (timestamp < repo.timestamp) { + throw new UpdateException(repo, "index.jar is older that current index! " + + timestamp + " < " + repo.timestamp); + } + + signingCertFromJar = getSigningCertFromJar(indexEntry); + + // JarEntry can only read certificates after the file represented by that JarEntry + // has been read completely, so verification cannot run until now... + assertSigningCertFromXmlCorrect(); + commitToDb(); + } catch (SAXException | ParserConfigurationException | IOException e) { + throw new UpdateException(repo, "Error parsing index", e); + } finally { + FDroidApp.enableSpongyCastleOnLollipop(); + Utils.closeQuietly(indexInputStream); + if (downloadedFile != null) { + if (!downloadedFile.delete()) { + Log.w(TAG, "Couldn't delete file: " + downloadedFile.getAbsolutePath()); + } + } + } + } + + private void commitToDb() throws UpdateException { + Log.i(TAG, "Repo signature verified, saving app metadata to database."); + if (committingProgressListener != null) { + try { + //TODO this should be an event, not a progress listener + committingProgressListener.onProgress(new URL(indexUrl), 0, -1); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + } + persister.commit(repoDetailsToSave); + } + + private void assertSigningCertFromXmlCorrect() throws SigningException { + + // no signing cert read from database, this is the first use + if (repo.signingCertificate == null) { + verifyAndStoreTOFUCerts(signingCertFromIndexXml, signingCertFromJar); + } + verifyCerts(signingCertFromIndexXml, signingCertFromJar); + + } + + /** + * Update tracking data for the repo represented by this instance (index version, etag, + * description, human-readable name, etc. + */ + private ContentValues prepareRepoDetailsForSaving(String name, String description, int maxAge, int version, long timestamp) { + ContentValues values = new ContentValues(); + + values.put(RepoTable.Cols.LAST_UPDATED, Utils.formatTime(new Date(), "")); + + if (repo.lastetag == null || !repo.lastetag.equals(cacheTag)) { + values.put(RepoTable.Cols.LAST_ETAG, cacheTag); + } + + if (version != -1 && version != repo.version) { + Utils.debugLog(TAG, "Repo specified a new version: from " + repo.version + " to " + version); + values.put(RepoTable.Cols.VERSION, version); + } + + if (maxAge != -1 && maxAge != repo.maxage) { + Utils.debugLog(TAG, "Repo specified a new maximum age - updated"); + values.put(RepoTable.Cols.MAX_AGE, maxAge); + } + + if (description != null && !description.equals(repo.description)) { + values.put(RepoTable.Cols.DESCRIPTION, description); + } + + if (name != null && !name.equals(repo.name)) { + values.put(RepoTable.Cols.NAME, name); + } + + if (timestamp != repo.timestamp) { + values.put(RepoTable.Cols.TIMESTAMP, timestamp); + } + + return values; + } + + public static class UpdateException extends Exception { + + private static final long serialVersionUID = -4492452418826132803L; + public final Repo repo; + + public UpdateException(Repo repo, String message) { + super(message); + this.repo = repo; + } + + public UpdateException(Repo repo, String message, Exception cause) { + super(message, cause); + this.repo = repo; + } + } + + public static class SigningException extends UpdateException { + public SigningException(Repo repo, String message) { + super(repo, "Repository was not signed correctly: " + message); + } + } + + /** + * FDroid's index.jar is signed using a particular format and does not allow lots of + * signing setups that would be valid for a regular jar. This validates those + * restrictions. + */ + private X509Certificate getSigningCertFromJar(JarEntry jarEntry) throws SigningException { + final CodeSigner[] codeSigners = jarEntry.getCodeSigners(); + if (codeSigners == null || codeSigners.length == 0) { + throw new SigningException(repo, "No signature found in index"); + } + /* we could in theory support more than 1, but as of now we do not */ + if (codeSigners.length > 1) { + throw new SigningException(repo, "index.jar must be signed by a single code signer!"); + } + List certs = codeSigners[0].getSignerCertPath().getCertificates(); + if (certs.size() != 1) { + throw new SigningException(repo, "index.jar code signers must only have a single certificate!"); + } + return (X509Certificate) certs.get(0); + } + + /** + * A new repo can be added with or without the fingerprint of the signing + * certificate. If no fingerprint is supplied, then do a pure TOFU and just + * store the certificate as valid. If there is a fingerprint, then first + * check that the signing certificate in the jar matches that fingerprint. + */ + private void verifyAndStoreTOFUCerts(String certFromIndexXml, X509Certificate rawCertFromJar) + throws SigningException { + if (repo.signingCertificate != null) { + return; // there is a repo.signingCertificate already, nothing to TOFU + } + + /* The first time a repo is added, it can be added with the signing certificate's + * fingerprint. In that case, check that fingerprint against what is + * actually in the index.jar itself. If no fingerprint, just store the + * signing certificate */ + if (repo.fingerprint != null) { + String fingerprintFromIndexXml = Utils.calcFingerprint(certFromIndexXml); + String fingerprintFromJar = Utils.calcFingerprint(rawCertFromJar); + if (!repo.fingerprint.equalsIgnoreCase(fingerprintFromIndexXml) + || !repo.fingerprint.equalsIgnoreCase(fingerprintFromJar)) { + throw new SigningException(repo, "Supplied certificate fingerprint does not match!"); + } + } // else - no info to check things are valid, so just Trust On First Use + + Utils.debugLog(TAG, "Saving new signing certificate in the database for " + repo.address); + ContentValues values = new ContentValues(2); + values.put(RepoTable.Cols.LAST_UPDATED, Utils.formatDate(new Date(), "")); + values.put(RepoTable.Cols.SIGNING_CERT, Hasher.hex(rawCertFromJar)); + RepoProvider.Helper.update(context, repo, values); + } + + /** + * FDroid works with three copies of the signing certificate: + *
  • in the downloaded jar
  • + *
  • in the index XML
  • + *
  • stored in the local database
  • + * It would work better removing the copy from the index XML, but it needs to stay + * there for backwards compatibility since the old TOFU process requires it. Therefore, + * since all three have to be present, all three are compared. + * + * @param certFromIndexXml the cert written into the header of the index XML + * @param rawCertFromJar the {@link X509Certificate} embedded in the downloaded jar + */ + private void verifyCerts(String certFromIndexXml, X509Certificate rawCertFromJar) throws SigningException { + // convert binary data to string version that is used in FDroid's database + String certFromJar = Hasher.hex(rawCertFromJar); + + // repo and repo.signingCertificate must be pre-loaded from the database + if (TextUtils.isEmpty(repo.signingCertificate) + || TextUtils.isEmpty(certFromJar) + || TextUtils.isEmpty(certFromIndexXml)) { + throw new SigningException(repo, "A empty repo or signing certificate is invalid!"); + } + + // though its called repo.signingCertificate, its actually a X509 certificate + if (repo.signingCertificate.equals(certFromJar) + && repo.signingCertificate.equals(certFromIndexXml) + && certFromIndexXml.equals(certFromJar)) { + return; // we have a match! + } + throw new SigningException(repo, "Signing certificate does not match!"); + } + +} diff --git a/app/src/main/java/org/fdroid/fdroid/#RepoXMLHandler.java# b/app/src/main/java/org/fdroid/fdroid/#RepoXMLHandler.java# new file mode 100644 index 000000000..5173da35d --- /dev/null +++ b/app/src/main/java/org/fdroid/fdroid/#RepoXMLHandler.java# @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2010-12 Ciaran Gultnieks, ciaran@ciarang.com + * Copyright (C) 2009 Roberto Jacinto, roberto.jacinto@caixamagica.pt + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.fdroid.fdroid; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.fdroid.fdroid.data.Apk; +import org.fdroid.fdroid.data.App; +import org.fdroid.fdroid.data.Repo; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import java.util.ArrayList; +import java.util.List; + +/** + * Parses the index.xml into Java data structures. + */ +public class RepoXMLHandler extends DefaultHandler { + + // The repo we're processing. + private final Repo repo; + + private List apksList = new ArrayList<>(); + + private App curapp; + private Apk curapk; + + private String currentApkHashType; + + // After processing the XML, these will be -1 if the index didn't specify + // them - otherwise it will be the value specified. + private int repoMaxAge = -1; + private int repoVersion; + private long repoTimestamp; + private String repoDescription; + private String repoName; + + // the X.509 signing certificate stored in the header of index.xml + private String repoSigningCert; + + private final StringBuilder curchars = new StringBuilder(); + + interface IndexReceiver { + void receiveRepo(String name, String description, String signingCert, int maxage, int version, long timestamp); + + void receiveApp(App app, List packages); + } + + private final IndexReceiver receiver; + + public RepoXMLHandler(Repo repo, @NonNull IndexReceiver receiver) { + this.repo = repo; + this.receiver = receiver; + } + + @Override + public void characters(char[] ch, int start, int length) { + curchars.append(ch, start, length); + } + + @Override + public void endElement(String uri, String localName, String qName) + throws SAXException { + + if ("application".equals(localName) && curapp != null) { + onApplicationParsed(); + } else if ("package".equals(localName) && curapk != null && curapp != null) { + apksList.add(curapk); + curapk = null; + } else if ("repo".equals(localName)) { + onRepoParsed(); + } else if (curchars.length() == 0) { + // All options below require non-empty content + return; + } + final String str = curchars.toString().trim(); + if (curapk != null) { + switch (localName) { + case "version": + curapk.versionName = str; + break; + case "versioncode": + curapk.versionCode = Utils.parseInt(str, -1); + break; + case "size": + curapk.size = Utils.parseInt(str, 0); + break; + case "hash": + if (currentApkHashType == null || "md5".equals(currentApkHashType)) { + if (curapk.hash == null) { + curapk.hash = str; + curapk.hashType = "SHA-256"; + } + } else if ("sha256".equals(currentApkHashType)) { + curapk.hash = str; + curapk.hashType = "SHA-256"; + } + break; + case "sig": + curapk.sig = str; + break; + case "srcname": + curapk.srcname = str; + break; + case "apkname": + curapk.apkName = str; + break; + case "sdkver": + curapk.minSdkVersion = Utils.parseInt(str, Apk.SDK_VERSION_MIN_VALUE); + break; + case "targetSdkVersion": + curapk.targetSdkVersion = Utils.parseInt(str, Apk.SDK_VERSION_MIN_VALUE); + break; + case "maxsdkver": + curapk.maxSdkVersion = Utils.parseInt(str, Apk.SDK_VERSION_MAX_VALUE); + if (curapk.maxSdkVersion == 0) { + // before fc0df0dcf4dd0d5f13de82d7cd9254b2b48cb62d, this could be 0 + curapk.maxSdkVersion = Apk.SDK_VERSION_MAX_VALUE; + } + break; + case "added": + curapk.added = Utils.parseDate(str, null); + break; + case "permissions": + curapk.permissions = Utils.parseCommaSeparatedString(str); + break; + case "features": + curapk.features = Utils.parseCommaSeparatedString(str); + break; + case "nativecode": + curapk.nativecode = Utils.parseCommaSeparatedString(str); + break; + } + } else if (curapp != null) { + switch (localName) { + case "name": + curapp.name = str; + break; + case "icon": + curapp.icon = str; + break; + case "description": + // This is the old-style description. We'll read it + // if present, to support old repos, but in newer + // repos it will get overwritten straight away! + curapp.description = "

    " + str + "

    "; + break; + case "desc": + // New-style description. + curapp.description = str; + break; + case "summary": + curapp.summary = str; + break; + case "license": + curapp.license = str; + break; + case "author": + curapp.author = str; + break; + case "email": + curapp.email = str; + break; + case "source": + curapp.sourceURL = str; + break; + case "changelog": + curapp.changelogURL = str; + break; + case "donate": + curapp.donateURL = str; + break; + case "bitcoin": + curapp.bitcoinAddr = str; + break; + case "litecoin": + curapp.litecoinAddr = str; + break; + case "flattr": + curapp.flattrID = str; + break; + case "web": + curapp.webURL = str; + break; + case "tracker": + curapp.trackerURL = str; + break; + case "added": + curapp.added = Utils.parseDate(str, null); + break; + case "lastupdated": + curapp.lastUpdated = Utils.parseDate(str, null); + break; + case "marketversion": + curapp.upstreamVersionName = str; + break; + case "marketvercode": + curapp.upstreamVersionCode = Utils.parseInt(str, -1); + break; + case "categories": + curapp.categories = Utils.parseCommaSeparatedString(str); + break; + case "antifeatures": + curapp.antiFeatures = Utils.parseCommaSeparatedString(str); + break; + case "requirements": + curapp.requirements = Utils.parseCommaSeparatedString(str); + break; + } + } else if ("description".equals(localName)) { + repoDescription = cleanWhiteSpace(str); + } + } + + private void onApplicationParsed() { + receiver.receiveApp(curapp, apksList); + curapp = null; + apksList = new ArrayList<>(); + // If the app packageName is already present in this apps list, then it + // means the same index file has a duplicate app, which should + // not be allowed. + // However, I'm thinking that it should be undefined behaviour, + // because it is probably a bug in the fdroid server that made it + // happen, and I don't *think* it will crash the client, because + // the first app will insert, the second one will update the newly + // inserted one. + + //////#TODO + + /* + + sample this as a xml handlers + should add screen shot id the project as it be + and also there is no way to contact developer + another is to create a place to collect userr data and vote for a package + + */ + } + + private void onRepoParsed() { + receiver.receiveRepo(repoName, repoDescription, repoSigningCert, repoMaxAge, repoVersion, repoTimestamp); + } + + @Override + public void startElement(String uri, String localName, String qName, + Attributes attributes) throws SAXException { + super.startElement(uri, localName, qName, attributes); + + if ("repo".equals(localName)) { + repoSigningCert = attributes.getValue("", "pubkey"); + repoMaxAge = Utils.parseInt(attributes.getValue("", "maxage"), -1); + repoVersion = Utils.parseInt(attributes.getValue("", "version"), -1); + repoName = cleanWhiteSpace(attributes.getValue("", "name")); + repoDescription = cleanWhiteSpace(attributes.getValue("", "description")); + repoTimestamp = parseLong(attributes.getValue("", "timestamp"), 0); + } else if ("application".equals(localName) && curapp == null) { + curapp = new App(); + curapp.packageName = attributes.getValue("", "id"); + } else if ("package".equals(localName) && curapp != null && curapk == null) { + curapk = new Apk(); + curapk.packageName = curapp.packageName; + curapk.repo = repo.getId(); + currentApkHashType = null; + + } else if ("hash".equals(localName) && curapk != null) { + currentApkHashType = attributes.getValue("", "type"); + } + curchars.setLength(0); + } + + private static String cleanWhiteSpace(@Nullable String str) { + return str == null ? null : str.replaceAll("\\s", " "); + } + + private static long parseLong(String str, long fallback) { + if (str == null || str.length() == 0) { + return fallback; + } + long result; + try { + result = Long.parseLong(str); + } catch (NumberFormatException e) { + result = fallback; + } + return result; + } +} diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index cff450ab3..0a1dbc924 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -13,7 +13,7 @@ نمایش یک آگهی هنگام موجود بودن به‌روز رسانی‌ها به‌روزرسانی تاریخچه چنین کاره‌ای پیدا نشد. - دربارهٔ F-Droid + دربارهٔ فروشگاه بِل رایانامه نصب شده نصب نشده @@ -28,7 +28,7 @@ افزودن کلید جانویسی به‌روز رسانی‌ها - به‌روزرسانی‌های اف‌دروید موجودند + به‌روزرسانی‌های فروشگاه بِل موجودند هیچ روش ارسال با بلوتوثی پیدا نشد، یکی را برگزینید! گزینش روش ارسال بلوتوث نشانی مخزن @@ -71,7 +71,7 @@ تا %s %1$s تا %2$s نیاز دارد: %1$s -اِف‌-دروید +فروشگاه بِل نگارش جدید با کلیدی متفاوت با قبلی امضا شده است. برای نصب نگارش جدید، باید نخست نگارش قدیمی را حذف کرد. لطفاً این کار را انجام داده و دوباره تلاش کنید. (به خاطر داشته باشید که حذف نصب، تمام داده‌های درونی ذخیره شدهٔ کاره را پاک می‌کند) نگارش @@ -81,7 +81,7 @@ آگهی‌های به‌روز رسانی روزهایی که کاره جدید یا اخیر محسوب شود: %s افزونهٔ ممتاز - استفاده از افزونهٔ ممتاز اف‌دروید برای نصب، به‌روز رسانی و حذف بسته‌ها + استفاده از افزونهٔ ممتاز فروشگاه بِل برای نصب، به‌روز رسانی و حذف بسته‌ها به‌روز رسانی/حذف افزونهٔ ممتاز گشودن صفحهٔ جزییات افزونهٔ ممتاز برای به‌روز رسانی/حذف آن نام مخزن محلّی شما @@ -123,7 +123,7 @@ این مخزن از پیش برپا شده است. تأیید کنید که می‌خواهید دوباره فعّالش کنید. اثر انگشت بد این یک نشانی اینترنتی معتیبر نیست. - هم‌رسانی اف‌دروید با بلوتوث + هم‌رسانی فروشگاه بِل با بلوتوث تنظیمات مخزن جدید @@ -146,7 +146,7 @@ همواره شامل کاره‌هایی که نیازمند صفحه‌لمسی هستند مخزن محلّی - اف‌دروید آمادهٔ تبادل است + فروشگاه بِل آمادهٔ تبادل است برای دیدن جزییات و اجازه به دیگرات برای تبادل کاره‌هایتان، لمس کنید. در حال حذف مخزن جاری… در حال افزودن %s به مخزن… @@ -211,7 +211,7 @@ تمام کاره‌ها به‌روز هستند.\n\nتبریک! تمام کاره‌هایتان به‌روز هستند (یا مخزن‌هایتان منقضی شده‌اند). دسترسی ریشه داده نشد - یا افزارهٔ اندرویدیتان روت شده نیست، یا درخواست ریشه برای اف‌دروید را رد کردید. + یا افزارهٔ اندرویدیتان روت شده نیست، یا درخواست ریشه برای فروشگاه بِل را رد کردید. حذف نصب برای تبادل، لمس کنید @@ -224,7 +224,7 @@ برای گشودن شبکه‌های موجود، ضربه بزنید برای تعویض به یک شبکهٔ وای‌فای، ضربه بزنید گشودن پویشگر QR - به اف‌دروید خوش آمدید! + به فروشگاه بِل خوش آمدید! می‌خواهید اکنون کاره‌ها را از %1$s بگیرید؟ دیگر این را نشان نده لازم است یک نفر رمز دیگری را پوییده، یا نشانی اینترنتیش را در مرورگری بنویسد. @@ -242,7 +242,7 @@ غیرقابل مشاهده از طریق وای‌فای نام افزاره نمی‌توانید کسی که دنبالشید را پیدا کنید؟ - ارسال اف‌دروید + ارسال فروشگاه بِل نمی‌توان افراد نزدیک را برای تبادل با آن‌ها پیدا کرد. در حال اتّصال تأیید تبادل @@ -273,36 +273,36 @@ تمام مخزن‌های دیگر خطایی ایاد نکردند. این مخزن هنوز استفاده نشده است. برای دیدن کاره‌هایی که فراهم می‌کند، باید فعّالش کنید. - حذف یک مخزن به این معناست که کاره‌هایش دیگر در اف‌دروید موجود نخواهند بود.\n\nتوجّه: تمام کاره‌های نصب شدهٔ پیشین، روی افزاره‌تان باقی خواهند ماند. + حذف یک مخزن به این معناست که کاره‌هایش دیگر در فروشگاه بِل موجود نخواهند بود.\n\nتوجّه: تمام کاره‌های نصب شدهٔ پیشین، روی افزاره‌تان باقی خواهند ماند. %1$s غیرفعّال شد.\n\nبرای نصب کاره‌ها از آن باید دوباره فعّالش کنید. - مخزن اف‌دروید %1$s ذخیره شد. - در حال گشتن به دنبال مخزن اف‌دروید در\n%1$s - هیچ کاره‌ای نصب نشده است.\n\nکاره‌هایی روی افزاره‌تان وجود دارند، ولی در اف‌دروید موجود نیستند. دلیل این امر می‌تواند این دلیل باشد که مخازنتان کاره‌هایتان را موجود ندارند یا باید به‌روز شوند. + مخزن فروشگاه بِل %1$s ذخیره شد. + در حال گشتن به دنبال مخزن فروشگاه بِل در\n%1$s + هیچ کاره‌ای نصب نشده است.\n\nکاره‌هایی روی افزاره‌تان وجود دارند، ولی در فروشگاه بِل موجود نیستند. دلیل این امر می‌تواند این دلیل باشد که مخازنتان کاره‌هایتان را موجود ندارند یا باید به‌روز شوند. هیچ کاره‌ای در این دسته نیست.\n\nتلاش کنید دسته‌ای دیگر برگزیده یا مخازنتان را برای گرفتن فهرست تازه‌ای از کاره‌ها به‌روز کنید. شکست در نصب به دلیل خطایی ناشناخته شکست در نصب به دلیل خطایی ناشناخته - افزونهٔ ممتاز اف‌دروید موجود نیست - این گزینه فقط هنگام نصب بودن افزونهٔ ممتاز اف‌دروید، موجود است. + افزونهٔ ممتاز فروشگاه بِل موجود نیست + این گزینه فقط هنگام نصب بودن افزونهٔ ممتاز فروشگاه بِل، موجود است. امضای افزونه اشتباه است! لطفاً یک گزارش خطا ایجاد کنید! اجازه‌های ممتاز به افزونه داده نشده است! لطفاً یک گزارش خطا ایجاد کنید! نصب گشودن افزونه - افزونهٔ ممتاز اف‌دروید با موفّقیت نصب شد - نصب افزونهٔ ممتاز اف‌دروید شکست خورد - افزونهٔ ممتاز اف‌دروید با موفّقیت نصب شد. این مار به اف‌دروید اجازه می‌دهد کاره‌ها را توسّط خودش نصب کرده، ارتفا دهد و حدف کند. - نصب افزونهٔ ممتاز اف‌دروید شکست خورد. روش نصب توسّط همهٔ توزیع‌های اندروید پشتیبانی نمی‌شود. لطفاً برای اطّلاعات بیش‌تر با ردیاب مشکل اف‌دروید مشاوره کنید. + افزونهٔ ممتاز فروشگاه بِل با موفّقیت نصب شد + نصب افزونهٔ ممتاز فروشگاه بِل شکست خورد + افزونهٔ ممتاز فروشگاه بِل با موفّقیت نصب شد. این مار به فروشگاه بِل اجازه می‌دهد کاره‌ها را توسّط خودش نصب کرده، ارتفا دهد و حدف کند. + نصب افزونهٔ ممتاز فروشگاه بِل شکست خورد. روش نصب توسّط همهٔ توزیع‌های اندروید پشتیبانی نمی‌شود. لطفاً برای اطّلاعات بیش‌تر با ردیاب مشکل فروشگاه بِل مشاوره کنید. در حال نصب… در حال نصب و راه‌انداری دوباره… در حال حذف نصب… - می‌خواهید افزونهٔ ممتاز اف‌دروید را نصب کنید؟ + می‌خواهید افزونهٔ ممتاز فروشگاه بِل را نصب کنید؟ این کار بیش از ۱۰ ثانیه زمان می‌برد. این کار بیش از ۱۰ ثانیه زمان برده و پس از آن، افزاره راه‌اندازی مجدّد خواهد شد. - می‌خواهید افزونهٔ ممتاز اف‌دروید را حذف کنید؟ - اگر دوستتان اف‌دروید داشته و NFC اش روشن است، افزاره‌هایتان را با هم تماس بدهید. + می‌خواهید افزونهٔ ممتاز فروشگاه بِل را حذف کنید؟ + اگر دوستتان فروشگاه بِل داشته و NFC اش روشن است، افزاره‌هایتان را با هم تماس بدهید. برای تبادل با استفاده از وای‌فای، مطمئن شوید که روی شبکهٔ یک‌سانی هستید. اگر به شبکهٔ یک‌سان دسترسی ندارید، یکی از شما می‌تواند یک نقطهٔ داغ وای‌فای ایجاد کند. رمز QR ای که پوییدید، شبیه یک رمز تبادل نیست. بلوتوث موجود نیست - نمی‌توان اف‌دروید را فرستاد، زیرا بلوتوث روی این افزاره موحود نیست. + نمی‌توان فروشگاه بِل را فرستاد، زیرا بلوتوث روی این افزاره موحود نیست. در حال بار کردن… هنگام اتّصال به افزاره خطایی رخ داد. به نظر می‌رسد با این وضع نمی‌توانیم تبادل کنیم. تبادل فعّال نیست @@ -323,7 +323,7 @@ هیچ برنامهٔ مطابقی وجود ندارد. هیچ برنامهٔ مطابقی برای به‌روز رسانی وجود ندارد. - اف‌دروید از کار افتاد + فروشگاه بِل از کار افتاد خطایی پیش‌بینی نشده رخ داد که برنامه را وادار به توقّف کرد. می‌خواهید جزئیات را برای کمک به حل کردن مشکل رایانامه کنید؟ در این‌جا می‌توانید اطّلاعات اضافی و نظرها را بیفزایید: استفاده از تور @@ -339,7 +339,7 @@ در حال بارگیری %1$s - در حال حاضر نصب افزونهٔ ممتاز اف‌دروید روی اندروید ۵.۱ یا بالاتر پشتیبانی نمی‌شود. + در حال حاضر نصب افزونهٔ ممتاز فروشگاه بِل روی اندروید ۵.۱ یا بالاتر پشتیبانی نمی‌شود. ۱ ساعت ۱ روز diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 4881e3840..2d6cb5fac 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -5,7 +5,7 @@ #ff999999 #ffdd2c00 - #ff1976d2 + #ffdd2c00 #ff0d47a1 #ff042570 #ff8ab000 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1820746a3..035a69d36 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,7 +1,7 @@ - F-Droid + Bel Market The new version is signed with a different key to the old one. To install the new version, the old one must be uninstalled first. Please do this and try again. (Note that uninstalling will erase any internal data stored by the application) It seems like this package is not compatible with your device. Do you want to try and install it anyway? @@ -27,7 +27,7 @@ Update history Days to consider apps new or recent: %s Privileged Extension - Use F-Droid Privileged Extension to install, update, and remove packages + Use Bel Market Privileged Extension to install, update, and remove packages Update/Uninstall Privileged Extension Open details screen of Privileged Extension to update/uninstall it Name of your Local Repo @@ -44,7 +44,7 @@ App Details No such app found. - About F-Droid + About Bel Market Version Website Source code @@ -82,7 +82,7 @@ Updates (%d) 1 update is available. %d updates are available. - F-Droid Updates Available + Bel Market Updates Available +%1$d more… No Bluetooth send method found, choose one! Choose Bluetooth send method @@ -101,7 +101,7 @@ Update Repos Repositories - Share F-Droid by Bluetooth + Share Bel Market by Bluetooth Settings About Search @@ -152,7 +152,7 @@ Always include apps that require touchscreen Local Repo - F-Droid is ready to swap + Bel Market is ready to swap Touch to view details and allow others to swap your apps. Deleting current repo… Adding %s to repo… @@ -216,12 +216,12 @@ Unknown Delete Repository? Deleting a repository means - apps from it will no longer be available from F-Droid.\n\nNote: All + apps from it will no longer be available from Bel Market.\n\nNote: All previously installed apps will remain on your device. Disabled "%1$s".\n\nYou will need to re-enable this repository to install apps from it. - Saved F-Droid repository %1$s. - Looking for F-Droid repository at\n%1$s + Saved Bel Market repository %1$s. + Looking for Bel Market repository at\n%1$s %s or later Up to %s %1$s up to %2$s @@ -255,7 +255,7 @@ Time Writing - No apps installed.\n\nThere are apps on your device, but they are not available from F-Droid. This could be because you need to update your repositories, or the repositories genuinely don\'t have your apps available. + No apps installed.\n\nThere are apps on your device, but they are not available from Bel Market. This could be because you need to update your repositories, or the repositories genuinely don\'t have your apps available. No apps in this category.\n\nTry selecting a different category or updating your repositories to get a fresh list of apps. All apps up to date.\n\nCongratulations! All of your apps are up to date (or your repositories are out of date). No matching installed applications. @@ -264,31 +264,31 @@ Requesting root access… Root access denied - Either your Android device is not rooted or you have denied root access for F-Droid. + Either your Android device is not rooted or you have denied root access for Bel Market. Failed to install due to an unknown error Failed to uninstall due to an unknown error - F-Droid Privileged Extension is not available - This option is only available when F-Droid Privileged Extension is installed. + Bel Market Privileged Extension is not available + This option is only available when Bel Market Privileged Extension is installed. The signature of the extension is wrong! Please create a bug report! The privileged permissions have not been granted to the extension! Please create a bug report! Install Open Extension - Successfully installed F-Droid Privileged Extension - Installation of F-Droid Privileged Extension failed - F-Droid Privileged Extension has been successfully installed. This allows F-Droid to install, upgrade and uninstall apps on its own. - The installation of F-Droid Privileged Extension has failed. The installation method is not supported by all Android distributions, please consult the F-Droid bug tracker for more information. + Successfully installed Bel Market Privileged Extension + Installation of Bel Market Privileged Extension failed + Bel Market Privileged Extension has been successfully installed. This allows Bel Market to install, upgrade and uninstall apps on its own. + The installation of Bel Market Privileged Extension has failed. The installation method is not supported by all Android distributions, please consult the Bel Market bug tracker for more information. Installing… Installing and rebooting… Uninstalling… - Do you want to install F-Droid Privileged Extension? + Do you want to install Bel Market Privileged Extension? This takes up to 10 seconds. This takes up to 10 seconds and the device will be <b>rebooted</b> afterwards. - Do you want to uninstall F-Droid Privileged Extension? + Do you want to uninstall Bel Market Privileged Extension? Uninstall - Installation of F-Droid Privileged Extension is currently not supported on Android 5.1 or later. + Installation of Bel Market Privileged Extension is currently not supported on Android 5.1 or later. Touch to swap - If your friend has F-Droid and NFC turned on touch your devices together. + If your friend has Bel Market and NFC turned on touch your devices together. Join the same Wi-Fi as your friend To swap using Wi-Fi, ensure you are on the same network. If you don\'t have access to the same network, one of you can create a Wi-Fi Hotspot. Help your friend join your hotspot @@ -299,7 +299,7 @@ Tap to open available networks Tap to switch to a Wi-Fi network Open QR Scanner - Welcome to F-Droid! + Welcome to Bel Market! Do you want to get apps from %1$s now? Don\'t show this again One person needs to scan the code, or type the URL of the other in a browser. @@ -318,13 +318,13 @@ Not visible via Wi-Fi Device Name Can\'t find who you\'re looking for? - Send F-Droid + Send Bel Market Could not find people nearby to swap with. Connecting Confirm swap The QR code you scanned doesn\'t look like a swap code. Bluetooth unavailable - Cannot send F-Droid, because Bluetooth is unavailable on this device. + Cannot send Bel Market, because Bluetooth is unavailable on this device. Loading… An error occurred while connecting to device, we can\'t seem to swap with it. Swapping not enabled @@ -383,7 +383,7 @@ Dark Night - F-Droid has crashed + Bel Market has crashed An unexpected error occurred forcing the application to stop. Would you like to e-mail the details to help fix the issue?