That fuck shit the fascists are using
at master 393 lines 19 kB view raw
1package org.tm.archive.devicetransfer; 2 3import android.content.ActivityNotFoundException; 4import android.content.Intent; 5import android.location.LocationManager; 6import android.net.wifi.WifiManager; 7import android.os.Bundle; 8import android.provider.Settings; 9import android.text.method.LinkMovementMethod; 10import android.view.View; 11import android.widget.TextView; 12import android.widget.Toast; 13 14import androidx.activity.OnBackPressedCallback; 15import androidx.annotation.NonNull; 16import androidx.annotation.Nullable; 17import androidx.annotation.StringRes; 18import androidx.constraintlayout.widget.Group; 19import androidx.core.content.ContextCompat; 20import androidx.lifecycle.ViewModelProvider; 21import androidx.navigation.fragment.NavHostFragment; 22 23import com.google.android.material.button.MaterialButton; 24import com.google.android.material.dialog.MaterialAlertDialogBuilder; 25 26import org.greenrobot.eventbus.EventBus; 27import org.greenrobot.eventbus.Subscribe; 28import org.greenrobot.eventbus.ThreadMode; 29import org.signal.core.util.ThreadUtil; 30import org.signal.core.util.logging.Log; 31import org.signal.devicetransfer.DeviceToDeviceTransferService; 32import org.signal.devicetransfer.TransferStatus; 33import org.signal.devicetransfer.WifiDirect; 34import org.tm.archive.LoggingFragment; 35import org.tm.archive.R; 36import org.tm.archive.logsubmit.SubmitDebugLogActivity; 37import org.tm.archive.permissions.Permissions; 38import org.tm.archive.util.CommunicationActions; 39import org.tm.archive.util.SpanUtil; 40import org.tm.archive.util.ViewUtil; 41 42import java.util.Locale; 43import java.util.concurrent.TimeUnit; 44 45/** 46 * Responsible for driving the UI of all the legwork to startup Wi-Fi Direct and 47 * establish the connection between the two devices. It's capable of being used by both 48 * the new and old device, but delegates some of the UI (mostly strings and navigation) to 49 * a subclass for old or new device. 50 * <p> 51 * Handles showing setup progress, verification codes, connecting states, error states, and troubleshooting. 52 * <p> 53 * It's state driven by the view model so it's easy to transition from step to step in the 54 * process. 55 */ 56public abstract class DeviceTransferSetupFragment extends LoggingFragment { 57 58 private static final String TAG = Log.tag(DeviceTransferSetupFragment.class); 59 60 private static final long PREPARE_TAKING_TOO_LONG_TIME = TimeUnit.SECONDS.toMillis(30); 61 private static final long WAITING_TAKING_TOO_LONG_TIME = TimeUnit.SECONDS.toMillis(90); 62 63 private final OnBackPressed onBackPressed = new OnBackPressed(); 64 private DeviceTransferSetupViewModel viewModel; 65 private Runnable takingTooLong; 66 67 public DeviceTransferSetupFragment() { 68 super(R.layout.device_transfer_setup_fragment); 69 } 70 71 @Override 72 public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 73 Group progressGroup = view.findViewById(R.id.device_transfer_setup_fragment_progress_group); 74 Group errorGroup = view.findViewById(R.id.device_transfer_setup_fragment_error_group); 75 View verifyGroup = view.findViewById(R.id.device_transfer_setup_fragment_verify); 76 View waitingGroup = view.findViewById(R.id.device_transfer_setup_fragment_waiting); 77 View troubleshooting = view.findViewById(R.id.device_transfer_setup_fragment_troubleshooting); 78 TextView status = view.findViewById(R.id.device_transfer_setup_fragment_status); 79 TextView error = view.findViewById(R.id.device_transfer_setup_fragment_error); 80 MaterialButton errorResolve = view.findViewById(R.id.device_transfer_setup_fragment_error_resolve); 81 TextView sasNumber = view.findViewById(R.id.device_transfer_setup_fragment_sas_verify_code); 82 MaterialButton verifyNo = view.findViewById(R.id.device_transfer_setup_fragment_sas_verify_no); 83 MaterialButton verifyYes = view.findViewById(R.id.device_transfer_setup_fragment_sas_verify_yes); 84 85 viewModel = new ViewModelProvider(this).get(DeviceTransferSetupViewModel.class); 86 87 viewModel.getState().observe(getViewLifecycleOwner(), state -> { 88 SetupStep step = state.getCurrentSetupStep(); 89 90 progressGroup.setVisibility(step.isProgress() ? View.VISIBLE : View.GONE); 91 errorGroup.setVisibility(step.isError() ? View.VISIBLE : View.GONE); 92 verifyGroup.setVisibility(step == SetupStep.VERIFY ? View.VISIBLE : View.GONE); 93 waitingGroup.setVisibility(step == SetupStep.WAITING_FOR_OTHER_TO_VERIFY ? View.VISIBLE : View.GONE); 94 troubleshooting.setVisibility(step == SetupStep.TROUBLESHOOTING ? View.VISIBLE : View.GONE); 95 96 Log.i(TAG, "Handling step: " + step.name()); 97 switch (step) { 98 case INITIAL: 99 status.setText(""); 100 case PERMISSIONS_CHECK: 101 requestRequiredPermission(); 102 break; 103 case PERMISSIONS_DENIED: 104 error.setText(getErrorTextForStep(step)); 105 errorResolve.setText(R.string.DeviceTransferSetup__grant_location_permission); 106 errorResolve.setOnClickListener(v -> viewModel.checkPermissions()); 107 break; 108 case LOCATION_CHECK: 109 verifyLocationEnabled(); 110 break; 111 case LOCATION_DISABLED: 112 error.setText(getErrorTextForStep(step)); 113 errorResolve.setText(R.string.DeviceTransferSetup__turn_on_location_services); 114 errorResolve.setOnClickListener(v -> openLocationServices()); 115 break; 116 case WIFI_CHECK: 117 verifyWifiEnabled(); 118 break; 119 case WIFI_DISABLED: 120 error.setText(getErrorTextForStep(step)); 121 errorResolve.setText(R.string.DeviceTransferSetup__turn_on_wifi); 122 errorResolve.setOnClickListener(v -> openWifiSettings()); 123 break; 124 case WIFI_DIRECT_CHECK: 125 verifyWifiDirectAvailable(); 126 break; 127 case WIFI_DIRECT_UNAVAILABLE: 128 error.setText(getErrorTextForStep(step)); 129 errorResolve.setText(getErrorResolveButtonTextForStep(step)); 130 errorResolve.setOnClickListener(v -> navigateWhenWifiDirectUnavailable()); 131 break; 132 case START: 133 status.setText(getStatusTextForStep(SetupStep.SETTING_UP, false)); 134 startTransfer(); 135 break; 136 case SETTING_UP: 137 status.setText(getStatusTextForStep(step, false)); 138 startTakingTooLong(() -> status.setText(getStatusTextForStep(step, true)), PREPARE_TAKING_TOO_LONG_TIME); 139 break; 140 case WAITING: 141 status.setText(getStatusTextForStep(step, false)); 142 cancelTakingTooLong(); 143 startTakingTooLong(() -> { 144 DeviceToDeviceTransferService.stop(requireContext()); 145 viewModel.onWaitingTookTooLong(); 146 }, WAITING_TAKING_TOO_LONG_TIME); 147 break; 148 case VERIFY: 149 cancelTakingTooLong(); 150 sasNumber.setText(String.format(Locale.US, "%07d", state.getAuthenticationCode())); 151 //noinspection CodeBlock2Expr 152 verifyNo.setOnClickListener(v -> { 153 new MaterialAlertDialogBuilder(requireContext()).setTitle(R.string.DeviceTransferSetup__the_numbers_do_not_match) 154 .setMessage(R.string.DeviceTransferSetup__if_the_numbers_on_your_devices_do_not_match_its_possible_you_connected_to_the_wrong_device) 155 .setPositiveButton(R.string.DeviceTransferSetup__stop_transfer, (d, w) -> { 156 EventBus.getDefault().unregister(this); 157 DeviceToDeviceTransferService.setAuthenticationCodeVerified(requireContext(), false); 158 DeviceToDeviceTransferService.stop(requireContext()); 159 EventBus.getDefault().removeStickyEvent(TransferStatus.class); 160 navigateAwayFromTransfer(); 161 }) 162 .setNegativeButton(android.R.string.cancel, null) 163 .show(); 164 }); 165 verifyYes.setOnClickListener(v -> { 166 DeviceToDeviceTransferService.setAuthenticationCodeVerified(requireContext(), true); 167 viewModel.onVerified(); 168 }); 169 break; 170 case WAITING_FOR_OTHER_TO_VERIFY: 171 break; 172 case CONNECTED: 173 Log.d(TAG, "Connected! isNotShutdown: " + viewModel.isNotShutdown()); 174 if (viewModel.isNotShutdown()) { 175 navigateToTransferConnected(); 176 } 177 break; 178 case TROUBLESHOOTING: 179 TextView title = troubleshooting.findViewById(R.id.device_transfer_setup_fragment_troubleshooting_title); 180 title.setText(getStatusTextForStep(step, false)); 181 182 int gapWidth = ViewUtil.dpToPx(12); 183 TextView step1 = troubleshooting.findViewById(R.id.device_transfer_setup_fragment_troubleshooting_step1); 184 step1.setText(SpanUtil.bullet(getString(R.string.DeviceTransferSetup__make_sure_the_following_permissions_are_enabled), gapWidth)); 185 TextView step2 = troubleshooting.findViewById(R.id.device_transfer_setup_fragment_troubleshooting_step2); 186 step2.setMovementMethod(LinkMovementMethod.getInstance()); 187 step2.setText(SpanUtil.clickSubstring(requireContext(), 188 SpanUtil.bullet(getString(R.string.DeviceTransferSetup__on_the_wifi_direct_screen_remove_all_remembered_groups_and_unlink_any_invited_or_connected_devices), gapWidth), 189 getString(R.string.DeviceTransferSetup__wifi_direct_screen), 190 v -> openWifiDirectSettings())); 191 TextView step3 = troubleshooting.findViewById(R.id.device_transfer_setup_fragment_troubleshooting_step3); 192 step3.setText(SpanUtil.bullet(getString(R.string.DeviceTransferSetup__try_turning_wifi_off_and_on_on_both_devices), gapWidth)); 193 TextView step4 = troubleshooting.findViewById(R.id.device_transfer_setup_fragment_troubleshooting_step4); 194 step4.setText(SpanUtil.bullet(getString(R.string.DeviceTransferSetup__make_sure_both_devices_are_in_transfer_mode), gapWidth)); 195 196 troubleshooting.findViewById(R.id.device_transfer_setup_fragment_troubleshooting_location_permission) 197 .setOnClickListener(v -> openApplicationSystemSettings()); 198 troubleshooting.findViewById(R.id.device_transfer_setup_fragment_troubleshooting_location_services) 199 .setOnClickListener(v -> openLocationServices()); 200 troubleshooting.findViewById(R.id.device_transfer_setup_fragment_troubleshooting_wifi) 201 .setOnClickListener(v -> openWifiSettings()); 202 troubleshooting.findViewById(R.id.device_transfer_setup_fragment_troubleshooting_go_to_support) 203 .setOnClickListener(v -> gotoSupport()); 204 troubleshooting.findViewById(R.id.device_transfer_setup_fragment_troubleshooting_try_again) 205 .setOnClickListener(v -> viewModel.checkPermissions()); 206 break; 207 case ERROR: 208 error.setText(getErrorTextForStep(step)); 209 errorResolve.setText(R.string.DeviceTransferSetup__retry); 210 errorResolve.setOnClickListener(v -> viewModel.checkPermissions()); 211 DeviceToDeviceTransferService.stop(requireContext()); 212 cancelTakingTooLong(); 213 new MaterialAlertDialogBuilder(requireContext()).setTitle(R.string.DeviceTransferSetup__error_connecting) 214 .setMessage(getStatusTextForStep(step, false)) 215 .setPositiveButton(R.string.DeviceTransferSetup__retry, (d, w) -> viewModel.checkPermissions()) 216 .setNegativeButton(android.R.string.cancel, (d, w) -> { 217 EventBus.getDefault().unregister(this); 218 EventBus.getDefault().removeStickyEvent(TransferStatus.class); 219 navigateAwayFromTransfer(); 220 }) 221 .setNeutralButton(R.string.DeviceTransferSetup__submit_debug_logs, (d, w) -> { 222 EventBus.getDefault().unregister(this); 223 EventBus.getDefault().removeStickyEvent(TransferStatus.class); 224 navigateAwayFromTransfer(); 225 startActivity(new Intent(requireContext(), SubmitDebugLogActivity.class)); 226 }) 227 .setCancelable(false) 228 .show(); 229 break; 230 } 231 }); 232 } 233 234 protected abstract @StringRes int getStatusTextForStep(@NonNull SetupStep step, boolean takingTooLongInStep); 235 236 protected abstract @StringRes int getErrorTextForStep(@NonNull SetupStep step); 237 238 protected abstract @StringRes int getErrorResolveButtonTextForStep(@NonNull SetupStep step); 239 240 protected abstract void navigateWhenWifiDirectUnavailable(); 241 242 protected abstract void startTransfer(); 243 244 protected abstract void navigateToTransferConnected(); 245 246 protected abstract void navigateAwayFromTransfer(); 247 248 @Override 249 public void onActivityCreated(@Nullable Bundle savedInstanceState) { 250 super.onActivityCreated(savedInstanceState); 251 requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), onBackPressed); 252 253 TransferStatus event = EventBus.getDefault().getStickyEvent(TransferStatus.class); 254 if (event == null) { 255 viewModel.checkPermissions(); 256 } else { 257 Log.i(TAG, "Sticky event already exists for transfer, assuming service is running and we are reattaching"); 258 } 259 260 EventBus.getDefault().register(this); 261 } 262 263 @Override 264 public void onResume() { 265 super.onResume(); 266 viewModel.onResume(); 267 } 268 269 @Override 270 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 271 Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults); 272 } 273 274 @Override 275 public void onDestroyView() { 276 cancelTakingTooLong(); 277 EventBus.getDefault().unregister(this); 278 super.onDestroyView(); 279 } 280 281 private void requestRequiredPermission() { 282 Permissions.with(this) 283 .request(WifiDirect.requiredPermission()) 284 .ifNecessary() 285 .withRationaleDialog(getString(getErrorTextForStep(SetupStep.PERMISSIONS_DENIED)), false, R.drawable.symbol_location_white_24) 286 .withPermanentDenialDialog(getString(getErrorTextForStep(SetupStep.PERMISSIONS_DENIED))) 287 .onAllGranted(() -> viewModel.onPermissionsGranted()) 288 .onAnyDenied(() -> viewModel.onLocationPermissionDenied()) 289 .execute(); 290 } 291 292 private void openApplicationSystemSettings() { 293 startActivity(Permissions.getApplicationSettingsIntent(requireContext())); 294 } 295 296 private void verifyLocationEnabled() { 297 LocationManager locationManager = ContextCompat.getSystemService(requireContext(), LocationManager.class); 298 if (locationManager != null && locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) { 299 viewModel.onLocationEnabled(); 300 } else { 301 viewModel.onLocationDisabled(); 302 } 303 } 304 305 private void openLocationServices() { 306 try { 307 startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)); 308 } catch (ActivityNotFoundException e) { 309 Log.w(TAG, "No location settings", e); 310 Toast.makeText(requireContext(), R.string.DeviceTransferSetup__unable_to_open_wifi_settings, Toast.LENGTH_LONG).show(); 311 } 312 } 313 314 private void verifyWifiEnabled() { 315 WifiManager wifiManager = ContextCompat.getSystemService(requireContext(), WifiManager.class); 316 if (wifiManager != null && wifiManager.isWifiEnabled()) { 317 viewModel.onWifiEnabled(); 318 } else { 319 viewModel.onWifiDisabled(wifiManager == null); 320 } 321 } 322 323 private void openWifiSettings() { 324 try { 325 startActivity(new Intent(Settings.ACTION_WIFI_SETTINGS)); 326 } catch (ActivityNotFoundException e) { 327 Log.w(TAG, "No wifi settings", e); 328 Toast.makeText(requireContext(), R.string.DeviceTransferSetup__unable_to_open_wifi_settings, Toast.LENGTH_LONG).show(); 329 } 330 } 331 332 private void openWifiDirectSettings() { 333 try { 334 Intent wifiDirect = new Intent(Intent.ACTION_MAIN); 335 wifiDirect.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 336 .setClassName("com.android.settings", "com.android.settings.Settings$WifiP2pSettingsActivity"); 337 338 startActivity(wifiDirect); 339 } catch (ActivityNotFoundException e) { 340 Log.w(TAG, "Unable to open wifi direct settings", e); 341 openWifiSettings(); 342 } 343 } 344 345 private void verifyWifiDirectAvailable() { 346 WifiDirect.AvailableStatus availability = WifiDirect.getAvailability(requireContext()); 347 if (availability != WifiDirect.AvailableStatus.AVAILABLE) { 348 viewModel.onWifiDirectUnavailable(availability); 349 } else { 350 viewModel.onWifiDirectAvailable(); 351 } 352 } 353 354 private void gotoSupport() { 355 CommunicationActions.openBrowserLink(requireContext(), getString(R.string.transfer_support_url)); 356 } 357 358 @Subscribe(sticky = true, threadMode = ThreadMode.MAIN) 359 public void onEventMainThread(@NonNull TransferStatus event) { 360 viewModel.onTransferEvent(event); 361 } 362 363 private void startTakingTooLong(@NonNull Runnable runnable, long tooLong) { 364 if (takingTooLong == null) { 365 takingTooLong = () -> { 366 takingTooLong = null; 367 runnable.run(); 368 }; 369 ThreadUtil.runOnMainDelayed(takingTooLong, tooLong); 370 } 371 } 372 373 private void cancelTakingTooLong() { 374 if (takingTooLong != null) { 375 ThreadUtil.cancelRunnableOnMain(takingTooLong); 376 takingTooLong = null; 377 } 378 } 379 380 private class OnBackPressed extends OnBackPressedCallback { 381 382 public OnBackPressed() { 383 super(true); 384 } 385 386 @Override 387 public void handleOnBackPressed() { 388 DeviceToDeviceTransferService.stop(requireContext()); 389 EventBus.getDefault().removeStickyEvent(TransferStatus.class); 390 NavHostFragment.findNavController(DeviceTransferSetupFragment.this).popBackStack(); 391 } 392 } 393}