[android] Notifications. UGC is not authenticated notification is added.

This commit is contained in:
Arsentiy Milchakov 2018-04-03 10:44:01 +03:00 committed by Aleksandr Zatsepin
parent 56f4f45051
commit 9fab79459c
18 changed files with 335 additions and 36 deletions

View file

@ -420,6 +420,10 @@
android:name="com.mapswithme.maps.background.WorkerService"
android:exported="false"/>
<service
android:name="com.mapswithme.maps.background.NotificationService"
android:exported="false"/>
<receiver android:name="com.mapswithme.maps.location.TrackRecorderWakeReceiver">
<intent-filter>
<action android:name="com.mapswithme.maps.TRACK_RECORDER_ALARM"/>

View file

@ -48,6 +48,7 @@ set(
com/mapswithme/maps/editor/Editor.cpp
com/mapswithme/maps/editor/OsmOAuth.cpp
com/mapswithme/maps/Framework.cpp
com/mapswithme/maps/LightFramework.cpp
com/mapswithme/maps/LocationState.cpp
com/mapswithme/maps/LocationHelper.cpp
com/mapswithme/maps/MapFragment.cpp

View file

@ -0,0 +1,22 @@
#include "map/framework_light.hpp"
#include "com/mapswithme/core/jni_helper.hpp"
using namespace lightweight;
extern "C"
{
JNIEXPORT jboolean JNICALL
Java_com_mapswithme_maps_LightFramework_nativeIsAuthenticated(JNIEnv * env, jclass clazz)
{
Framework const framework(REQUEST_TYPE_USER_AUTH_STATUS);
return static_cast<jboolean>(framework.Get<REQUEST_TYPE_USER_AUTH_STATUS>());
}
JNIEXPORT jint JNICALL
Java_com_mapswithme_maps_LightFramework_nativeGetNumberUnsentUGC(JNIEnv * env, jclass clazz)
{
Framework const framework(REQUEST_TYPE_NUMBER_OF_UNSENT_UGC);
return static_cast<jint>(framework.Get<REQUEST_TYPE_NUMBER_OF_UNSENT_UGC>());
}
} // extern "C"

View file

@ -129,8 +129,6 @@ void Platform::Initialize(JNIEnv * env, jobject functorProcessObject, jstring ap
m_sendPushWooshTagsMethod = env->GetMethodID(functorProcessClass, "sendPushWooshTags", "(Ljava/lang/String;[Ljava/lang/String;)V");
m_myTrackerTrackMethod = env->GetStaticMethodID(g_myTrackerClazz, "trackEvent", "(Ljava/lang/String;)Z");
m_secureStorage.Init();
m_guiThread = my::make_unique<GuiThread>(m_functorProcessObject);
std::string const flavor = jni::ToNativeString(env, flavorName);
@ -249,10 +247,9 @@ void Platform::SetGuiThread(unique_ptr<base::TaskLoop> guiThread)
m_guiThread = std::move(guiThread);
}
void Platform::AndroidSecureStorage::Init()
void Platform::AndroidSecureStorage::Init(JNIEnv * env)
{
JNIEnv * env = jni::GetEnv();
if (env == nullptr)
if (m_secureStorageClass != nullptr)
return;
m_secureStorageClass = jni::GetGlobalClassRef(env, "com/mapswithme/util/SecureStorage");
@ -265,6 +262,8 @@ void Platform::AndroidSecureStorage::Save(std::string const & key, std::string c
if (env == nullptr)
return;
Init(env);
static jmethodID const saveMethodId =
jni::GetStaticMethodID(env, m_secureStorageClass, "save", "(Ljava/lang/String;Ljava/lang/String;)V");
@ -279,6 +278,8 @@ bool Platform::AndroidSecureStorage::Load(std::string const & key, std::string &
if (env == nullptr)
return false;
Init(env);
static jmethodID const loadMethodId =
jni::GetStaticMethodID(env, m_secureStorageClass, "load", "(Ljava/lang/String;)Ljava/lang/String;");
@ -297,6 +298,8 @@ void Platform::AndroidSecureStorage::Remove(std::string const & key)
if (env == nullptr)
return;
Init(env);
static jmethodID const removeMethodId =
jni::GetStaticMethodID(env, m_secureStorageClass, "remove", "(Ljava/lang/String;)V");

View file

@ -48,12 +48,13 @@ public:
class AndroidSecureStorage
{
public:
void Init();
void Save(std::string const & key, std::string const & value);
bool Load(std::string const & key, std::string & value);
void Remove(std::string const & key);
private:
void Init(JNIEnv * env);
jclass m_secureStorageClass = nullptr;
};

View file

@ -0,0 +1,7 @@
package com.mapswithme.maps;
public class LightFramework
{
public static native boolean nativeIsAuthenticated();
public static native int nativeGetNumberUnsentUGC();
}

View file

@ -38,6 +38,8 @@ import com.mapswithme.maps.api.ParsedRoutingData;
import com.mapswithme.maps.api.ParsedSearchRequest;
import com.mapswithme.maps.api.ParsedUrlMwmRequest;
import com.mapswithme.maps.api.RoutePoint;
import com.mapswithme.maps.auth.PassportAuthDialogFragment;
import com.mapswithme.maps.background.Notifier;
import com.mapswithme.maps.base.BaseMwmFragmentActivity;
import com.mapswithme.maps.base.OnBackPressListener;
import com.mapswithme.maps.bookmarks.BookmarkCategoriesActivity;
@ -51,7 +53,6 @@ import com.mapswithme.maps.downloader.DownloaderFragment;
import com.mapswithme.maps.downloader.MapManager;
import com.mapswithme.maps.downloader.MigrationFragment;
import com.mapswithme.maps.downloader.OnmapDownloader;
import com.mapswithme.maps.editor.AuthDialogFragment;
import com.mapswithme.maps.editor.Editor;
import com.mapswithme.maps.editor.EditorActivity;
import com.mapswithme.maps.editor.EditorHostFragment;
@ -101,6 +102,8 @@ import com.mapswithme.util.ThemeUtils;
import com.mapswithme.util.UiUtils;
import com.mapswithme.util.Utils;
import com.mapswithme.util.concurrency.UiThread;
import com.mapswithme.util.log.Logger;
import com.mapswithme.util.log.LoggerFactory;
import com.mapswithme.util.permissions.PermissionsResult;
import com.mapswithme.util.sharing.ShareOption;
import com.mapswithme.util.sharing.SharingHelper;
@ -131,6 +134,9 @@ public class MwmActivity extends BaseMwmFragmentActivity
DiscoveryFragment.DiscoveryListener,
FloatingSearchToolbarController.SearchToolbarListener
{
private static final Logger LOGGER = LoggerFactory.INSTANCE.getLogger(LoggerFactory.Type.MISC);
private static final String TAG = MwmActivity.class.getSimpleName();
public static final String EXTRA_TASK = "map_task";
public static final String EXTRA_LAUNCH_BY_DEEP_LINK = "launch_by_deep_link";
private static final String EXTRA_CONSUMED = "mwm.extra.intent.processed";
@ -321,6 +327,14 @@ public class MwmActivity extends BaseMwmFragmentActivity
return new Intent(context, DownloadResourcesLegacyActivity.class)
.putExtra(DownloadResourcesLegacyActivity.EXTRA_COUNTRY, countryId);
}
public static Intent createAuthenticateIntent()
{
return new Intent(MwmApplication.get(), MwmActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
.putExtra(MwmActivity.EXTRA_TASK,
new MwmActivity.ShowDialogTask(PassportAuthDialogFragment.class.getName()));
}
@Override
@ -1192,6 +1206,11 @@ public class MwmActivity extends BaseMwmFragmentActivity
if (intent == null)
return false;
if (intent.hasExtra(SplashActivity.EXTRA_INTENT))
intent = intent.getParcelableExtra(SplashActivity.EXTRA_INTENT);
Notifier.processNotificationExtras(intent);
if (intent.hasExtra(EXTRA_TASK))
{
addTask(intent);
@ -1299,8 +1318,6 @@ public class MwmActivity extends BaseMwmFragmentActivity
}
}
@Override
protected void onPause()
{
@ -2389,13 +2406,21 @@ public class MwmActivity extends BaseMwmFragmentActivity
showSearch(query);
}
public static class ShowAuthorizationTask implements MapTask
public static class ShowDialogTask implements MapTask
{
@NonNull
private String mDialogName;
public ShowDialogTask(@NonNull String dialogName)
{
mDialogName = dialogName;
}
@Override
public boolean run(MwmActivity target)
{
final DialogFragment fragment = (DialogFragment) Fragment.instantiate(target, AuthDialogFragment.class.getName());
fragment.show(target.getSupportFragmentManager(), AuthDialogFragment.class.getName());
final DialogFragment fragment = (DialogFragment) Fragment.instantiate(target, mDialogName);
fragment.show(target.getSupportFragmentManager(), mDialogName);
return true;
}
}

View file

@ -33,6 +33,7 @@ public class SplashActivity extends AppCompatActivity
{
public static final String EXTRA_INTENT = "extra_intent";
private static final String EXTRA_ACTIVITY_TO_START = "extra_activity_to_start";
private static final String EXTRA_INITIAL_INTENT = "extra_initial_intent";
private static final int REQUEST_PERMISSIONS = 1;
private static final long FIRST_START_DELAY = 1000;
private static final long DELAY = 100;
@ -82,10 +83,14 @@ public class SplashActivity extends AppCompatActivity
};
public static void start(@NonNull Context context,
@Nullable Class<? extends Activity> activityToStart)
@Nullable Class<? extends Activity> activityToStart,
@Nullable Intent initialIntent)
{
Intent intent = new Intent(context, SplashActivity.class);
intent.putExtra(EXTRA_ACTIVITY_TO_START, activityToStart);
if (activityToStart != null)
intent.putExtra(EXTRA_ACTIVITY_TO_START, activityToStart);
if (initialIntent != null)
intent.putExtra(EXTRA_INITIAL_INTENT, initialIntent);
context.startActivity(intent);
}
@ -306,15 +311,21 @@ public class SplashActivity extends AppCompatActivity
private void processNavigation()
{
Intent input = getIntent();
Intent intent = new Intent(this, DownloadResourcesLegacyActivity.class);
Intent result = new Intent(this, DownloadResourcesLegacyActivity.class);
if (input != null)
{
Class<? extends Activity> type = (Class<? extends Activity>) input.getSerializableExtra(EXTRA_ACTIVITY_TO_START);
if (type != null)
intent = new Intent(this, type);
intent.putExtra(EXTRA_INTENT, input);
if (input.hasExtra(EXTRA_ACTIVITY_TO_START))
{
result = new Intent(this,
(Class<? extends Activity>) input.getSerializableExtra(EXTRA_ACTIVITY_TO_START));
}
Intent extraIntent = input.hasExtra(EXTRA_INITIAL_INTENT) ?
input.getParcelableExtra(EXTRA_INITIAL_INTENT) :
input;
result.putExtra(EXTRA_INTENT, extraIntent);
}
startActivity(intent);
startActivity(result);
finish();
}
}

View file

@ -0,0 +1,38 @@
package com.mapswithme.maps.auth;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.mapswithme.maps.base.BaseMwmDialogFragment;
public class PassportAuthDialogFragment extends BaseMwmDialogFragment
{
private Authorizer mAuthorizer = new Authorizer(this);
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)
{
mAuthorizer.authorize();
return null;
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
mAuthorizer.onActivityResult(requestCode, resultCode, data);
dismiss();
}
@Override
protected int getStyle()
{
return STYLE_NO_TITLE;
}
}

View file

@ -28,5 +28,6 @@ public class ConnectivityChangedReceiver extends BroadcastReceiver
+ !backgroundTracker().isForeground();
LOGGER.i(TAG, msg);
CrashlyticsUtils.log(Log.INFO, TAG, msg);
NotificationService.startOnConnectivityChanged(context);
}
}

View file

@ -0,0 +1,120 @@
package com.mapswithme.maps.background;
import android.app.IntentService;
import android.content.Context;
import android.content.Intent;
import android.support.annotation.Nullable;
import com.mapswithme.maps.LightFramework;
import com.mapswithme.maps.MwmApplication;
import com.mapswithme.maps.routing.RoutingController;
import com.mapswithme.util.NetworkPolicy;
import com.mapswithme.util.PermissionsUtils;
import com.mapswithme.util.log.Logger;
import com.mapswithme.util.log.LoggerFactory;
import static com.mapswithme.maps.MwmApplication.prefs;
public class NotificationService extends IntentService
{
private static final Logger LOGGER = LoggerFactory.INSTANCE.getLogger(LoggerFactory.Type.MISC);
private static final String TAG = NotificationService.class.getSimpleName();
private static final String LAST_AUTH_NOTIFICATION_TIMESTAMP = "DownloadOrUpdateTimestamp";
private static final int MIN_COUNT_UNSENT_UGC = 2;
private static final long MIN_AUTH_EVENT_DELTA_MILLIS = 5 * 24 * 60 * 60 * 1000; // 5 days
private static final String CONNECTIVITY_CHANGED =
"com.mapswithme.maps.notification_service.action.connectivity_changed";
private interface NotificationExecutor
{
boolean tryToNotify();
}
static void startOnConnectivityChanged(Context context)
{
final Intent intent = new Intent(context, NotificationService.class);
intent.setAction(NotificationService.CONNECTIVITY_CHANGED);
context.startService(intent);
}
private static boolean notifyIsNotAuthenticated()
{
if (!PermissionsUtils.isExternalStorageGranted() ||
!NetworkPolicy.getCurrentNetworkUsageStatus() ||
LightFramework.nativeIsAuthenticated() ||
LightFramework.nativeGetNumberUnsentUGC() < MIN_COUNT_UNSENT_UGC)
{
LOGGER.d(TAG, "Authentication nofification is rejected. External storage granted: " +
PermissionsUtils.isExternalStorageGranted() + ". Is user authenticated: " +
LightFramework.nativeIsAuthenticated() + ". Current network usage status: " +
NetworkPolicy.getCurrentNetworkUsageStatus() + ". Number of unsent UGC: " +
LightFramework.nativeGetNumberUnsentUGC());
return false;
}
// Do not show push when user is in the navigation mode.
if (MwmApplication.get().arePlatformAndCoreInitialized() &&
RoutingController.get().isNavigating())
{
LOGGER.d(TAG, "Authentication nofification is rejected. The user is in navigation mode.");
return false;
}
final long lastEventTimestamp = prefs().getLong(LAST_AUTH_NOTIFICATION_TIMESTAMP, 0);
if (System.currentTimeMillis() - lastEventTimestamp > MIN_AUTH_EVENT_DELTA_MILLIS)
{
LOGGER.d(TAG, "Authentication nofification will be sent.");
prefs().edit()
.putLong(LAST_AUTH_NOTIFICATION_TIMESTAMP, System.currentTimeMillis())
.apply();
Notifier.notifyIsNotAuthenticated();
return true;
}
LOGGER.d(TAG, "Authentication nofification is rejected. Last event timestamp: " +
lastEventTimestamp + "Current time milliseconds: " + System.currentTimeMillis());
return false;
}
public NotificationService()
{
super(NotificationService.class.getSimpleName());
}
@Override
protected void onHandleIntent(@Nullable Intent intent)
{
if (intent == null)
return;
final String action = intent.getAction();
if (action == null)
return;
switch(action)
{
case CONNECTIVITY_CHANGED:
onConnectivityChanged();
break;
}
}
private static void onConnectivityChanged()
{
final NotificationExecutor notifyOrder[] =
{
NotificationService::notifyIsNotAuthenticated,
};
// Only one notification should be shown at a time.
for (NotificationExecutor executor : notifyOrder)
{
if (executor.tryToNotify())
return;
}
}
}

View file

@ -5,6 +5,8 @@ import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.support.annotation.IntDef;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import com.mapswithme.maps.MwmActivity;
@ -13,20 +15,36 @@ import com.mapswithme.maps.R;
import com.mapswithme.util.StringUtils;
import com.mapswithme.util.statistics.Statistics;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
public final class Notifier
{
private final static int ID_DOWNLOAD_FAILED = 2;
private static final String EXTRA_CANCEL_NOTIFICATION = "mwm.extra.intent.cancel_notification";
private static final String EXTRA_NOTIFICATION_CLICKED = "mwm.extra.intent.notification_clicked";
public final static int ID_NONE = 0;
public final static int ID_DOWNLOAD_FAILED = 1;
public final static int ID_IS_NOT_AUTHENTICATED = 2;
@Retention(RetentionPolicy.SOURCE)
@IntDef({ ID_NONE, ID_DOWNLOAD_FAILED, ID_IS_NOT_AUTHENTICATED })
public @interface NotificationId
{
}
private static final MwmApplication APP = MwmApplication.get();
private Notifier() { }
private Notifier()
{
}
public static void notifyDownloadFailed(String id, String name)
{
String title = APP.getString(R.string.app_name);
String content = APP.getString(R.string.download_country_failed, name);
PendingIntent pi = PendingIntent.getActivity(APP, 0, MwmActivity.createShowMapIntent(APP, id, false)
PendingIntent pi = PendingIntent.getActivity(APP, 0, MwmActivity.createShowMapIntent(APP, id)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
PendingIntent.FLAG_UPDATE_CURRENT);
@ -34,12 +52,56 @@ public final class Notifier
Statistics.INSTANCE.trackEvent(Statistics.EventName.DOWNLOAD_COUNTRY_NOTIFICATION_SHOWN);
}
public static void cancelDownloadFailed()
public static void notifyIsNotAuthenticated()
{
getNotificationManager().cancel(ID_DOWNLOAD_FAILED);
Intent authIntent = MwmActivity.createAuthenticateIntent();
authIntent.putExtra(EXTRA_CANCEL_NOTIFICATION, Notifier.ID_IS_NOT_AUTHENTICATED);
authIntent.putExtra(EXTRA_NOTIFICATION_CLICKED,
Statistics.EventName.UGC_NOT_AUTH_NOTIFICATION_CLICKED);
PendingIntent pi = PendingIntent.getActivity(APP, 0, authIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Builder builder =
getBuilder(APP.getString(R.string.notification_unsent_reviews_title),
APP.getString(R.string.notification_unsent_reviews_message), pi);
builder.addAction(0, APP.getString(R.string.authorization_button_sign_in), pi);
getNotificationManager().notify(ID_IS_NOT_AUTHENTICATED, builder.build());
Statistics.INSTANCE.trackEvent(Statistics.EventName.UGC_NOT_AUTH_NOTIFICATION_SHOWN);
}
private static void placeNotification(String title, String content, PendingIntent pendingIntent, int notificationId)
public static void cancelNotification(@NotificationId int id)
{
if (id == ID_NONE)
return;
getNotificationManager().cancel(id);
}
public static void processNotificationExtras(@Nullable Intent intent)
{
if (intent == null)
return;
if (intent.hasExtra(Notifier.EXTRA_CANCEL_NOTIFICATION))
{
@Notifier.NotificationId
int notificationId = intent.getIntExtra(Notifier.EXTRA_CANCEL_NOTIFICATION, Notifier.ID_NONE);
cancelNotification(notificationId);
}
if (intent.hasExtra(Notifier.EXTRA_NOTIFICATION_CLICKED))
{
String eventName = intent.getStringExtra(Notifier.EXTRA_NOTIFICATION_CLICKED);
Statistics.INSTANCE.trackEvent(eventName);
}
}
private static void placeNotification(String title, String content, PendingIntent pendingIntent,
int notificationId)
{
final Notification notification = getBuilder(title, content, pendingIntent)
.build();
@ -47,7 +109,8 @@ public final class Notifier
getNotificationManager().notify(notificationId, notification);
}
private static NotificationCompat.Builder getBuilder(String title, String content, PendingIntent pendingIntent)
private static NotificationCompat.Builder getBuilder(String title, String content,
PendingIntent pendingIntent)
{
return new NotificationCompat.Builder(APP)
.setAutoCancel(true)

View file

@ -58,7 +58,7 @@ public abstract class BaseMwmFragmentActivity extends AppCompatActivity
|| !PermissionsUtils.isExternalStorageGranted())
{
super.onCreate(savedInstanceState);
goToSplashScreen();
goToSplashScreen(getIntent());
return;
}
mInitializationCompleted = true;
@ -178,7 +178,7 @@ public abstract class BaseMwmFragmentActivity extends AppCompatActivity
super.onResume();
if (!PermissionsUtils.isExternalStorageGranted())
{
goToSplashScreen();
goToSplashScreen(null);
return;
}
@ -266,12 +266,12 @@ public abstract class BaseMwmFragmentActivity extends AppCompatActivity
return android.R.id.content;
}
private void goToSplashScreen()
private void goToSplashScreen(@Nullable Intent initialIntent)
{
Class<? extends Activity> type = null;
if (!(this instanceof MwmActivity))
if (!(this instanceof MwmActivity) || initialIntent != null)
type = getClass();
SplashActivity.start(this, type);
SplashActivity.start(this, type, initialIntent);
finish();
}
}

View file

@ -397,7 +397,7 @@ class DownloaderAdapter extends RecyclerView.Adapter<DownloaderAdapter.ViewHolde
@Override
public void run()
{
Notifier.cancelDownloadFailed();
Notifier.cancelNotification(Notifier.ID_DOWNLOAD_FAILED);
}
});
break;

View file

@ -141,7 +141,7 @@ public final class MapManager
@Override
public void run()
{
Notifier.cancelDownloadFailed();
Notifier.cancelNotification(Notifier.ID_DOWNLOAD_FAILED);
if (dialogClickListener != null)
dialogClickListener.invoke(true);

View file

@ -215,7 +215,7 @@ public class OnmapDownloader implements MwmActivity.LeftAnimationTrackListener
boolean retry = (mCurrentCountry.status == CountryItem.STATUS_FAILED);
if (retry)
{
Notifier.cancelDownloadFailed();
Notifier.cancelNotification(Notifier.ID_DOWNLOAD_FAILED);
MapManager.nativeRetry(mCurrentCountry.id);
}
else

View file

@ -319,7 +319,8 @@ public class EditorHostFragment extends BaseMwmToolbarFragment
final Activity parent = getActivity();
Intent intent = new Intent(parent, MwmActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
intent.putExtra(MwmActivity.EXTRA_TASK, new MwmActivity.ShowAuthorizationTask());
intent.putExtra(MwmActivity.EXTRA_TASK,
new MwmActivity.ShowDialogTask(AuthDialogFragment.class.getName()));
parent.startActivity(intent);
if (parent instanceof MwmActivity)

View file

@ -228,6 +228,8 @@ public enum Statistics
public static final String ACTIVE_CONNECTION = "Connection";
public static final String STATISTICS_STATUS_CHANGED = "Statistics status changed";
public static final String TTS_FAILURE_LOCATION = "TTS failure location";
public static final String UGC_NOT_AUTH_NOTIFICATION_SHOWN = "UGC_UnsentNotification_shown";
public static final String UGC_NOT_AUTH_NOTIFICATION_CLICKED = "UGC_UnsentNotification_clicked";
// routing
public static final String ROUTING_BUILD = "Routing. Build";