diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1fc331021..2545fc625 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -25,13 +25,14 @@
package="com.aurora.store">
-
+
+
+
+
+
diff --git a/app/src/main/java/com/aurora/store/Constants.java b/app/src/main/java/com/aurora/store/Constants.java
index 5c5314701..bc2cd6693 100644
--- a/app/src/main/java/com/aurora/store/Constants.java
+++ b/app/src/main/java/com/aurora/store/Constants.java
@@ -24,6 +24,8 @@ public class Constants {
public static final String SHARED_PREFERENCES_KEY = "com.aurora.store";
public static final String SERVICE_PACKAGE = "com.aurora.services";
public static final String APP_DETAIL_URL = "https://play.google.com/store/apps/details?id=";
+ public static final String APP_ICON_URL = "https://gitlab.com/AuroraOSS/AuroraStore/raw/master/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png";
+ public static final String UPDATE_URL = "https://gitlab.com/AuroraOSS/AuroraStore/raw/master/updates.json";
public static final String INTENT_DEVICE_NAME = "INTENT_DEVICE_NAME";
public static final String INTENT_DEVICE_INDEX = "INTENT_DEVICE_INDEX";
@@ -36,7 +38,6 @@ public class Constants {
public static final String PUB_PREFIX = "pub:";
public static final String TAG = "Aurora Store";
public static final String FILES = "Files";
- public static final String GZIPPED = "GZipped";
public static final String RECENT_HISTORY = "RECENT_HISTORY";
@@ -108,4 +109,5 @@ public class Constants {
public static final String PREFERENCE_TOP_FAMILY = "PREFERENCE_TOP_FAMILY";
public static final String PREFERENCE_INSTALLED_APPS = "PREFERENCE_INSTALLED_APPS";
public static final String PREFERENCE_CACHE_DATE = "PREFERENCE_CACHE_DATE";
+ public static final String PREFERENCE_SELF_UPDATE_DATE = "PREFERENCE_SELF_UPDATE_DATE";
}
diff --git a/app/src/main/java/com/aurora/store/SelfUpdateService.java b/app/src/main/java/com/aurora/store/SelfUpdateService.java
new file mode 100644
index 000000000..b559ed0fa
--- /dev/null
+++ b/app/src/main/java/com/aurora/store/SelfUpdateService.java
@@ -0,0 +1,205 @@
+package com.aurora.store;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Intent;
+import android.graphics.Color;
+import android.os.Build;
+import android.os.IBinder;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.core.app.NotificationCompat;
+
+import com.aurora.store.download.DownloadManager;
+import com.aurora.store.download.RequestBuilder;
+import com.aurora.store.model.App;
+import com.aurora.store.model.Update;
+import com.aurora.store.task.NetworkTask;
+import com.aurora.store.utility.CertUtil;
+import com.aurora.store.utility.Log;
+import com.aurora.store.utility.PackageUtil;
+import com.google.gson.Gson;
+import com.tonyodev.fetch2.AbstractFetchGroupListener;
+import com.tonyodev.fetch2.Download;
+import com.tonyodev.fetch2.EnqueueAction;
+import com.tonyodev.fetch2.Error;
+import com.tonyodev.fetch2.Fetch;
+import com.tonyodev.fetch2.FetchGroup;
+import com.tonyodev.fetch2.FetchListener;
+import com.tonyodev.fetch2.Request;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import io.reactivex.Observable;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.disposables.CompositeDisposable;
+import io.reactivex.schedulers.Schedulers;
+
+public class SelfUpdateService extends Service {
+
+ public static SelfUpdateService instance = null;
+
+ private CompositeDisposable disposable = new CompositeDisposable();
+ private int hashCode = BuildConfig.APPLICATION_ID.hashCode();
+ private App app;
+ private Fetch fetch;
+ private FetchListener fetchListener;
+ private Gson gson = new Gson();
+
+ public static boolean isServiceRunning() {
+ try {
+ return instance != null && instance.isRunning();
+ } catch (NullPointerException e) {
+ return false;
+ }
+ }
+
+ private boolean isRunning() {
+ return true;
+ }
+
+ @Nullable
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ return START_NOT_STICKY;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
+ startForeground(1, getNotification());
+ } else {
+ Notification notification = getNotification(new NotificationCompat.Builder(this));
+ startForeground(1, notification);
+ }
+ startUpdate();
+ }
+
+ @Override
+ public void onDestroy() {
+ instance = null;
+ super.onDestroy();
+ }
+
+ private void destroyService() {
+ Log.e("Self-update service destroyed");
+ stopForeground(true);
+ stopSelf();
+ }
+
+ private void startUpdate() {
+ disposable.add(Observable.fromCallable(() -> new NetworkTask(this)
+ .get(Constants.UPDATE_URL))
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(response -> {
+ try {
+ gson = new Gson();
+ Update update = gson.fromJson(response, Update.class);
+ downloadAndUpdate(update);
+ } catch (Exception e) {
+ Log.e("Error checking self-update");
+ destroyService();
+ }
+ }));
+ }
+
+ private void downloadAndUpdate(Update update) {
+ app = new App();
+ app.setPackageName(BuildConfig.APPLICATION_ID);
+ app.setDisplayName(getString(R.string.app_name));
+ app.setVersionName(update.getVersionName());
+ app.setVersionCode(update.getVersionCode());
+
+ Request request = RequestBuilder.buildRequest(this, app, isFDroidVariant() ? update.getFdroidBuild() : update.getAuroraBuild());
+ request.setEnqueueAction(EnqueueAction.REPLACE_EXISTING);
+ List requestList = new ArrayList<>();
+ requestList.add(request);
+
+ fetch = DownloadManager.getFetchInstance(this);
+ fetch.enqueue(requestList, result -> {
+ Log.d("Downloading latest self-update");
+ });
+
+ fetchListener = getFetchListener();
+ fetch.addListener(fetchListener);
+
+ //Add and to PseudoMaps
+ PackageUtil.addToPseudoPackageMap(this, app.getPackageName(), app.getDisplayName());
+ PackageUtil.addToPseudoURLMap(this, app.getPackageName(), Constants.APP_ICON_URL);
+ }
+
+ private boolean isFDroidVariant() {
+ return CertUtil.isFDroidApp(this, BuildConfig.APPLICATION_ID);
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ private Notification getNotification() {
+ String NOTIFICATION_CHANNEL_ID = BuildConfig.APPLICATION_ID;
+ String channelName = "Self Update Service";
+
+ NotificationChannel notificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_HIGH);
+ notificationChannel.setLightColor(Color.BLUE);
+ notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
+
+ NotificationManager manager = (NotificationManager) getSystemService(SelfUpdateService.NOTIFICATION_SERVICE);
+ manager.createNotificationChannel(notificationChannel);
+
+ NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID);
+ return getNotification(notificationBuilder);
+ }
+
+ private Notification getNotification(NotificationCompat.Builder builder) {
+ int versionCode = Build.VERSION.SDK_INT;
+ return builder
+ .setAutoCancel(true)
+ .setCategory(Notification.CATEGORY_PROGRESS)
+ .setContentTitle("Self update")
+ .setContentText("Updating Aurora Store in background")
+ .setOngoing(false)
+ .setPriority(versionCode >= Build.VERSION_CODES.O ? NotificationCompat.PRIORITY_DEFAULT : Notification.PRIORITY_DEFAULT)
+ .setSmallIcon(R.drawable.ic_update)
+ .build();
+ }
+
+ private FetchListener getFetchListener() {
+ return new AbstractFetchGroupListener() {
+ @Override
+ public void onError(int groupId, @NotNull Download download, @NotNull Error error,
+ @org.jetbrains.annotations.Nullable Throwable throwable, @NotNull FetchGroup fetchGroup) {
+ if (groupId == hashCode) {
+ Log.e("Error self-updating %s", app.getDisplayName());
+ destroyService();
+ }
+ }
+
+ @Override
+ public void onCompleted(int groupId, @NotNull Download download, @NotNull FetchGroup fetchGroup) {
+ if (groupId == hashCode && fetchGroup.getGroupDownloadProgress() == 100) {
+ AuroraApplication.getInstaller().install(app);
+ destroyService();
+ }
+ }
+
+ @Override
+ public void onCancelled(int groupId, @NotNull Download download, @NotNull FetchGroup fetchGroup) {
+ if (groupId == hashCode) {
+ Log.e("Self-update cancelled %s", app.getDisplayName());
+ destroyService();
+ }
+ }
+ };
+ }
+}
diff --git a/app/src/main/java/com/aurora/store/activity/AuroraActivity.java b/app/src/main/java/com/aurora/store/activity/AuroraActivity.java
index fc4a85508..7ec713d0e 100644
--- a/app/src/main/java/com/aurora/store/activity/AuroraActivity.java
+++ b/app/src/main/java/com/aurora/store/activity/AuroraActivity.java
@@ -21,7 +21,6 @@
package com.aurora.store.activity;
import android.Manifest;
-import android.app.StatusBarManager;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
@@ -38,23 +37,37 @@ import androidx.core.graphics.ColorUtils;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
+import com.aurora.store.BuildConfig;
import com.aurora.store.Constants;
import com.aurora.store.R;
+import com.aurora.store.SelfUpdateService;
import com.aurora.store.adapter.ViewPagerAdapter;
import com.aurora.store.fragment.AppsFragment;
import com.aurora.store.fragment.HomeFragment;
import com.aurora.store.fragment.SearchFragment;
+import com.aurora.store.model.Update;
+import com.aurora.store.task.NetworkTask;
import com.aurora.store.utility.Accountant;
+import com.aurora.store.utility.CertUtil;
+import com.aurora.store.utility.Log;
import com.aurora.store.utility.PrefUtil;
+import com.aurora.store.utility.TextUtil;
import com.aurora.store.utility.ThemeUtil;
import com.aurora.store.utility.Util;
import com.aurora.store.utility.ViewUtil;
import com.aurora.store.view.CustomViewPager;
import com.google.android.material.bottomnavigation.BottomNavigationView;
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+import com.google.gson.Gson;
+
+import java.util.Calendar;
import butterknife.BindView;
import butterknife.ButterKnife;
+import io.reactivex.Observable;
+import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
+import io.reactivex.schedulers.Schedulers;
public class AuroraActivity extends AppCompatActivity {
@@ -67,6 +80,7 @@ public class AuroraActivity extends AppCompatActivity {
CustomViewPager viewPager;
@BindView(R.id.bottom_navigation)
BottomNavigationView bottomNavigationView;
+
private ActionBar actionBar;
private ViewPagerAdapter pagerAdapter;
private ThemeUtil themeUtil = new ThemeUtil();
@@ -86,6 +100,7 @@ public class AuroraActivity extends AppCompatActivity {
ButterKnife.bind(this);
fragmentCur = Util.getDefaultTab(this);
onNewIntent(getIntent());
+
if (!PrefUtil.getBoolean(this, Constants.PREFERENCE_DO_NOT_SHOW_INTRO)) {
PrefUtil.putBoolean(this, Constants.PREFERENCE_DO_NOT_SHOW_INTRO, true);
startActivity(new Intent(this, IntroActivity.class));
@@ -100,6 +115,9 @@ public class AuroraActivity extends AppCompatActivity {
if (Util.isCacheObsolete(this))
Util.clearCache(this);
+ if (Util.shouldCheckUpdate(this))
+ checkSelfUpdate();
+
checkPermissions();
}
@@ -187,7 +205,6 @@ public class AuroraActivity extends AppCompatActivity {
disposable.clear();
}
-
private void setupActionbar() {
setSupportActionBar(toolbar);
actionBar = getSupportActionBar();
@@ -240,6 +257,35 @@ public class AuroraActivity extends AppCompatActivity {
.getItem(fragmentCur).getItemId());
}
+ private void checkSelfUpdate() {
+ disposable.add(Observable.fromCallable(() -> new NetworkTask(this)
+ .get(Constants.UPDATE_URL))
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(response -> {
+ try {
+ Util.setSelfUpdateTime(this, Calendar.getInstance().getTimeInMillis());
+ Gson gson = new Gson();
+ Update update = gson.fromJson(response, Update.class);
+ if (update.getVersionCode() > BuildConfig.VERSION_CODE) {
+ if (CertUtil.isFDroidApp(this, BuildConfig.APPLICATION_ID)
+ && TextUtil.emptyIfNull(update.getFdroidBuild()).isEmpty()) {
+ Log.d("FDroid build of latest version is not published yet");
+ return;
+ }
+
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
+ showAddRepoDialog(update);
+ } else {
+ Log.i("No new update available");
+ }
+ }
+ } catch (Exception e) {
+ Log.e("Error checking updates");
+ }
+ }));
+ }
+
private void checkPermissions() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
@@ -248,4 +294,25 @@ public class AuroraActivity extends AppCompatActivity {
1337);
}
}
+
+ protected void showAddRepoDialog(Update update) {
+ MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this)
+ .setTitle("New update available")
+ .setMessage(new StringBuilder()
+ .append(update.getVersionName())
+ .append("\nChangelog:\n")
+ .append(TextUtil.emptyIfNull(update.getChangelog()))
+ .append("\n")
+ .append("Do you wish to update now ?")
+ .toString())
+ .setPositiveButton(getString(android.R.string.yes), (dialog, which) -> {
+ Intent intent = new Intent(this, SelfUpdateService.class);
+ startService(intent);
+ })
+ .setNegativeButton(getString(android.R.string.no), (dialog, which) -> {
+ dialog.dismiss();
+ });
+ builder.create();
+ builder.show();
+ }
}
diff --git a/app/src/main/java/com/aurora/store/model/Update.java b/app/src/main/java/com/aurora/store/model/Update.java
new file mode 100644
index 000000000..fd1078bd5
--- /dev/null
+++ b/app/src/main/java/com/aurora/store/model/Update.java
@@ -0,0 +1,62 @@
+package com.aurora.store.model;
+
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+
+public class Update {
+ @SerializedName("version_name")
+ @Expose
+ private String versionName;
+ @SerializedName("version_code")
+ @Expose
+ private Integer versionCode;
+ @SerializedName("aurora_build")
+ @Expose
+ private String auroraBuild;
+ @SerializedName("fdroid_build")
+ @Expose
+ private String fdroidBuild;
+ @SerializedName("changelog")
+ @Expose
+ private String changelog;
+
+ public String getVersionName() {
+ return versionName;
+ }
+
+ public void setVersionName(String versionName) {
+ this.versionName = versionName;
+ }
+
+ public Integer getVersionCode() {
+ return versionCode;
+ }
+
+ public void setVersionCode(Integer versionCode) {
+ this.versionCode = versionCode;
+ }
+
+ public String getAuroraBuild() {
+ return auroraBuild;
+ }
+
+ public void setAuroraBuild(String auroraBuild) {
+ this.auroraBuild = auroraBuild;
+ }
+
+ public String getFdroidBuild() {
+ return fdroidBuild;
+ }
+
+ public void setFdroidBuild(String fdroidBuild) {
+ this.fdroidBuild = fdroidBuild;
+ }
+
+ public String getChangelog() {
+ return changelog;
+ }
+
+ public void setChangelog(String changelog) {
+ this.changelog = changelog;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aurora/store/utility/Util.java b/app/src/main/java/com/aurora/store/utility/Util.java
index 9a2064ee5..e11a6003c 100644
--- a/app/src/main/java/com/aurora/store/utility/Util.java
+++ b/app/src/main/java/com/aurora/store/utility/Util.java
@@ -489,7 +489,7 @@ public class Util {
Log.i("Periodic update preferences updated");
}
- public static void clearOldInstallationSessions(Context context){
+ public static void clearOldInstallationSessions(Context context) {
final PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
for (PackageInstaller.SessionInfo sessionInfo : packageInstaller.getMySessions()) {
final int sessionId = sessionInfo.getSessionId();
@@ -501,4 +501,20 @@ public class Util {
}
}
}
+
+ public static boolean shouldCheckUpdate(Context context) {
+ try {
+ long lastSyncDate = Long.parseLong(PrefUtil.getString(context, Constants.PREFERENCE_SELF_UPDATE_DATE));
+ long currentSyncDate = Calendar.getInstance().getTimeInMillis();
+ long diffDatesInMillis = currentSyncDate - lastSyncDate;
+ long diffInDays = TimeUnit.MILLISECONDS.toDays(diffDatesInMillis);
+ return diffInDays >= 1;
+ } catch (Exception e) {
+ return true;
+ }
+ }
+
+ public static void setSelfUpdateTime(Context context, Long dateInMillis) {
+ PrefUtil.putString(context, Constants.PREFERENCE_SELF_UPDATE_DATE, String.valueOf(dateInMillis));
+ }
}