diff --git a/F-Droid/src/org/fdroid/fdroid/privileged/IPrivilegedCallback.aidl b/F-Droid/src/org/fdroid/fdroid/privileged/IPrivilegedCallback.aidl new file mode 100644 index 000000000..fc476fafe --- /dev/null +++ b/F-Droid/src/org/fdroid/fdroid/privileged/IPrivilegedCallback.aidl @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2015 Dominik Schürmann + * + * 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.privileged; + +interface IPrivilegedCallback { + + void handleResult(in String packageName, in int returnCode); + +} \ No newline at end of file diff --git a/F-Droid/src/org/fdroid/fdroid/privileged/IPrivilegedService.aidl b/F-Droid/src/org/fdroid/fdroid/privileged/IPrivilegedService.aidl new file mode 100644 index 000000000..b5b74ff4b --- /dev/null +++ b/F-Droid/src/org/fdroid/fdroid/privileged/IPrivilegedService.aidl @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2015 Dominik Schürmann + * + * 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.privileged; + +import org.fdroid.fdroid.privileged.IPrivilegedCallback; + +/** + * Asynchronous (oneway) IPC calls! + */ +oneway interface IPrivilegedService { + + /** + * Docs based on PackageManager.installPackage() + * + * Install a package. Since this may take a little while, the result will + * be posted back to the given callback. An installation will fail if the + * package named in the package file's manifest is already installed, or if there's no space + * available on the device. + * + * @param packageURI The location of the package file to install. This can be a 'file:' or a + * 'content:' URI. + * @param flags - possible values: {@link #INSTALL_FORWARD_LOCK}, + * {@link #INSTALL_REPLACE_EXISTING}, {@link #INSTALL_ALLOW_TEST}. + * @param installerPackageName Optional package name of the application that is performing the + * installation. This identifies which market the package came from. + * @param callback An callback to get notified when the package installation is + * complete. + */ + void installPackage(in Uri packageURI, in int flags, in String installerPackageName, + in IPrivilegedCallback callback); + + + /** + * Docs based on PackageManager.deletePackage() + * + * Attempts to delete a package. Since this may take a little while, the result will + * be posted back to the given observer. A deletion will fail if the + * named package cannot be found, or if the named package is a "system package". + * + * @param packageName The name of the package to delete + * @param flags - possible values: {@link #DELETE_KEEP_DATA}, + * {@link #DELETE_ALL_USERS}. + * @param callback An callback to get notified when the package deletion is + * complete. + */ + void deletePackage(in String packageName, in int flags, in IPrivilegedCallback callback); + +} \ No newline at end of file diff --git a/Privileged-F-Droid/.gitignore b/Privileged-F-Droid/.gitignore new file mode 100644 index 000000000..a44cc0f0f --- /dev/null +++ b/Privileged-F-Droid/.gitignore @@ -0,0 +1,33 @@ +#Android specific +bin +gen +obj +lint.xml +local.properties +release.properties +ant.properties +*.class +*.apk + +#Gradle +.gradle +build +gradle.properties + +#Maven +target +pom.xml.* + +#Eclipse +.project +.classpath +.settings +.metadata + +#IntelliJ IDEA +.idea +*.iml + +#Lint output +lint-report.html +lint-report_files/* \ No newline at end of file diff --git a/Privileged-F-Droid/build.gradle b/Privileged-F-Droid/build.gradle new file mode 100644 index 000000000..5545f4366 --- /dev/null +++ b/Privileged-F-Droid/build.gradle @@ -0,0 +1,51 @@ +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:1.3.1' + } +} + +apply plugin: 'com.android.application' + +android { + compileSdkVersion 22 + buildToolsVersion '23.0.0' + + defaultConfig { + minSdkVersion 8 + targetSdkVersion 22 + versionCode 1 + versionName "1.0" + } + + compileOptions { + compileOptions.encoding = "UTF-8" + + // Use Java 1.7, requires minSdk 8 + sourceCompatibility JavaVersion.VERSION_1_7 + targetCompatibility JavaVersion.VERSION_1_7 + } + + buildTypes { + release { + minifyEnabled true + + // Reference them in the java files with e.g. BuildConfig.F_DROID_CERT_SHA512. + buildConfigField "String", "F_DROID_CERT_SHA512", "\"9ba5d51a0d5c3627e270c2542b761937c721b0d8a1caed88bd3b2f21add138c2f16e295ce67bef21b21e0b75d5a0c6fd13d67efeb85c198cffa365755c94f4c2\"" + } + + debug { + + // Reference them in the java files with e.g. BuildConfig.F_DROID_CERT_SHA512. + buildConfigField "String", "F_DROID_CERT_SHA512", "\"9ba5d51a0d5c3627e270c2542b761937c721b0d8a1caed88bd3b2f21add138c2f16e295ce67bef21b21e0b75d5a0c6fd13d67efeb85c198cffa365755c94f4c2\"" + } + } + + lintOptions { + // Do not abort build if lint finds errors + abortOnError false + } + +} diff --git a/Privileged-F-Droid/src/main/AndroidManifest.xml b/Privileged-F-Droid/src/main/AndroidManifest.xml new file mode 100644 index 000000000..63bf561ed --- /dev/null +++ b/Privileged-F-Droid/src/main/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + diff --git a/Privileged-F-Droid/src/main/aidl/org/fdroid/fdroid/privileged/IPrivilegedCallback.aidl b/Privileged-F-Droid/src/main/aidl/org/fdroid/fdroid/privileged/IPrivilegedCallback.aidl new file mode 100644 index 000000000..fc476fafe --- /dev/null +++ b/Privileged-F-Droid/src/main/aidl/org/fdroid/fdroid/privileged/IPrivilegedCallback.aidl @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2015 Dominik Schürmann + * + * 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.privileged; + +interface IPrivilegedCallback { + + void handleResult(in String packageName, in int returnCode); + +} \ No newline at end of file diff --git a/Privileged-F-Droid/src/main/aidl/org/fdroid/fdroid/privileged/IPrivilegedService.aidl b/Privileged-F-Droid/src/main/aidl/org/fdroid/fdroid/privileged/IPrivilegedService.aidl new file mode 100644 index 000000000..b5b74ff4b --- /dev/null +++ b/Privileged-F-Droid/src/main/aidl/org/fdroid/fdroid/privileged/IPrivilegedService.aidl @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2015 Dominik Schürmann + * + * 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.privileged; + +import org.fdroid.fdroid.privileged.IPrivilegedCallback; + +/** + * Asynchronous (oneway) IPC calls! + */ +oneway interface IPrivilegedService { + + /** + * Docs based on PackageManager.installPackage() + * + * Install a package. Since this may take a little while, the result will + * be posted back to the given callback. An installation will fail if the + * package named in the package file's manifest is already installed, or if there's no space + * available on the device. + * + * @param packageURI The location of the package file to install. This can be a 'file:' or a + * 'content:' URI. + * @param flags - possible values: {@link #INSTALL_FORWARD_LOCK}, + * {@link #INSTALL_REPLACE_EXISTING}, {@link #INSTALL_ALLOW_TEST}. + * @param installerPackageName Optional package name of the application that is performing the + * installation. This identifies which market the package came from. + * @param callback An callback to get notified when the package installation is + * complete. + */ + void installPackage(in Uri packageURI, in int flags, in String installerPackageName, + in IPrivilegedCallback callback); + + + /** + * Docs based on PackageManager.deletePackage() + * + * Attempts to delete a package. Since this may take a little while, the result will + * be posted back to the given observer. A deletion will fail if the + * named package cannot be found, or if the named package is a "system package". + * + * @param packageName The name of the package to delete + * @param flags - possible values: {@link #DELETE_KEEP_DATA}, + * {@link #DELETE_ALL_USERS}. + * @param callback An callback to get notified when the package deletion is + * complete. + */ + void deletePackage(in String packageName, in int flags, in IPrivilegedCallback callback); + +} \ No newline at end of file diff --git a/Privileged-F-Droid/src/main/java/android/content/pm/IPackageDeleteObserver.java b/Privileged-F-Droid/src/main/java/android/content/pm/IPackageDeleteObserver.java new file mode 100644 index 000000000..be0d4de81 --- /dev/null +++ b/Privileged-F-Droid/src/main/java/android/content/pm/IPackageDeleteObserver.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2015 Dominik Schürmann + * + * 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 android.content.pm; + +import android.os.Binder; +import android.os.IBinder; +import android.os.IInterface; +import android.os.Parcel; +import android.os.RemoteException; + +/** + * Just a non-working implementation of this Stub to satisfy compiler! + */ +public interface IPackageDeleteObserver extends IInterface { + + abstract class Stub extends Binder implements android.content.pm.IPackageDeleteObserver { + + public Stub() { + throw new RuntimeException("Stub!"); + } + + public static IPackageDeleteObserver asInterface(IBinder obj) { + throw new RuntimeException("Stub!"); + } + + public IBinder asBinder() { + throw new RuntimeException("Stub!"); + } + + public boolean onTransact(int code, Parcel data, Parcel reply, int flags) + throws RemoteException { + throw new RuntimeException("Stub!"); + } + } + + void packageDeleted(java.lang.String packageName, int returnCode) throws RemoteException; +} diff --git a/Privileged-F-Droid/src/main/java/android/content/pm/IPackageInstallObserver.java b/Privileged-F-Droid/src/main/java/android/content/pm/IPackageInstallObserver.java new file mode 100644 index 000000000..ae5b3ab12 --- /dev/null +++ b/Privileged-F-Droid/src/main/java/android/content/pm/IPackageInstallObserver.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2015 Dominik Schürmann + * + * 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 android.content.pm; + +import android.os.Binder; +import android.os.IBinder; +import android.os.IInterface; +import android.os.Parcel; +import android.os.RemoteException; + +/** + * Just a non-working implementation of this Stub to satisfy compiler! + */ +public interface IPackageInstallObserver extends IInterface { + + abstract class Stub extends Binder implements android.content.pm.IPackageInstallObserver { + + public Stub() { + throw new RuntimeException("Stub!"); + } + + public static android.content.pm.IPackageInstallObserver asInterface(IBinder obj) { + throw new RuntimeException("Stub!"); + } + + public IBinder asBinder() { + throw new RuntimeException("Stub!"); + } + + public boolean onTransact(int code, Parcel data, Parcel reply, int flags) + throws RemoteException { + throw new RuntimeException("Stub!"); + } + } + + void packageInstalled(String packageName, int returnCode) throws RemoteException; +} \ No newline at end of file diff --git a/Privileged-F-Droid/src/main/java/org/fdroid/fdroid/privileged/PrivilegedService.java b/Privileged-F-Droid/src/main/java/org/fdroid/fdroid/privileged/PrivilegedService.java new file mode 100644 index 000000000..b727e7408 --- /dev/null +++ b/Privileged-F-Droid/src/main/java/org/fdroid/fdroid/privileged/PrivilegedService.java @@ -0,0 +1,305 @@ +/* + * Copyright (C) 2015 Dominik Schürmann + * + * 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.privileged; + +import android.annotation.SuppressLint; +import android.app.Service; +import android.content.Intent; +import android.content.pm.IPackageDeleteObserver; +import android.content.pm.IPackageInstallObserver; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.Signature; +import android.net.Uri; +import android.os.Binder; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.reflect.Method; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * This service provides an API via AIDL IPC for the main F-Droid app to install/delete packages. + *

+ * Security: + * Binding only works when,... + * - packageName is "org.fdroid.fdroid" + * - signature is equal or BuildConfig.DEBUG + */ +public class PrivilegedService extends Service { + + public static final String TAG = "PrivilegedFDroid"; + + private static final String F_DROID_PACKAGE = "org.fdroid.fdroid"; + + private Method mInstallMethod; + private Method mDeleteMethod; + + private void installPackageImpl(Uri packageURI, int flags, String installerPackageName, + final IPrivilegedCallback callback) { + + // Internal callback from the system + IPackageInstallObserver.Stub installObserver = new IPackageInstallObserver.Stub() { + @Override + public void packageInstalled(String packageName, int returnCode) throws RemoteException { + // forward this internal callback to our callback + try { + callback.handleResult(packageName, returnCode); + } catch (RemoteException e1) { + Log.e(TAG, "RemoteException", e1); + } + } + }; + + // execute internal method + try { + mInstallMethod.invoke(getPackageManager(), packageURI, installObserver, + flags, installerPackageName); + } catch (Exception e) { + Log.e(TAG, "Android not compatible!", e); + try { + callback.handleResult(null, 0); + } catch (RemoteException e1) { + Log.e(TAG, "RemoteException", e1); + } + } + } + + private void deletePackageImpl(String packageName, int flags, final IPrivilegedCallback callback) { + + // Internal callback from the system + IPackageDeleteObserver.Stub deleteObserver = new IPackageDeleteObserver.Stub() { + @Override + public void packageDeleted(String packageName, int returnCode) throws RemoteException { + // forward this internal callback to our callback + try { + callback.handleResult(packageName, returnCode); + } catch (RemoteException e1) { + Log.e(TAG, "RemoteException", e1); + } + } + }; + + // execute internal method + try { + mDeleteMethod.invoke(getPackageManager(), packageName, deleteObserver, flags); + } catch (Exception e) { + Log.e(TAG, "Android not compatible!", e); + try { + callback.handleResult(null, 0); + } catch (RemoteException e1) { + Log.e(TAG, "RemoteException", e1); + } + } + + } + + private final IPrivilegedService.Stub mBinder = new IPrivilegedService.Stub() { + @Override + public void installPackage(Uri packageURI, int flags, String installerPackageName, + IPrivilegedCallback callback) { + if (isAllowed()) { + installPackageImpl(packageURI, flags, installerPackageName, callback); + } + } + + @Override + public void deletePackage(String packageName, int flags, IPrivilegedCallback callback) { + if (isAllowed()) { + deletePackageImpl(packageName, flags, callback); + } + } + }; + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + private boolean isAllowed() { + // Check that binding app is allowed to use this API + try { + + barrierPackageName(); + barrierPackageCertificate(); + + } catch (WrongPackageCertificateException e) { + Log.e(TAG, "package certificate is not allowed!", e); + return false; + } catch (WrongPackageNameException e) { + Log.e(TAG, "package name is not allowed!", e); + return false; + } + + return true; + } + + /** + * Checks if process that binds to this service (i.e. the package name corresponding to the + * process) is allowed. Only returns when package name is allowed! + * + * @throws WrongPackageNameException + */ + private void barrierPackageName() throws WrongPackageNameException { + int uid = Binder.getCallingUid(); + String[] callingPackages = getPackageManager().getPackagesForUid(uid); + + // is calling package allowed to use this service? + for (String currentPkg : callingPackages) { + if (F_DROID_PACKAGE.equals(currentPkg)) { + return; + } + } + + throw new WrongPackageNameException("package name is not allowed"); + } + + private void barrierPackageCertificate() throws WrongPackageCertificateException { + String packageName = getCurrentCallingPackage(); + + byte[] packageCertificate; + try { + packageCertificate = getPackageCertificate(packageName); + } catch (PackageManager.NameNotFoundException e) { + throw new WrongPackageCertificateException(e.getMessage()); + } + + MessageDigest md; + try { + md = MessageDigest.getInstance("SHA-512"); + } catch (NoSuchAlgorithmException e) { + throw new WrongPackageCertificateException("SHA-512 not available!"); + } + byte[] hash = md.digest(packageCertificate); + + Log.d(TAG, "hash:" + getHex(hash)); + Log.d(TAG, "F_DROID_CERT_SHA512:" + BuildConfig.F_DROID_CERT_SHA512); + + if (getHex(hash).equals(BuildConfig.F_DROID_CERT_SHA512) + || BuildConfig.DEBUG) { + return; + } + + throw new WrongPackageCertificateException("certificate not allowed!"); + } + + private byte[] getPackageCertificate(String packageName) throws PackageManager.NameNotFoundException { + // we do check the byte array of *all* signatures + @SuppressLint("PackageManagerGetSignatures") + PackageInfo pkgInfo = getPackageManager().getPackageInfo(packageName, PackageManager.GET_SIGNATURES); + + // NOTE: Silly Android API naming: Signatures are actually certificates + Signature[] certificates = pkgInfo.signatures; + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + for (Signature cert : certificates) { + try { + outputStream.write(cert.toByteArray()); + } catch (IOException e) { + throw new RuntimeException("Should not happen! Writing ByteArrayOutputStream to concat certificates failed"); + } + } + + // Even if an apk has several certificates, these certificates should never change + // Google Play does not allow the introduction of new certificates into an existing apk + // Also see this attack: http://stackoverflow.com/a/10567852 + return outputStream.toByteArray(); + } + + /** + * Returns package name associated with the UID, which is assigned to the process that sent you the + * current transaction that is being processed :) + * + * @return package name + */ + protected String getCurrentCallingPackage() { + String[] callingPackages = getPackageManager().getPackagesForUid(Binder.getCallingUid()); + + // NOTE: No support for sharedUserIds + // callingPackages contains more than one entry when sharedUserId has been used + // No plans to support sharedUserIds due to many bugs connected to them: + // http://java-hamster.blogspot.de/2010/05/androids-shareduserid.html + return callingPackages[0]; + } + + @Override + public void onCreate() { + super.onCreate(); + + // get internal methods via reflection + try { + Class[] installTypes = { + Uri.class, IPackageInstallObserver.class, int.class, + String.class + }; + Class[] deleteTypes = { + String.class, IPackageDeleteObserver.class, + int.class + }; + + PackageManager pm = getPackageManager(); + mInstallMethod = pm.getClass().getMethod("installPackage", installTypes); + mDeleteMethod = pm.getClass().getMethod("deletePackage", deleteTypes); + } catch (NoSuchMethodException e) { + Log.e(TAG, "Android not compatible!", e); + stopSelf(); + } + } + + private String getHex(byte[] byteData) { + StringBuilder hexString = new StringBuilder(); + for (byte aByteData : byteData) { + String hex = Integer.toHexString(0xff & aByteData); + if (hex.length() == 1) { + hexString.append('0'); + } + hexString.append(hex); + } + + return hexString.toString(); + } + + public static class WrongPackageCertificateException extends Exception { + private static final long serialVersionUID = -1294642703122196028L; + + public WrongPackageCertificateException(String message) { + super(message); + } + } + + public static class WrongPackageNameException extends Exception { + private static final long serialVersionUID = -2294642703111196028L; + + public WrongPackageNameException(String message) { + super(message); + } + } + + public static class AndroidNotCompatibleException extends Exception { + private static final long serialVersionUID = -3294642703111196028L; + + public AndroidNotCompatibleException(String message) { + super(message); + } + } +} diff --git a/Privileged-F-Droid/src/main/res/drawable-hdpi/ic_launcher.png b/Privileged-F-Droid/src/main/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 000000000..88138a47a Binary files /dev/null and b/Privileged-F-Droid/src/main/res/drawable-hdpi/ic_launcher.png differ diff --git a/Privileged-F-Droid/src/main/res/drawable-ldpi/ic_launcher.png b/Privileged-F-Droid/src/main/res/drawable-ldpi/ic_launcher.png new file mode 100644 index 000000000..f1e8bc847 Binary files /dev/null and b/Privileged-F-Droid/src/main/res/drawable-ldpi/ic_launcher.png differ diff --git a/Privileged-F-Droid/src/main/res/drawable-mdpi/ic_launcher.png b/Privileged-F-Droid/src/main/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 000000000..18ef68deb Binary files /dev/null and b/Privileged-F-Droid/src/main/res/drawable-mdpi/ic_launcher.png differ diff --git a/Privileged-F-Droid/src/main/res/drawable-xhdpi/ic_launcher.png b/Privileged-F-Droid/src/main/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 000000000..98cb52a3e Binary files /dev/null and b/Privileged-F-Droid/src/main/res/drawable-xhdpi/ic_launcher.png differ diff --git a/Privileged-F-Droid/src/main/res/drawable-xxhdpi/ic_launcher.png b/Privileged-F-Droid/src/main/res/drawable-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..9d29cc939 Binary files /dev/null and b/Privileged-F-Droid/src/main/res/drawable-xxhdpi/ic_launcher.png differ diff --git a/Privileged-F-Droid/src/main/res/drawable-xxxhdpi/ic_launcher.png b/Privileged-F-Droid/src/main/res/drawable-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..c012986f5 Binary files /dev/null and b/Privileged-F-Droid/src/main/res/drawable-xxxhdpi/ic_launcher.png differ diff --git a/Privileged-F-Droid/src/main/res/values/strings.xml b/Privileged-F-Droid/src/main/res/values/strings.xml new file mode 100644 index 000000000..e375b6e4c --- /dev/null +++ b/Privileged-F-Droid/src/main/res/values/strings.xml @@ -0,0 +1,6 @@ + + + + F-Droid Privileged + +