- プッシュ通知の動作改善
- ファボ/ブーストを解除した時に「お気に入りしました」と表示される不具合の修正
- プル通知のチェックでタイムアウトが出たら死んだタンスかもしれないので警告を表示する
This commit is contained in:
tateisu 2017-09-08 04:44:13 +09:00
parent 321d865792
commit 40e10d1b0f
12 changed files with 389 additions and 82 deletions

View File

@ -10,6 +10,7 @@
<w>enty</w>
<w>favourited</w>
<w>firebase</w>
<w>foregrounder</w>
<w>gifv</w>
<w>hashtag</w>
<w>hashtags</w>

View File

@ -37,7 +37,7 @@
<ConfirmationsSetting value="0" id="Add" />
<ConfirmationsSetting value="0" id="Remove" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View File

@ -6,6 +6,7 @@
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<application
android:name=".App1"
@ -36,6 +37,10 @@
android:permission="android.permission.BIND_JOB_SERVICE"
/>
<service
android:name=".PollingForegrounder"
/>
<activity
android:name=".ActMain"
android:label="@string/app_name"

View File

@ -3925,12 +3925,21 @@ public class ActMain extends AppCompatActivity
Utils.showToast( ActMain.this, false, R.string.favourite_succeeded );
}
};
final ActMain.RelationChangedCallback unfavourite_complete_callback = new ActMain.RelationChangedCallback() {
@Override public void onRelationChanged(){
Utils.showToast( ActMain.this, false, R.string.unfavourite_succeeded );
}
};
final ActMain.RelationChangedCallback boost_complete_callback = new ActMain.RelationChangedCallback() {
@Override public void onRelationChanged(){
Utils.showToast( ActMain.this, false, R.string.boost_succeeded );
}
};
final ActMain.RelationChangedCallback unboost_complete_callback = new ActMain.RelationChangedCallback() {
@Override public void onRelationChanged(){
Utils.showToast( ActMain.this, false, R.string.unboost_succeeded );
}
};
private void openOSSLicense(){
startActivity( new Intent( this, ActOSSLicense.class ) );
}

View File

@ -1,6 +1,11 @@
package jp.juggler.subwaytooter;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
@ -14,14 +19,10 @@ public class MyFirebaseMessagingService extends FirebaseMessagingService {
@Override public void onCreate(){
super.onCreate();
// メインスレッド上でPollingWorkerを初期化しておく
PollingWorker.getInstance( getApplicationContext() );
}
@Override public void onMessageReceived( RemoteMessage remoteMessage ){
long time_start = SystemClock.elapsedRealtime();
super.onMessageReceived( remoteMessage );
String tag = null;
@ -35,8 +36,15 @@ public class MyFirebaseMessagingService extends FirebaseMessagingService {
}
}
}
PollingWorker.handleFCMMessage( getApplicationContext(), time_start, tag );
Context context = getApplicationContext();
Intent intent = new Intent( context,PollingForegrounder.class);
if(tag != null ) intent.putExtra(PollingWorker.EXTRA_TAG,tag);
if( Build.VERSION.SDK_INT >= 26 ){
context.startForegroundService( intent );
}else{
context.startService(intent);
}
}
}

View File

@ -0,0 +1,107 @@
package jp.juggler.subwaytooter;
import android.app.IntentService;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.ContextCompat;
import android.text.TextUtils;
import jp.juggler.subwaytooter.util.LogCategory;
import jp.juggler.subwaytooter.util.NotificationHelper;
public class PollingForegrounder extends IntentService {
static final LogCategory log = new LogCategory( "PollingForegrounder" );
static final int NOTIFICATION_ID_FOREGROUNDER = 2;
public PollingForegrounder(){
super( "PollingForegrounder" );
}
@Nullable @Override public IBinder onBind( Intent intent ){
return null;
}
@Override public void onCreate(){
log.d( "onCreate" );
super.onCreate();
// メインスレッド上でPollingWorkerを初期化しておく
PollingWorker.getInstance( getApplicationContext() );
startForeground( NOTIFICATION_ID_FOREGROUNDER, createNotification( getApplicationContext(), "" ) );
}
@Override public void onDestroy(){
log.d( "onDestroy" );
stopForeground( true );
super.onDestroy();
}
private Notification createNotification( Context context, String text ){
// 通知タップ時のPendingIntent
Intent intent_click = new Intent( context, ActMain.class );
intent_click.addFlags( Intent.FLAG_ACTIVITY_NEW_TASK );
PendingIntent pi_click = PendingIntent.getActivity( context, 2, intent_click, PendingIntent.FLAG_UPDATE_CURRENT );
NotificationCompat.Builder builder;
if( Build.VERSION.SDK_INT >= 26 ){
// Android 8 から通知のスタイルはユーザが管理することになった
// NotificationChannel を端末に登録しておけばチャネルごとに管理画面が作られる
NotificationChannel channel = NotificationHelper.createNotificationChannel(
context
, "PollingForegrounder" // id
, "real-time message notifier" // The user-visible name of the channel.
, null // The user-visible description of the channel.
, NotificationManager.IMPORTANCE_LOW
);
builder = new NotificationCompat.Builder( context, channel.getId() );
}else{
builder = new NotificationCompat.Builder( context, "not_used" );
}
builder
.setContentIntent( pi_click )
.setAutoCancel( false )
.setOngoing( true )
.setSmallIcon( R.drawable.ic_notification ) // ここは常に白テーマのアイコンを使う
.setColor( ContextCompat.getColor( context, R.color.Light_colorAccent ) ) // ここは常に白テーマの色を使う
.setWhen( System.currentTimeMillis() )
.setContentTitle( context.getString( R.string.loading_notification_title ) )
.setContentText( text )
;
// Android 7.0 ではグループを指定しないと勝手に通知が束ねられてしまう
// 束ねられた通知をタップしても pi_click が実行されないので困るため
// アカウント別にグループキーを設定する
builder.setGroup( context.getPackageName() + ":PollingForegrounder" );
return builder.build();
}
String last_status = null;
@Override protected void onHandleIntent( @Nullable Intent intent ){
if( intent == null ) return;
String tag = intent.getStringExtra( PollingWorker.EXTRA_TAG );
final Context context = getApplicationContext();
PollingWorker.handleFCMMessage( this, tag, new PollingWorker.JobStatusCallback() {
@Override public void onStatus( final String sv ){
if( TextUtils.isEmpty( sv ) || sv.equals( last_status ) ) return;
last_status = sv;
log.d( "onStatus %s",sv );
startForeground( NOTIFICATION_ID_FOREGROUNDER, createNotification( context, sv ) );
}
} );
}
}

View File

@ -11,6 +11,8 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.net.wifi.WifiManager;
import android.os.Build;
@ -38,6 +40,7 @@ import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.TreeSet;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
@ -68,6 +71,7 @@ public class PollingWorker {
static final LogCategory log = new LogCategory( "PollingWorker" );
static final int NOTIFICATION_ID = 1;
static final int NOTIFICATION_ID_ERROR = 3;
// Notification のJSONObject を日時でソートするためにデータを追加する
static final String KEY_TIME = "<>time";
@ -125,6 +129,7 @@ public class PollingWorker {
final Context context;
final Handler handler;
final SharedPreferences pref;
final ConnectivityManager connectivityManager;
final NotificationManager notification_manager;
final JobScheduler scheduler;
final PowerManager power_manager;
@ -136,6 +141,7 @@ public class PollingWorker {
log.d( "ctor" );
this.context = c.getApplicationContext();
this.connectivityManager = (ConnectivityManager) context.getSystemService( Context.CONNECTIVITY_SERVICE );
this.notification_manager = (NotificationManager) context.getSystemService( Context.NOTIFICATION_SERVICE );
this.scheduler = (JobScheduler) context.getSystemService( Context.JOB_SCHEDULER_SERVICE );
@ -209,6 +215,7 @@ public class PollingWorker {
public void run(){
log.e( "worker thread start." );
job_status.set( "worker thread start." );
while( ! bThreadCancelled.get() ){
JobItem item = null;
try{
@ -221,15 +228,19 @@ public class PollingWorker {
break;
}
}
if( item == null ){
job_status.set( "no job to run." );
waitEx( 86400000L );
continue;
}
job_status.set( "start job " + item.jobId );
acquirePowerLock();
try{
item.refWorker.set( Worker.this );
item.run();
}finally{
job_status.set( "end job " + item.jobId );
item.refWorker.set( null );
releasePowerLock();
}
@ -237,6 +248,7 @@ public class PollingWorker {
log.trace( ex );
}
}
job_status.set( "worker thread end." );
log.e( "worker thread end." );
}
}
@ -263,7 +275,7 @@ public class PollingWorker {
// JobService#onStartJob から呼ばれる
public boolean onStartJob( @NonNull JobService jobService, @NonNull JobParameters params ){
JobItem item = new JobItem( jobService, params );
addJob( item );
addJob( item, true );
return true;
// return True if your context needs to process the work (on a separate thread).
// return False if there's no more work to be done for this job.
@ -272,9 +284,7 @@ public class PollingWorker {
// FCMメッセージイベントから呼ばれる
private boolean hasJob( int jobId ){
synchronized( job_list ){
Iterator< JobItem > it = job_list.iterator();
while( it.hasNext() ){
JobItem itemOld = it.next();
for( JobItem itemOld : job_list ){
if( itemOld.jobId == jobId ) return true;
}
}
@ -282,23 +292,25 @@ public class PollingWorker {
}
// FCMメッセージイベントから呼ばれる
private void addJob( int jobId ){
addJob( new JobItem( jobId ) );
private void addJob( int jobId, boolean bRemoveOld ){
addJob( new JobItem( jobId ), bRemoveOld );
}
private void addJob( @NonNull JobItem item ){
private void addJob( @NonNull JobItem item, boolean bRemoveOld ){
int jobId = item.jobId;
// 同じジョブ番号がジョブリストにあるか
synchronized( job_list ){
Iterator< JobItem > it = job_list.iterator();
while( it.hasNext() ){
JobItem itemOld = it.next();
if( itemOld.jobId == jobId ){
log.w( "onStartJob: jobId=%s, old job cancelled." );
// 同じジョブをすぐに始めるのだからrescheduleはfalse
itemOld.cancel( false );
it.remove();
if( bRemoveOld ){
Iterator< JobItem > it = job_list.iterator();
while( it.hasNext() ){
JobItem itemOld = it.next();
if( itemOld.jobId == jobId ){
log.w( "addJob: jobId=%s, old job cancelled.", jobId );
// 同じジョブをすぐに始めるのだからrescheduleはfalse
itemOld.cancel( false );
it.remove();
}
}
}
log.d( "addJob: jobId=%s, add to list.", jobId );
@ -397,11 +409,25 @@ public class PollingWorker {
public void run(){
job_status.set( "job start." );
try{
log.d( "(JobItem.run jobId=%s", jobId );
if( isJobCancelled() ) throw new JobCancelledException();
job_status.set( "check network status.." );
long net_wait_start = SystemClock.elapsedRealtime();
while( ! checkNetwork() ){
if( isJobCancelled() ) throw new JobCancelledException();
long now = SystemClock.elapsedRealtime();
long delta = now - net_wait_start;
if( delta >= 10000L ){
log.d( "network state timeout." );
break;
}
waitWorkerThread( 333L );
}
muted_app = MutedApp.getNameSet();
muted_word = MutedWord.getNameSet();
@ -418,6 +444,7 @@ public class PollingWorker {
// タスクがなかった場合でも定期実行ジョブからの実行ならポーリングを行う
new TaskRunner().runTask( JobItem.this, TASK_POLLING, null );
}
job_status.set( "make next schedule." );
if( ! isJobCancelled() && bPollingComplete ){
// ポーリングが完了したのならポーリングが必要かどうかに合わせてジョブのスケジュールを変更する
@ -447,6 +474,8 @@ public class PollingWorker {
}catch( Throwable ex ){
log.trace( ex );
log.e( ex, "job execution failed." );
}finally{
job_status.set( "job finished." );
}
// ジョブ終了報告
if( ! isJobCancelled() ){
@ -474,6 +503,23 @@ public class PollingWorker {
log.d( ")JobItem.run jobId=%s, cancel=%s", jobId, isJobCancelled() );
}
private boolean checkNetwork(){
NetworkInfo ni = connectivityManager.getActiveNetworkInfo();
if( ni == null ){
log.d( "checkNetwork: getActiveNetworkInfo() returns null." );
return false;
}else{
NetworkInfo.State state = ni.getState();
NetworkInfo.DetailedState detail = ni.getDetailedState();
log.d( "checkNetwork: state=%s,detail=%s", state, detail );
if( state != NetworkInfo.State.CONNECTED ){
log.d( "checkNetwork: not connected." );
return false;
}else{
return true;
}
}
}
}
//////////////////////////////////////////////////////////////////////
@ -490,13 +536,17 @@ public class PollingWorker {
JobItem job;
int taskId;
final ArrayList< String > error_instance = new ArrayList<>();
public void runTask( JobItem job, int taskId, JSONObject taskData ){
try{
log.e( "(runTask: taskId=%s", taskId );
job_status.set( "start task " + taskId );
this.job = job;
this.taskId = taskId;
long process_db_id = -1L;
long process_db_id = - 1L;
if( taskId == TASK_APP_DATA_IMPORT_BEFORE ){
scheduler.cancelAll();
@ -569,6 +619,8 @@ public class PollingWorker {
loadCustomStreamListenerSetting();
job_status.set( "make install id" );
// インストールIDを生成する
// インストールID生成時にSavedAccountテーブルを操作することがあるので
// アカウントリストの取得より先に行う
@ -576,16 +628,19 @@ public class PollingWorker {
job.install_id = getInstallId();
}
job_status.set( "create account thread" );
LinkedList< AccountThread > thread_list = new LinkedList<>();
for( SavedAccount _a : SavedAccount.loadAccountList( context, log ) ){
if( _a.isPseudo() ) continue;
if( process_db_id != -1L && _a.db_id != process_db_id ) continue;
if( process_db_id != - 1L && _a.db_id != process_db_id ) continue;
AccountThread t = new AccountThread( _a );
thread_list.add( t );
t.start();
}
for( ; ; ){
TreeSet< String > set = new TreeSet<>();
Iterator< AccountThread > it = thread_list.iterator();
while( it.hasNext() ){
AccountThread t = it.next();
@ -593,15 +648,28 @@ public class PollingWorker {
it.remove();
continue;
}
set.add( t.account.host );
if( job.isJobCancelled() ){
t.cancel();
}
}
if( thread_list.isEmpty() ) break;
int remain = thread_list.size();
if( remain <= 0 ) break;
//
StringBuilder sb = new StringBuilder();
for( String s : set ){
if( sb.length() > 0 ) sb.append( ", " );
sb.append( s );
}
job_status.set( "waiting " + sb.toString() );
//
job.waitWorkerThread( job.isJobCancelled() ? 50L : 1000L );
}
synchronized( error_instance ){
createErrorNotification( error_instance );
}
if( ! job.isJobCancelled() ) job.bPollingComplete = true;
}catch( Throwable ex ){
@ -609,9 +677,67 @@ public class PollingWorker {
log.e( ex, "task execution failed." );
}finally{
log.e( ")runTask: taskId=%s", taskId );
job_status.set( "end task " + taskId );
}
}
private void createErrorNotification( ArrayList< String > error_instance ){
if( error_instance.isEmpty() ){
return;
}
// 通知タップ時のPendingIntent
Intent intent_click = new Intent( context, ActCallback.class );
intent_click.addFlags( Intent.FLAG_ACTIVITY_NEW_TASK );
PendingIntent pi_click = PendingIntent.getActivity( context, 3, intent_click, PendingIntent.FLAG_UPDATE_CURRENT );
NotificationCompat.Builder builder;
if( Build.VERSION.SDK_INT >= 26 ){
// Android 8 から通知のスタイルはユーザが管理することになった
// NotificationChannel を端末に登録しておけばチャネルごとに管理画面が作られる
NotificationChannel channel = NotificationHelper.createNotificationChannel(
context
, "ErrorNotification"
, "Error"
, null
, NotificationManager.IMPORTANCE_LOW
);
builder = new NotificationCompat.Builder( context, channel.getId() );
}else{
builder = new NotificationCompat.Builder( context, "not_used" );
}
builder
.setContentIntent( pi_click )
.setAutoCancel( true )
.setSmallIcon( R.drawable.ic_notification ) // ここは常に白テーマのアイコンを使う
.setColor( ContextCompat.getColor( context, R.color.Light_colorAccent ) ) // ここは常に白テーマの色を使う
.setWhen( System.currentTimeMillis() )
.setGroup( context.getPackageName() + ":" + "Error" )
;
{
String header = context.getString( R.string.error_notification_title );
String summary = context.getString( R.string.error_notification_summary );
builder
.setContentTitle( header )
.setContentText( summary + ": " + error_instance.get( 0 ) )
;
NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle()
.setBigContentTitle( header )
.setSummaryText( summary );
for( int i = 0 ; i < 5 ; ++ i ){
if( i >= error_instance.size() ) break;
style.addLine( error_instance.get( i ) );
}
builder.setStyle( style );
}
notification_manager.notify( NOTIFICATION_ID_ERROR, builder.build() );
}
void loadCustomStreamListenerSetting(){
mCustomStreamListenerSetting = null;
mCustomStreamListenerSecret = null;
@ -950,6 +1076,22 @@ public class PollingWorker {
break;
}else{
log.d( "error. %s", result.error );
String sv = result.error;
if( sv.contains( "Timeout" ) ){
synchronized( error_instance ){
boolean bFound = false;
for( String x : error_instance ){
if( x.equals( sv ) ){
bFound = true;
break;
}
}
if( ! bFound ){
error_instance.add( sv );
}
}
}
}
}
}
@ -1356,43 +1498,6 @@ public class PollingWorker {
// FCMメッセージの処理
//
public static void handleFCMMessage( @NonNull Context context, long time_start, @Nullable String tag ){
// FirebaseMessagingService#onMessageReceived はバックグラウンドスレッドから実行されるので少しなら待機してもよい
// https://firebase.google.com/docs/cloud-messaging/android/receive
// 10秒を超えるとプロセスごと殺されるかもしれない
// タスクを追加
JSONObject data = new JSONObject();
try{
if( tag != null ) data.putOpt( EXTRA_TAG, tag );
data.put( EXTRA_TASK_ID, TASK_FCM_MESSAGE );
}catch( JSONException ignored ){
}
task_list.addLast( context, true, data );
// JobScheduler 経由ではないがジョブを追加して実行開始
PollingWorker pw = getInstance( context );
pw.addJob( JOB_FCM );
for( ; ; ){
long now = SystemClock.elapsedRealtime();
if( ! pw.hasJob( JOB_FCM ) ){
log.d( "handleFCMMessage: JOB_FCM completed. time=%.2f",(now-time_start)/1000f );
break;
}
if( now - time_start >= ( 1000L * 300 ) ){
log.d( "handleFCMMessage: JOB_FCM timeout. exit onMessageReceived..." );
break;
}
try{
Thread.sleep( 50L );
}catch( InterruptedException ex ){
break;
}
}
}
////////////////////////////////////////////////////////////////////////////
// タスクの追加
@ -1478,4 +1583,55 @@ public class PollingWorker {
public static void queuePackageReplaced( Context context ){
addTask( context, true, TASK_PACKAGE_REPLACED, null );
}
public interface JobStatusCallback {
void onStatus( String sv );
}
static final AtomicReference< String > job_status = new AtomicReference<>( null );
public static void handleFCMMessage( Context context, String tag, JobStatusCallback callback ){
log.d( "handleFCMMessage: start. tag=%s", tag );
long time_start = SystemClock.elapsedRealtime();
callback.onStatus( "=>" );
// タスクを追加
JSONObject data = new JSONObject();
try{
if( tag != null ) data.putOpt( EXTRA_TAG, tag );
data.put( EXTRA_TASK_ID, TASK_FCM_MESSAGE );
}catch( JSONException ignored ){
}
task_list.addLast( context, true, data );
callback.onStatus( "==>" );
// 疑似ジョブを開始
PollingWorker pw = getInstance( context );
pw.addJob( JOB_FCM, false );
// 疑似ジョブが終了するまで待機する
for( ; ; ){
// ジョブが完了した
long now = SystemClock.elapsedRealtime();
if( ! pw.hasJob( JOB_FCM ) ){
log.d( "handleFCMMessage: JOB_FCM completed. time=%.2f", ( now - time_start ) / 1000f );
break;
}
// ジョブの状況を通知する
String sv = job_status.get();
if( sv == null ) sv = "(null)";
callback.onStatus( sv );
// 少し待機
try{
Thread.sleep( 50L );
}catch( InterruptedException ex ){
log.e( ex, "handleFCMMessage: blocking is interrupted." );
break;
}
}
}
}

View File

@ -162,7 +162,7 @@ class StatusButtons implements View.OnClickListener, View.OnLongClickListener {
, ActMain.NOT_CROSS_ACCOUNT
, ! status.reblogged
, false
, bSimpleList ? activity.boost_complete_callback : null
, !bSimpleList ? null : status.reblogged ? activity.boost_complete_callback : activity.unboost_complete_callback
);
}
break;
@ -176,7 +176,7 @@ class StatusButtons implements View.OnClickListener, View.OnLongClickListener {
, status
, ActMain.NOT_CROSS_ACCOUNT
, ! status.favourited
, bSimpleList ? activity.favourite_complete_callback : null
, !bSimpleList ? null : status.favourited ? activity.unfavourite_complete_callback : activity.favourite_complete_callback
);
}
break;

View File

@ -5,6 +5,7 @@ import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import jp.juggler.subwaytooter.R;
import jp.juggler.subwaytooter.table.SavedAccount;
@ -15,19 +16,24 @@ public class NotificationHelper {
@TargetApi(26)
public static NotificationChannel createNotificationChannel( @NonNull Context context, @NonNull SavedAccount account ){
return createNotificationChannel(context
,account.acct
,account.acct
,context.getString( R.string.notification_channel_description, account.acct )
,NotificationManager.IMPORTANCE_DEFAULT // : NotificationManager.IMPORTANCE_LOW;
);
}
@TargetApi(26)
public static NotificationChannel createNotificationChannel(
@NonNull Context context
, @NonNull String channel_id // id
, @NonNull String name // The user-visible name of the channel.
, @Nullable String description // The user-visible description of the channel.
, int importance
){
NotificationManager notification_manager = (NotificationManager) context.getSystemService( Context.NOTIFICATION_SERVICE );
// The id of the channel.
String channel_id = account.acct;
// The user-visible name of the channel.
CharSequence name = context.getString( R.string.notification_for, account.acct );
// The user-visible description of the channel.
String description = context.getString( R.string.notification_channel_description, account.acct );
//
int importance = NotificationManager.IMPORTANCE_DEFAULT; // : NotificationManager.IMPORTANCE_LOW;
//
NotificationChannel channel = null;
try{
@ -39,7 +45,8 @@ public class NotificationHelper {
channel = new NotificationChannel( channel_id, name, importance );
}
channel.setName( name );
channel.setDescription( description );
channel.setImportance( importance );
if( description != null ) channel.setDescription( description );
notification_manager.createNotificationChannel( channel );
return channel;
}

View File

@ -51,6 +51,7 @@
<string name="boost">Partager</string>
<string name="boost_from_another_account">Partager à partir d\'un autre compte</string>
<string name="boost_succeeded">Partagé avec succès</string>
<string name="unboost_succeeded">boost removed</string>
<string name="boosted_by">Partagé par …</string>
<string name="button_background_color">Couleur d\'arrière plan des onglets</string>
<string name="button_foreground_color">Couleur des onglets</string>
@ -132,6 +133,7 @@
<string name="favourite">Favori</string>
<string name="favourite_from_another_account">Placer dans les favoris à partir d\'un autre compte</string>
<string name="favourite_succeeded">Mis en favori avec succès</string>
<string name="unfavourite_succeeded">Mis en unfavori avec succès</string>
<string name="favourited_by">Mis en favori par …</string>
<string name="favourites">Favoris</string>
<string name="federate_timeline">Chronologie fédérée</string>
@ -468,6 +470,9 @@
<string name="length_warning">\"%1$s\" is too long (%2$d/%3$d characters).\nIt is not acceptable in the standard instance, but it may be acceptable in some instances.\nAre you sure?</string>
<string name="avatar_icon_size">Avatar icon size (unit:dp. default:48. app restart required)</string>
<string name="short_acct_local_user">Short acct for local user (app restart required)</string>
<string name="loading_notification_title">Loading notification…</string>
<string name="error_notification_title">Server Timeout</string>
<string name="error_notification_summary">If it\'s dead instance, please remove account on that server.</string>
<!--<string name="abc_action_bar_home_description">Revenir à l\'accueil</string>-->
<!--<string name="abc_action_bar_home_description_format">%1$s, %2$s</string>-->

View File

@ -369,8 +369,10 @@
<string name="application_is">アプリ: %1$s</string>
<!---->
<string name="boost_succeeded">ブーストできました</string>
<string name="unboost_succeeded">ブースト解除しました</string>
<!---->
<string name="favourite_succeeded">お気に入りにしました</string>
<string name="unfavourite_succeeded">お気に入り解除しました</string>
<!---->
<string name="mute_app_desc">スワイプで解除。解除した後はカラムをリロードすると再表示されます</string>
<!---->
@ -755,5 +757,7 @@
<string name="length_warning">\"%1$s\"が長すぎます(%2$d/%3$d文字).\n標準的なインスタンスではエラーとなりますが、いくつかのインスタンスでは許容されるかも。\nよろしいですか?</string>
<string name="avatar_icon_size">アバターアイコンサイズ(単位:dp. デフォルト:48. アプリ再起動が必要)</string>
<string name="short_acct_local_user">ローカルユーザのAcct表記を短くする(アプリ再起動が必要)</string>
<string name="loading_notification_title">通知の取得中…</string>
<string name="error_notification_title">サーバータイムアウト</string>
<string name="error_notification_summary">もし死んだインスタンスなら、そのサーバ上のアカウントを除去してください</string>
</resources>

View File

@ -190,7 +190,9 @@
<string name="mute_app_of">mute app \"%1$s\"</string>
<string name="app_was_muted">App was muted.</string>
<string name="favourite_succeeded">favourite succeeded</string>
<string name="unfavourite_succeeded">favourite removed</string>
<string name="boost_succeeded">boost succeeded</string>
<string name="unboost_succeeded">boost removed</string>
<string name="notification_option">notification option</string>
<string name="sound">sound</string>
<string name="vibration">vibration</string>
@ -462,5 +464,8 @@
<string name="length_warning">\"%1$s\" is too long (%2$d/%3$d characters).\nIt is not acceptable in the standard instance, but it may be acceptable in some instances.\nAre you sure?</string>
<string name="avatar_icon_size">Avatar icon size (unit:dp. default:48. app restart required)</string>
<string name="short_acct_local_user">Short acct for local user (app restart required)</string>
<string name="loading_notification_title">Loading notification…</string>
<string name="error_notification_title">Server Timeout</string>
<string name="error_notification_summary">If it\'s dead instance, please remove account on that server.</string>
</resources>