I got an email from google play support saying "Intent Redirection Your app(s) are vulnerable to Intent Redirection. To address this issue, follow the steps in this Google Help Center article."
After reading through the article, I'm guessing the key is my app should not call startActivity, startService, sendBroadcast, or setResult on untrusted Intents (intents used by external apps to invoke my app for example) without validating or sanitizing these Intents.
However, solution 1 in the article doesn't work in my case because my component needs to receive Intents from other apps.
Solution 2 is not applicable to my case because I don't know in advance which app would invoke my app, so I don't know what would getCallingActivity returns.
Solution 3 seems to be the most promising one, I tried to removeFlags of intents, however, when I resubmit my app, Google Play again alerts this vulnerability. I am about to try checking whether an Intent grants a URI permission using methods like getFlags and submit my app again to see the result. Does anyone know how do Google check this vulnerability anyway, and could someone spot the vulnerability in my source code and suggest a way to resolve it?
The exact message from Google Play is
Intent Redirection
Your app(s) are vulnerable to Intent Redirection.
To address this issue, follow the steps in this Google Help Center article.
com.mydomain.somepackage.a->a
And the following is the simplified source code.
// MainActivity.java
public class MainActivity extends CordovaActivity
{
SpecialUtil specialUtil;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
specialUtil = new specialUtil(MainActivity.this);
}
#Override
public void onResume() {
super.onResume();
specialUtil.verifyServerIfNeeded(MainActivity.this);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == this.specialUtil.CERT_INVALID_POPUP_REQUEST_CODE) {
// the user clicked the return button in the alert dialog within WhiteScreen activity
this.specialUtil.declareAsFailure();
}
}
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
}
}
// com/mydomain/somepackage/SpecialUtil.java
public class SpecialUtil {
private SharedPreferences mSharedPreferences;
private SharedPreferences.Editor mSharedPreferencesEditor;
private SharedPreferences.OnSharedPreferenceChangeListener listener;
private Activity activity;
private boolean shownCertInvalidPopup = false;
public final int CERT_INVALID_POPUP_REQUEST_CODE = 1000;
public SpecialUtil(Activity activity) {
this.activity = activity;
this.mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity);
this.mSharedPreferencesEditor = mSharedPreferences.edit();
this.listener = new SharedPreferences.OnSharedPreferenceChangeListener() {
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (key.equals("SOME_RESULT")) {
String result = mSharedPreferences.getString("SOME_RESULT", "");
if (result.equals("RESULT_OK")) {
SpecialUtil.this.declareAsSuccess();
} else if (result.equals("RESULT_CANCELED")) {
SpecialUtil.this.declareAsFailure();
}
}
}
};
this.mSharedPreferences.registerOnSharedPreferenceChangeListener(listener);
}
public void verifyServerIfNeeded(Activity activity) {
Intent intent = activity.getIntent();
if (this.isFlowA(intent)) {
this.removePermission(intent);
String url = intent.getStringExtra("url");
this.verifyServer(url);
} else if (this.isFlowB(intent)) {
this.removePermission(intent);
String payment_request_object_url = intent.getData().getQueryParameter("pay_req_obj");
String callback_url = intent.getData().getQueryParameter("callback");
this.verifyServer(payment_request_object_url);
}
}
public boolean isFlowA(Intent intent) {
if (intent.getAction().equals("someAction")) {
return true;
}
return false;
}
public boolean isFlowB(Intent intent) {
if (intent.getData() != null) {
String path = intent.getData().getPath();
if (path.equals("something")) {
return true;
}
}
return false;
}
public void verifyServer(final String httpsURL) {
new Thread(new Runnable() {
#Override
public void run() {
try {
boolean isCertValid = SpecialUtil.this.verify(httpsURL);
if (isCertValid) {
// do somthing
} else {
// show a white screen with an alert msg
SpecialUtil.this.activity.runOnUiThread(new Runnable() {
public void run() {
if (!shownCertInvalidPopup) {
shownCertInvalidPopup = true;
Intent intent = new Intent(SpecialUtil.this.activity, WhiteScreen.class);
SpecialUtil.this.activity.startActivityForResult(intent, CERT_INVALID_POPUP_REQUEST_CODE);
}
}
});
}
} catch (IOException e) {
SpecialUtil.this.declareAsFailure();
}
}
}).start();
}
private void declareAsSuccess() {
this.activity.setResult(Activity.RESULT_OK, SpecialUtil.this.activity.getIntent());
this.activity.finishAndRemoveTask();
}
public void declareAsFailure() {
this.activity.setResult(Activity.RESULT_CANCELED, this.activity.getIntent());
this.activity.finishAndRemoveTask();
}
private void removePermission(Intent intent) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
intent.removeFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.removeFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
}
}
// com/mydomain/somepackage/WhiteScreen.java
public class WhiteScreen extends Activity {
SpecialUtil specialUtil;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
specialUtil = new SpecialUtil(WhiteScreen.this);
String title = "someTitle";
final AlertDialog.Builder builder = new AlertDialog.Builder(WhiteScreen.this)
.setTitle(title)
.setPositiveButton(btn_text, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// Don't start the process, quit App immediately
WhiteScreen.this.setResult(Activity.RESULT_CANCELED, WhiteScreen.this.getIntent());
WhiteScreen.this.finishAndRemoveTask();
}
});
AlertDialog alertDialog = builder.create();
alertDialog.show();
}
}
I am trying to launch the NavigationLauncher.startNavigation, from another activity but am unable to do so. I have a button in the second activity which I want to use to start the navigation.
Any suggestions are welcome. Thanks
Here is my code:
/*
fabstart.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
boolean simulateRoute = false;
NavigationLauncherOptions options = NavigationLauncherOptions.builder()
.directionsRoute(route)
.shouldSimulateRoute(simulateRoute)
.build();
// Call this method with Context from within an Activity
NavigationLauncher.startNavigation(MainActivity.this, options);
}
});
*/
fabstart.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this, Before_Go.class);
startActivity(intent);
// Here is where I want to go to a new activity, inside this activity have a button to
// launch the "NavigationLauncher.startNavigation"
}
});
NavigationLauncher.startnavigation() opens a new activity that handles the navigation. Therefore it does not matter from where it is called. However, wherever it is called from, the NavigationLauncher must be defined, and the options object that is passed as the second parameter has to contain the directionsRoute object.
Can you please share code of the activity from which you call it, as well as how you initialize and define the options object?
thanks for your response to my question. I am new to stakoverflow, so i hope i get this additional posting of code right.
MainActivity
public class MainActivity extends AppCompatActivity implements OnMapReadyCallback, PermissionsListener,
MapboxMap.OnMapLongClickListener, OnRouteSelectionChangeListener {
private static final int REQUEST_CODE_AUTOCOMPLETE = 1;
private static final int ONE_HUNDRED_MILLISECONDS = 100;
//private static final String DROPPED_MARKER_LAYER_ID = "DROPPED_MARKER_LAYER_ID";
//Mapbox
private MapView mapView;
private MapboxMap mapboxMap;
private LocationComponent locationComponent;
private PermissionsManager permissionsManager;
private LocationEngine locationEngine;
private long DEFAULT_INTERVAL_IN_MILLISECONDS = 1000L;
private long DEFAULT_MAX_WAIT_TIME = DEFAULT_INTERVAL_IN_MILLISECONDS * 5;
private final MainActivityLocationCallback callback = new MainActivityLocationCallback(this);
private NavigationMapRoute mapRoute;
private DirectionsRoute route;
private String symbolIconId = "symbolIconId";
private String geojsonSourceLayerId = "geojsonSourceLayerId";
private StyleCycle styleCycle = new StyleCycle();
CarmenFeature selectedCarmenFeature;
CarmenFeature feature;
Layer layer;
private static final String TAG = "DirectionsActivity";
// variables
private FloatingActionButton fablocation;
private FloatingActionButton fabstart;
private FloatingActionButton fabRemoveRoute;
private FloatingActionButton fabStyles;
private TextView search;
private TextView kmDisplay;
private TextView timeDisplay;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Mapbox.getInstance(this, getString(R.string.access_token));
setContentView(R.layout.activity_main);
mapView = findViewById(R.id.mapView);
mapView.onCreate(savedInstanceState);
mapView.getMapAsync(this);
search=findViewById(R.id.search);
kmDisplay = findViewById(R.id.kmDisplay);
timeDisplay = findViewById(R.id.timeDisplay);
fablocation=findViewById(R.id.fabLocation);
fabstart=findViewById(R.id.fabStart);
fabRemoveRoute=findViewById(R.id.fabRemoveRoute);
fabStyles=findViewById(R.id.fabStyles);
fablocation.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Location lastKnownLocation = mapboxMap.getLocationComponent().getLastKnownLocation();
// Move map camera back to current device location
mapboxMap.animateCamera(CameraUpdateFactory.newCameraPosition(
new CameraPosition.Builder()
.target(new LatLng(lastKnownLocation.getLatitude(), lastKnownLocation.getLongitude()))
.zoom(15)
.build()), 3000);
}
});
}
#Override
public boolean onMapLongClick(#NonNull LatLng point) {
vibrate();
Point destinationPoint = Point.fromLngLat(point.getLongitude(), point.getLatitude());
Point originPoint = Point.fromLngLat(locationComponent.getLastKnownLocation().getLongitude(),
locationComponent.getLastKnownLocation().getLatitude());
GeoJsonSource source = mapboxMap.getStyle().getSourceAs("destination-source-id");
if (source != null) {
source.setGeoJson(Feature.fromGeometry(destinationPoint));
} else {
// Use the map camera target's coordinates to make a reverse geocoding search
reverseGeocode(Point.fromLngLat(point.getLongitude(), point.getLatitude()));
}
getRoute(originPoint, destinationPoint);
if(destinationPoint !=originPoint) {
fabRemoveRoute.setOnClickListener(new View.OnClickListener() {
#SuppressLint("RestrictedApi")
#Override
public void onClick(View view) {
removeRouteAndMarkers();
fabstart.setVisibility(View.INVISIBLE);
fabRemoveRoute.setVisibility(View.INVISIBLE);
//fablocation.setVisibility(View.INVISIBLE);
kmDisplay.setText("");
timeDisplay.setText("");
search.setText(String.format(getString(R.string.hint_where_to)));
Location lastKnownLocation = mapboxMap.getLocationComponent().getLastKnownLocation();
// Move map camera back to current device location
mapboxMap.animateCamera(CameraUpdateFactory.newCameraPosition(
new CameraPosition.Builder()
.target(new LatLng(lastKnownLocation.getLatitude(), lastKnownLocation.getLongitude()))
.zoom(15)
.build()), 3000);
}
});
}
//imageView.setEnabled(true);
//imageView.setBackgroundResource(R.color.mapboxBlue);
return true;
}
private void getRoute(Point origin, Point destination) {
NavigationRoute.builder(this)
.accessToken(Mapbox.getAccessToken())
.origin(origin)
.destination(destination)
.voiceUnits(DirectionsCriteria.METRIC)
.alternatives(true)
.build()
.getRoute(new Callback<DirectionsResponse>() {
#SuppressLint("RestrictedApi")
#Override
public void onResponse(Call<DirectionsResponse> call, Response<DirectionsResponse> response) {
// You can get the generic HTTP info about the response
if (response.isSuccessful()
&& response.body() != null
&& !response.body().routes().isEmpty()) {
List<DirectionsRoute> routes = response.body().routes();
mapRoute.addRoutes(routes);
//routeLoading.setVisibility(View.INVISIBLE);
fabRemoveRoute.setVisibility(View.VISIBLE);
fablocation.setVisibility(View.VISIBLE);
fabstart.setVisibility(View.VISIBLE);
route = response.body().routes().get(0);
routeCalcs();
}
// Once you have the route zoom the camera out to show the route within the bounds of the device
mapboxMap.easeCamera(CameraUpdateFactory.newLatLngBounds(
new LatLngBounds.Builder()
.include(new LatLng(origin.latitude(), origin.longitude()))
.include(new LatLng(destination.latitude(), destination.longitude()))
.build(), 150), 4000);
}
#Override
public void onFailure(Call<DirectionsResponse> call, Throwable throwable) {
Log.e(TAG, "Error: " + throwable.getMessage());
}
});
}
private void removeRouteAndMarkers() {
mapRoute.removeRoute();
toggleLayer();
}
#Override
public void onNewPrimaryRouteSelected(DirectionsRoute directionsRoute) {
route = directionsRoute;
routeCalcs();
}
private void routeCalcs(){
// rounds the kilometer to zero decimals
kmDisplay.setText((int) Math.ceil(route.distance()/1000) + " km");
//Log.d(TAG1,(int) Math.ceil(currentRoute.distance()/1000) + " km");
// This converts to output of duration() in seconds to minutes and hours format
int minutes = (int) (route.duration() / 60);
long hour = TimeUnit.MINUTES.toHours(minutes);
long remainMinute = minutes - TimeUnit.HOURS.toMinutes(hour);
if (hour >= 1) {
timeDisplay.setText(String.format(getString(R.string.hours_textview),hour)
+ String.format(getString(R.string.minutes_textview),remainMinute));
} else {
timeDisplay.setText(String.format(getString(R.string.minutes_textview), remainMinute));
}
}
private void reverseGeocode(Point point) {
if (selectedCarmenFeature == null) {
try {
MapboxGeocoding client = MapboxGeocoding.builder()
.accessToken(getString(R.string.access_token))
.query(Point.fromLngLat(point.longitude(), point.latitude()))
.geocodingTypes(GeocodingCriteria.TYPE_ADDRESS)
.build();
client.enqueueCall(new Callback<GeocodingResponse>() {
#Override
public void onResponse(Call<GeocodingResponse> call, Response<GeocodingResponse> response) {
if (response.body() != null) {
List<CarmenFeature> results = response.body().features();
if (results.size() > 0) {
feature = results.get(0);
// If the geocoder returns a result, we take the first in the list and show a Toast with the place name.
mapboxMap.getStyle(new Style.OnStyleLoaded() {
#Override
public void onStyleLoaded(#NonNull Style style) {
if (style.getLayer("SYMBOL_LAYER_ID") != null) {
search.setText(feature.placeName());
}
}
});
} else {
Toast.makeText(MainActivity.this,
getString(R.string.location_picker_dropped_marker_snippet_no_results), Toast.LENGTH_SHORT).show();
}
}
}
#Override
public void onFailure(Call<GeocodingResponse> call, Throwable throwable) {
Timber.e("Geocoding Failure: %s", throwable.getMessage());
}
});
} catch (ServicesException servicesException) {
Timber.e("Error geocoding: %s", servicesException.toString());
servicesException.printStackTrace();
}
}
}
#Override
public void onMapReady(#NonNull MapboxMap mapboxMap) {
this.mapboxMap = mapboxMap;
mapboxMap.setStyle(Style.MAPBOX_STREETS, new Style.OnStyleLoaded() {
#Override
public void onStyleLoaded(#NonNull Style style){
mapRoute = new NavigationMapRoute(null, mapView, mapboxMap);
mapRoute.setOnRouteSelectionChangeListener(MainActivity.this::onNewPrimaryRouteSelected);
mapboxMap.addOnMapLongClickListener(MainActivity.this);
initializeLocationComponent(style);
// Add the symbol layer icon to map for future use
style.addImage(symbolIconId, BitmapFactory.decodeResource(
MainActivity.this.getResources(), R.drawable.mapbox_marker_icon_default));
// Create an empty GeoJSON source using the empty feature collection
setUpSource(style);
// Set up a new symbol layer for displaying the searched location's feature coordinates
setupLayer(style);
fabStyles.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (mapboxMap != null) {
mapboxMap.setStyle(styleCycle.getNextStyle());
}
}
});
initSearchFab();
}
});
// This is the code in the docs, but this launches the turn by turn navigation inside this activity
// and this is not what I need
/* fabstart.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
boolean simulateRoute = false;
NavigationLauncherOptions options = NavigationLauncherOptions.builder()
.directionsRoute(route)
.shouldSimulateRoute(simulateRoute)
.build();
// Call this method with Context from within an Activity
NavigationLauncher.startNavigation(MainActivity.this, options);
}
});*/
fabstart.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this, Before_Go.class);
startActivity(intent);
// Here is where I want to go to a new activity, inside this activity have a button to
// launch the "NavigationLauncher.startNavigation"
}
});
}
private static class StyleCycle {
private static final String[] STYLES = new String[] {
Style.MAPBOX_STREETS,
Style.OUTDOORS,
Style.LIGHT,
Style.DARK,
//Style.SATELLITE_STREETS,
Style.TRAFFIC_DAY,
Style.TRAFFIC_NIGHT
};
private int index;
private String getNextStyle() {
index++;
if (index == STYLES.length) {
index = 0;
}
return getStyle();
}
private String getStyle() {
return STYLES[index];
}
}
private void initSearchFab() {
findViewById(R.id.search).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent intent = new PlaceAutocomplete.IntentBuilder()
.accessToken(Mapbox.getAccessToken())
.placeOptions(PlaceOptions.builder()
.backgroundColor(Color.parseColor("#EEEEEE"))
.limit(10)
//.addInjectedFeature(home)
//.addInjectedFeature(work)
.build(PlaceOptions.MODE_CARDS))
.build(MainActivity.this);
startActivityForResult(intent, REQUEST_CODE_AUTOCOMPLETE);
}
});
}
#SuppressWarnings( {"MissingPermission"})
private void initializeLocationComponent(#NonNull Style loadedMapStyle) {
// Check if permissions are enabled and if not request
if (PermissionsManager.areLocationPermissionsGranted(this)) {
locationComponent = mapboxMap.getLocationComponent();
// Set the LocationComponent activation options
LocationComponentActivationOptions locationComponentActivationOptions =
LocationComponentActivationOptions.builder(this, loadedMapStyle)
.useDefaultLocationEngine(false)
.build();
// Activate with the LocationComponentActivationOptions object
locationComponent.activateLocationComponent(locationComponentActivationOptions);
locationComponent.setLocationComponentEnabled(true);
locationComponent.setRenderMode(RenderMode.NORMAL);
locationComponent.setCameraMode(CameraMode.TRACKING);
//locationComponent.zoomWhileTracking(10d);
initLocationEngine();
} else {
permissionsManager = new PermissionsManager(this);
permissionsManager.requestLocationPermissions(this);
}
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_CODE_AUTOCOMPLETE) {
// Retrieve selected location's CarmenFeature
selectedCarmenFeature = PlaceAutocomplete.getPlace(data);
search.setText(selectedCarmenFeature.placeName());
// Create a new FeatureCollection and add a new Feature to it using selectedCarmenFeature above.
// Then retrieve and update the source designated for showing a selected location's symbol layer icon
if (mapboxMap != null) {
Style style = mapboxMap.getStyle();
if (style != null) {
GeoJsonSource source = style.getSourceAs(geojsonSourceLayerId);
if (source != null) {
source.setGeoJson(FeatureCollection.fromFeatures(
new Feature[] {Feature.fromJson(selectedCarmenFeature.toJson())}));
}
// Move map camera to the selected location
mapboxMap.animateCamera(CameraUpdateFactory.newCameraPosition(
new CameraPosition.Builder()
.target(new LatLng(((Point) selectedCarmenFeature.geometry()).latitude(),
((Point) selectedCarmenFeature.geometry()).longitude()))
.zoom(14)
.build()), 4000);
}
}
}
}
#SuppressLint("MissingPermission")
private void vibrate() {
Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
if (vibrator == null) {
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
vibrator.vibrate(VibrationEffect.createOneShot(ONE_HUNDRED_MILLISECONDS, VibrationEffect.DEFAULT_AMPLITUDE));
} else {
vibrator.vibrate(ONE_HUNDRED_MILLISECONDS);
}
}
/**
* Set up the LocationEngine and the parameters for querying the device's location
*/
#SuppressLint("MissingPermission")
private void initLocationEngine() {
locationEngine = LocationEngineProvider.getBestLocationEngine(this);
LocationEngineRequest request = new LocationEngineRequest.Builder(DEFAULT_INTERVAL_IN_MILLISECONDS)
.setPriority(LocationEngineRequest.PRIORITY_HIGH_ACCURACY)
.setMaxWaitTime(DEFAULT_MAX_WAIT_TIME).build();
locationEngine.requestLocationUpdates(request, callback, getMainLooper());
locationEngine.getLastLocation(callback);
}
#Override
public void onExplanationNeeded(List<String> permissionsToExplain) {
Toast.makeText(this, R.string.user_location_permission_explanation, Toast.LENGTH_LONG).show();
}
#Override
public void onPermissionResult(boolean granted) {
if (granted) {
if (mapboxMap.getStyle() != null) {
initializeLocationComponent(mapboxMap.getStyle());
}
} else {
Toast.makeText(this, R.string.user_location_permission_not_granted, Toast.LENGTH_LONG).show();
finish();
}
}
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
permissionsManager.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
private static class MainActivityLocationCallback
implements LocationEngineCallback<LocationEngineResult> {
private final WeakReference<MainActivity> activityWeakReference;
MainActivityLocationCallback(MainActivity activity) {
this.activityWeakReference = new WeakReference<>(activity);
}
#Override
public void onSuccess(LocationEngineResult result) {
MainActivity activity = activityWeakReference.get();
if (activity != null) {
Location location = result.getLastLocation();
if (location == null) {
return;
}
if (activity.mapboxMap != null && result.getLastLocation() != null) {
activity.mapboxMap.getLocationComponent().forceLocationUpdate(result.getLastLocation());
}
}
}
#Override
public void onFailure(#NonNull Exception exception) {
Timber.d(exception.getLocalizedMessage());
MainActivity activity = activityWeakReference.get();
if (activity != null) {
Toast.makeText(activity, exception.getLocalizedMessage(),
Toast.LENGTH_SHORT).show();
}
}
}
/**
* Adds the GeoJSON source to the map
*/
private void setUpSource(#NonNull Style loadedMapStyle) {
loadedMapStyle.addSource(new GeoJsonSource(geojsonSourceLayerId));
}
/**
* Setup a layer with maki icons, eg. west coast city.
*/
private void setupLayer(#NonNull Style loadedMapStyle) {
loadedMapStyle.addLayer(new SymbolLayer("SYMBOL_LAYER_ID", geojsonSourceLayerId).withProperties(
iconImage(symbolIconId),
iconOffset(new Float[] {0f, -8f})
));
}
// This method will remove the destination icon
private void toggleLayer() {
mapboxMap.getStyle(new Style.OnStyleLoaded() {
#Override
public void onStyleLoaded(#NonNull Style style) {
layer = style.getLayer("SYMBOL_LAYER_ID");
if (layer != null) {
if (VISIBLE.equals(layer.getVisibility().getValue())) {
layer.setProperties(visibility(NONE));
} else {
layer.setProperties(visibility(VISIBLE));
}
}
}
});
}
// Add the mapView lifecycle to the activity's lifecycle methods
#Override
public void onResume() {
super.onResume();
mapView.onResume();
}
#Override
protected void onStart() {
super.onStart();
mapView.onStart();
if (mapRoute != null) {
mapRoute.onStart();
}
}
#Override
protected void onStop() {
super.onStop();
mapView.onStop();
if (mapRoute != null) {
mapRoute.onStop();
}
}
#Override
public void onPause() {
super.onPause();
mapView.onPause();
}
#Override
public void onLowMemory() {
super.onLowMemory();
mapView.onLowMemory();
}
#Override
protected void onDestroy() {
super.onDestroy();
mapView.onDestroy();
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mapView.onSaveInstanceState(outState);
}
}
Before_Go - this is the activity where i want to click the button and launch the navigationlauncher
public class Before_Go extends AppCompatActivity {
Button btnGo;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_before__go);
btnGo=findViewById(R.id.btnGo);
btnGo.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent btnGo = new Intent(Before_Go.this, Go.class);
startActivity(btnGo);
}
});
}
}
GoActivity, this is where the navigation launcher needs to launch after the button click in the Before_Go activity
public class Go extends AppCompatActivity implements OnNavigationReadyCallback,
NavigationListener {
private NavigationView navigationView;
private DirectionsRoute route;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
setTheme(R.style.Theme_AppCompat_NoActionBar);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_go);
navigationView = findViewById(R.id.navigationViewB);
navigationView.onCreate(savedInstanceState);
}
/*fabstart.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
boolean simulateRoute = false;
NavigationLauncherOptions options = NavigationLauncherOptions.builder()
.directionsRoute(route)
.shouldSimulateRoute(simulateRoute)
.build();
// Call this method with Context from within an Activity
NavigationLauncher.startNavigation(MainActivity.this, options);
}
});*/
#Override
public void onNavigationReady(boolean isRunning) {
MapboxNavigationOptions.Builder navigationOptions = MapboxNavigationOptions.builder();
NavigationViewOptions.Builder options = NavigationViewOptions.builder();
options.navigationListener(this);
extractRoute(options);
extractConfiguration(options);
options.navigationOptions(navigationOptions.build());
//options.navigationOptions(new MapboxNavigationOptions.Builder().build());
launchNavigationWithRoute();
//navigationView.startNavigation(options.build());
navigationView.initialize(this);
/*MapboxNavigationOptions.Builder navigationOptions = MapboxNavigationOptions.builder();
NavigationViewOptions.Builder options = NavigationViewOptions.builder();
options.navigationListener(this);
extractRoute(options);
options.navigationOptions(navigationOptions.build());
//navigationView = NavigationLauncher.startNavigation(options.build());
navigationView.startNavigation(options.build());
initialize();*/
}
#Override
public void onCancelNavigation() {
// Navigation canceled, finish the activity
showCustomCancel();
finishNavigation();
}
#Override
public void onNavigationFinished() {
}
#Override
public void onNavigationRunning() {
}
private void launchNavigationWithRoute() {
if (route != null) {
NavigationLauncher.startNavigation(this, route);
}
}
private void extractRoute(NavigationViewOptions.Builder options) {
route = NavigationLauncher.extractRoute(this);
options.directionsRoute(route);
}
private void extractConfiguration(NavigationViewOptions.Builder options) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
options.shouldSimulateRoute(preferences.getBoolean(NavigationConstants.NAVIGATION_VIEW_SIMULATE_ROUTE, false));
}
private void finishNavigation() {
NavigationLauncher.cleanUpPreferences(this);
finish();
}
private void showCustomCancel(){
ViewGroup viewGroup = findViewById(android.R.id.content);
View dialogView2 = LayoutInflater.from(this).inflate(R.layout.my_dialog_logout, viewGroup, false);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setView(dialogView2);
AlertDialog alertDialog2 = builder.create();
Button buttonNo = dialogView2.findViewById(R.id.buttonNo);
Button buttonYes = dialogView2.findViewById(R.id.buttonYes);
buttonYes.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (alertDialog2 != null && alertDialog2.isShowing()) {
alertDialog2.dismiss();
}
Intent intentCancelTrip_Yes = new Intent(getApplicationContext(),MainActivity.class);
startActivity(intentCancelTrip_Yes);
finish();
navigationView.stopNavigation();
}
});
buttonNo.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (alertDialog2 != null && alertDialog2.isShowing()) {
alertDialog2.dismiss();
}
navigationView.onResume();
}
});
alertDialog2.show();
}
/* private void initialize() {
Parcelable position = getIntent().getParcelableExtra(NavigationConstants.NAVIGATION_VIEW_INITIAL_MAP_POSITION);
if (position != null) {
navigationView.initialize(this, (CameraPosition) position);
} else {
navigationView.initialize(this);
}
}*/
#Override
public void onStart() {
super.onStart();
navigationView.onStart();
}
#Override
public void onResume() {
super.onResume();
navigationView.onResume();
}
#Override
public void onLowMemory() {
super.onLowMemory();
navigationView.onLowMemory();
}
#Override
public void onBackPressed() {
// If the navigation view didn't need to do anything, call super
if (!navigationView.onBackPressed()) {
super.onBackPressed();
}
}
#Override
protected void onSaveInstanceState(Bundle outState) {
navigationView.onSaveInstanceState(outState);
super.onSaveInstanceState(outState);
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
navigationView.onRestoreInstanceState(savedInstanceState);
}
#Override
public void onPause() {
super.onPause();
navigationView.onPause();
}
#Override
public void onStop() {
super.onStop();
navigationView.onStop();
}
#Override
protected void onDestroy() {
super.onDestroy();
navigationView.onDestroy();
}
}
NavigationLauncher class i am using to try initiate the navigation inside the Go activity
public class NavigationLauncher {
public static void startNavigation(Activity activity, DirectionsRoute route) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
SharedPreferences.Editor editor = preferences.edit();
editor.putString(NavigationConstants.NAVIGATION_VIEW_ROUTE_KEY, new Gson().toJson(route));
//editor.putString(NavigationConstants.NAVIGATION_VIEW_AWS_POOL_ID, awsPoolId);
//editor.putBoolean(NavigationConstants.NAVIGATION_VIEW_SIMULATE_ROUTE, simulateRoute);
editor.apply();
Intent navigationActivity = new Intent(activity, Go.class);
activity.startActivity(navigationActivity);
}
/* public static void startNavigation(Activity activity, NavigationLauncherOptions options){
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
SharedPreferences.Editor editor = preferences.edit();
storeDirectionsRouteValue(options, editor);
storeConfiguration(options, editor);
editor.apply();
Intent navigationActivity = new Intent(activity, Go.class);
storeInitialMapPosition(options, navigationActivity);
activity.startActivity(navigationActivity);
}*/
private static void storeConfiguration(NavigationLauncherOptions options, SharedPreferences.Editor editor) {
editor.putBoolean(NavigationConstants.NAVIGATION_VIEW_SIMULATE_ROUTE, options.shouldSimulateRoute());
}
private static void storeDirectionsRouteValue(NavigationLauncherOptions options, SharedPreferences.Editor editor) {
editor.putString(NavigationConstants.NAVIGATION_VIEW_ROUTE_KEY, options.directionsRoute().toJson());
/*if (options.directionsRoute() != null) {
storeDirectionsRouteValue(options, editor);
}
else {
throw new RuntimeException("A valid DirectionsRoute or origin and "
+ "destination must be provided in NavigationViewOptions");
}*/
}
static DirectionsRoute extractRoute(Context context) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
String directionsRouteJson = preferences.getString(NavigationConstants.NAVIGATION_VIEW_ROUTE_KEY, "");
return DirectionsRoute.fromJson(directionsRouteJson);
}
static void cleanUpPreferences(Context context) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = preferences.edit();
editor
.remove(NavigationConstants.NAVIGATION_VIEW_ROUTE_KEY)
.remove(NavigationConstants.NAVIGATION_VIEW_SIMULATE_ROUTE)
.apply();
}
private static void storeInitialMapPosition(NavigationLauncherOptions options, Intent navigationActivity) {
if (options.initialMapCameraPosition() != null) {
navigationActivity.putExtra(
NavigationConstants.NAVIGATION_VIEW_INITIAL_MAP_POSITION, options.initialMapCameraPosition()
);
}
}
}
I assume that the application crashes once the activity "Go.java" is launched.
The problem appears to be that you are not instantiating the Mapbox object in this activity. You are doing it correclty in your "mainactivity.java" however you are missing it in "go.java". Make sure to have the line Mapbox.getInstance(this, getString(R.string.access_token)); also in your "go.java" activity before calling setContentView()
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Mapbox.getInstance(this, getString(R.string.access_token));
setContentView(R.layout.activity_main);
I'm trying to load a VAST ad using Exoplayer IMA extension (using tutorials 1, 2). I want to keep ad and content progress, so if app goes background or screen rotates, user continues from the point he/she was watching. You can see my code here and main codes below for convenience. (Duo to some limitations, I'm stuck with version 2.9.6 of Exoplayer library)
public class MainActivity extends AppCompatActivity {
private PlayerView playerView;
private SimpleExoPlayer player;
private static final String KEY_WINDOW = "window";
private int currentWindow;
private static final String KEY_POSITION = "position";
private long playbackPosition;
private static final String KEY_AUTO_PLAY = "auto_play";
private boolean playWhenReady;
private ImaAdsLoader adsLoader;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
playerView = findViewById(R.id.exo_video_view);
if (adsLoader == null)
adsLoader = new ImaAdsLoader(this, Uri.parse(getString(R.string.ad_tag_url)));
if (savedInstanceState != null) {
currentWindow = savedInstanceState.getInt(KEY_WINDOW);
playbackPosition = savedInstanceState.getLong(KEY_POSITION);
playWhenReady = savedInstanceState.getBoolean(KEY_AUTO_PLAY, true);
}
}
private MediaSource buildMediaSource(Uri uri, DataSource.Factory dataSourceFactory) {
return new ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(uri);
}
private MediaSource buildAdMediaSource(MediaSource contentMediaSource, DataSource.Factory dataSourceFactory){
// Create the AdsMediaSource using the AdsLoader and the MediaSource.
return new AdsMediaSource(contentMediaSource, dataSourceFactory, adsLoader, playerView);
}
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
}
private void initializePlayer() {
player = ExoPlayerFactory.newSimpleInstance(this);
playerView.setPlayer(player);
if (adsLoader != null)
adsLoader.setPlayer(player);
Uri contentUri = Uri.parse(getString(R.string.media_url_mp4));
DataSource.Factory dataSourceFactory =
new DefaultDataSourceFactory(this, "exoplayer-codelab");
MediaSource contentMediaSource = buildMediaSource(contentUri, dataSourceFactory);
MediaSource adMediaSource = buildAdMediaSource(contentMediaSource, dataSourceFactory);
player.setPlayWhenReady(playWhenReady);
player.seekTo(currentWindow, playbackPosition);
player.prepare(adMediaSource, false, false);
}
#Override
public void onStart() {
super.onStart();
if (Util.SDK_INT >= 24) {
initializePlayer();
if (playerView != null) {
playerView.onResume();
}
}
}
#Override
public void onResume() {
super.onResume();
hideSystemUi();
if ((Util.SDK_INT < 24 || player == null)) {
initializePlayer();
if (playerView != null) {
playerView.onResume();
}
}
}
private void hideSystemUi() {
playerView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
}
#Override
public void onPause() {
super.onPause();
if (Util.SDK_INT < 24) {
if (playerView != null) {
playerView.onPause();
}
releasePlayer();
}
}
#Override
public void onStop() {
super.onStop();
if (Util.SDK_INT >= 24) {
if (playerView != null) {
playerView.onPause();
}
releasePlayer();
}
}
#Override
protected void onDestroy() {
super.onDestroy();
releaseAdsLoader();
}
private void releasePlayer() {
if (player != null) {
updateStartPosition();
player.release();
player = null;
}
if (adsLoader != null) {
adsLoader.setPlayer(null);
}
}
private void releaseAdsLoader() {
if (adsLoader != null) {
adsLoader.release();
adsLoader = null;
if (playerView.getOverlayFrameLayout() != null)
playerView.getOverlayFrameLayout().removeAllViews();
}
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
updateStartPosition();
outState.putBoolean(KEY_AUTO_PLAY, playWhenReady);
outState.putInt(KEY_WINDOW, currentWindow);
outState.putLong(KEY_POSITION, playbackPosition);
}
private void updateStartPosition() {
if (player != null) {
playWhenReady = player.getPlayWhenReady();
currentWindow = player.getCurrentWindowIndex();
playbackPosition = Math.max(0, player.getContentPosition());
}
}
}
Problem is, when screen rotates, activity fields like ImaAdsLoader becomes null and ad and content video play from start. I've saved player progress and can successfully restore it's position but that's not case for ImaAdsLoader since I couldn't find a way to restore its state. Am I doing anything wrong? How Ad progress state should be saved and restored?
A common pattern with video players is to prevent your activity from being recreated with orientation changes so you should add these flags to your manifest.
android:configChanges="keyboardHidden|orientation|screenSize"
This would also prevent the surface view that shows your video from being destroyed with orientation changes too.
The README.md of the Exoplayer IMA extension is quite clear about this:
You need to persist a reference to the ImaAdsLoader. When recreating the view pass that reference back when AdsLoaderProvider.getAdsLoader(MediaItem.AdsConfiguration adsConfiguration) is called.
Additionally, persist the player position when the view gets destroyed by storing the value of player.getContentPosition(). After recreation seek that position before preparing the new player instance.
You can find an example in the Exoplayer's demo app's PlayerActivity.java.
I use dm77/barcodescanner library to Scan QrCode. But When using This in my app, camera focus time is 1000L, and this is not optimal parameter for all phones.
How to improve the focus speed of camera?
I found the answer to this question with help #TeunVR in github.
You must create a class and extends from ZXingScannerView and Override setupCameraPreview and setAutoFocus .
public class ZXingAutofocusScannerView extends ZXingScannerView {
private boolean callbackFocus = false ;
public ZXingAutofocusScannerView(Context context) {
super(context);
}
#Override
public void setupCameraPreview(CameraWrapper cameraWrapper) {
Camera.Parameters parameters= cameraWrapper.mCamera.getParameters();
if(parameters != null)
{
try {
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
cameraWrapper.mCamera.setParameters(parameters);
}catch (Exception e)
{
fallbackFocus = true ;
}
// cameraWrapper.mCamera.getParameters()
}
super.setupCameraPreview(cameraWrapper);
}
#Override
public void setAutoFocus(boolean state) {
super.setAutoFocus(callbackFocus);
}
}
Now you must use this class instead ZXingScannerView.
public class SimpleScannerActivity extends AppCompatActivity implements
ZXingAutofocusScannerView.ResultHandler {
private ZXingAutofocusScannerView mScannerView;
#Override
public void onCreate(Bundle state) {
super.onCreate(state);
mScannerView = new ZXingAutofocusScannerView(this);
setContentView(mScannerView);
#Override
public void onResume() {
super.onResume();
mScannerView.setResultHandler(this);
mScannerView.startCamera();
}
#Override
public void onPause() {
super.onPause();
mScannerView.stopCamera();
}
#Override
public void handleResult(Result rawResult) {
Toast.makeText(this, ""+rawResult.getText(), Toast.LENGTH_SHORT).show();
mScannerView.resumeCameraPreview(this);
}
}
If You Use Koltin see this answer :
class ZXingAutofocusScannerView(context: Context) :
ZXingScannerView(context) {
private val TAG = ZXingAutofocusScannerView::class.qualifiedName
private var callbackFocus = false
override fun setupCameraPreview(cameraWrapper: CameraWrapper?) {
cameraWrapper?.mCamera?.parameters?.let{parameters->
try {
parameters.focusMode =
Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE
cameraWrapper.mCamera.parameters = parameters
}catch(ex:Exception){
Log.e(TAG, "Failed to set CONTINOUS_PICTURE", ex)
callbackFocus = true
}
}
super.setupCameraPreview(cameraWrapper)
}
override fun setAutoFocus(state: Boolean) {
super.setAutoFocus(callbackFocus)
}
}
I am using XWalkView to show a mobile web site as an application. My problem is when application goes background and comes back it reloads the page it shows. I want to keep it state and continue from that state when it comes from background. Here is my code:
public class MainActivity extends AppCompatActivity {
static final String URL = "https://www.biletdukkani.com.tr";
static final int MY_PERMISSIONS_REQUEST_ACCESS_LOCATION = 55;
static final String SHOULD_ASK_FOR_LOCATION_PERMISSION = "shouldAskForLocationPermission";
static final String TAG = "MainActivity";
static final String COMMAND = "/system/bin/ping -c 1 185.22.184.184";
static XWalkView xWalkWebView;
TextView noInternet;
static Bundle stateBundle;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate");
// Check whether we're recreating a previously destroyed instance
if (savedInstanceState != null) {
// Restore value of members from saved state
stateBundle = savedInstanceState.getBundle("xwalk");
}
setContentView(R.layout.activity_main);
initNoInternetTextView();
}
public void onRestoreInstanceState(Bundle savedInstanceState) {
// Always call the superclass so it can restore the view hierarchy
super.onRestoreInstanceState(savedInstanceState);
stateBundle = savedInstanceState.getBundle("xwalk");
Log.d(TAG, "onRestoreInstanceState");
}
/**
* İnternet yok mesajı gösteren TextVidew'i ayarlar.
*/
private void initNoInternetTextView() {
Log.d(TAG, "initNoInternetTextView");
noInternet = (TextView) findViewById(R.id.no_internet);
if (noInternet != null) {
noInternet.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
checkInternetConnection();
}
});
}
}
/**
* WebView'i ayarlar.
*/
private void initWebView() {
Log.d(TAG, "initWebView");
if (xWalkWebView == null) {
ProgressBar progressBar = (ProgressBar) findViewById(R.id.progressBar);
xWalkWebView = (XWalkView) findViewById(R.id.webView);
//xWalkWebView.clearCache(true);
xWalkWebView.load(URL, null);
xWalkWebView.setResourceClient(new BDResourceClient(xWalkWebView, progressBar));
}
}
#Override
protected void onResume() {
super.onResume();
Log.d(TAG, "onResume");
checkLocationPermissions();
checkInternetConnection();
if (xWalkWebView != null && stateBundle != null) {
xWalkWebView.restoreState(stateBundle);
}
}
#Override
protected void onPause() {
super.onPause();
Log.d(TAG, "onPause");
if (xWalkWebView != null) {
stateBundle = new Bundle();
xWalkWebView.saveState(stateBundle);
}
}
public void onSaveInstanceState(Bundle savedInstanceState) {
Log.d(TAG, "onSaveInstanceState");
// Save the user's current game state
savedInstanceState.putBundle("xwalk", stateBundle);
// Always call the superclass so it can save the view hierarchy state
super.onSaveInstanceState(savedInstanceState);
}
#Override
public void onBackPressed() {
Log.d(TAG, "onBackPressed");
if (xWalkWebView != null && xWalkWebView.getNavigationHistory().canGoBack()) {
xWalkWebView.getNavigationHistory().navigate(XWalkNavigationHistory.Direction.BACKWARD, 1);
} else {
super.onBackPressed();
}
}
}
I have also tried to add following lines to manifest but didn't work.
android:launchMode="singleTask"
android:alwaysRetainTaskState="true"
How can i do that?
Thanks in advcance.
One way would be to initialize the view inside a fragment which is set to retain it's instance.