diff --git a/mastodon/src/main/java/org/joinmastodon/android/updater/GithubSelfUpdater.java b/mastodon/src/main/java/org/joinmastodon/android/updater/GithubSelfUpdater.java
index 90a20e815..27a61cb28 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/updater/GithubSelfUpdater.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/updater/GithubSelfUpdater.java
@@ -20,7 +20,7 @@ public abstract class GithubSelfUpdater{
}
public static boolean needSelfUpdating(){
- return BuildConfig.BUILD_TYPE.equals("githubRelease") || BuildConfig.BUILD_TYPE.equals("debug");
+ return BuildConfig.BUILD_TYPE.equals("githubRelease") || BuildConfig.BUILD_TYPE.equals("debug") || BuildConfig.BUILD_TYPE.equals("nightly");
}
public abstract void checkForUpdates();
diff --git a/mastodon/src/nightly/AndroidManifest.xml b/mastodon/src/nightly/AndroidManifest.xml
index cb2da7db1..539f6cb36 100644
--- a/mastodon/src/nightly/AndroidManifest.xml
+++ b/mastodon/src/nightly/AndroidManifest.xml
@@ -15,6 +15,11 @@
+
\ No newline at end of file
diff --git a/mastodon/src/nightly/java/org/joinmastodon/android/updater/GithubSelfUpdaterImpl.java b/mastodon/src/nightly/java/org/joinmastodon/android/updater/GithubSelfUpdaterImpl.java
new file mode 100644
index 000000000..76eb2d853
--- /dev/null
+++ b/mastodon/src/nightly/java/org/joinmastodon/android/updater/GithubSelfUpdaterImpl.java
@@ -0,0 +1,369 @@
+package org.joinmastodon.android.updater;
+
+import android.app.Activity;
+import android.app.DownloadManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.pm.PackageInstaller;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+import org.joinmastodon.android.BuildConfig;
+import org.joinmastodon.android.E;
+import org.joinmastodon.android.GlobalUserPreferences;
+import org.joinmastodon.android.MastodonApp;
+import org.joinmastodon.android.R;
+import org.joinmastodon.android.api.MastodonAPIController;
+import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
+
+import java.io.File;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import androidx.annotation.Keep;
+import okhttp3.Call;
+import okhttp3.Request;
+import okhttp3.Response;
+
+@Keep
+public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
+ private static final long CHECK_PERIOD=6*3600*1000L;
+ private static final String TAG="GithubSelfUpdater";
+
+ private UpdateState state=UpdateState.NO_UPDATE;
+ private UpdateInfo info;
+ private long downloadID;
+ private BroadcastReceiver downloadCompletionReceiver=new BroadcastReceiver(){
+ @Override
+ public void onReceive(Context context, Intent intent){
+ if(downloadID!=0 && intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0)==downloadID){
+ MastodonApp.context.unregisterReceiver(this);
+ setState(UpdateState.DOWNLOADED);
+ }
+ }
+ };
+
+ public GithubSelfUpdaterImpl(){
+ SharedPreferences prefs=getPrefs();
+ int checkedByBuild=prefs.getInt("checkedByBuild", 0);
+ if(prefs.contains("version") && checkedByBuild==BuildConfig.VERSION_CODE){
+ info=new UpdateInfo();
+ info.version=prefs.getString("version", null);
+ info.size=prefs.getLong("apkSize", 0);
+ info.changelog=prefs.getString("changelog", null);
+ downloadID=prefs.getLong("downloadID", 0);
+ if(downloadID==0 || !getUpdateApkFile().exists()){
+ state=UpdateState.UPDATE_AVAILABLE;
+ }else{
+ DownloadManager dm=MastodonApp.context.getSystemService(DownloadManager.class);
+ state=dm.getUriForDownloadedFile(downloadID)==null ? UpdateState.DOWNLOADING : UpdateState.DOWNLOADED;
+ if(state==UpdateState.DOWNLOADING){
+ MastodonApp.context.registerReceiver(downloadCompletionReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
+ }
+ }
+ }else if(checkedByBuild!=BuildConfig.VERSION_CODE && checkedByBuild>0){
+ // We are in a new version, running for the first time after update. Gotta clean things up.
+ long id=getPrefs().getLong("downloadID", 0);
+ if(id!=0){
+ MastodonApp.context.getSystemService(DownloadManager.class).remove(id);
+ }
+ getUpdateApkFile().delete();
+ getPrefs().edit()
+ .remove("apkSize")
+ .remove("version")
+ .remove("apkURL")
+ .remove("checkedByBuild")
+ .remove("downloadID")
+ .remove("changelog")
+ .apply();
+ }
+ }
+
+ private SharedPreferences getPrefs(){
+ return MastodonApp.context.getSharedPreferences("githubUpdater", Context.MODE_PRIVATE);
+ }
+
+ @Override
+ public void maybeCheckForUpdates(){
+ if(state!=UpdateState.NO_UPDATE && state!=UpdateState.UPDATE_AVAILABLE)
+ return;
+ long timeSinceLastCheck=System.currentTimeMillis()-getPrefs().getLong("lastCheck", CHECK_PERIOD);
+ if(timeSinceLastCheck>=CHECK_PERIOD){
+ setState(UpdateState.CHECKING);
+ MastodonAPIController.runInBackground(this::actuallyCheckForUpdates);
+ }
+ }
+
+ @Override
+ public void checkForUpdates() {
+ setState(UpdateState.CHECKING);
+ MastodonAPIController.runInBackground(this::actuallyCheckForUpdates);
+ }
+
+ private void actuallyCheckForUpdates(){
+ Request req=new Request.Builder()
+ .url("https://api.github.com/repos/LucasGGamerM/moshidon/releases")
+ .build();
+ Call call=MastodonAPIController.getHttpClient().newCall(req);
+ try(Response resp=call.execute()){
+ JsonArray arr=JsonParser.parseReader(resp.body().charStream()).getAsJsonArray();
+ for (JsonElement jsonElement : arr) {
+ JsonObject obj = jsonElement.getAsJsonObject();
+ if (obj.get("prerelease").getAsBoolean() && !GlobalUserPreferences.enablePreReleases) continue;
+
+ String tag=obj.get("tag_name").getAsString();
+ String changelog=obj.get("body").getAsString();
+ Pattern pattern=Pattern.compile("v?(\\d+)\\.(\\d+)\\.(\\d+)\\+fork\\.(\\d+)");
+ Matcher matcher=pattern.matcher(tag);
+ if(!matcher.find()){
+ Log.w(TAG, "actuallyCheckForUpdates: release tag has wrong format: "+tag);
+ return;
+ }
+ int newMajor=Integer.parseInt(matcher.group(1)),
+ newMinor=Integer.parseInt(matcher.group(2)),
+ newRevision=Integer.parseInt(matcher.group(3)),
+ newForkNumber=Integer.parseInt(matcher.group(4));
+ matcher=pattern.matcher(BuildConfig.VERSION_NAME);
+ String[] currentParts=BuildConfig.VERSION_NAME.split("[.+]");
+ if(!matcher.find()){
+ Log.w(TAG, "actuallyCheckForUpdates: current version has wrong format: "+BuildConfig.VERSION_NAME);
+ return;
+ }
+ int curMajor=Integer.parseInt(matcher.group(1)),
+ curMinor=Integer.parseInt(matcher.group(2)),
+ curRevision=Integer.parseInt(matcher.group(3)),
+ curForkNumber=Integer.parseInt(matcher.group(4));
+ long newVersion=((long)newMajor << 32) | ((long)newMinor << 16) | newRevision;
+ long curVersion=((long)curMajor << 32) | ((long)curMinor << 16) | curRevision;
+ if(newVersion>curVersion || newForkNumber>curForkNumber){
+ String version=newMajor+"."+newMinor+"."+newRevision+"+fork."+newForkNumber;
+ Log.d(TAG, "actuallyCheckForUpdates: new version: "+version);
+ for(JsonElement el:obj.getAsJsonArray("assets")){
+ JsonObject asset=el.getAsJsonObject();
+ if("moshidon.apk".equals(asset.get("name").getAsString()) && "application/vnd.android.package-archive".equals(asset.get("content_type").getAsString()) && "uploaded".equals(asset.get("state").getAsString())){
+ long size=asset.get("size").getAsLong();
+ String url=asset.get("browser_download_url").getAsString();
+
+ UpdateInfo info=new UpdateInfo();
+ info.size=size;
+ info.version=version;
+ info.changelog=changelog;
+ this.info=info;
+
+ getPrefs().edit()
+ .putLong("apkSize", size)
+ .putString("version", version)
+ .putString("apkURL", url)
+ .putString("changelog", changelog)
+ .putInt("checkedByBuild", BuildConfig.VERSION_CODE)
+ .remove("downloadID")
+ .apply();
+
+ break;
+ }
+ }
+ }
+ getPrefs().edit().putLong("lastCheck", System.currentTimeMillis()).apply();
+ break;
+ }
+ }catch(Exception x){
+ Log.w(TAG, "actuallyCheckForUpdates", x);
+ }finally{
+ setState(info==null ? UpdateState.NO_UPDATE : UpdateState.UPDATE_AVAILABLE);
+ }
+ }
+
+ private void setState(UpdateState state){
+ this.state=state;
+ E.post(new SelfUpdateStateChangedEvent(state));
+ }
+
+ @Override
+ public UpdateState getState(){
+ return state;
+ }
+
+ @Override
+ public UpdateInfo getUpdateInfo(){
+ return info;
+ }
+
+ public File getUpdateApkFile(){
+ return new File(MastodonApp.context.getExternalCacheDir(), "update.apk");
+ }
+
+ @Override
+ public void downloadUpdate(){
+ if(state==UpdateState.DOWNLOADING)
+ throw new IllegalStateException();
+ DownloadManager dm=MastodonApp.context.getSystemService(DownloadManager.class);
+ MastodonApp.context.registerReceiver(downloadCompletionReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
+ downloadID=dm.enqueue(
+ new DownloadManager.Request(Uri.parse(getPrefs().getString("apkURL", null)))
+ .setDestinationUri(Uri.fromFile(getUpdateApkFile()))
+ );
+ getPrefs().edit().putLong("downloadID", downloadID).apply();
+ setState(UpdateState.DOWNLOADING);
+ }
+
+ @Override
+ public void installUpdate(Activity activity){
+ if(state!=UpdateState.DOWNLOADED)
+ throw new IllegalStateException();
+ Uri uri;
+ Intent intent=new Intent(Intent.ACTION_INSTALL_PACKAGE);
+ if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
+ uri=new Uri.Builder().scheme("content").authority(activity.getPackageName()+".self_update_provider").path("update.apk").build();
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ }else{
+ uri=Uri.fromFile(getUpdateApkFile());
+ }
+ intent.setDataAndType(uri, "application/vnd.android.package-archive");
+ activity.startActivity(intent);
+
+ // TODO figure out how to restart the app when updating via this new API
+ /*
+ PackageInstaller installer=activity.getPackageManager().getPackageInstaller();
+ try{
+ final int sid=installer.createSession(new PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL));
+ installer.registerSessionCallback(new PackageInstaller.SessionCallback(){
+ @Override
+ public void onCreated(int i){
+
+ }
+
+ @Override
+ public void onBadgingChanged(int i){
+
+ }
+
+ @Override
+ public void onActiveChanged(int i, boolean b){
+
+ }
+
+ @Override
+ public void onProgressChanged(int id, float progress){
+
+ }
+
+ @Override
+ public void onFinished(int id, boolean success){
+ activity.getPackageManager().setComponentEnabledSetting(new ComponentName(activity, AfterUpdateRestartReceiver.class), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
+ }
+ });
+ activity.getPackageManager().setComponentEnabledSetting(new ComponentName(activity, AfterUpdateRestartReceiver.class), PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
+ PackageInstaller.Session session=installer.openSession(sid);
+ try(OutputStream out=session.openWrite("mastodon.apk", 0, info.size); InputStream in=new FileInputStream(getUpdateApkFile())){
+ byte[] buffer=new byte[16384];
+ int read;
+ while((read=in.read(buffer))>0){
+ out.write(buffer, 0, read);
+ }
+ }
+// PendingIntent intent=PendingIntent.getBroadcast(activity, 1, new Intent(activity, InstallerStatusReceiver.class), PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE);
+ PendingIntent intent=PendingIntent.getActivity(activity, 1, new Intent(activity, MainActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
+ session.commit(intent.getIntentSender());
+ }catch(IOException x){
+ Log.w(TAG, "installUpdate", x);
+ Toast.makeText(activity, x.getMessage(), Toast.LENGTH_SHORT).show();
+ }
+ */
+ }
+
+ @Override
+ public float getDownloadProgress(){
+ if(state!=UpdateState.DOWNLOADING)
+ throw new IllegalStateException();
+ DownloadManager dm=MastodonApp.context.getSystemService(DownloadManager.class);
+ try(Cursor cursor=dm.query(new DownloadManager.Query().setFilterById(downloadID))){
+ if(cursor.moveToFirst()){
+ long loaded=cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
+ long total=cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
+// Log.d(TAG, "getDownloadProgress: "+loaded+" of "+total);
+ return total>0 ? (float)loaded/total : 0f;
+ }
+ }
+ return 0;
+ }
+
+ @Override
+ public void cancelDownload(){
+ if(state!=UpdateState.DOWNLOADING)
+ throw new IllegalStateException();
+ DownloadManager dm=MastodonApp.context.getSystemService(DownloadManager.class);
+ dm.remove(downloadID);
+ downloadID=0;
+ getPrefs().edit().remove("downloadID").apply();
+ setState(UpdateState.UPDATE_AVAILABLE);
+ }
+
+ @Override
+ public void handleIntentFromInstaller(Intent intent, Activity activity){
+ int status=intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 0);
+ if(status==PackageInstaller.STATUS_PENDING_USER_ACTION){
+ Intent confirmIntent=intent.getParcelableExtra(Intent.EXTRA_INTENT);
+ activity.startActivity(confirmIntent);
+ }else if(status!=PackageInstaller.STATUS_SUCCESS){
+ String msg=intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
+ Toast.makeText(activity, activity.getString(R.string.error)+":\n"+msg, Toast.LENGTH_LONG).show();
+ }
+ }
+
+ /*public static class InstallerStatusReceiver extends BroadcastReceiver{
+
+ @Override
+ public void onReceive(Context context, Intent intent){
+ int status=intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 0);
+ if(status==PackageInstaller.STATUS_PENDING_USER_ACTION){
+ Intent confirmIntent=intent.getParcelableExtra(Intent.EXTRA_INTENT);
+ context.startActivity(confirmIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ }else if(status!=PackageInstaller.STATUS_SUCCESS){
+ String msg=intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
+ Toast.makeText(context, context.getString(R.string.error)+":\n"+msg, Toast.LENGTH_LONG).show();
+ }
+ }
+ }
+
+ public static class AfterUpdateRestartReceiver extends BroadcastReceiver{
+
+ @Override
+ public void onReceive(Context context, Intent intent){
+ if(Intent.ACTION_MY_PACKAGE_REPLACED.equals(intent.getAction())){
+ context.getPackageManager().setComponentEnabledSetting(new ComponentName(context, AfterUpdateRestartReceiver.class), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
+ Toast.makeText(context, R.string.update_installed, Toast.LENGTH_SHORT).show();
+ Intent restartIntent=new Intent(context, MainActivity.class)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .setPackage(context.getPackageName());
+ if(Build.VERSION.SDK_INT
+
+
diff --git a/mastodon/src/nightly/res/drawable/ic_fluent_mail_inbox_dismiss_24_regular.xml b/mastodon/src/nightly/res/drawable/ic_fluent_mail_inbox_dismiss_24_regular.xml
new file mode 100644
index 000000000..e32e4fab5
--- /dev/null
+++ b/mastodon/src/nightly/res/drawable/ic_fluent_mail_inbox_dismiss_24_regular.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/mastodon/src/nightly/res/drawable/ic_fluent_mail_inbox_dismiss_28_regular.xml b/mastodon/src/nightly/res/drawable/ic_fluent_mail_inbox_dismiss_28_regular.xml
new file mode 100644
index 000000000..2ea945f3d
--- /dev/null
+++ b/mastodon/src/nightly/res/drawable/ic_fluent_mail_inbox_dismiss_28_regular.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/mastodon/src/nightly/res/drawable/ic_fluent_mention_20_regular.xml b/mastodon/src/nightly/res/drawable/ic_fluent_mention_20_regular.xml
new file mode 100644
index 000000000..77a55c495
--- /dev/null
+++ b/mastodon/src/nightly/res/drawable/ic_fluent_mention_20_regular.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/mastodon/src/nightly/res/drawable/ic_fluent_open_24_regular.xml b/mastodon/src/nightly/res/drawable/ic_fluent_open_24_regular.xml
new file mode 100644
index 000000000..e18fe0aed
--- /dev/null
+++ b/mastodon/src/nightly/res/drawable/ic_fluent_open_24_regular.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/mastodon/src/nightly/res/drawable/ic_fluent_sign_out_24_regular.xml b/mastodon/src/nightly/res/drawable/ic_fluent_sign_out_24_regular.xml
new file mode 100644
index 000000000..d20ea1330
--- /dev/null
+++ b/mastodon/src/nightly/res/drawable/ic_fluent_sign_out_24_regular.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/mastodon/src/nightly/res/drawable/ic_fluent_speaker_0_24_regular.xml b/mastodon/src/nightly/res/drawable/ic_fluent_speaker_0_24_regular.xml
new file mode 100644
index 000000000..8a35ff41e
--- /dev/null
+++ b/mastodon/src/nightly/res/drawable/ic_fluent_speaker_0_24_regular.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/mastodon/src/nightly/res/drawable/ic_fluent_speaker_0_28_regular.xml b/mastodon/src/nightly/res/drawable/ic_fluent_speaker_0_28_regular.xml
new file mode 100644
index 000000000..53c6f5b6a
--- /dev/null
+++ b/mastodon/src/nightly/res/drawable/ic_fluent_speaker_0_28_regular.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/mastodon/src/nightly/res/drawable/ic_fluent_speaker_off_24_regular.xml b/mastodon/src/nightly/res/drawable/ic_fluent_speaker_off_24_regular.xml
new file mode 100644
index 000000000..e1b6ba1c9
--- /dev/null
+++ b/mastodon/src/nightly/res/drawable/ic_fluent_speaker_off_24_regular.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/mastodon/src/nightly/res/drawable/ic_fluent_speaker_off_28_regular.xml b/mastodon/src/nightly/res/drawable/ic_fluent_speaker_off_28_regular.xml
new file mode 100644
index 000000000..05defaa38
--- /dev/null
+++ b/mastodon/src/nightly/res/drawable/ic_fluent_speaker_off_28_regular.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/mastodon/src/nightly/res/drawable/ic_launcher_foreground_debug.xml b/mastodon/src/nightly/res/drawable/ic_launcher_foreground_debug.xml
new file mode 100644
index 000000000..d939ad5c7
--- /dev/null
+++ b/mastodon/src/nightly/res/drawable/ic_launcher_foreground_debug.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/mastodon/src/nightly/res/drawable/ic_launcher_foreground_monochrome_debug.xml b/mastodon/src/nightly/res/drawable/ic_launcher_foreground_monochrome_debug.xml
new file mode 100644
index 000000000..d939ad5c7
--- /dev/null
+++ b/mastodon/src/nightly/res/drawable/ic_launcher_foreground_monochrome_debug.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/mastodon/src/nightly/res/mipmap-anydpi-v26/ic_launcher.xml b/mastodon/src/nightly/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 000000000..16a3a2677
--- /dev/null
+++ b/mastodon/src/nightly/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mastodon/src/nightly/res/mipmap-anydpi-v26/ic_launcher_round.xml b/mastodon/src/nightly/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 000000000..16a3a2677
--- /dev/null
+++ b/mastodon/src/nightly/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mastodon/src/nightly/res/mipmap-hdpi/ic_launcher.png b/mastodon/src/nightly/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..e867bc877
Binary files /dev/null and b/mastodon/src/nightly/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/mastodon/src/nightly/res/mipmap-hdpi/ic_launcher_round.png b/mastodon/src/nightly/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 000000000..4e20506d7
Binary files /dev/null and b/mastodon/src/nightly/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/mastodon/src/nightly/res/mipmap-mdpi/ic_launcher.png b/mastodon/src/nightly/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..4bb62962b
Binary files /dev/null and b/mastodon/src/nightly/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/mastodon/src/nightly/res/mipmap-mdpi/ic_launcher_round.png b/mastodon/src/nightly/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 000000000..065ca7d71
Binary files /dev/null and b/mastodon/src/nightly/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/mastodon/src/nightly/res/mipmap-xhdpi/ic_launcher.png b/mastodon/src/nightly/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..df1a45557
Binary files /dev/null and b/mastodon/src/nightly/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/mastodon/src/nightly/res/mipmap-xhdpi/ic_launcher_round.png b/mastodon/src/nightly/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 000000000..d336b3dc9
Binary files /dev/null and b/mastodon/src/nightly/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/mastodon/src/nightly/res/mipmap-xxhdpi/ic_launcher.png b/mastodon/src/nightly/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..165514c03
Binary files /dev/null and b/mastodon/src/nightly/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/mastodon/src/nightly/res/mipmap-xxhdpi/ic_launcher_round.png b/mastodon/src/nightly/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 000000000..8d8aa6fa9
Binary files /dev/null and b/mastodon/src/nightly/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/mastodon/src/nightly/res/mipmap-xxxhdpi/ic_launcher.png b/mastodon/src/nightly/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..351a649b1
Binary files /dev/null and b/mastodon/src/nightly/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/mastodon/src/nightly/res/mipmap-xxxhdpi/ic_launcher_round.png b/mastodon/src/nightly/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 000000000..e0709f895
Binary files /dev/null and b/mastodon/src/nightly/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/mastodon/src/nightly/res/values/ic_launcher_background.xml b/mastodon/src/nightly/res/values/ic_launcher_background.xml
new file mode 100644
index 000000000..beab31f75
--- /dev/null
+++ b/mastodon/src/nightly/res/values/ic_launcher_background.xml
@@ -0,0 +1,4 @@
+
+
+ #000000
+
\ No newline at end of file