SelfUpdate : Add initial bits of self update module

This commit is contained in:
Rahul Kumar Patel
2019-09-23 00:41:09 +05:30
parent 1cbc650e5e
commit 2dadd83082
6 changed files with 361 additions and 5 deletions

View File

@@ -25,13 +25,14 @@
package="com.aurora.store">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
<uses-permission android:name="android.permission.INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission
android:name="android.permission.ACCESS_MOCK_LOCATION"
@@ -168,7 +169,10 @@
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<service android:name=".installer.InstallerService" />
<service android:name=".SelfUpdateService" />
<meta-data
android:name="preloaded_fonts"
android:resource="@array/preloaded_fonts" />

View File

@@ -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";
}

View File

@@ -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<Request> 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 <PackageName,DisplayName> and <PackageName,IconURL> 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();
}
}
};
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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));
}
}