From d5d3abe2a3bc9fdc84c6d2b45cd3ec578679b9cc Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Fri, 1 Jun 2018 17:17:21 +0200 Subject: [PATCH 1/4] fix lint "Implied default locale in case conversion" find app/src/full/java/kellinwood/ -name \*.java |xargs sed -i 's,\.toLowerCase(),.toLowerCase(Locale.ROOT),g' --- app/lint.xml | 2 ++ .../zipsigner/DefaultResourceAdapter.java | 4 +++- .../zipsigner/optional/Fingerprint.java | 3 ++- .../security/zipsigner/optional/JKS.java | 24 +++++++++---------- .../optional/KeyStoreFileManager.java | 5 ++-- .../main/java/kellinwood/zipio/ZioEntry.java | 13 +++++----- .../kellinwood/zipio/ZioEntryInputStream.java | 9 +++---- .../main/java/kellinwood/zipio/ZipInput.java | 5 ++-- .../kellinwood/zipio/ZipListingHelper.java | 3 ++- 9 files changed, 39 insertions(+), 29 deletions(-) diff --git a/app/lint.xml b/app/lint.xml index f09a57bfc..a2eed735d 100644 --- a/app/lint.xml +++ b/app/lint.xml @@ -6,7 +6,9 @@ + + diff --git a/app/src/main/java/kellinwood/security/zipsigner/DefaultResourceAdapter.java b/app/src/main/java/kellinwood/security/zipsigner/DefaultResourceAdapter.java index 35a82532a..90d0235e8 100644 --- a/app/src/main/java/kellinwood/security/zipsigner/DefaultResourceAdapter.java +++ b/app/src/main/java/kellinwood/security/zipsigner/DefaultResourceAdapter.java @@ -1,6 +1,8 @@ package kellinwood.security.zipsigner; +import java.util.Locale; + /** * Default resource adapter. */ @@ -25,7 +27,7 @@ public class DefaultResourceAdapter implements ResourceAdapter { case GENERATING_SIGNATURE_BLOCK: return "Generating signature block file"; case COPYING_ZIP_ENTRY: - return String.format("Copying zip entry %d of %d", args[0], args[1]); + return String.format(Locale.ENGLISH, "Copying zip entry %d of %d", args[0], args[1]); default: throw new IllegalArgumentException("Unknown item " + item); } diff --git a/app/src/main/java/kellinwood/security/zipsigner/optional/Fingerprint.java b/app/src/main/java/kellinwood/security/zipsigner/optional/Fingerprint.java index c9d4be21b..7107b931f 100644 --- a/app/src/main/java/kellinwood/security/zipsigner/optional/Fingerprint.java +++ b/app/src/main/java/kellinwood/security/zipsigner/optional/Fingerprint.java @@ -7,6 +7,7 @@ import kellinwood.security.zipsigner.Base64; import org.bouncycastle.util.encoders.HexTranslator; import java.security.MessageDigest; +import java.util.Locale; /** * User: ken @@ -41,7 +42,7 @@ public class Fingerprint { builder.append((char) hex[i + 1]); if (i != (hex.length - 2)) builder.append(':'); } - return builder.toString().toUpperCase(); + return builder.toString().toUpperCase(Locale.ENGLISH); } catch (Exception x) { logger.error(x.getMessage(), x); } diff --git a/app/src/main/java/kellinwood/security/zipsigner/optional/JKS.java b/app/src/main/java/kellinwood/security/zipsigner/optional/JKS.java index 024a46aa8..fd919ab0f 100644 --- a/app/src/main/java/kellinwood/security/zipsigner/optional/JKS.java +++ b/app/src/main/java/kellinwood/security/zipsigner/optional/JKS.java @@ -23,7 +23,6 @@ power to enforce restrictions on reverse-engineering of their software, and it is irresponsible for them to claim they can. */ - package kellinwood.security.zipsigner.optional; import javax.crypto.EncryptedPrivateKeyInfo; @@ -53,6 +52,7 @@ import java.util.Date; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; +import java.util.Locale; import java.util.Vector; /** @@ -183,7 +183,7 @@ public class JKS extends KeyStoreSpi { public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException, UnrecoverableKeyException { - alias = alias.toLowerCase(); + alias = alias.toLowerCase(Locale.ENGLISH); if (!privateKeys.containsKey(alias)) return null; @@ -204,12 +204,12 @@ public class JKS extends KeyStoreSpi { } public Certificate[] engineGetCertificateChain(String alias) { - alias = alias.toLowerCase(); + alias = alias.toLowerCase(Locale.ENGLISH); return (Certificate[]) certChains.get(alias); } public Certificate engineGetCertificate(String alias) { - alias = alias.toLowerCase(); + alias = alias.toLowerCase(Locale.ENGLISH); if (engineIsKeyEntry(alias)) { Certificate[] certChain = (Certificate[]) certChains.get(alias); if (certChain != null && certChain.length > 0) return certChain[0]; @@ -218,7 +218,7 @@ public class JKS extends KeyStoreSpi { } public Date engineGetCreationDate(String alias) { - alias = alias.toLowerCase(); + alias = alias.toLowerCase(Locale.ENGLISH); return (Date) dates.get(alias); } @@ -226,7 +226,7 @@ public class JKS extends KeyStoreSpi { public void engineSetKeyEntry(String alias, Key key, char[] passwd, Certificate[] certChain) throws KeyStoreException { - alias = alias.toLowerCase(); + alias = alias.toLowerCase(Locale.ENGLISH); if (trustedCerts.containsKey(alias)) throw new KeyStoreException("\"" + alias + " is a trusted certificate entry"); privateKeys.put(alias, encryptKey(key, charsToBytes(passwd))); @@ -242,7 +242,7 @@ public class JKS extends KeyStoreSpi { public void engineSetKeyEntry(String alias, byte[] encodedKey, Certificate[] certChain) throws KeyStoreException { - alias = alias.toLowerCase(); + alias = alias.toLowerCase(Locale.ENGLISH); if (trustedCerts.containsKey(alias)) throw new KeyStoreException("\"" + alias + "\" is a trusted certificate entry"); try { @@ -263,7 +263,7 @@ public class JKS extends KeyStoreSpi { public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException { - alias = alias.toLowerCase(); + alias = alias.toLowerCase(Locale.ENGLISH); if (privateKeys.containsKey(alias)) throw new KeyStoreException("\"" + alias + "\" is a private key entry"); if (cert == null) @@ -276,7 +276,7 @@ public class JKS extends KeyStoreSpi { } public void engineDeleteEntry(String alias) throws KeyStoreException { - alias = alias.toLowerCase(); + alias = alias.toLowerCase(Locale.ENGLISH); aliases.remove(alias); } @@ -285,7 +285,7 @@ public class JKS extends KeyStoreSpi { } public boolean engineContainsAlias(String alias) { - alias = alias.toLowerCase(); + alias = alias.toLowerCase(Locale.ENGLISH); return aliases.contains(alias); } @@ -294,12 +294,12 @@ public class JKS extends KeyStoreSpi { } public boolean engineIsKeyEntry(String alias) { - alias = alias.toLowerCase(); + alias = alias.toLowerCase(Locale.ENGLISH); return privateKeys.containsKey(alias); } public boolean engineIsCertificateEntry(String alias) { - alias = alias.toLowerCase(); + alias = alias.toLowerCase(Locale.ENGLISH); return trustedCerts.containsKey(alias); } diff --git a/app/src/main/java/kellinwood/security/zipsigner/optional/KeyStoreFileManager.java b/app/src/main/java/kellinwood/security/zipsigner/optional/KeyStoreFileManager.java index 1123704d6..10e67f64c 100644 --- a/app/src/main/java/kellinwood/security/zipsigner/optional/KeyStoreFileManager.java +++ b/app/src/main/java/kellinwood/security/zipsigner/optional/KeyStoreFileManager.java @@ -17,6 +17,7 @@ import java.security.KeyStore; import java.security.Provider; import java.security.Security; import java.security.cert.Certificate; +import java.util.Locale; /** @@ -60,7 +61,7 @@ public class KeyStoreFileManager { public static KeyStore createKeyStore(String keystorePath, char[] password) throws Exception { KeyStore ks = null; - if (keystorePath.toLowerCase().endsWith(".bks")) { + if (keystorePath.toLowerCase(Locale.ENGLISH).endsWith(".bks")) { ks = KeyStore.getInstance("bks", new BouncyCastleProvider()); } else ks = new JksKeyStore(); ks.load(null, password); @@ -206,7 +207,7 @@ public class KeyStoreFileManager { try { KeyStore ks = loadKeyStore(keystorePath, storePass); - if (ks instanceof JksKeyStore) newKeyName = newKeyName.toLowerCase(); + if (ks instanceof JksKeyStore) newKeyName = newKeyName.toLowerCase(Locale.ENGLISH); if (ks.containsAlias(newKeyName)) throw new KeyNameConflictException(); diff --git a/app/src/main/java/kellinwood/zipio/ZioEntry.java b/app/src/main/java/kellinwood/zipio/ZioEntry.java index f61cec7c0..e09db6ad9 100644 --- a/app/src/main/java/kellinwood/zipio/ZioEntry.java +++ b/app/src/main/java/kellinwood/zipio/ZioEntry.java @@ -27,6 +27,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.SequenceInputStream; import java.util.Date; +import java.util.Locale; import java.util.zip.CRC32; import java.util.zip.Inflater; import java.util.zip.InflaterInputStream; @@ -91,7 +92,7 @@ public class ZioEntry implements Cloneable { this.compressedSize = this.size; if (getLogger().isDebugEnabled()) - getLogger().debug(String.format("Computing CRC for %s, size=%d", sourceDataFile, size)); + getLogger().debug(String.format(Locale.ENGLISH, "Computing CRC for %s, size=%d", sourceDataFile, size)); // compute CRC CRC32 crc = new CRC32(); @@ -292,10 +293,10 @@ public class ZioEntry implements Cloneable { output.writeBytes(alignBytes, 0, numAlignBytes); } - if (debug) getLogger().debug(String.format("Data position 0x%08x", output.getFilePointer())); + if (debug) getLogger().debug(String.format(Locale.ENGLISH, "Data position 0x%08x", output.getFilePointer())); if (data != null) { output.writeBytes(data); - if (debug) getLogger().debug(String.format("Wrote %d bytes", data.length)); + if (debug) getLogger().debug(String.format(Locale.ENGLISH, "Wrote %d bytes", data.length)); } else { if (debug) getLogger().debug(String.format("Seeking to position 0x%08x", dataPosition)); @@ -309,10 +310,10 @@ public class ZioEntry implements Cloneable { int numRead = zipInput.in.read(buffer, 0, (int) Math.min(compressedSize - totalCount, bufferSize)); if (numRead > 0) { output.writeBytes(buffer, 0, numRead); - if (debug) getLogger().debug(String.format("Wrote %d bytes", numRead)); + if (debug) getLogger().debug(String.format(Locale.ENGLISH, "Wrote %d bytes", numRead)); totalCount += numRead; } else - throw new IllegalStateException(String.format("EOF reached while copying %s with %d bytes left to go", filename, compressedSize - totalCount)); + throw new IllegalStateException(String.format(Locale.ENGLISH, "EOF reached while copying %s with %d bytes left to go", filename, compressedSize - totalCount)); } } } @@ -434,7 +435,7 @@ public class ZioEntry implements Cloneable { while (count != size) { int numRead = din.read(tmpdata, count, size - count); if (numRead < 0) - throw new IllegalStateException(String.format("Read failed, expecting %d bytes, got %d instead", size, count)); + throw new IllegalStateException(String.format(Locale.ENGLISH, "Read failed, expecting %d bytes, got %d instead", size, count)); count += numRead; } return tmpdata; diff --git a/app/src/main/java/kellinwood/zipio/ZioEntryInputStream.java b/app/src/main/java/kellinwood/zipio/ZioEntryInputStream.java index 7ed226c62..2d01f919c 100644 --- a/app/src/main/java/kellinwood/zipio/ZioEntryInputStream.java +++ b/app/src/main/java/kellinwood/zipio/ZioEntryInputStream.java @@ -23,6 +23,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.RandomAccessFile; +import java.util.Locale; /** @@ -47,7 +48,7 @@ public class ZioEntryInputStream extends InputStream { raf = entry.getZipInput().in; long dpos = entry.getDataPosition(); if (dpos >= 0) { - if (debug) log.debug(String.format("Seeking to %d", entry.getDataPosition())); + if (debug) log.debug(String.format(Locale.ENGLISH, "Seeking to %d", entry.getDataPosition())); raf.seek(entry.getDataPosition()); } else { // seeks to, then reads, the local header, causing the @@ -78,7 +79,7 @@ public class ZioEntryInputStream extends InputStream { @Override public int available() throws IOException { int available = size - offset; - if (debug) log.debug(String.format("Available = %d", available)); + if (debug) log.debug(String.format(Locale.ENGLISH, "Available = %d", available)); if (available == 0 && returnDummyByte) return 1; else return available; } @@ -119,7 +120,7 @@ public class ZioEntryInputStream extends InputStream { if (monitor != null) monitor.write(b, off, numRead); offset += numRead; } - if (debug) log.debug(String.format("Read %d bytes for read(b,%d,%d)", numRead, off, len)); + if (debug) log.debug(String.format(Locale.ENGLISH, "Read %d bytes for read(b,%d,%d)", numRead, off, len)); return numRead; } @@ -132,7 +133,7 @@ public class ZioEntryInputStream extends InputStream { public long skip(long n) throws IOException { long numToSkip = Math.min(n, available()); raf.seek(raf.getFilePointer() + numToSkip); - if (debug) log.debug(String.format("Skipped %d bytes", numToSkip)); + if (debug) log.debug(String.format(Locale.ENGLISH, "Skipped %d bytes", numToSkip)); return numToSkip; } } diff --git a/app/src/main/java/kellinwood/zipio/ZipInput.java b/app/src/main/java/kellinwood/zipio/ZipInput.java index 54f2ea101..60aef4c31 100644 --- a/app/src/main/java/kellinwood/zipio/ZipInput.java +++ b/app/src/main/java/kellinwood/zipio/ZipInput.java @@ -25,6 +25,7 @@ import java.io.IOException; import java.io.RandomAccessFile; import java.util.Collection; import java.util.LinkedHashMap; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.TreeSet; @@ -149,8 +150,8 @@ public class ZipInput implements Closeable { boolean debug = getLogger().isDebugEnabled(); if (debug) { - getLogger().debug(String.format("EOCD found in %d iterations", scanIterations)); - getLogger().debug(String.format("Directory entries=%d, size=%d, offset=%d/0x%08x", centralEnd.totalCentralEntries, + getLogger().debug(String.format(Locale.ENGLISH, "EOCD found in %d iterations", scanIterations)); + getLogger().debug(String.format(Locale.ENGLISH, "Directory entries=%d, size=%d, offset=%d/0x%08x", centralEnd.totalCentralEntries, centralEnd.centralDirectorySize, centralEnd.centralStartOffset, centralEnd.centralStartOffset)); ZipListingHelper.listHeader(getLogger()); diff --git a/app/src/main/java/kellinwood/zipio/ZipListingHelper.java b/app/src/main/java/kellinwood/zipio/ZipListingHelper.java index 62812fda2..1f7c74956 100644 --- a/app/src/main/java/kellinwood/zipio/ZipListingHelper.java +++ b/app/src/main/java/kellinwood/zipio/ZipListingHelper.java @@ -21,6 +21,7 @@ import kellinwood.logging.LoggerInterface; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.Locale; /** * @@ -38,7 +39,7 @@ public class ZipListingHelper { public static void listEntry(LoggerInterface log, ZioEntry entry) { int ratio = 0; if (entry.getSize() > 0) ratio = (100 * (entry.getSize() - entry.getCompressedSize())) / entry.getSize(); - log.debug(String.format("%8d %6s %8d %4d%% %s %08x %s", + log.debug(String.format(Locale.ENGLISH, "%8d %6s %8d %4d%% %s %08x %s", entry.getSize(), entry.getCompression() == 0 ? "Stored" : "Defl:N", entry.getCompressedSize(), From c8f804d0f66ae213febb85e2dcc14ae2d859bc84 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Wed, 6 Jun 2018 21:34:52 +0200 Subject: [PATCH 2/4] support push requests when using the index-v1.json Before, push requests were only supported when using index.xml. This adds support for using push requests in index-v1.json. `fdroid update` has been generating them in both index versions for a while now. --- .../org/fdroid/fdroid/IndexV1Updater.java | 45 +++++++++++++++++-- .../java/org/fdroid/fdroid/RepoUpdater.java | 9 ++-- .../fdroid/fdroid/data/RepoPushRequest.java | 2 +- 3 files changed, 48 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/fdroid/fdroid/IndexV1Updater.java b/app/src/main/java/org/fdroid/fdroid/IndexV1Updater.java index a0ac78cbb..1b61d69a1 100644 --- a/app/src/main/java/org/fdroid/fdroid/IndexV1Updater.java +++ b/app/src/main/java/org/fdroid/fdroid/IndexV1Updater.java @@ -1,3 +1,25 @@ +/* + * Copyright (C) 2017-2018 Hans-Christoph Steiner + * Copyright (C) 2017 Peter Serwylo + * Copyright (C) 2017 Chirayu Desai + * Copyright (C) 2018 Senecto Limited + * + * 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.content.ContentValues; @@ -20,6 +42,7 @@ 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.RepoPushRequest; import org.fdroid.fdroid.data.Schema; import org.fdroid.fdroid.net.Downloader; import org.fdroid.fdroid.net.DownloaderFactory; @@ -312,9 +335,8 @@ public class IndexV1Updater extends RepoUpdater { repoPersister.commit(contentValues, repo.getId()); profiler.log("Persited to database."); - - // TODO RepoUpdater.processRepoPushRequests(context, repoPushRequestList); - Utils.debugLog(TAG, "Repo Push Requests: " + requests); + processRepoPushRequests(requests); + Utils.debugLog(TAG, "Completed Repo Push Requests: " + requests); } private int getIntRepoValue(Map repoMap, String key) { @@ -434,4 +456,21 @@ public class IndexV1Updater extends RepoUpdater { throw new SigningException("Signing certificate does not match!"); } + /** + * The {@code index-v1} version of {@link RepoUpdater#processRepoPushRequests(List)} + */ + private void processRepoPushRequests(Map requests) { + if (requests == null) { + Utils.debugLog(TAG, "RepoPushRequests are null"); + } else { + List repoPushRequestList = new ArrayList<>(); + for (Map.Entry requestEntry : requests.entrySet()) { + String request = requestEntry.getKey(); + for (String packageName : requestEntry.getValue()) { + repoPushRequestList.add(new RepoPushRequest(request, packageName, null)); + } + } + processRepoPushRequests(repoPushRequestList); + } + } } diff --git a/app/src/main/java/org/fdroid/fdroid/RepoUpdater.java b/app/src/main/java/org/fdroid/fdroid/RepoUpdater.java index 95fedf9c5..ba6095efd 100644 --- a/app/src/main/java/org/fdroid/fdroid/RepoUpdater.java +++ b/app/src/main/java/org/fdroid/fdroid/RepoUpdater.java @@ -1,7 +1,8 @@ /* + * Copyright (C) 2018 Senecto Limited * Copyright (C) 2016 Blue Jay Wireless * Copyright (C) 2015-2016 Daniel Martí - * Copyright (C) 2014-2016 Hans-Christoph Steiner + * Copyright (C) 2014-2018 Hans-Christoph Steiner * Copyright (C) 2014-2016 Peter Serwylo * * This program is free software; you can redistribute it and/or @@ -162,7 +163,7 @@ public class RepoUpdater { // successful download, then we will have a file ready to use: cacheTag = downloader.getCacheTag(); processDownloadedFile(downloader.outputFile); - processRepoPushRequests(); + processRepoPushRequests(repoPushRequestList); } return true; } @@ -448,8 +449,8 @@ public class RepoUpdater { * should always accept, prompt the user, or ignore those requests on a * per repo basis. */ - void processRepoPushRequests() { - for (RepoPushRequest repoPushRequest : repoPushRequestList) { + void processRepoPushRequests(List requestEntries) { + for (RepoPushRequest repoPushRequest : requestEntries) { String packageName = repoPushRequest.packageName; PackageInfo packageInfo = Utils.getPackageInfo(context, packageName); if (RepoPushRequest.INSTALL.equals(repoPushRequest.request)) { diff --git a/app/src/main/java/org/fdroid/fdroid/data/RepoPushRequest.java b/app/src/main/java/org/fdroid/fdroid/data/RepoPushRequest.java index 1c89b0884..49082c873 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/RepoPushRequest.java +++ b/app/src/main/java/org/fdroid/fdroid/data/RepoPushRequest.java @@ -38,7 +38,7 @@ public class RepoPushRequest { @Nullable public final Integer versionCode; - public RepoPushRequest(String request, String packageName, String versionCode) { + public RepoPushRequest(String request, String packageName, @Nullable String versionCode) { this.request = request; this.packageName = packageName; From 3cb34aa4b0d73e824be8fbfd765a0f758e8dd514 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Wed, 6 Jun 2018 18:02:08 +0200 Subject: [PATCH 3/4] prevent crash if push uninstall request is app not in any repo --- app/src/main/java/org/fdroid/fdroid/RepoUpdater.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/fdroid/fdroid/RepoUpdater.java b/app/src/main/java/org/fdroid/fdroid/RepoUpdater.java index ba6095efd..80e1ad988 100644 --- a/app/src/main/java/org/fdroid/fdroid/RepoUpdater.java +++ b/app/src/main/java/org/fdroid/fdroid/RepoUpdater.java @@ -487,7 +487,11 @@ public class RepoUpdater { || repoPushRequest.versionCode == packageInfo.versionCode) { Apk apk = ApkProvider.Helper.findApkFromAnyRepo(context, repoPushRequest.packageName, packageInfo.versionCode); - InstallerService.uninstall(context, apk); + if (apk == null) { + Log.i(TAG, "Push " + repoPushRequest.packageName + " request not found in any repo!"); + } else { + InstallerService.uninstall(context, apk); + } } else { Utils.debugLog(TAG, "ignoring request based on versionCode:" + repoPushRequest); } From 3878b781fdb38970c307b08ba5fa26f478bcd775 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Fri, 8 Jun 2018 12:11:19 +0200 Subject: [PATCH 4/4] maven.google.com must be added before jcenter() * https://stackoverflow.com/a/50570206 * https://stackoverflow.com/a/50563942 * https://developer.android.com/studio/build/dependencies#google-maven --- build.gradle | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/build.gradle b/build.gradle index b91938e06..30be214f4 100644 --- a/build.gradle +++ b/build.gradle @@ -1,10 +1,7 @@ buildscript { repositories { + maven { url 'https://maven.google.com/' } // :-| must be before jcenter() jcenter() - maven { - url 'https://maven.google.com/' - name 'Google' - } } dependencies { classpath 'com.android.tools.build:gradle:3.1.1' @@ -12,10 +9,7 @@ buildscript { } allprojects { repositories { + maven { url 'https://maven.google.com/' } // :-| must be before jcenter() jcenter() - maven { - url 'https://maven.google.com/' - name 'Google' - } } }