diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1b717a3e5..8438fdf83 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -172,6 +172,11 @@ + + requestPermissionLauncher; - public static Intent newIntent(Context context, int resItem) { return new Intent(context, MainActivity.class).putExtra(RES_ITEM, resItem).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); } @@ -97,14 +88,9 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { .withExtra(AUTOTEST_DIALOG) .build().show(getSupportFragmentManager(), null); } else if (notificationManager.shouldShow()) { - new ConfirmDialogFragment.Builder() - .withTitle(getString(R.string.Modal_EnableNotifications_Title)) - .withMessage(getString(R.string.Modal_EnableNotifications_Paragraph)) - .withPositiveButton(getString(R.string.Modal_SoundsGreat)) - .withNegativeButton(getString(R.string.Modal_NotNow)) - .withNeutralButton(getString(R.string.Modal_DontAskAgain)) - .withExtra(NOTIFICATION_DIALOG) - .build().show(getSupportFragmentManager(), null); + registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { + if (result.getResultCode() == Activity.RESULT_OK) {} + }).launch(PromptActivity.newIntent(this, PromptActivity.Prompt.CENSORSHIP_CONSENT)); } ThirdPartyServices.checkUpdates(this); } @@ -118,46 +104,8 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); } } - requestNotificationPermission(); } - private void requestNotificationPermission() { - - requestPermissionLauncher = registerForActivityResult( - new ActivityResultContracts.RequestPermission(), - (result) -> { - if (!result) { - Snackbar.make( - binding.getRoot(), - "Please grant Notification permission from App Settings", - Snackbar.LENGTH_LONG - ).setAction(R.string.Settings_Title, view -> { - Intent intent = new Intent(); - intent.setAction("android.settings.APP_NOTIFICATION_SETTINGS"); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - //for Android 5-7 - intent.putExtra("app_package", getPackageName()); - intent.putExtra("app_uid", getApplicationInfo().uid); - - // for Android 8 and above - intent.putExtra("android.provider.extra.APP_PACKAGE", getPackageName()); - - startActivity(intent); - }).show(); - } - } - ); - NotificationUtility.setChannel(getApplicationContext(), CHANNEL_ID, getString(R.string.Settings_AutomatedTesting_Label), false, false, false); - if (ContextCompat.checkSelfPermission( - this, - Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED - ) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS); - } - } - } @Override protected void onNewIntent(Intent intent) { @@ -179,17 +127,6 @@ else if (intent.getExtras().containsKey(NOTIFICATION_DIALOG)){ @Override public void onConfirmation(Serializable extra, int i) { if (extra == null) return; - if (extra.equals(NOTIFICATION_DIALOG)) { - notificationManager.getUpdates(i == DialogInterface.BUTTON_POSITIVE); - - //If positive answer reload consents and init notification - if (i == DialogInterface.BUTTON_POSITIVE){ - ThirdPartyServices.reloadConsents((Application) getApplication()); - } - else if (i == DialogInterface.BUTTON_NEUTRAL){ - notificationManager.disableAskNotificationDialog(); - } - } if (extra.equals(AUTOTEST_DIALOG)) { preferenceManager.setNotificationsFromDialog(i == DialogInterface.BUTTON_POSITIVE); if (i == DialogInterface.BUTTON_POSITIVE){ diff --git a/app/src/main/java/org/openobservatory/ooniprobe/activity/PromptActivity.java b/app/src/main/java/org/openobservatory/ooniprobe/activity/PromptActivity.java new file mode 100644 index 000000000..303ed944b --- /dev/null +++ b/app/src/main/java/org/openobservatory/ooniprobe/activity/PromptActivity.java @@ -0,0 +1,206 @@ +package org.openobservatory.ooniprobe.activity; + + +import android.Manifest; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Bundle; +import android.view.View; + +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.StringRes; +import androidx.core.content.ContextCompat; + +import com.google.android.material.snackbar.Snackbar; + +import org.openobservatory.ooniprobe.R; +import org.openobservatory.ooniprobe.databinding.ActivityPromptBinding; +import org.openobservatory.ooniprobe.domain.UpdatesNotificationManager; + +import javax.inject.Inject; + +public class PromptActivity extends AbstractActivity { + private static final String PROMPT_ITEM = "promptItem"; + @Inject + UpdatesNotificationManager notificationManager; + private ActivityPromptBinding binding; + private Prompt prompt; + + private ActivityResultLauncher requestPermissionLauncher; + + public static Intent newIntent(Context context, Prompt prompt) { + return new Intent(context, PromptActivity.class).putExtra(PROMPT_ITEM, prompt); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getActivityComponent().inject(this); + binding = ActivityPromptBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + + prompt = (Prompt) getIntent().getExtras().get(PROMPT_ITEM); + binding.title.setText(prompt.title); + binding.description.setText(prompt.paragraph); + + registerPermissionRequest(); + setUpClickListeners(); + } + + private void registerPermissionRequest() { + requestPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), (result) -> { + if (!result) { + Snackbar.make(binding.getRoot(), "Please grant Notification permission from App Settings", Snackbar.LENGTH_LONG).setAction(R.string.Settings_Title, view -> { + Intent intent = new Intent(); + intent.setAction("android.settings.APP_NOTIFICATION_SETTINGS"); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + //for Android 5-7 + intent.putExtra("app_package", getPackageName()); + intent.putExtra("app_uid", getApplicationInfo().uid); + + // for Android 8 and above + intent.putExtra("android.provider.extra.APP_PACKAGE", getPackageName()); + + startActivity(intent); + }).show(); + } + setResult(result ? Activity.RESULT_OK : Activity.RESULT_CANCELED); + finish(); + }); + } + + private void setUpClickListeners() { + OnPromptAction actions; + switch (prompt) { + case CENSORSHIP_CONSENT: + actions = new InternetCensorshipConsentActions(); + break; + case TEST_PROGRESS_CONSENT: + actions = new TestProgressNotificationConsentActions(); + break; + default: + actions = new OnPromptAction() { + @Override + public void onClickPositive(View view) { /*No Implementation*/ } + + @Override + public void onClickNeutral(View view) { /*No Implementation*/ } + + @Override + public void onClickNegative(View view) { /*No Implementation*/ } + }; + } + + binding.soundsGreat.setOnClickListener(actions::onClickPositive); + binding.notNow.setOnClickListener(actions::onClickNeutral); + binding.dontAskAgain.setOnClickListener(actions::onClickNegative); + } + + public void requestNotificationPermission() { + if (ContextCompat.checkSelfPermission( + this, Manifest.permission.POST_NOTIFICATIONS) + != PackageManager.PERMISSION_GRANTED) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS); + } + } + } + + public enum Prompt { + CENSORSHIP_CONSENT(R.string.Modal_EnableNotifications_Title, R.string.Modal_EnableNotifications_Paragraph), + TEST_PROGRESS_CONSENT(R.string.Prompt_EnableTestProgressNotifications_Title, R.string.Prompt_EnableTestProgressNotifications_Paragraph); + + private final int title; + private final int paragraph; + + Prompt(@StringRes int title, @StringRes int paragraph) { + this.title = title; + this.paragraph = paragraph; + } + } + + interface OnPromptAction { + /** + * Callback for View#onClick of `soundsGreat` button. + * + * @param view The view that was clicked. + */ + void onClickPositive(View view); + + /** + * Callback for View#onClick of `notNow` button. + * + * @param view The view that was clicked. + */ + void onClickNeutral(View view); + + /** + * Callback for View#onClick of `dontAskAgain` button. + * + * @param view The view that was clicked. + */ + void onClickNegative(View view); + } + + private class InternetCensorshipConsentActions implements OnPromptAction { + + @Override + public void onClickPositive(View view) { + notificationManager.getUpdates(true); + if (ContextCompat.checkSelfPermission( + PromptActivity.this, + Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED){ + PromptActivity.this.requestNotificationPermission(); + } else { + setResult(Activity.RESULT_OK); + finish(); + } + } + + @Override + public void onClickNeutral(View view) { + PromptActivity.this.setResult(Activity.RESULT_CANCELED); + PromptActivity.this.finish(); + } + + @Override + public void onClickNegative(View view) { + notificationManager.disableAskNotificationDialog(); + onClickNeutral(view); + } + } + + private class TestProgressNotificationConsentActions implements OnPromptAction { + + @Override + public void onClickPositive(View view) { + notificationManager.setTestProgressNotificationConsent(true); + if (ContextCompat.checkSelfPermission( + PromptActivity.this, + Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED){ + PromptActivity.this.requestNotificationPermission(); + } else { + setResult(Activity.RESULT_OK); + finish(); + } + } + + @Override + public void onClickNeutral(View view) { + PromptActivity.this.setResult(Activity.RESULT_CANCELED); + PromptActivity.this.finish(); + } + + @Override + public void onClickNegative(View view) { + notificationManager.disableAskTestProgressNotificationConsent(); + onClickNeutral(view); + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/openobservatory/ooniprobe/activity/RunningActivity.java b/app/src/main/java/org/openobservatory/ooniprobe/activity/RunningActivity.java index c65b13df1..465da02dc 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/activity/RunningActivity.java +++ b/app/src/main/java/org/openobservatory/ooniprobe/activity/RunningActivity.java @@ -70,6 +70,11 @@ public static void runAsForegroundService(AbstractActivity context, ArrayList testSuites, OnTestServiceStartedListener onTestServiceStartedListener, PreferenceManager iPreferenceManager) { + + if (iPreferenceManager.shouldShowTestProgressConsent()){ + context.startActivity(PromptActivity.newIntent(context, PromptActivity.Prompt.TEST_PROGRESS_CONSENT)); + } + if (ReachabilityManager.getNetworkType(context).equals(ReachabilityManager.NO_INTERNET)) { new MessageDialogFragment.Builder() .withTitle(context.getString(R.string.Modal_Error)) diff --git a/app/src/main/java/org/openobservatory/ooniprobe/common/PreferenceManager.java b/app/src/main/java/org/openobservatory/ooniprobe/common/PreferenceManager.java index ba228a80b..c7b38a860 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/common/PreferenceManager.java +++ b/app/src/main/java/org/openobservatory/ooniprobe/common/PreferenceManager.java @@ -27,6 +27,7 @@ public class PreferenceManager { public static final int NOTIFICATION_DIALOG_COUNT = 7; public static final int AUTOTEST_DIALOG_COUNT = 5; static final String NOTIFICATION_DIALOG_DISABLE = "isNotificationDialogDisabled"; + static final String TEST_PROGRESS_NOTIFICATION_DISABLE = "isTestProgressNotificationDisabled"; private static final String AUTOTEST_DIALOG_DISABLE = "isAutomaticTestDialogDisabled"; private static final String TOKEN = "token"; static final String SHOW_ONBOARDING = "first_run"; @@ -115,6 +116,31 @@ public boolean isNotifications() { return sp.getBoolean(r.getString(R.string.notifications_enabled), false); } + public boolean isTestProgressNotifications() { + return sp.getBoolean(r.getString(R.string.test_progress_notifications_enabled), false); + } + + public boolean isAskTestProgressNotificationConsent() { + return sp.getBoolean(TEST_PROGRESS_NOTIFICATION_DISABLE, false); + } + + public void disableAskTestProgressNotificationConsent() { + sp.edit().putBoolean(TEST_PROGRESS_NOTIFICATION_DISABLE, true) + .apply(); + } + + public void setTestProgressNotificationConsent(boolean enabled) { + //set notification value and increment app open + sp.edit() + .putBoolean(r.getString(R.string.test_progress_notifications_enabled), enabled) + .apply(); + } + + public boolean shouldShowTestProgressConsent() { + return !isTestProgressNotifications() + && !isAskTestProgressNotificationConsent(); + } + public boolean isDarkTheme() { return sp.getBoolean(r.getString(R.string.theme_enabled), false); } diff --git a/app/src/main/java/org/openobservatory/ooniprobe/di/ActivityComponent.java b/app/src/main/java/org/openobservatory/ooniprobe/di/ActivityComponent.java index f88835ebf..e3a54992b 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/di/ActivityComponent.java +++ b/app/src/main/java/org/openobservatory/ooniprobe/di/ActivityComponent.java @@ -7,6 +7,7 @@ import org.openobservatory.ooniprobe.activity.MeasurementDetailActivity; import org.openobservatory.ooniprobe.activity.OoniRunActivity; import org.openobservatory.ooniprobe.activity.OverviewActivity; +import org.openobservatory.ooniprobe.activity.PromptActivity; import org.openobservatory.ooniprobe.activity.ProxyActivity; import org.openobservatory.ooniprobe.activity.ResultDetailActivity; import org.openobservatory.ooniprobe.activity.RunningActivity; @@ -28,4 +29,5 @@ public interface ActivityComponent { void inject(RunningActivity activity); void inject(TextActivity activity); void inject(LogActivity activity); + void inject(PromptActivity activity); } \ No newline at end of file diff --git a/app/src/main/java/org/openobservatory/ooniprobe/domain/UpdatesNotificationManager.java b/app/src/main/java/org/openobservatory/ooniprobe/domain/UpdatesNotificationManager.java index da20fd3f7..3a4790381 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/domain/UpdatesNotificationManager.java +++ b/app/src/main/java/org/openobservatory/ooniprobe/domain/UpdatesNotificationManager.java @@ -34,4 +34,12 @@ public void getUpdates(boolean notificationUpdates) { public void disableAskNotificationDialog() { pm.disableAskNotificationDialog(); } + + public void disableAskTestProgressNotificationConsent() { + pm.disableAskTestProgressNotificationConsent(); + } + + public void setTestProgressNotificationConsent(boolean enabled) { + pm.setTestProgressNotificationConsent(enabled); + } } diff --git a/app/src/main/java/org/openobservatory/ooniprobe/fragment/PreferenceFragment.java b/app/src/main/java/org/openobservatory/ooniprobe/fragment/PreferenceFragment.java index a3de2c4b8..e3daa7f45 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/fragment/PreferenceFragment.java +++ b/app/src/main/java/org/openobservatory/ooniprobe/fragment/PreferenceFragment.java @@ -2,10 +2,12 @@ import static org.openobservatory.ooniprobe.common.PreferenceManager.COUNT_WEBSITE_CATEGORIES; +import android.Manifest; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -17,10 +19,14 @@ import android.view.MenuItem; import android.widget.Toast; +import androidx.activity.result.ActivityResultCallerLauncher; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.IdRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.XmlRes; +import androidx.core.content.ContextCompat; import androidx.preference.EditTextPreference; import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; @@ -48,6 +54,7 @@ public class PreferenceFragment extends ExtendedPreferenceFragment requestPermissionLauncher; public static PreferenceFragment newInstance(@XmlRes int preferencesResId, @IdRes int preferencesContainerResId, String rootKey) { PreferenceFragment fragment = new PreferenceFragment(); @@ -59,6 +66,12 @@ public static PreferenceFragment newInstance(@XmlRes int preferencesResId, @IdRe return fragment; } + @Override + public void onAttach(@NonNull Context context) { + requestPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), (result) -> {}); + super.onAttach(context); + } + @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { this.rootKey = rootKey; @@ -171,9 +184,19 @@ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, Strin if (key.equals(getString(R.string.automated_testing_charging)) || key.equals(getString(R.string.automated_testing_wifionly))){ //stop and re-enable scheduler in case of wifi charging option changed + ServiceUtil.stopJob(getContext()); ServiceUtil.scheduleJob(getContext()); } + if (key.equals(getString(R.string.test_progress_notifications_enabled))){ + if (ContextCompat.checkSelfPermission( + getContext(), Manifest.permission.POST_NOTIFICATIONS) + != PackageManager.PERMISSION_GRANTED) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS); + } + } + } if (key.equals(getString(R.string.send_crash)) || key.equals(getString(R.string.notifications_enabled))){ ThirdPartyServices.reloadConsents((Application) getActivity().getApplication()); diff --git a/app/src/main/res/drawable/ooni_phone.xml b/app/src/main/res/drawable/ooni_phone.xml new file mode 100644 index 000000000..231083216 --- /dev/null +++ b/app/src/main/res/drawable/ooni_phone.xml @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_prompt.xml b/app/src/main/res/layout/activity_prompt.xml new file mode 100644 index 000000000..a1115b081 --- /dev/null +++ b/app/src/main/res/layout/activity_prompt.xml @@ -0,0 +1,85 @@ + + + + + + + + + +