diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index b1521ba66c..6358e9dc46 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -32,7 +32,7 @@ - + @@ -455,6 +455,14 @@ android:exported="false" android:permission="android.permission.BIND_JOB_SERVICE"/> + + + + + + diff --git a/android/build.gradle b/android/build.gradle index 9aa246159e..faacb85f40 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -109,6 +109,7 @@ dependencies { } implementation 'com.android.support.constraint:constraint-layout:1.1.0' implementation 'com.android.billingclient:billing:1.1' + implementation 'com.firebase:firebase-jobdispatcher:0.8.5' } def getDate() { diff --git a/android/src/com/mapswithme/maps/MwmApplication.java b/android/src/com/mapswithme/maps/MwmApplication.java index 620f3df3d9..e5a83715e9 100644 --- a/android/src/com/mapswithme/maps/MwmApplication.java +++ b/android/src/com/mapswithme/maps/MwmApplication.java @@ -33,10 +33,6 @@ import com.mapswithme.maps.routing.RoutingController; import com.mapswithme.maps.scheduling.ConnectivityListener; import com.mapswithme.maps.scheduling.ConnectivityJobScheduler; import com.mapswithme.maps.sound.TtsPlayer; -import com.mapswithme.maps.routing.RoutingController; -import com.mapswithme.maps.scheduling.ConnectivityListener; -import com.mapswithme.maps.scheduling.ConnectivityJobScheduler; -import com.mapswithme.maps.sound.TtsPlayer; import com.mapswithme.maps.ugc.UGC; import com.mapswithme.util.Config; import com.mapswithme.util.Counters; diff --git a/android/src/com/mapswithme/maps/scheduling/ConnectivityJobScheduler.java b/android/src/com/mapswithme/maps/scheduling/ConnectivityJobScheduler.java index 94aaf66eec..46fd4cb220 100644 --- a/android/src/com/mapswithme/maps/scheduling/ConnectivityJobScheduler.java +++ b/android/src/com/mapswithme/maps/scheduling/ConnectivityJobScheduler.java @@ -5,35 +5,50 @@ import android.app.job.JobInfo; import android.app.job.JobScheduler; import android.content.ComponentName; import android.content.Context; -import android.content.IntentFilter; -import android.net.ConnectivityManager; import android.os.Build; import android.support.annotation.NonNull; +import com.crashlytics.android.Crashlytics; +import com.firebase.jobdispatcher.Constraint; +import com.firebase.jobdispatcher.FirebaseJobDispatcher; +import com.firebase.jobdispatcher.GooglePlayDriver; +import com.firebase.jobdispatcher.Job; +import com.firebase.jobdispatcher.Lifetime; +import com.firebase.jobdispatcher.Trigger; +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.GoogleApiAvailability; import com.mapswithme.maps.MwmApplication; -import com.mapswithme.maps.background.ConnectivityChangedReceiver; -import com.mapswithme.util.ConnectionState; +import com.mapswithme.util.Utils; import java.util.Objects; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.TimeUnit; public class ConnectivityJobScheduler implements ConnectivityListener { - public static final int PERIODIC_IN_MILLIS = 4000; + private static final int SCHEDULE_PERIOD_IN_HOURS = 1; @NonNull private final ConnectivityListener mMasterConnectivityListener; - @NonNull - private final AtomicInteger mCurrentNetworkType; - public ConnectivityJobScheduler(@NonNull MwmApplication context) { - mMasterConnectivityListener = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP - ? new NativeConnectivityListener(context) - : new LegacyConnectivityListener(context); + mMasterConnectivityListener = Utils.isLollipopOrLater() + ? createNativeJobScheduler(context) + : createCompatJobScheduler(context); + } - mCurrentNetworkType = new AtomicInteger(getCurrentNetworkType().ordinal()); + @NonNull + private ConnectivityListener createCompatJobScheduler(@NonNull MwmApplication context) + { + int status = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context); + boolean isAvailable = status == ConnectionResult.SUCCESS; + return isAvailable ? new ConnectivityListenerCompat(context) : new ConnectivityListenerStub(); + } + + @NonNull + private NativeConnectivityListener createNativeJobScheduler(@NonNull MwmApplication context) + { + return new NativeConnectivityListener(context); } @Override @@ -42,22 +57,6 @@ public class ConnectivityJobScheduler implements ConnectivityListener mMasterConnectivityListener.listen(); } - @NonNull - public NetworkStatus getNetworkStatus() - { - ConnectionState.Type currentNetworkType = getCurrentNetworkType(); - int prevTypeIndex = mCurrentNetworkType.getAndSet(currentNetworkType.ordinal()); - ConnectionState.Type prevNetworkType = ConnectionState.Type.values()[prevTypeIndex]; - boolean isNetworkChanged = prevNetworkType != currentNetworkType; - return new NetworkStatus(isNetworkChanged, currentNetworkType); - } - - @NonNull - private ConnectionState.Type getCurrentNetworkType() - { - return ConnectionState.requestCurrentType(); - } - public static ConnectivityJobScheduler from(@NonNull Context context) { MwmApplication application = (MwmApplication) context.getApplicationContext(); @@ -88,55 +87,53 @@ public class ConnectivityJobScheduler implements ConnectivityListener JobInfo jobInfo = new JobInfo .Builder(jobId, component) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) - .setPeriodic(PERIODIC_IN_MILLIS) + .setPersisted(true) + .setMinimumLatency(TimeUnit.HOURS.toMillis(SCHEDULE_PERIOD_IN_HOURS)) .build(); mJobScheduler.schedule(jobInfo); } } - public static class NetworkStatus - { - private final boolean mNetworkStateChanged; - @NonNull - private final ConnectionState.Type mCurrentNetworkType; - - NetworkStatus(boolean networkStateChanged, - @NonNull ConnectionState.Type currentNetworkType) - { - mNetworkStateChanged = networkStateChanged; - mCurrentNetworkType = currentNetworkType; - } - - public boolean isNetworkStateChanged() - { - return mNetworkStateChanged; - } - - @NonNull - public ConnectionState.Type getCurrentNetworkType() - { - return mCurrentNetworkType; - } - } - - private static class LegacyConnectivityListener implements ConnectivityListener + private static class ConnectivityListenerCompat implements ConnectivityListener { @NonNull - private final MwmApplication mContext; - @NonNull - private final ConnectivityChangedReceiver mReceiver; + private final FirebaseJobDispatcher mJobDispatcher; - LegacyConnectivityListener(@NonNull MwmApplication context) + ConnectivityListenerCompat(@NonNull MwmApplication context) { - mContext = context; - mReceiver = new ConnectivityChangedReceiver(); + mJobDispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(context)); } @Override public void listen() { - IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); - mContext.registerReceiver(mReceiver, filter); + String tag = String.valueOf(FirebaseJobService.class.hashCode()); + int executionWindowStart = (int) TimeUnit.HOURS.toSeconds(SCHEDULE_PERIOD_IN_HOURS); + Job job = mJobDispatcher.newJobBuilder() + .setTag(tag) + .setService(FirebaseJobService.class) + .setConstraints(Constraint.ON_ANY_NETWORK) + .setLifetime(Lifetime.FOREVER) + .setTrigger(Trigger.executionWindow(executionWindowStart, ++executionWindowStart)) + .build(); + mJobDispatcher.mustSchedule(job); } } + + private static class ConnectivityListenerStub implements ConnectivityListener + { + ConnectivityListenerStub() + { + IllegalStateException exception = new IllegalStateException("Play services doesn't exist on" + + " the device"); + Crashlytics.logException(exception); + } + + @Override + public void listen() + { + /* Do nothing */ + } + } + } diff --git a/android/src/com/mapswithme/maps/scheduling/FirebaseJobService.java b/android/src/com/mapswithme/maps/scheduling/FirebaseJobService.java new file mode 100644 index 0000000000..fa4321c96b --- /dev/null +++ b/android/src/com/mapswithme/maps/scheduling/FirebaseJobService.java @@ -0,0 +1,27 @@ +package com.mapswithme.maps.scheduling; + +import com.firebase.jobdispatcher.JobParameters; +import com.firebase.jobdispatcher.JobService; +import com.mapswithme.util.log.Logger; +import com.mapswithme.util.log.LoggerFactory; + +public class FirebaseJobService extends JobService +{ + private static final Logger LOGGER = LoggerFactory.INSTANCE.getLogger(LoggerFactory.Type.MISC); + private static final String TAG = NativeJobService.class.getSimpleName(); + + @Override + public boolean onStartJob(JobParameters job) + { + LOGGER.d(TAG, "onStartJob FirebaseJobService"); + JobServiceDelegate delegate = new JobServiceDelegate(getApplication()); + delegate.onStartJob(); + return true; + } + + @Override + public boolean onStopJob(JobParameters job) + { + return false; + } +} diff --git a/android/src/com/mapswithme/maps/scheduling/JobServiceDelegate.java b/android/src/com/mapswithme/maps/scheduling/JobServiceDelegate.java new file mode 100644 index 0000000000..c258d962e4 --- /dev/null +++ b/android/src/com/mapswithme/maps/scheduling/JobServiceDelegate.java @@ -0,0 +1,33 @@ +package com.mapswithme.maps.scheduling; + +import android.app.Application; +import android.support.annotation.NonNull; + +import com.mapswithme.maps.background.NotificationService; +import com.mapswithme.util.ConnectionState; + +class JobServiceDelegate +{ + @NonNull + private final Application mApp; + + JobServiceDelegate(@NonNull Application app) + { + mApp = app; + } + + public void onStartJob() + { + ConnectionState.Type type = ConnectionState.requestCurrentType(); + if (type == ConnectionState.Type.WIFI) + NotificationService.startOnConnectivityChanged(mApp); + + retryJob(); + } + + + private void retryJob() + { + ConnectivityJobScheduler.from(mApp).listen(); + } +} diff --git a/android/src/com/mapswithme/maps/scheduling/NativeJobService.java b/android/src/com/mapswithme/maps/scheduling/NativeJobService.java index 5e4c6cb29f..32bb01b17d 100644 --- a/android/src/com/mapswithme/maps/scheduling/NativeJobService.java +++ b/android/src/com/mapswithme/maps/scheduling/NativeJobService.java @@ -1,46 +1,28 @@ package com.mapswithme.maps.scheduling; import android.annotation.TargetApi; -import android.app.job.JobInfo; import android.app.job.JobParameters; -import android.app.job.JobScheduler; import android.app.job.JobService; -import android.content.ComponentName; import android.os.Build; -import com.mapswithme.maps.background.NotificationService; - -import java.util.Objects; +import com.mapswithme.util.log.Logger; +import com.mapswithme.util.log.LoggerFactory; @TargetApi(Build.VERSION_CODES.LOLLIPOP) public class NativeJobService extends JobService { + private static final Logger LOGGER = LoggerFactory.INSTANCE.getLogger(LoggerFactory.Type.MISC); + private static final String TAG = NativeJobService.class.getSimpleName(); + @Override public boolean onStartJob(JobParameters params) { - ConnectivityJobScheduler jobDispatcher = ConnectivityJobScheduler.from(this); - ConnectivityJobScheduler.NetworkStatus status = jobDispatcher.getNetworkStatus(); - if (status.isNetworkStateChanged()) - NotificationService.startOnConnectivityChanged(this); - - if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - scheduleRefresh(); - + LOGGER.d(TAG, "onStartJob"); + JobServiceDelegate delegate = new JobServiceDelegate(getApplication()); + delegate.onStartJob(); return true; } - private void scheduleRefresh() - { - JobScheduler service = (JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE); - ComponentName component = new ComponentName(getApplicationContext(), NativeJobService.class); - int jobId = NativeJobService.class.hashCode(); - JobInfo jobInfo = new JobInfo.Builder(jobId, component) - .setMinimumLatency(ConnectivityJobScheduler.PERIODIC_IN_MILLIS) - .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) - .build(); - Objects.requireNonNull(service).schedule(jobInfo); - } - @Override public boolean onStopJob(JobParameters params) { diff --git a/android/src/com/mapswithme/util/ConnectionState.java b/android/src/com/mapswithme/util/ConnectionState.java index d5e57b4df2..fb0b056a9c 100644 --- a/android/src/com/mapswithme/util/ConnectionState.java +++ b/android/src/com/mapswithme/util/ConnectionState.java @@ -24,23 +24,23 @@ public class ConnectionState WIFI(CONNECTION_WIFI, ConnectivityManager.TYPE_WIFI), WWAN(CONNECTION_WWAN, ConnectivityManager.TYPE_MOBILE); - private final byte mValue; - private final int mNetwork; + private final byte mNativeRepresentation; + private final int mPlatformRepresentation; - Type(byte value, int network) + Type(byte nativeRepresentation, int platformRepresentation) { - mValue = value; - mNetwork = network; + mNativeRepresentation = nativeRepresentation; + mPlatformRepresentation = platformRepresentation; } - public byte getValue() + public byte getNativeRepresentation() { - return mValue; + return mNativeRepresentation; } - public int getNetwork() + public int getPlatformRepresentation() { - return mNetwork; + return mPlatformRepresentation; } } @@ -118,9 +118,11 @@ public class ConnectionState return info != null && info.isRoaming(); } + /*jni call*/ + @SuppressWarnings("unused") public static byte getConnectionState() { - return requestCurrentType().getValue(); + return requestCurrentType().getNativeRepresentation(); } @NonNull @@ -128,7 +130,7 @@ public class ConnectionState { for (ConnectionState.Type each : ConnectionState.Type.values()) { - if (isNetworkConnected(each.getNetwork())) + if (isNetworkConnected(each.getPlatformRepresentation())) return each; } return NONE;