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