diff --git a/.idea/dictionaries/tateisu.xml b/.idea/dictionaries/tateisu.xml index cb8e0600..fda9ce3d 100644 --- a/.idea/dictionaries/tateisu.xml +++ b/.idea/dictionaries/tateisu.xml @@ -6,6 +6,7 @@ emojione enty favourited + firebase hashtag hashtags idempotency diff --git a/.idea/misc.xml b/.idea/misc.xml index 5d199810..fbb68289 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -37,7 +37,7 @@ - + diff --git a/app/build.gradle b/app/build.gradle index 06c412cd..e6a03165 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,8 +9,8 @@ android { applicationId "jp.juggler.subwaytooter" minSdkVersion 21 targetSdkVersion 25 - versionCode 67 - versionName "0.6.7" + versionCode 68 + versionName "0.6.8" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } @@ -55,6 +55,9 @@ dependencies { compile 'com.android.support:customtabs:25.3.1' compile 'com.android.support:support-v4:25.3.1' + compile 'com.google.firebase:firebase-core:10.0.1' + compile 'com.google.firebase:firebase-messaging:10.0.1' + // compile 'com.android.support.constraint:constraint-layout:1.0.2' testCompile 'junit:junit:4.12' @@ -74,3 +77,5 @@ dependencies { // annotationProcessor 'com.github.bumptech.glide:compiler:3.8.0' compile 'com.github.bumptech.glide:okhttp3-integration:1.5.0' } + +apply plugin: 'com.google.gms.google-services' diff --git a/app/google-services.json b/app/google-services.json new file mode 100644 index 00000000..98c27437 --- /dev/null +++ b/app/google-services.json @@ -0,0 +1,42 @@ +{ + "project_info": { + "project_number": "433682361381", + "firebase_url": "https://subway-tooter.firebaseio.com", + "project_id": "subway-tooter", + "storage_bucket": "subway-tooter.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:433682361381:android:7ae4daf10abb936c", + "android_client_info": { + "package_name": "jp.juggler.subwaytooter" + } + }, + "oauth_client": [ + { + "client_id": "433682361381-33pqgervqgcehbm9hup1g6qu967vs427.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyDVF995DFPjoY3ynGjGiI-5KDs8_BemE98" + } + ], + "services": { + "analytics_service": { + "status": 1 + }, + "appinvite_service": { + "status": 1, + "other_platform_oauth_client": [] + }, + "ads_service": { + "status": 2 + } + } + } + ], + "configuration_version": "1" +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index eb870280..2dbc25b4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -171,6 +171,32 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.java b/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.java index 5aef3199..27b2687d 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.java +++ b/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.java @@ -56,7 +56,7 @@ public class ActAccountSetting extends AppCompatActivity } @Override protected void onStop(){ - AlarmService.startCheck( this ); + AlarmService.startCheck( this ,false); super.onStop(); } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActMain.java b/app/src/main/java/jp/juggler/subwaytooter/ActMain.java index 984c12b9..9a4e6f3a 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActMain.java +++ b/app/src/main/java/jp/juggler/subwaytooter/ActMain.java @@ -128,7 +128,7 @@ public class ActMain extends AppCompatActivity } } - AlarmService.startCheck( this ); + AlarmService.startCheck( this ,false); } @Override protected void onDestroy(){ @@ -1192,7 +1192,7 @@ public class ActMain extends AppCompatActivity account.saveSetting(); } Utils.showToast( ActMain.this, false, R.string.account_confirmed ); - AlarmService.startCheck( ActMain.this ); + AlarmService.startCheck( ActMain.this ,false); long count = SavedAccount.getCount(); if( count > 1 ){ addColumn( getDefaultInsertPosition(), account, Column.TYPE_HOME ); diff --git a/app/src/main/java/jp/juggler/subwaytooter/AlarmService.java b/app/src/main/java/jp/juggler/subwaytooter/AlarmService.java index 2e49df0d..8bbb0e29 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/AlarmService.java +++ b/app/src/main/java/jp/juggler/subwaytooter/AlarmService.java @@ -15,6 +15,7 @@ import android.support.annotation.Nullable; import android.support.v4.app.NotificationCompat; import android.support.v4.content.ContextCompat; import android.support.v4.content.WakefulBroadcastReceiver; +import android.text.TextUtils; import org.json.JSONArray; import org.json.JSONException; @@ -25,6 +26,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.LinkedList; +import java.util.UUID; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicBoolean; @@ -39,6 +41,10 @@ import jp.juggler.subwaytooter.table.SavedAccount; import jp.juggler.subwaytooter.util.LogCategory; import jp.juggler.subwaytooter.util.Utils; import jp.juggler.subwaytooter.util.WordTrieTree; +import okhttp3.Call; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; public class AlarmService extends IntentService { @@ -61,6 +67,10 @@ public class AlarmService extends IntentService { public static final AtomicBoolean mBusyAppDataImportBefore = new AtomicBoolean( false ); public static final AtomicBoolean mBusyAppDataImportAfter = new AtomicBoolean( false ); public static final String ACTION_APP_DATA_IMPORT_AFTER = "app_data_import_after"; + public static final String ACTION_DEVICE_TOKEN = "device_token"; + private static final String ACTION_RESET_LAST_LOAD = "reset_last_load"; + + static final String APP_SERVER = "https://mastodon-msg.juggler.jp"; public AlarmService(){ // name: Used to name the worker thread, important only for debugging. @@ -92,6 +102,7 @@ public class AlarmService extends IntentService { Intent next_intent = new Intent( this, AlarmReceiver.class ); pi_next = PendingIntent.getBroadcast( this, PENDING_CODE_ALARM, next_intent, PendingIntent.FLAG_UPDATE_CURRENT ); + } @Override public void onDestroy(){ @@ -101,6 +112,8 @@ public class AlarmService extends IntentService { super.onDestroy(); } + String install_id; + // IntentService は onHandleIntent をワーカースレッドから呼び出す // 同期処理を行って良い @Override protected void onHandleIntent( @Nullable Intent intent ){ @@ -109,6 +122,10 @@ public class AlarmService extends IntentService { // データベースへアクセスできるようにする App1.prepareDB( this.getApplicationContext() ); + + install_id = getInstallId(); + + if( intent != null ){ String action = intent.getAction(); log.d( "onHandleIntent action=%s", action ); @@ -138,10 +155,19 @@ public class AlarmService extends IntentService { if( intent != null ){ String action = intent.getAction(); - if( ACTION_DATA_DELETED.equals( action ) ){ + if( ACTION_DEVICE_TOKEN.equals( action ) ){ + // デバイストークンが更新された + // TODO 過去に中継サーバに登録したものは登録解除して登録しなおす必要がある + + }else if( ACTION_RESET_LAST_LOAD.equals( action ) ){ + NotificationTracking.resetLastLoad(); + + }else if( ACTION_DATA_DELETED.equals( action ) ){ deleteCacheData( intent.getLongExtra( EXTRA_DB_ID, - 1L ) ); + }else if( ACTION_DATA_INJECTED.equals( action ) ){ processInjectedData(); + }else if( AlarmReceiver.ACTION_FROM_RECEIVER.equals( action ) ){ WakefulBroadcastReceiver.completeWakefulIntent( intent ); // @@ -192,26 +218,36 @@ public class AlarmService extends IntentService { @Override public void run(){ try{ - if( account.notification_mention - || account.notification_boost - || account.notification_favourite - || account.notification_follow + if( account.isPseudo() ) return; + + if( ! account.notification_mention + && ! account.notification_boost + && ! account.notification_favourite + && ! account.notification_follow ){ - bAlarmRequired.set( true ); - - TootApiClient client = new TootApiClient( AlarmService.this, new TootApiClient.Callback() { - @Override public boolean isApiCancelled(){ - return false; - } - - @Override public void publishApiProgress( String s ){ - } - } ); - - ArrayList< Data > data_list = new ArrayList<>(); - checkAccount( client, data_list, account, muted_app, muted_word ); - showNotification( account.db_id, data_list ); + unregisterDeviceToken( account ); + return; } + + if( registerDeviceToken( account ) ){ + return; + } + + bAlarmRequired.set( true ); + + TootApiClient client = new TootApiClient( AlarmService.this, new TootApiClient.Callback() { + @Override public boolean isApiCancelled(){ + return false; + } + + @Override public void publishApiProgress( String s ){ + } + } ); + + ArrayList< Data > data_list = new ArrayList<>(); + checkAccount( client, data_list, account, muted_app, muted_word ); + showNotification( account.db_id, data_list ); + }catch( Throwable ex ){ ex.printStackTrace(); } @@ -244,6 +280,118 @@ public class AlarmService extends IntentService { } } + String getInstallId(){ + String sv = pref.getString(Pref.KEY_INSTALL_ID,null); + if( ! TextUtils.isEmpty( sv ) ) return sv; + + try{ + String device_token = pref.getString( Pref.KEY_DEVICE_TOKEN, null ); + if( TextUtils.isEmpty( device_token ) ) return null; + + Request request = new Request.Builder() + .url( APP_SERVER + "/counter" ) + .build(); + + Call call = App1.ok_http_client.newCall( request ); + + Response response = call.execute(); + + if( ! response.isSuccessful() ){ + log.e("getInstallId: get /counter failed. %s",response); + return null; + } + + //noinspection ConstantConditions + sv = Utils.digestSHA256( device_token + UUID.randomUUID() + response.body().string() ); + pref.edit().putString(Pref.KEY_INSTALL_ID, sv).apply(); + + return sv; + + }catch( Throwable ex ){ + ex.printStackTrace(); + return null; + } + } + + private void unregisterDeviceToken( @NonNull SavedAccount account ){ + try{ + // ネットワーク的な事情でインストールIDを取得できなかったのなら、何もしない + if( TextUtils.isEmpty( install_id ) ) return; + + String device_token = pref.getString( Pref.KEY_DEVICE_TOKEN, null ); + if( TextUtils.isEmpty( device_token ) ) return; + + String tag = account.notification_tag; + if( TextUtils.isEmpty( tag ) ) return; + + String post_data = "instance_url=" + Uri.encode( "https://" + account.host ) + + "&app_id=" + Uri.encode( getPackageName() ) + + "&tag=" + tag; + + Request request = new Request.Builder() + .url( APP_SERVER + "/unregister" ) + .post( RequestBody.create( TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED, post_data ) ) + .build(); + + Call call = App1.ok_http_client.newCall( request ); + + Response response = call.execute(); + + log.e( "unregisterDeviceToken:%s", response ); + + }catch( Throwable ex ){ + ex.printStackTrace(); + } + } + + // 定期的な通知更新が不要なら真を返す + private boolean registerDeviceToken( @NonNull SavedAccount account ){ + try{ + // ネットワーク的な事情でインストールIDを取得できなかったのなら、何もしない + if( TextUtils.isEmpty( install_id ) ) return false; + + String device_token = pref.getString( Pref.KEY_DEVICE_TOKEN, null ); + if( TextUtils.isEmpty( device_token ) ) return false; + + String tag = account.notification_tag; + if( TextUtils.isEmpty( tag ) ){ + tag = account.notification_tag = Utils.digestSHA256( install_id + account.db_id + account.acct ); + account.saveNotificationTag(); + } + + // サーバ情報APIを使う + String post_data = "instance_url=" + + Uri.encode( "https://" + account.host ) + + "&app_id=" + + Uri.encode( getPackageName() ) + + "&tag=" + + tag + + "&access_token=" + + Utils.optStringX( account.token_info, "access_token" ) + + "&device_token=" + + device_token; + + Request request = new Request.Builder() + .url( APP_SERVER + "/register" ) + .post( RequestBody.create( TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED, post_data ) ) + .build(); + + Call call = App1.ok_http_client.newCall( request ); + + Response response = call.execute(); + + log.e( "registerDeviceToken:%s", response ); + + // TODO 登録結果をDBに記録する + // 登録してから一定時間は再登録しない + + }catch( Throwable ex ){ + + ex.printStackTrace(); + } + return false; + } + private static class Data { SavedAccount access_info; TootNotification notification; @@ -492,9 +640,11 @@ public class AlarmService extends IntentService { //////////////////////////////////////////////////////////////////////////// // Activity との連携 - public static void startCheck( @NonNull Context context ){ - Intent intent = new Intent( context, AlarmReceiver.class ); - context.sendBroadcast( intent ); + public static void startCheck( @NonNull Context context, boolean bResetLastLoad ){ + Intent intent = new Intent( context, AlarmService.class ); + if( bResetLastLoad ) intent.setAction( ACTION_RESET_LAST_LOAD ); + + context.startService( intent ); } private static class InjectData { diff --git a/app/src/main/java/jp/juggler/subwaytooter/App1.java b/app/src/main/java/jp/juggler/subwaytooter/App1.java index 04075fb2..0dcc7c58 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/App1.java +++ b/app/src/main/java/jp/juggler/subwaytooter/App1.java @@ -50,7 +50,7 @@ public class App1 extends Application { static final LogCategory log = new LogCategory( "App1" ); static final String DB_NAME = "app_db"; - static final int DB_VERSION = 12; + static final int DB_VERSION = 13; // 2017/4/25 v10 1=>2 SavedAccount に通知設定を追加 // 2017/4/25 v10 1=>2 NotificationTracking テーブルを追加 // 2017/4/29 v20 2=>5 MediaShown,ContentWarningのインデクスが間違っていたので貼り直す @@ -61,11 +61,11 @@ public class App1 extends Application { // 2017/5/04 v33 9=>10 SavedAccountに項目追加 // 2017/5/08 v41 10=>11 MutedWord テーブルの追加 // 2017/5/17 v59 11=>12 PostDraft テーブルの追加 + // 2017/5/23 v68 12=>13 SavedAccountに項目追加 private static DBOpenHelper db_open_helper; public static SQLiteDatabase getDB(){ - return db_open_helper.getWritableDatabase(); } diff --git a/app/src/main/java/jp/juggler/subwaytooter/AppDataExporter.java b/app/src/main/java/jp/juggler/subwaytooter/AppDataExporter.java index 79613184..8960593d 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/AppDataExporter.java +++ b/app/src/main/java/jp/juggler/subwaytooter/AppDataExporter.java @@ -314,6 +314,13 @@ public class AppDataExporter { reader.skipValue(); e.remove( k ); break; + + // just ignore + case Pref.KEY_DEVICE_TOKEN: + case Pref.KEY_INSTALL_ID: + reader.skipValue(); + e.remove( k ); + break; } } reader.endObject(); diff --git a/app/src/main/java/jp/juggler/subwaytooter/MyFirebaseInstanceIDService.java b/app/src/main/java/jp/juggler/subwaytooter/MyFirebaseInstanceIDService.java new file mode 100644 index 00000000..ec17c6a5 --- /dev/null +++ b/app/src/main/java/jp/juggler/subwaytooter/MyFirebaseInstanceIDService.java @@ -0,0 +1,32 @@ +package jp.juggler.subwaytooter; + +import android.content.Intent; + +import com.google.firebase.iid.FirebaseInstanceId; +import com.google.firebase.iid.FirebaseInstanceIdService; + +import jp.juggler.subwaytooter.util.LogCategory; + +public class MyFirebaseInstanceIDService extends FirebaseInstanceIdService { + static final LogCategory log = new LogCategory("MyFirebaseInstanceIDService"); + + // Called when the system determines that the tokens need to be refreshed. + @Override public void onTokenRefresh(){ + super.onTokenRefresh(); + + try{ + String token = FirebaseInstanceId.getInstance().getToken(); + log.d("onTokenRefresh: instance_token=%s",token); + + Pref.pref(this).edit().putString(Pref.KEY_DEVICE_TOKEN,token).apply(); + + + Intent intent = new Intent(this,AlarmService.class); + intent.setAction( AlarmService.ACTION_DEVICE_TOKEN ); + startService( intent ); + + }catch(Throwable ex){ + ex.printStackTrace(); + } + } +} diff --git a/app/src/main/java/jp/juggler/subwaytooter/MyFirebaseMessagingService.java b/app/src/main/java/jp/juggler/subwaytooter/MyFirebaseMessagingService.java new file mode 100644 index 00000000..708e15b7 --- /dev/null +++ b/app/src/main/java/jp/juggler/subwaytooter/MyFirebaseMessagingService.java @@ -0,0 +1,24 @@ +package jp.juggler.subwaytooter; + +import com.google.firebase.messaging.FirebaseMessagingService; +import com.google.firebase.messaging.RemoteMessage; + +import java.util.Map; + +import jp.juggler.subwaytooter.util.LogCategory; + +public class MyFirebaseMessagingService extends FirebaseMessagingService { + static final LogCategory log = new LogCategory("MyFirebaseMessagingService"); + + @Override public void onMessageReceived( RemoteMessage remoteMessage ){ + super.onMessageReceived( remoteMessage ); + + Map< String, String > data = remoteMessage.getData(); + if( data != null ){ + for( Map.Entry< String, String > entry : data.entrySet() ){ + log.d( "onMessageReceived: %s=%s", entry.getKey(), entry.getValue() ); + } + } + AlarmService.startCheck( getApplicationContext() ,true); + } +} diff --git a/app/src/main/java/jp/juggler/subwaytooter/Pref.java b/app/src/main/java/jp/juggler/subwaytooter/Pref.java index ebe5173a..3d63a26c 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/Pref.java +++ b/app/src/main/java/jp/juggler/subwaytooter/Pref.java @@ -47,8 +47,10 @@ public class Pref { static final String KEY_MEDIA_THUMB_HEIGHT = "MediaThumbHeight"; static final String KEY_TIMELINE_FONT = "timeline_font"; static final String KEY_DONT_CROP_MEDIA_THUMBNAIL = "DontCropMediaThumb"; + static final String KEY_DEVICE_TOKEN = "device_token"; + static final String KEY_INSTALL_ID = "install_id"; + - // 項目を追加したらAppDataExporter#importPref のswitch文も更新すること } diff --git a/app/src/main/java/jp/juggler/subwaytooter/table/NotificationTracking.java b/app/src/main/java/jp/juggler/subwaytooter/table/NotificationTracking.java index 91f6f298..bd3aeab6 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/table/NotificationTracking.java +++ b/app/src/main/java/jp/juggler/subwaytooter/table/NotificationTracking.java @@ -33,6 +33,7 @@ public class NotificationTracking { // 最後に表示した通知のID private static final String COL_POST_ID = "pi"; + // 最後に表示した通知の作成時刻 private static final String COL_POST_TIME = "pt"; @@ -187,7 +188,19 @@ public class NotificationTracking { App1.getDB().update( table, cv,null,null); }catch( Throwable ex ){ - log.e( ex, "save failed." ); + log.e( ex, "resetPostAll failed." ); } } + + public static void resetLastLoad(){ + try{ + ContentValues cv = new ContentValues(); + cv.put( COL_LAST_LOAD, 0 ); + App1.getDB().update( table, cv,null,null); + + }catch( Throwable ex ){ + log.e( ex, "resetLastLoad failed." ); + } + + } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/table/SavedAccount.java b/app/src/main/java/jp/juggler/subwaytooter/table/SavedAccount.java index 0f9870fa..a4655cee 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/table/SavedAccount.java +++ b/app/src/main/java/jp/juggler/subwaytooter/table/SavedAccount.java @@ -25,7 +25,7 @@ public class SavedAccount extends TootAccount implements LinkClickContext { public static final String table = "access_info"; - public static final String COL_ID = BaseColumns._ID; + private static final String COL_ID = BaseColumns._ID; private static final String COL_HOST = "h"; private static final String COL_USER = "u"; private static final String COL_ACCOUNT = "a"; @@ -44,6 +44,9 @@ public class SavedAccount extends TootAccount implements LinkClickContext { private static final String COL_CONFIRM_FOLLOW_LOCKED = "confirm_follow_locked"; private static final String COL_CONFIRM_UNFOLLOW = "confirm_unfollow"; private static final String COL_CONFIRM_POST = "confirm_post"; + + // スキーマ13から + private static final String COL_NOTIFICATION_TAG = "notification_server"; public static final long INVALID_ID = - 1L; @@ -65,6 +68,8 @@ public class SavedAccount extends TootAccount implements LinkClickContext { public boolean confirm_unfollow; public boolean confirm_post; + public String notification_tag; + // アプリデータのインポート時に呼ばれる public static void onDBDelete( SQLiteDatabase db ){ try{ @@ -97,6 +102,11 @@ public class SavedAccount extends TootAccount implements LinkClickContext { + "," + COL_CONFIRM_FOLLOW_LOCKED + " integer default 1" + "," + COL_CONFIRM_UNFOLLOW + " integer default 1" + "," + COL_CONFIRM_POST + " integer default 1" + + // 以下はDBスキーマ13で更新 + + "," + COL_NOTIFICATION_TAG + " text default ''" + + + ")" ); db.execSQL( "create index if not exists " + table + "_user on " + table + "(u)" ); @@ -148,6 +158,13 @@ public class SavedAccount extends TootAccount implements LinkClickContext { ex.printStackTrace(); } } + if( oldVersion < 13 && newVersion >= 13 ){ + try{ + db.execSQL( "alter table " + table + " add column " + COL_NOTIFICATION_TAG + " text default ''" ); + }catch( Throwable ex ){ + ex.printStackTrace(); + } + } } private SavedAccount(){ @@ -178,6 +195,9 @@ public class SavedAccount extends TootAccount implements LinkClickContext { dst.confirm_unfollow = ( 0 != cursor.getInt( cursor.getColumnIndex( COL_CONFIRM_UNFOLLOW ) ) ); dst.confirm_post = ( 0 != cursor.getInt( cursor.getColumnIndex( COL_CONFIRM_POST ) ) ); + int idx_notification_tag = cursor.getColumnIndex( COL_NOTIFICATION_TAG ); + dst.notification_tag = cursor.isNull(idx_notification_tag) ? null : cursor.getString( idx_notification_tag ); + dst.token_info = new JSONObject( cursor.getString( cursor.getColumnIndex( COL_TOKEN ) ) ); } return dst; @@ -237,6 +257,18 @@ public class SavedAccount extends TootAccount implements LinkClickContext { cv.put( COL_CONFIRM_UNFOLLOW, confirm_unfollow ? 1 : 0 ); cv.put( COL_CONFIRM_POST, confirm_post ? 1 : 0 ); + if( notification_tag != null ) cv.put( COL_NOTIFICATION_TAG, notification_tag ); + + App1.getDB().update( table, cv, COL_ID + "=?", new String[]{ Long.toString( db_id ) } ); + } + + public void saveNotificationTag(){ + if( db_id == INVALID_ID ) + throw new RuntimeException( "SavedAccount.saveSetting missing db_id" ); + + ContentValues cv = new ContentValues(); + cv.put( COL_NOTIFICATION_TAG, notification_tag ); + App1.getDB().update( table, cv, COL_ID + "=?", new String[]{ Long.toString( db_id ) } ); } @@ -254,6 +286,7 @@ public class SavedAccount extends TootAccount implements LinkClickContext { this.notification_boost = b.notification_boost; this.notification_favourite = b.notification_favourite; this.notification_follow = b.notification_follow; + this.notification_tag = b.notification_tag; } public static @Nullable SavedAccount loadAccount( @NonNull LogCategory log, long db_id ){ @@ -370,4 +403,5 @@ public class SavedAccount extends TootAccount implements LinkClickContext { return 0L; } + } diff --git a/build.gradle b/build.gradle index aa7b440e..296e498a 100644 --- a/build.gradle +++ b/build.gradle @@ -8,6 +8,8 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:2.3.2' + classpath 'com.google.gms:google-services:3.0.0' + // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files }