diff --git a/ApiDemos/java/app/src/gms/AndroidManifest.xml b/ApiDemos/java/app/src/gms/AndroidManifest.xml new file mode 100644 index 000000000..e572f5858 --- /dev/null +++ b/ApiDemos/java/app/src/gms/AndroidManifest.xml @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/AdvancedMarkersDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/AdvancedMarkersDemoActivity.java new file mode 100644 index 000000000..fdab92600 --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/AdvancedMarkersDemoActivity.java @@ -0,0 +1,139 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.example.mapdemo; + +import android.graphics.Color; +import android.os.Bundle; +import android.util.Log; +import android.widget.TextView; + +import androidx.appcompat.app.AppCompatActivity; + +import com.google.android.gms.maps.CameraUpdateFactory; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.model.AdvancedMarkerOptions; +import com.google.android.gms.maps.model.BitmapDescriptorFactory; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.MapCapabilities; +import com.google.android.gms.maps.model.Marker; +import com.google.android.gms.maps.model.PinConfig; + +/** + * The following sample showcases how to create Advanced Markers, and use all their customization + * possibilities. + */ +// [START maps_android_sample_marker_advanced] +public class AdvancedMarkersDemoActivity extends AppCompatActivity implements OnMapReadyCallback { + + private static final LatLng SINGAPORE = new LatLng(1.3521, 103.8198); + private static final LatLng KUALA_LUMPUR = new LatLng(3.1390, 101.6869); + private static final LatLng JAKARTA = new LatLng(-6.2088, 106.8456); + private static final LatLng BANGKOK = new LatLng(13.7563, 100.5018); + private static final LatLng MANILA = new LatLng(14.5995, 120.9842); + private static final LatLng HO_CHI_MINH_CITY = new LatLng(10.7769, 106.7009); + + private static final float ZOOM_LEVEL = 3.5f; + + private static final String TAG = AdvancedMarkersDemoActivity.class.getName(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.advanced_markers_demo); + + SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map); + if (mapFragment != null) { + mapFragment.getMapAsync(this); + } + } + + @Override + public void onMapReady(GoogleMap map) { + map.moveCamera(CameraUpdateFactory.newLatLngZoom(SINGAPORE, ZOOM_LEVEL)); + + MapCapabilities capabilities = map.getMapCapabilities(); + Log.d(TAG, "Are advanced markers enabled? " + capabilities.isAdvancedMarkersAvailable()); + + // This sample sets a view as the iconView for the Advanced Marker + TextView textView = new TextView(this); + textView.setText("Hello!"); + Marker advancedMarkerView = map.addMarker(new AdvancedMarkerOptions() + .position(SINGAPORE) + .iconView(textView) + .zIndex(1f)); + + // This uses PinConfig.Builder to create an instance of PinConfig. + PinConfig.Builder pinConfigBuilder = PinConfig.builder(); + pinConfigBuilder.setBackgroundColor(Color.MAGENTA); + PinConfig pinConfig = pinConfigBuilder.build(); + + // Use the PinConfig instance to set the icon for AdvancedMarkerOptions. + AdvancedMarkerOptions advancedMarkerOptions = new AdvancedMarkerOptions() + .icon(BitmapDescriptorFactory.fromPinConfig(pinConfig)) + .position(KUALA_LUMPUR); + + // Pass the AdvancedMarkerOptions instance to addMarker(). + Marker marker = map.addMarker(advancedMarkerOptions); + + // This sample changes the border color of the advanced marker + PinConfig.Builder pinConfigBuilder2 = PinConfig.builder(); + pinConfigBuilder2.setBorderColor(Color.BLUE); + PinConfig pinConfig2 = pinConfigBuilder2.build(); + + AdvancedMarkerOptions advancedMarkerOptions2 = new AdvancedMarkerOptions() + .icon(BitmapDescriptorFactory.fromPinConfig(pinConfig2)) + .position(JAKARTA); + + Marker marker2 = map.addMarker(advancedMarkerOptions2); + + // Set the glyph text. + PinConfig.Builder pinConfigBuilder3 = PinConfig.builder(); + PinConfig.Glyph glyphText = new PinConfig.Glyph("A"); + + // Alternatively, you can set the text color: + // Glyph glyphText = new Glyph("A", Color.GREEN); + pinConfigBuilder3.setGlyph(glyphText); + PinConfig pinConfig3 = pinConfigBuilder3.build(); + + AdvancedMarkerOptions advancedMarkerOptions3 = new AdvancedMarkerOptions() + .icon(BitmapDescriptorFactory.fromPinConfig(pinConfig3)) + .position(BANGKOK); + + Marker marker3 = map.addMarker(advancedMarkerOptions3); + + // Create a transparent glyph. + PinConfig.Builder pinConfigBuilder4 = PinConfig.builder(); + pinConfigBuilder4.setBackgroundColor(Color.MAGENTA); + pinConfigBuilder4.setGlyph(new PinConfig.Glyph(Color.TRANSPARENT)); + PinConfig pinConfig4 = pinConfigBuilder4.build(); + + AdvancedMarkerOptions advancedMarkerOptions4 = new AdvancedMarkerOptions() + .icon(BitmapDescriptorFactory.fromPinConfig(pinConfig4)) + .position(MANILA); + + Marker marker4 = map.addMarker(advancedMarkerOptions4); + + // Collision behavior can only be changed in the AdvancedMarkerOptions object. + // Changes to collision behavior after a marker has been created are not possible + int collisionBehavior = AdvancedMarkerOptions.CollisionBehavior.REQUIRED_AND_HIDES_OPTIONAL; + AdvancedMarkerOptions advancedMarkerOptions5 = new AdvancedMarkerOptions() + .position(HO_CHI_MINH_CITY) + .collisionBehavior(collisionBehavior); + + Marker marker5 = map.addMarker(advancedMarkerOptions5); + } +} +// [END maps_android_sample_marker_advanced] diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/BackgroundColorCustomizationDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/BackgroundColorCustomizationDemoActivity.java new file mode 100644 index 000000000..f8d0707d8 --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/BackgroundColorCustomizationDemoActivity.java @@ -0,0 +1,61 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.example.mapdemo; + +import android.os.Bundle; +import android.widget.CheckBox; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import com.google.android.gms.common.internal.Preconditions; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.MarkerOptions; + +/** + * This shows how to create a simple activity with a custom background color appiled to the map, and + * add a marker on the map. + */ +public class BackgroundColorCustomizationDemoActivity extends AppCompatActivity + implements OnMapReadyCallback { + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.background_color_customization_demo); + + SupportMapFragment mapFragment = + (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map); + + Preconditions.checkNotNull(mapFragment) + .getMapAsync(this); + } + + /** + * This is where we can add markers or lines, add listeners or move the camera. In this case, we + * just add a marker near Africa. + */ + @Override + public void onMapReady(GoogleMap map) { + map.setMapType(GoogleMap.MAP_TYPE_NONE); + + CheckBox mapTypeToggleCheckbox = (CheckBox) findViewById(R.id.map_type_toggle); + mapTypeToggleCheckbox.setOnCheckedChangeListener( + (view, isChecked) -> map.setMapType(isChecked ? GoogleMap.MAP_TYPE_NORMAL : GoogleMap.MAP_TYPE_NONE)); + + map.addMarker(new MarkerOptions().position(new LatLng(0, 0)).title("Marker")); + } +} \ No newline at end of file diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/BackgroundColorCustomizationProgrammaticDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/BackgroundColorCustomizationProgrammaticDemoActivity.java new file mode 100644 index 000000000..2aeb8b8c1 --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/BackgroundColorCustomizationProgrammaticDemoActivity.java @@ -0,0 +1,76 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.example.mapdemo; + +import android.graphics.Color; +import android.os.Bundle; +import android.widget.CheckBox; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.fragment.app.FragmentTransaction; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.GoogleMapOptions; +import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.MarkerOptions; + +/** + * This shows how to to instantiate a SupportMapFragment programmatically with a custom background + * color applied to the map, and add a marker on the map. + */ +public class BackgroundColorCustomizationProgrammaticDemoActivity extends AppCompatActivity + implements OnMapReadyCallback { + + private static final String MAP_FRAGMENT_TAG = "map"; + + private static final Integer LIGHT_PINK_COLOR = Color.argb(153, 240, 178, 221); + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.background_color_customization_programmatic_demo); + + // It isn't possible to set a fragment's id programmatically so we set a tag instead and + // search for it using that. + SupportMapFragment mapFragment = + (SupportMapFragment) getSupportFragmentManager().findFragmentByTag(MAP_FRAGMENT_TAG); + + // We only create a fragment if it doesn't already exist. + if (mapFragment == null) { + // To programmatically add the map, we first create a SupportMapFragment, with the + // GoogleMapOptions to set the custom background color displayed before the map tiles load. + mapFragment = + SupportMapFragment.newInstance(new GoogleMapOptions().backgroundColor(LIGHT_PINK_COLOR)); + + // Then we add the fragment using a FragmentTransaction. + FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction(); + fragmentTransaction.replace(R.id.map, mapFragment, MAP_FRAGMENT_TAG); + fragmentTransaction.commit(); + } + mapFragment.getMapAsync(this); + } + + @Override + public void onMapReady(GoogleMap map) { + map.setMapType(GoogleMap.MAP_TYPE_NONE); + + CheckBox mapTypeToggleCheckbox = (CheckBox) findViewById(R.id.map_type_toggle); + mapTypeToggleCheckbox.setOnCheckedChangeListener( + (view, isChecked) -> map.setMapType(isChecked ? GoogleMap.MAP_TYPE_NORMAL : GoogleMap.MAP_TYPE_NONE)); + + map.addMarker(new MarkerOptions().position(new LatLng(0, 0)).title("Marker")); + } +} \ No newline at end of file diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/BasicMapDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/BasicMapDemoActivity.java new file mode 100755 index 000000000..807d3b9f3 --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/BasicMapDemoActivity.java @@ -0,0 +1,53 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.example.mapdemo; + +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.MarkerOptions; + +import android.os.Bundle; + +import androidx.appcompat.app.AppCompatActivity; + +/** + * This shows how to create a simple activity with a map and a marker on the map. + */ +// [START maps_android_sample_basic_map] +public class BasicMapDemoActivity extends AppCompatActivity implements OnMapReadyCallback { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.basic_demo); + + SupportMapFragment mapFragment = + (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map); + mapFragment.getMapAsync(this); + } + + /** + * This is where we can add markers or lines, add listeners or move the camera. In this case, + * we + * just add a marker near Africa. + */ + @Override + public void onMapReady(GoogleMap map) { + map.addMarker(new MarkerOptions().position(new LatLng(0, 0)).title("Marker")); + } +} +// [END maps_android_sample_basic_map] \ No newline at end of file diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/CameraClampingDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/CameraClampingDemoActivity.java new file mode 100644 index 000000000..f04f81913 --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/CameraClampingDemoActivity.java @@ -0,0 +1,184 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +package com.example.mapdemo; + +import com.google.android.gms.maps.CameraUpdateFactory; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.GoogleMap.OnCameraIdleListener; +import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.model.CameraPosition; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.LatLngBounds; + +import android.os.Bundle; +import android.view.View; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.appcompat.app.AppCompatActivity; + +/** + * This shows how to constrain the camera to specific boundaries and zoom levels. + */ +public class CameraClampingDemoActivity extends AppCompatActivity + implements OnMapReadyCallback, OnCameraIdleListener { + + private static final String TAG = CameraClampingDemoActivity.class.getSimpleName(); + + private static final float ZOOM_DELTA = 2.0f; + private static final float DEFAULT_MIN_ZOOM = 2.0f; + private static final float DEFAULT_MAX_ZOOM = 22.0f; + + private static final LatLngBounds ADELAIDE = new LatLngBounds( + new LatLng(-35.0, 138.58), new LatLng(-34.9, 138.61)); + private static final CameraPosition ADELAIDE_CAMERA = new CameraPosition.Builder() + .target(new LatLng(-34.92873, 138.59995)).zoom(20.0f).bearing(0).tilt(0).build(); + + private static final LatLngBounds PACIFIC = new LatLngBounds( + new LatLng(-15.0, 165.0), new LatLng(15.0, -165.0)); + private static final CameraPosition PACIFIC_CAMERA = new CameraPosition.Builder() + .target(new LatLng(0, -180)).zoom(4.0f).bearing(0).tilt(0).build(); + + private GoogleMap mMap; + + /** + * Internal min zoom level that can be toggled via the demo. + */ + private float mMinZoom; + + /** + * Internal max zoom level that can be toggled via the demo. + */ + private float mMaxZoom; + + private TextView mCameraTextView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.camera_clamping_demo); + + mMap = null; + resetMinMaxZoom(); + + mCameraTextView = (TextView) findViewById(R.id.camera_text); + + SupportMapFragment mapFragment = + (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map); + mapFragment.getMapAsync(this); + } + + @Override + protected void onResume() { + super.onResume(); + } + + @Override + public void onMapReady(GoogleMap map) { + mMap = map; + map.setOnCameraIdleListener(this); + } + + @Override + public void onCameraIdle() { + mCameraTextView.setText(mMap.getCameraPosition().toString()); + } + + /** + * Before the map is ready many calls will fail. + * This should be called on all entry points that call methods on the Google Maps API. + */ + private boolean checkReady() { + if (mMap == null) { + Toast.makeText(this, R.string.map_not_ready, Toast.LENGTH_SHORT).show(); + return false; + } + return true; + } + + private void toast(String msg) { + Toast.makeText(getBaseContext(), msg, Toast.LENGTH_SHORT).show(); + } + + private void resetMinMaxZoom() { + mMinZoom = DEFAULT_MIN_ZOOM; + mMaxZoom = DEFAULT_MAX_ZOOM; + } + + /** + * Click handler for clamping to Adelaide button. + * @param view + */ + public void onClampToAdelaide(View view) { + if (!checkReady()) { + return; + } + mMap.setLatLngBoundsForCameraTarget(ADELAIDE); + mMap.animateCamera(CameraUpdateFactory.newCameraPosition(ADELAIDE_CAMERA)); + } + + /** + * Click handler for clamping to Pacific button. + * @param view + */ + public void onClampToPacific(View view) { + if (!checkReady()) { + return; + } + mMap.setLatLngBoundsForCameraTarget(PACIFIC); + mMap.animateCamera(CameraUpdateFactory.newCameraPosition(PACIFIC_CAMERA)); + } + + public void onLatLngClampReset(View view) { + if (!checkReady()) { + return; + } + // Setting bounds to null removes any previously set bounds. + mMap.setLatLngBoundsForCameraTarget(null); + toast("LatLngBounds clamp reset."); + } + + public void onSetMinZoomClamp(View view) { + if (!checkReady()) { + return; + } + mMinZoom += ZOOM_DELTA; + // Constrains the minimum zoom level. + mMap.setMinZoomPreference(mMinZoom); + toast("Min zoom preference set to: " + mMinZoom); + } + + public void onSetMaxZoomClamp(View view) { + if (!checkReady()) { + return; + } + mMaxZoom -= ZOOM_DELTA; + // Constrains the maximum zoom level. + mMap.setMaxZoomPreference(mMaxZoom); + toast("Max zoom preference set to: " + mMaxZoom); + } + + public void onMinMaxZoomClampReset(View view) { + if (!checkReady()) { + return; + } + resetMinMaxZoom(); + mMap.resetMinMaxZoomPreference(); + toast("Min/Max zoom preferences reset."); + } +} \ No newline at end of file diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/CameraDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/CameraDemoActivity.java new file mode 100644 index 000000000..4442b06b2 --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/CameraDemoActivity.java @@ -0,0 +1,422 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +package com.example.mapdemo; + +import android.graphics.Color; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.CompoundButton; +import android.widget.SeekBar; +import android.widget.Toast; + +import androidx.appcompat.app.AppCompatActivity; + +import com.google.android.gms.maps.CameraUpdate; +import com.google.android.gms.maps.CameraUpdateFactory; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.GoogleMap.CancelableCallback; +import com.google.android.gms.maps.GoogleMap.OnCameraIdleListener; +import com.google.android.gms.maps.GoogleMap.OnCameraMoveCanceledListener; +import com.google.android.gms.maps.GoogleMap.OnCameraMoveListener; +import com.google.android.gms.maps.GoogleMap.OnCameraMoveStartedListener; +import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.model.CameraPosition; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.PolylineOptions; + +/** + * This shows how to change the camera position for the map. + */ +// [START maps_camera_events] +public class CameraDemoActivity extends AppCompatActivity implements + OnCameraMoveStartedListener, + OnCameraMoveListener, + OnCameraMoveCanceledListener, + OnCameraIdleListener, + OnMapReadyCallback { + // [START_EXCLUDE silent] + private static final String TAG = CameraDemoActivity.class.getName(); + + /** + * The amount by which to scroll the camera. Note that this amount is in raw pixels, not dp + * (density-independent pixels). + */ + private static final int SCROLL_BY_PX = 100; + + public static final CameraPosition BONDI = + new CameraPosition.Builder().target(new LatLng(-33.891614, 151.276417)) + .zoom(15.5f) + .bearing(300) + .tilt(50) + .build(); + + public static final CameraPosition SYDNEY = + new CameraPosition.Builder().target(new LatLng(-33.87365, 151.20689)) + .zoom(15.5f) + .bearing(0) + .tilt(25) + .build(); + // [END_EXCLUDE] + + private GoogleMap map; + // [START_EXCLUDE silent] + private CompoundButton animateToggle; + private CompoundButton customDurationToggle; + private SeekBar customDurationBar; + private PolylineOptions currPolylineOptions; + private boolean isCanceled = false; + // [END_EXCLUDE] + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.camera_demo); + // [START_EXCLUDE silent] + animateToggle = findViewById(R.id.animate); + customDurationToggle = findViewById(R.id.duration_toggle); + customDurationBar = findViewById(R.id.duration_bar); + + updateEnabledState(); + // [END_EXCLUDE] + + SupportMapFragment mapFragment = + (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map); + mapFragment.getMapAsync(this); + } + + // [START_EXCLUDE silent] + @Override + protected void onResume() { + super.onResume(); + updateEnabledState(); + } + // [END_EXCLUDE] + + @Override + public void onMapReady(GoogleMap googleMap) { + map = googleMap; + + map.setOnCameraIdleListener(this); + map.setOnCameraMoveStartedListener(this); + map.setOnCameraMoveListener(this); + map.setOnCameraMoveCanceledListener(this); + // [START_EXCLUDE silent] + // We will provide our own zoom controls. + map.getUiSettings().setZoomControlsEnabled(false); + map.getUiSettings().setMyLocationButtonEnabled(true); + // [END_EXCLUDE] + + // Show Sydney + map.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(-33.87365, 151.20689), 10)); + } + + // [START_EXCLUDE silent] + /** + * When the map is not ready the CameraUpdateFactory cannot be used. This should be called on + * all entry points that call methods on the Google Maps API. + */ + private boolean checkReady() { + if (map == null) { + Toast.makeText(this, R.string.map_not_ready, Toast.LENGTH_SHORT).show(); + return false; + } + return true; + } + + /** + * Called when the Go To Bondi button is clicked. + */ + public void onGoToBondi(View view) { + if (!checkReady()) { + return; + } + + changeCamera(CameraUpdateFactory.newCameraPosition(BONDI)); + } + + /** + * Called when the Animate To Sydney button is clicked. + */ + public void onGoToSydney(View view) { + if (!checkReady()) { + return; + } + + changeCamera(CameraUpdateFactory.newCameraPosition(SYDNEY), new CancelableCallback() { + @Override + public void onFinish() { + Toast.makeText(getBaseContext(), "Animation to Sydney complete", Toast.LENGTH_SHORT) + .show(); + } + + @Override + public void onCancel() { + Toast.makeText(getBaseContext(), "Animation to Sydney canceled", Toast.LENGTH_SHORT) + .show(); + } + }); + } + + /** + * Called when the stop button is clicked. + */ + public void onStopAnimation(View view) { + if (!checkReady()) { + return; + } + + map.stopAnimation(); + } + + /** + * Called when the zoom in button (the one with the +) is clicked. + */ + public void onZoomIn(View view) { + if (!checkReady()) { + return; + } + + changeCamera(CameraUpdateFactory.zoomIn()); + } + + /** + * Called when the zoom out button (the one with the -) is clicked. + */ + public void onZoomOut(View view) { + if (!checkReady()) { + return; + } + + changeCamera(CameraUpdateFactory.zoomOut()); + } + + /** + * Called when the tilt more button (the one with the /) is clicked. + */ + public void onTiltMore(View view) { + if (!checkReady()) { + return; + } + + CameraPosition currentCameraPosition = map.getCameraPosition(); + float currentTilt = currentCameraPosition.tilt; + float newTilt = currentTilt + 10; + + newTilt = (newTilt > 90) ? 90 : newTilt; + + CameraPosition cameraPosition = new CameraPosition.Builder(currentCameraPosition) + .tilt(newTilt).build(); + + changeCamera(CameraUpdateFactory.newCameraPosition(cameraPosition)); + } + + /** + * Called when the tilt less button (the one with the \) is clicked. + */ + public void onTiltLess(View view) { + if (!checkReady()) { + return; + } + + CameraPosition currentCameraPosition = map.getCameraPosition(); + + float currentTilt = currentCameraPosition.tilt; + + float newTilt = currentTilt - 10; + newTilt = (newTilt > 0) ? newTilt : 0; + + CameraPosition cameraPosition = new CameraPosition.Builder(currentCameraPosition) + .tilt(newTilt).build(); + + changeCamera(CameraUpdateFactory.newCameraPosition(cameraPosition)); + } + + /** + * Called when the left arrow button is clicked. This causes the camera to move to the left + */ + public void onScrollLeft(View view) { + if (!checkReady()) { + return; + } + + changeCamera(CameraUpdateFactory.scrollBy(-SCROLL_BY_PX, 0)); + } + + /** + * Called when the right arrow button is clicked. This causes the camera to move to the right. + */ + public void onScrollRight(View view) { + if (!checkReady()) { + return; + } + + changeCamera(CameraUpdateFactory.scrollBy(SCROLL_BY_PX, 0)); + } + + /** + * Called when the up arrow button is clicked. The causes the camera to move up. + */ + public void onScrollUp(View view) { + if (!checkReady()) { + return; + } + + changeCamera(CameraUpdateFactory.scrollBy(0, -SCROLL_BY_PX)); + } + + /** + * Called when the down arrow button is clicked. This causes the camera to move down. + */ + public void onScrollDown(View view) { + if (!checkReady()) { + return; + } + + changeCamera(CameraUpdateFactory.scrollBy(0, SCROLL_BY_PX)); + } + + /** + * Called when the animate button is toggled + */ + public void onToggleAnimate(View view) { + updateEnabledState(); + } + + /** + * Called when the custom duration checkbox is toggled + */ + public void onToggleCustomDuration(View view) { + updateEnabledState(); + } + + /** + * Update the enabled state of the custom duration controls. + */ + private void updateEnabledState() { + customDurationToggle.setEnabled(animateToggle.isChecked()); + customDurationBar + .setEnabled(animateToggle.isChecked() && customDurationToggle.isChecked()); + } + + private void changeCamera(CameraUpdate update) { + changeCamera(update, null); + } + + /** + * Change the camera position by moving or animating the camera depending on the state of the + * animate toggle button. + */ + private void changeCamera(CameraUpdate update, CancelableCallback callback) { + if (animateToggle.isChecked()) { + if (customDurationToggle.isChecked()) { + int duration = customDurationBar.getProgress(); + // The duration must be strictly positive so we make it at least 1. + map.animateCamera(update, Math.max(duration, 1), callback); + } else { + map.animateCamera(update, callback); + } + } else { + map.moveCamera(update); + } + } + // [END_EXCLUDE] + + @Override + public void onCameraMoveStarted(int reason) { + // [START_EXCLUDE silent] + if (!isCanceled) { + map.clear(); + } + // [END_EXCLUDE] + + String reasonText = "UNKNOWN_REASON"; + // [START_EXCLUDE silent] + currPolylineOptions = new PolylineOptions().width(5); + // [END_EXCLUDE] + switch (reason) { + case OnCameraMoveStartedListener.REASON_GESTURE: + // [START_EXCLUDE silent] + currPolylineOptions.color(Color.BLUE); + // [END_EXCLUDE] + reasonText = "GESTURE"; + break; + case OnCameraMoveStartedListener.REASON_API_ANIMATION: + // [START_EXCLUDE silent] + currPolylineOptions.color(Color.RED); + // [END_EXCLUDE] + reasonText = "API_ANIMATION"; + break; + case OnCameraMoveStartedListener.REASON_DEVELOPER_ANIMATION: + // [START_EXCLUDE silent] + currPolylineOptions.color(Color.GREEN); + // [END_EXCLUDE] + reasonText = "DEVELOPER_ANIMATION"; + break; + } + Log.d(TAG, "onCameraMoveStarted(" + reasonText + ")"); + // [START_EXCLUDE silent] + addCameraTargetToPath(); + // [END_EXCLUDE] + } + + @Override + public void onCameraMove() { + // [START_EXCLUDE silent] + // When the camera is moving, add its target to the current path we'll draw on the map. + if (currPolylineOptions != null) { + addCameraTargetToPath(); + } + // [END_EXCLUDE] + Log.d(TAG, "onCameraMove"); + } + + @Override + public void onCameraMoveCanceled() { + // [START_EXCLUDE silent] + // When the camera stops moving, add its target to the current path, and draw it on the map. + if (currPolylineOptions != null) { + addCameraTargetToPath(); + map.addPolyline(currPolylineOptions); + } + isCanceled = true; // Set to clear the map when dragging starts again. + currPolylineOptions = null; + // [END_EXCLUDE] + Log.d(TAG, "onCameraMoveCancelled"); + } + + @Override + public void onCameraIdle() { + // [START_EXCLUDE silent] + if (currPolylineOptions != null) { + addCameraTargetToPath(); + map.addPolyline(currPolylineOptions); + } + currPolylineOptions = null; + isCanceled = false; // Set to *not* clear the map when dragging starts again. + // [END_EXCLUDE] + Log.d(TAG, "onCameraIdle"); + } + + // [START_EXCLUDE silent] + private void addCameraTargetToPath() { + LatLng target = map.getCameraPosition().target; + currPolylineOptions.add(target); + } + // [END_EXCLUDE] +} +// [END maps_camera_events] \ No newline at end of file diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/CircleDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/CircleDemoActivity.java new file mode 100755 index 000000000..5b08db466 --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/CircleDemoActivity.java @@ -0,0 +1,365 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.example.mapdemo; + +import android.graphics.Color; +import android.graphics.Point; +import android.location.Location; +import android.os.Bundle; +import android.view.View; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.ArrayAdapter; +import android.widget.CheckBox; +import android.widget.SeekBar; +import android.widget.SeekBar.OnSeekBarChangeListener; +import android.widget.Spinner; + +import androidx.appcompat.app.AppCompatActivity; + +import com.google.android.gms.maps.CameraUpdateFactory; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.GoogleMap.OnCircleClickListener; +import com.google.android.gms.maps.GoogleMap.OnMapLongClickListener; +import com.google.android.gms.maps.GoogleMap.OnMarkerDragListener; +import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.model.BitmapDescriptorFactory; +import com.google.android.gms.maps.model.Circle; +import com.google.android.gms.maps.model.CircleOptions; +import com.google.android.gms.maps.model.Dash; +import com.google.android.gms.maps.model.Dot; +import com.google.android.gms.maps.model.Gap; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.Marker; +import com.google.android.gms.maps.model.MarkerOptions; +import com.google.android.gms.maps.model.PatternItem; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + + +/** + * This shows how to draw circles on a map. + */ +public class CircleDemoActivity extends AppCompatActivity + implements OnSeekBarChangeListener, OnMarkerDragListener, OnMapLongClickListener, + OnItemSelectedListener, OnMapReadyCallback { + + private static final LatLng SYDNEY = new LatLng(-33.87365, 151.20689); + private static final double DEFAULT_RADIUS_METERS = 1000000; + private static final double RADIUS_OF_EARTH_METERS = 6371009; + + private static final int MAX_WIDTH_PX = 50; + private static final int MAX_HUE_DEGREES = 360; + private static final int MAX_ALPHA = 255; + + private static final int PATTERN_DASH_LENGTH_PX = 100; + private static final int PATTERN_GAP_LENGTH_PX = 200; + private static final Dot DOT = new Dot(); + private static final Dash DASH = new Dash(PATTERN_DASH_LENGTH_PX); + private static final Gap GAP = new Gap(PATTERN_GAP_LENGTH_PX); + private static final List PATTERN_DOTTED = Arrays.asList(DOT, GAP); + private static final List PATTERN_DASHED = Arrays.asList(DASH, GAP); + private static final List PATTERN_MIXED = Arrays.asList(DOT, GAP, DOT, DASH, GAP); + + private GoogleMap map; + + private List circles = new ArrayList<>(1); + + private int fillColorArgb; + private int strokeColorArgb; + + private SeekBar fillHueBar; + private SeekBar fillAlphaBar; + private SeekBar strokeWidthBar; + private SeekBar strokeHueBar; + private SeekBar strokeAlphaBar; + private Spinner strokePatternSpinner; + private CheckBox clickabilityCheckbox; + + // These are the options for stroke patterns. We use their + // string resource IDs as identifiers. + + private static final int[] PATTERN_TYPE_NAME_RESOURCE_IDS = { + R.string.pattern_solid, // Default + R.string.pattern_dashed, + R.string.pattern_dotted, + R.string.pattern_mixed, + }; + + private class DraggableCircle { + private final Marker centerMarker; + private final Marker radiusMarker; + private final Circle circle; + private double radiusMeters; + + public DraggableCircle(LatLng center, double radiusMeters) { + this.radiusMeters = radiusMeters; + centerMarker = map.addMarker(new MarkerOptions() + .position(center) + .draggable(true)); + radiusMarker = map.addMarker(new MarkerOptions() + .position(toRadiusLatLng(center, radiusMeters)) + .draggable(true) + .icon(BitmapDescriptorFactory.defaultMarker( + BitmapDescriptorFactory.HUE_AZURE))); + circle = map.addCircle(new CircleOptions() + .center(center) + .radius(radiusMeters) + .strokeWidth(strokeWidthBar.getProgress()) + .strokeColor(strokeColorArgb) + .fillColor(fillColorArgb) + .clickable(clickabilityCheckbox.isChecked())); + } + + public boolean onMarkerMoved(Marker marker) { + if (marker.equals(centerMarker)) { + circle.setCenter(marker.getPosition()); + radiusMarker.setPosition(toRadiusLatLng(marker.getPosition(), radiusMeters)); + return true; + } + if (marker.equals(radiusMarker)) { + radiusMeters = + toRadiusMeters(centerMarker.getPosition(), radiusMarker.getPosition()); + circle.setRadius(radiusMeters); + return true; + } + return false; + } + + public void onStyleChange() { + circle.setStrokeWidth(strokeWidthBar.getProgress()); + circle.setStrokeColor(strokeColorArgb); + circle.setFillColor(fillColorArgb); + } + + public void setStrokePattern(List pattern) { + circle.setStrokePattern(pattern); + } + + public void setClickable(boolean clickable) { + circle.setClickable(clickable); + } + } + + /** Generate LatLng of radius marker */ + private static LatLng toRadiusLatLng(LatLng center, double radiusMeters) { + double radiusAngle = Math.toDegrees(radiusMeters / RADIUS_OF_EARTH_METERS) / + Math.cos(Math.toRadians(center.latitude)); + return new LatLng(center.latitude, center.longitude + radiusAngle); + } + + private static double toRadiusMeters(LatLng center, LatLng radius) { + float[] result = new float[1]; + Location.distanceBetween(center.latitude, center.longitude, + radius.latitude, radius.longitude, result); + return result[0]; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.circle_demo); + + fillHueBar = findViewById(R.id.fillHueSeekBar); + fillHueBar.setMax(MAX_HUE_DEGREES); + fillHueBar.setProgress(MAX_HUE_DEGREES / 2); + + fillAlphaBar = findViewById(R.id.fillAlphaSeekBar); + fillAlphaBar.setMax(MAX_ALPHA); + fillAlphaBar.setProgress(MAX_ALPHA / 2); + + strokeWidthBar = findViewById(R.id.strokeWidthSeekBar); + strokeWidthBar.setMax(MAX_WIDTH_PX); + strokeWidthBar.setProgress(MAX_WIDTH_PX / 3); + + strokeHueBar = findViewById(R.id.strokeHueSeekBar); + strokeHueBar.setMax(MAX_HUE_DEGREES); + strokeHueBar.setProgress(0); + + strokeAlphaBar = findViewById(R.id.strokeAlphaSeekBar); + strokeAlphaBar.setMax(MAX_ALPHA); + strokeAlphaBar.setProgress(MAX_ALPHA); + + strokePatternSpinner = findViewById(R.id.strokePatternSpinner); + strokePatternSpinner.setAdapter(new ArrayAdapter<>( + this, android.R.layout.simple_spinner_item, + getResourceStrings(PATTERN_TYPE_NAME_RESOURCE_IDS))); + + clickabilityCheckbox = findViewById(R.id.toggleClickability); + + SupportMapFragment mapFragment = + (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map); + mapFragment.getMapAsync(this); + } + + private String[] getResourceStrings(int[] resourceIds) { + String[] strings = new String[resourceIds.length]; + for (int i = 0; i < resourceIds.length; i++) { + strings[i] = getString(resourceIds[i]); + } + return strings; + } + + @Override + public void onMapReady(GoogleMap googleMap) { + // Override the default content description on the view, for accessibility mode. + googleMap.setContentDescription(getString(R.string.map_circle_description)); + + map = googleMap; + map.setOnMarkerDragListener(this); + map.setOnMapLongClickListener(this); + + fillColorArgb = Color.HSVToColor( + fillAlphaBar.getProgress(), new float[]{fillHueBar.getProgress(), 1, 1}); + strokeColorArgb = Color.HSVToColor( + strokeAlphaBar.getProgress(), new float[]{strokeHueBar.getProgress(), 1, 1}); + + fillHueBar.setOnSeekBarChangeListener(this); + fillAlphaBar.setOnSeekBarChangeListener(this); + + strokeWidthBar.setOnSeekBarChangeListener(this); + strokeHueBar.setOnSeekBarChangeListener(this); + strokeAlphaBar.setOnSeekBarChangeListener(this); + + strokePatternSpinner.setOnItemSelectedListener(this); + + DraggableCircle circle = new DraggableCircle(SYDNEY, DEFAULT_RADIUS_METERS); + circles.add(circle); + + // Move the map so that it is centered on the initial circle + map.moveCamera(CameraUpdateFactory.newLatLngZoom(SYDNEY, 4.0f)); + + // Set up the click listener for the circle. + googleMap.setOnCircleClickListener(new OnCircleClickListener() { + @Override + public void onCircleClick(Circle circle) { + // Flip the red, green and blue components of the circle's stroke color. + circle.setStrokeColor(circle.getStrokeColor() ^ 0x00ffffff); + } + }); + + List pattern = getSelectedPattern(strokePatternSpinner.getSelectedItemPosition()); + for (DraggableCircle draggableCircle : circles) { + draggableCircle.setStrokePattern(pattern); + } + } + + private List getSelectedPattern(int pos) { + int id = PATTERN_TYPE_NAME_RESOURCE_IDS[pos]; + if (id == R.string.pattern_solid) { + return null; + } else if (id == R.string.pattern_dotted) { + return PATTERN_DOTTED; + } else if (id == R.string.pattern_dashed) { + return PATTERN_DASHED; + } else if (id == R.string.pattern_mixed) { + return PATTERN_MIXED; + } else { + return null; + } + } + + @Override + public void onItemSelected(AdapterView parent, View view, int pos, long id) { + if (parent.getId() == R.id.strokePatternSpinner) { + for (DraggableCircle draggableCircle : circles) { + draggableCircle.setStrokePattern(getSelectedPattern(pos)); + } + } + } + + @Override + public void onNothingSelected(AdapterView parent) { + // Don't do anything here. + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + // Don't do anything here. + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + // Don't do anything here. + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (seekBar == fillHueBar) { + fillColorArgb = + Color.HSVToColor(Color.alpha(fillColorArgb), new float[]{progress, 1, 1}); + } else if (seekBar == fillAlphaBar) { + fillColorArgb = Color.argb(progress, Color.red(fillColorArgb), + Color.green(fillColorArgb), Color.blue(fillColorArgb)); + } else if (seekBar == strokeHueBar) { + strokeColorArgb = + Color.HSVToColor(Color.alpha(strokeColorArgb), new float[]{progress, 1, 1}); + } else if (seekBar == strokeAlphaBar) { + strokeColorArgb = Color.argb(progress, Color.red(strokeColorArgb), + Color.green(strokeColorArgb), Color.blue(strokeColorArgb)); + } + + for (DraggableCircle draggableCircle : circles) { + draggableCircle.onStyleChange(); + } + } + + @Override + public void onMarkerDragStart(Marker marker) { + onMarkerMoved(marker); + } + + @Override + public void onMarkerDragEnd(Marker marker) { + onMarkerMoved(marker); + } + + @Override + public void onMarkerDrag(Marker marker) { + onMarkerMoved(marker); + } + + private void onMarkerMoved(Marker marker) { + for (DraggableCircle draggableCircle : circles) { + if (draggableCircle.onMarkerMoved(marker)) { + break; + } + } + } + + @Override + public void onMapLongClick(LatLng point) { + // We know the center, let's place the outline at a point 3/4 along the view. + View view = getSupportFragmentManager().findFragmentById(R.id.map).getView(); + LatLng radiusLatLng = map.getProjection().fromScreenLocation(new Point( + view.getHeight() * 3 / 4, view.getWidth() * 3 / 4)); + + // Create the circle. + DraggableCircle circle = new DraggableCircle(point, toRadiusMeters(point, radiusLatLng)); + circles.add(circle); + } + + public void toggleClickability(View view) { + boolean clickable = ((CheckBox) view).isChecked(); + // Set each of the circles to be clickable or not, based on the + // state of the checkbox. + for (DraggableCircle draggableCircle : circles) { + draggableCircle.setClickable(clickable); + } + } +} \ No newline at end of file diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/CloudBasedMapStylingDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/CloudBasedMapStylingDemoActivity.java new file mode 100644 index 000000000..f4b0510b5 --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/CloudBasedMapStylingDemoActivity.java @@ -0,0 +1,72 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.example.mapdemo; + +import android.os.Bundle; +import androidx.appcompat.app.AppCompatActivity; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.SupportMapFragment; + +/** + * This shows how to use Cloud-based Map Styling in a simple Activity. For more information on how + * to style a map using this method, see: + * https://developers.google.com/maps/documentation/android-sdk/cloud-based-map-styling + **/ +public class CloudBasedMapStylingDemoActivity extends AppCompatActivity implements OnMapReadyCallback { + + private static final String MAP_TYPE_KEY = "map_type"; + private GoogleMap map; + private int currentMapType = GoogleMap.MAP_TYPE_NORMAL; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (savedInstanceState != null) { + currentMapType = savedInstanceState.getInt(MAP_TYPE_KEY); + } + + // The underlying style the map will use has been set in the layout + // `cloud_styling_basic_demo` under the SupportMapFragment's `map:mapId` attribute. + setContentView(R.layout.cloud_styling_basic_demo); + SupportMapFragment mapFragment = + (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map); + mapFragment.getMapAsync(this); + + setUpButtonListeners(); + } + + @Override + public void onMapReady(GoogleMap map) { + this.map = map; + map.setMapType(currentMapType); + } + + private void setUpButtonListeners() { + findViewById(R.id.styling_normal_mode).setOnClickListener( + v -> setMapType(GoogleMap.MAP_TYPE_NORMAL)); + findViewById(R.id.styling_satellite_mode).setOnClickListener( + v -> setMapType(GoogleMap.MAP_TYPE_SATELLITE)); + findViewById(R.id.styling_hybrid_mode).setOnClickListener( + v -> setMapType(GoogleMap.MAP_TYPE_HYBRID)); + findViewById(R.id.styling_terrain_mode).setOnClickListener( + v -> setMapType(GoogleMap.MAP_TYPE_TERRAIN)); + } + + private void setMapType(int mapType) { + currentMapType = mapType; + map.setMapType(mapType); + } +} \ No newline at end of file diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/DemoDetails.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/DemoDetails.java new file mode 100755 index 000000000..d804ea0ff --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/DemoDetails.java @@ -0,0 +1,46 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +package com.example.mapdemo; + +import androidx.appcompat.app.AppCompatActivity; + +/** + * A simple POJO that holds the details about the demo that are used by the List Adapter. + */ +public class DemoDetails { + + /** + * The resource id of the title of the demo. + */ + public final int titleId; + + /** + * The resources id of the description of the demo. + */ + public final int descriptionId; + + /** + * The demo activity's class. + */ + public final Class activityClass; + + public DemoDetails( + int titleId, int descriptionId, Class activityClass) { + this.titleId = titleId; + this.descriptionId = descriptionId; + this.activityClass = activityClass; + } +} diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/DemoDetailsList.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/DemoDetailsList.java new file mode 100755 index 000000000..d35f75283 --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/DemoDetailsList.java @@ -0,0 +1,156 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +package com.example.mapdemo; + +/** + * A list of all the demos we have available. + */ +public final class DemoDetailsList { + + /** + * This class should not be instantiated. + */ + private DemoDetailsList() { + } + + public static final DemoDetails[] DEMOS = { + new DemoDetails(R.string.advanced_markers_demo_label, + R.string.advanced_markers_demo_details, + AdvancedMarkersDemoActivity.class), + new DemoDetails(R.string.basic_map_demo_label, + R.string.basic_map_demo_description, + BasicMapDemoActivity.class), + new DemoDetails( + R.string.background_color_customization_demo_label, + R.string.background_color_customization_demo_description, + BackgroundColorCustomizationDemoActivity.class), + new DemoDetails( + R.string.background_color_customization_programmatic_demo_label, + R.string.background_color_customization_programmatic_demo_description, + BackgroundColorCustomizationProgrammaticDemoActivity.class), + new DemoDetails(R.string.camera_demo_label, + R.string.camera_demo_description, + CameraDemoActivity.class), + new DemoDetails(R.string.camera_clamping_demo_label, + R.string.camera_clamping_demo_description, + CameraClampingDemoActivity.class), + new DemoDetails(R.string.cloud_styling_label, + R.string.cloud_styling_description, + CloudBasedMapStylingDemoActivity.class), + new DemoDetails(R.string.circle_demo_label, + R.string.circle_demo_description, + CircleDemoActivity.class), + new DemoDetails(R.string.events_demo_label, + R.string.events_demo_description, + EventsDemoActivity.class), + new DemoDetails(R.string.ground_overlay_demo_label, + R.string.ground_overlay_demo_description, + GroundOverlayDemoActivity.class), + new DemoDetails(R.string.indoor_demo_label, + R.string.indoor_demo_description, + IndoorDemoActivity.class), + new DemoDetails(R.string.layers_demo_label, + R.string.layers_demo_description, + LayersDemoActivity.class), + new DemoDetails(R.string.lite_demo_label, + R.string.lite_demo_description, + LiteDemoActivity.class), + new DemoDetails(R.string.lite_list_demo_label, + R.string.lite_list_demo_description, + LiteListDemoActivity.class), + new DemoDetails(R.string.location_source_demo_label, + R.string.location_source_demo_description, + LocationSourceDemoActivity.class), + new DemoDetails(R.string.map_in_pager_demo_label, + R.string.map_in_pager_demo_description, + MapInPagerDemoActivity.class), + new DemoDetails(R.string.map_color_scheme_demo_label, + R.string.map_color_scheme_demo_description, + MapColorSchemeActivity.class), + new DemoDetails(R.string.marker_demo_label, + R.string.marker_demo_description, + MarkerDemoActivity.class), + new DemoDetails(R.string.marker_close_info_window_on_retap_demo_label, + R.string.marker_close_info_window_on_retap_demo_description, + MarkerCloseInfoWindowOnRetapDemoActivity.class), + new DemoDetails(R.string.multi_map_demo_label, + R.string.multi_map_demo_description, + MultiMapDemoActivity.class), + new DemoDetails(R.string.my_location_demo_label, + R.string.my_location_demo_description, + MyLocationDemoActivity.class), + new DemoDetails(R.string.options_demo_label, + R.string.options_demo_description, + OptionsDemoActivity.class), + new DemoDetails(R.string.polygon_demo_label, + R.string.polygon_demo_description, + PolygonDemoActivity.class), + new DemoDetails(R.string.polyline_demo_label, + R.string.polyline_demo_description, + PolylineDemoActivity.class), + new DemoDetails(R.string.programmatic_demo_label, + R.string.programmatic_demo_description, + ProgrammaticDemoActivity.class), + new DemoDetails(R.string.raw_map_view_demo_label, + R.string.raw_map_view_demo_description, + RawMapViewDemoActivity.class), + new DemoDetails(R.string.retain_map_demo_label, + R.string.retain_map_demo_description, + RetainMapDemoActivity.class), + new DemoDetails(R.string.save_state_demo_label, + R.string.save_state_demo_description, + SaveStateDemoActivity.class), + new DemoDetails(R.string.snapshot_demo_label, + R.string.snapshot_demo_description, + SnapshotDemoActivity.class), + new DemoDetails(R.string.split_street_view_panorama_and_map_demo_label, + R.string.split_street_view_panorama_and_map_demo_description, + SplitStreetViewPanoramaAndMapDemoActivity.class), + new DemoDetails(R.string.street_view_panorama_basic_demo_label, + R.string.street_view_panorama_basic_demo_description, + StreetViewPanoramaBasicDemoActivity.class), + new DemoDetails(R.string.street_view_panorama_events_demo_label, + R.string.street_view_panorama_events_demo_description, + StreetViewPanoramaEventsDemoActivity.class), + new DemoDetails(R.string.street_view_panorama_navigation_demo_label, + R.string.street_view_panorama_navigation_demo_description, + StreetViewPanoramaNavigationDemoActivity.class), + new DemoDetails(R.string.street_view_panorama_options_demo_label, + R.string.street_view_panorama_options_demo_description, + StreetViewPanoramaOptionsDemoActivity.class), + new DemoDetails(R.string.street_view_panorama_view_demo_label, + R.string.street_view_panorama_view_demo_description, + StreetViewPanoramaViewDemoActivity.class), + new DemoDetails(R.string.styled_map_demo_label, + R.string.styled_map_demo_description, + StyledMapDemoActivity.class), + new DemoDetails(R.string.tags_demo_label, + R.string.tags_demo_description, + TagsDemoActivity.class), + new DemoDetails(R.string.tile_coordinate_demo_label, + R.string.tile_coordinate_demo_description, + TileCoordinateDemoActivity.class), + new DemoDetails(R.string.tile_overlay_demo_label, + R.string.tile_overlay_demo_description, + TileOverlayDemoActivity.class), + new DemoDetails(R.string.ui_settings_demo_label, + R.string.ui_settings_demo_description, + UiSettingsDemoActivity.class), + new DemoDetails(R.string.visible_region_demo_label, + R.string.visible_region_demo_description, + VisibleRegionDemoActivity.class), + }; +} diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/EventsDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/EventsDemoActivity.java new file mode 100644 index 000000000..c484e34de --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/EventsDemoActivity.java @@ -0,0 +1,79 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +package com.example.mapdemo; + +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.GoogleMap.OnCameraIdleListener; +import com.google.android.gms.maps.GoogleMap.OnMapClickListener; +import com.google.android.gms.maps.GoogleMap.OnMapLongClickListener; +import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.model.LatLng; + +import android.os.Bundle; +import android.widget.TextView; + +import androidx.appcompat.app.AppCompatActivity; + +/** + * This shows how to listen to some {@link GoogleMap} events. + */ +// [START maps_android_sample_events] +public class EventsDemoActivity extends AppCompatActivity + implements OnMapClickListener, OnMapLongClickListener, OnCameraIdleListener, + OnMapReadyCallback { + + private TextView tapTextView; + private TextView cameraTextView; + private GoogleMap map; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.events_demo); + + tapTextView = findViewById(R.id.tap_text); + cameraTextView = findViewById(R.id.camera_text); + + SupportMapFragment mapFragment = + (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map); + mapFragment.getMapAsync(this); + } + + @Override + public void onMapReady(GoogleMap map) { + this.map = map; + this.map.setOnMapClickListener(this); + this.map.setOnMapLongClickListener(this); + this.map.setOnCameraIdleListener(this); + } + + @Override + public void onMapClick(LatLng point) { + tapTextView.setText("tapped, point=" + point); + } + + @Override + public void onMapLongClick(LatLng point) { + tapTextView.setText("long pressed, point=" + point); + } + + @Override + public void onCameraIdle() { + cameraTextView.setText(map.getCameraPosition().toString()); + } +} +// [END maps_android_sample_events] \ No newline at end of file diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/FeatureView.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/FeatureView.java new file mode 100755 index 000000000..d24e82e8e --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/FeatureView.java @@ -0,0 +1,57 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +package com.example.mapdemo; + +import android.content.Context; +import android.view.LayoutInflater; +import android.widget.FrameLayout; +import android.widget.TextView; + +/** + * A widget that describes an activity that demonstrates a feature. + */ +public final class FeatureView extends FrameLayout { + + /** + * Constructs a feature view by inflating layout/feature.xml. + */ + public FeatureView(Context context) { + super(context); + + LayoutInflater layoutInflater = + (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + layoutInflater.inflate(R.layout.feature, this); + } + + /** + * Set the resource id of the title of the demo. + * + * @param titleId the resource id of the title of the demo + */ + public synchronized void setTitleId(int titleId) { + ((TextView) (findViewById(R.id.title))).setText(titleId); + } + + /** + * Set the resource id of the description of the demo. + * + * @param descriptionId the resource id of the description of the demo + */ + public synchronized void setDescriptionId(int descriptionId) { + ((TextView) (findViewById(R.id.description))).setText(descriptionId); + } + +} diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/GroundOverlayDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/GroundOverlayDemoActivity.java new file mode 100644 index 000000000..99f8c86cf --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/GroundOverlayDemoActivity.java @@ -0,0 +1,147 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +package com.example.mapdemo; + +import com.google.android.gms.maps.CameraUpdateFactory; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.model.BitmapDescriptor; +import com.google.android.gms.maps.model.BitmapDescriptorFactory; +import com.google.android.gms.maps.model.GroundOverlay; +import com.google.android.gms.maps.model.GroundOverlayOptions; +import com.google.android.gms.maps.model.LatLng; + +import android.os.Bundle; +import android.view.View; +import android.widget.CheckBox; +import android.widget.SeekBar; +import android.widget.SeekBar.OnSeekBarChangeListener; + +import androidx.appcompat.app.AppCompatActivity; + +import java.util.ArrayList; +import java.util.List; + +/** + * This shows how to add a ground overlay to a map. + */ +public class GroundOverlayDemoActivity extends AppCompatActivity + implements OnSeekBarChangeListener, OnMapReadyCallback, + GoogleMap.OnGroundOverlayClickListener { + + private static final int TRANSPARENCY_MAX = 100; + + private static final LatLng NEWARK = new LatLng(40.714086, -74.228697); + + private static final LatLng NEAR_NEWARK = + new LatLng(NEWARK.latitude - 0.001, NEWARK.longitude - 0.025); + + private final List images = new ArrayList(); + + private GroundOverlay groundOverlay; + + private GroundOverlay groundOverlayRotated; + + private SeekBar transparencyBar; + + private int currentEntry = 0; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.ground_overlay_demo); + + transparencyBar = findViewById(R.id.transparencySeekBar); + transparencyBar.setMax(TRANSPARENCY_MAX); + transparencyBar.setProgress(0); + + SupportMapFragment mapFragment = + (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map); + mapFragment.getMapAsync(this); + } + + @Override + public void onMapReady(GoogleMap map) { + // Register a listener to respond to clicks on GroundOverlays. + map.setOnGroundOverlayClickListener(this); + + map.moveCamera(CameraUpdateFactory.newLatLngZoom(NEWARK, 11)); + + images.clear(); + images.add(BitmapDescriptorFactory.fromResource(R.drawable.newark_nj_1922)); + images.add(BitmapDescriptorFactory.fromResource(R.drawable.newark_prudential_sunny)); + + // Add a small, rotated overlay that is clickable by default + // (set by the initial state of the checkbox.) + groundOverlayRotated = map.addGroundOverlay(new GroundOverlayOptions() + .image(images.get(1)).anchor(0, 1) + .position(NEAR_NEWARK, 4300f, 3025f) + .bearing(30) + .clickable(((CheckBox) findViewById(R.id.toggleClickability)).isChecked())); + + // Add a large overlay at Newark on top of the smaller overlay. + groundOverlay = map.addGroundOverlay(new GroundOverlayOptions() + .image(images.get(currentEntry)).anchor(0, 1) + .position(NEWARK, 8600f, 6500f)); + + transparencyBar.setOnSeekBarChangeListener(this); + + // Override the default content description on the view, for accessibility mode. + // Ideally this string would be localised. + map.setContentDescription("Google Map with ground overlay."); + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (groundOverlay != null) { + groundOverlay.setTransparency((float) progress / (float) TRANSPARENCY_MAX); + } + } + + public void switchImage(View view) { + currentEntry = (currentEntry + 1) % images.size(); + groundOverlay.setImage(images.get(currentEntry)); + } + + /** + * Toggles the visibility between 100% and 50% when a {@link GroundOverlay} is clicked. + */ + @Override + public void onGroundOverlayClick(GroundOverlay groundOverlay) { + // Toggle transparency value between 0.0f and 0.5f. Initial default value is 0.0f. + groundOverlayRotated.setTransparency(0.5f - groundOverlayRotated.getTransparency()); + } + + /** + * Toggles the clickability of the smaller, rotated overlay based on the state of the View that + * triggered this call. + * This callback is defined on the CheckBox in the layout for this Activity. + */ + public void toggleClickability(View view) { + if (groundOverlayRotated != null) { + groundOverlayRotated.setClickable(((CheckBox) view).isChecked()); + } + } +} diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/IndoorDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/IndoorDemoActivity.java new file mode 100755 index 000000000..62b078abe --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/IndoorDemoActivity.java @@ -0,0 +1,134 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +package com.example.mapdemo; + +import com.google.android.gms.maps.CameraUpdateFactory; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.model.IndoorBuilding; +import com.google.android.gms.maps.model.IndoorLevel; +import com.google.android.gms.maps.model.LatLng; + +import android.os.Bundle; +import android.view.View; +import android.widget.TextView; + +import androidx.appcompat.app.AppCompatActivity; + +import java.util.List; + +/** + * A demo activity showing how to use indoor. + */ +public class IndoorDemoActivity extends AppCompatActivity implements OnMapReadyCallback { + + private GoogleMap map; + + private boolean showLevelPicker = true; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.indoor_demo); + + SupportMapFragment mapFragment = + (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map); + mapFragment.getMapAsync(this); + } + + @Override + public void onMapReady(GoogleMap googleMap) { + map = googleMap; + map.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(37.614631, -122.385153), 18)); + } + + /** + * Called when the toggle level picker button is clicked. + */ + public void onToggleLevelPicker(View view) { + showLevelPicker = !showLevelPicker; + map.getUiSettings().setIndoorLevelPickerEnabled(showLevelPicker); + } + + /** + * Called when the focused building info is clicked. + */ + public void onFocusedBuildingInfo(View view) { + IndoorBuilding building = map.getFocusedBuilding(); + if (building != null) { + StringBuilder s = new StringBuilder(); + for (IndoorLevel level : building.getLevels()) { + s.append(level.getName()).append(" "); + } + if (building.isUnderground()) { + s.append("is underground"); + } + setText(s.toString()); + } else { + setText("No visible building"); + } + } + + /** + * Called when the focused level info is clicked. + */ + public void onVisibleLevelInfo(View view) { + IndoorBuilding building = map.getFocusedBuilding(); + if (building != null) { + IndoorLevel level = + building.getLevels().get(building.getActiveLevelIndex()); + if (level != null) { + setText(level.getName()); + } else { + setText("No visible level"); + } + } else { + setText("No visible building"); + } + } + + /** + * Called when the activate higher level is clicked. + */ + public void onHigherLevel(View view) { + IndoorBuilding building = map.getFocusedBuilding(); + if (building != null) { + List levels = building.getLevels(); + if (!levels.isEmpty()) { + int currentLevel = building.getActiveLevelIndex(); + // The levels are in 'display order' from top to bottom, + // i.e. higher levels in the building are lower numbered in the array. + int newLevel = currentLevel - 1; + if (newLevel == -1) { + newLevel = levels.size() - 1; + } + IndoorLevel level = levels.get(newLevel); + setText("Activating level " + level.getName()); + level.activate(); + } else { + setText("No levels in building"); + } + } else { + setText("No visible building"); + } + } + + private void setText(String message) { + TextView text = findViewById(R.id.top_text); + text.setText(message); + } +} diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/LayersDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/LayersDemoActivity.java new file mode 100755 index 000000000..60c7b3f7a --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/LayersDemoActivity.java @@ -0,0 +1,248 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +package com.example.mapdemo; + +import android.Manifest.permission; +import android.annotation.SuppressLint; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.SupportMapFragment; + +import android.Manifest; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.ArrayAdapter; +import android.widget.CheckBox; +import android.widget.Spinner; +import android.widget.Toast; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; + +import static com.google.android.gms.maps.GoogleMap.MAP_TYPE_HYBRID; +import static com.google.android.gms.maps.GoogleMap.MAP_TYPE_NONE; +import static com.google.android.gms.maps.GoogleMap.MAP_TYPE_NORMAL; +import static com.google.android.gms.maps.GoogleMap.MAP_TYPE_SATELLITE; +import static com.google.android.gms.maps.GoogleMap.MAP_TYPE_TERRAIN; + +/** + * Demonstrates the different base layers of a map. + */ +public class LayersDemoActivity extends AppCompatActivity + implements OnItemSelectedListener, OnMapReadyCallback, + ActivityCompat.OnRequestPermissionsResultCallback { + + private static final int LOCATION_PERMISSION_REQUEST_CODE = 1; + + private GoogleMap mMap; + + private CheckBox mTrafficCheckbox; + + private CheckBox mMyLocationCheckbox; + + private CheckBox mBuildingsCheckbox; + + private CheckBox mIndoorCheckbox; + + private Spinner mSpinner; + + /** + * Flag indicating whether a requested permission has been denied after returning in {@link + * #onRequestPermissionsResult(int, String[], int[])}. + */ + private boolean mShowPermissionDeniedDialog = false; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.layers_demo); + + mSpinner = findViewById(R.id.layers_spinner); + ArrayAdapter adapter = ArrayAdapter.createFromResource( + this, R.array.layers_array, android.R.layout.simple_spinner_item); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + mSpinner.setAdapter(adapter); + mSpinner.setOnItemSelectedListener(this); + + mTrafficCheckbox = findViewById(R.id.traffic); + mMyLocationCheckbox = findViewById(R.id.my_location); + mBuildingsCheckbox = findViewById(R.id.buildings); + mIndoorCheckbox = findViewById(R.id.indoor); + + SupportMapFragment mapFragment = + (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map); + mapFragment.getMapAsync(this); + } + + @Override + public void onMapReady(GoogleMap map) { + mMap = map; + updateMapType(); + updateTraffic(); + updateMyLocation(); + updateBuildings(); + updateIndoor(); + } + + private boolean checkReady() { + if (mMap == null) { + Toast.makeText(this, R.string.map_not_ready, Toast.LENGTH_SHORT).show(); + return false; + } + return true; + } + + /** + * Called when the Traffic checkbox is clicked. + */ + public void onTrafficToggled(View view) { + updateTraffic(); + } + + private void updateTraffic() { + if (!checkReady()) { + return; + } + mMap.setTrafficEnabled(mTrafficCheckbox.isChecked()); + } + + /** + * Called when the MyLocation checkbox is clicked. + */ + public void onMyLocationToggled(View view) { + updateMyLocation(); + } + + @SuppressLint("MissingPermission") + private void updateMyLocation() { + if (!checkReady()) { + return; + } + + if (!mMyLocationCheckbox.isChecked()) { + mMap.setMyLocationEnabled(false); + return; + } + + // Enable the location layer. Request the location permission if needed. + if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) + == PackageManager.PERMISSION_GRANTED + || ContextCompat.checkSelfPermission(this, permission.ACCESS_COARSE_LOCATION) + == PackageManager.PERMISSION_GRANTED) { + mMap.setMyLocationEnabled(true); + } else { + // Uncheck the box until the layer has been enabled and request missing permission. + mMyLocationCheckbox.setChecked(false); + PermissionUtils + .requestLocationPermissions(this, LOCATION_PERMISSION_REQUEST_CODE,false); + } + } + + @SuppressLint("MissingPermission") + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] results) { + if (requestCode != LOCATION_PERMISSION_REQUEST_CODE) { + super.onRequestPermissionsResult(requestCode, permissions, results); + return; + } + + if (PermissionUtils.isPermissionGranted(permissions, results, + permission.ACCESS_FINE_LOCATION)) { + mMap.setMyLocationEnabled(true); + mMyLocationCheckbox.setChecked(true); + } else { + mShowPermissionDeniedDialog = true; + } + } + + @Override + protected void onResumeFragments() { + super.onResumeFragments(); + if (mShowPermissionDeniedDialog) { + PermissionUtils.PermissionDeniedDialog + .newInstance(false).show(getSupportFragmentManager(), "dialog"); + mShowPermissionDeniedDialog = false; + } + } + + /** + * Called when the Buildings checkbox is clicked. + */ + public void onBuildingsToggled(View view) { + updateBuildings(); + } + + private void updateBuildings() { + if (!checkReady()) { + return; + } + mMap.setBuildingsEnabled(mBuildingsCheckbox.isChecked()); + } + + /** + * Called when the Indoor checkbox is clicked. + */ + public void onIndoorToggled(View view) { + updateIndoor(); + } + + private void updateIndoor() { + if (!checkReady()) { + return; + } + mMap.setIndoorEnabled(mIndoorCheckbox.isChecked()); + } + + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + updateMapType(); + } + + private void updateMapType() { + // No toast because this can also be called by the Android framework in onResume() at which + // point mMap may not be ready yet. + if (mMap == null) { + return; + } + + String layerName = ((String) mSpinner.getSelectedItem()); + if (layerName.equals(getString(R.string.normal))) { + mMap.setMapType(MAP_TYPE_NORMAL); + } else if (layerName.equals(getString(R.string.hybrid))) { + mMap.setMapType(MAP_TYPE_HYBRID); + + + } else if (layerName.equals(getString(R.string.satellite))) { + mMap.setMapType(MAP_TYPE_SATELLITE); + } else if (layerName.equals(getString(R.string.terrain))) { + mMap.setMapType(MAP_TYPE_TERRAIN); + } else if (layerName.equals(getString(R.string.none_map))) { + mMap.setMapType(MAP_TYPE_NONE); + } else { + Log.i("LDA", "Error setting layer with name " + layerName); + } + } + + @Override + public void onNothingSelected(AdapterView parent) { + // Do nothing. + } +} diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/LiteDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/LiteDemoActivity.java new file mode 100755 index 000000000..ec914142f --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/LiteDemoActivity.java @@ -0,0 +1,185 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +package com.example.mapdemo; + +import com.google.android.gms.maps.CameraUpdateFactory; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.model.BitmapDescriptorFactory; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.LatLngBounds; +import com.google.android.gms.maps.model.MarkerOptions; +import com.google.android.gms.maps.model.PolygonOptions; +import com.google.android.gms.maps.model.PolylineOptions; + +import android.graphics.Color; +import android.os.Bundle; +import android.view.View; + +import androidx.appcompat.app.AppCompatActivity; + +/** + * This demo shows some features supported in lite mode. + * In particular it demonstrates the use of {@link com.google.android.gms.maps.model.Marker}s to + * launch the Google Maps Mobile application, {@link com.google.android.gms.maps.CameraUpdate}s + * and {@link com.google.android.gms.maps.model.Polygon}s. + */ +public class LiteDemoActivity extends AppCompatActivity implements + OnMapAndViewReadyListener.OnGlobalLayoutAndMapReadyListener { + + private static final LatLng BRISBANE = new LatLng(-27.47093, 153.0235); + + private static final LatLng MELBOURNE = new LatLng(-37.81319, 144.96298); + + private static final LatLng SYDNEY = new LatLng(-33.87365, 151.20689); + + private static final LatLng ADELAIDE = new LatLng(-34.92873, 138.59995); + + private static final LatLng PERTH = new LatLng(-31.952854, 115.857342); + + private static final LatLng DARWIN = new LatLng(-12.459501, 130.839915); + + /** + * A Polygon with five points in the Norther Territory, Australia. + */ + private static final LatLng[] POLYGON = new LatLng[]{ + new LatLng(-18.000328, 130.473633), new LatLng(-16.173880, 135.087891), + new LatLng(-19.663970, 137.724609), new LatLng(-23.202307, 135.395508), + new LatLng(-19.705347, 129.550781)}; + + private GoogleMap map; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Set the layout + setContentView(R.layout.lite_demo); + + // Get the map and register for the ready callback + SupportMapFragment mapFragment = + (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map); + new OnMapAndViewReadyListener(mapFragment, this); + } + + /** + * Move the camera to center on Darwin. + */ + public void showDarwin(View v) { + // Wait until map is ready + if (map == null) { + return; + } + + // Center camera on Adelaide marker + map.moveCamera(CameraUpdateFactory.newLatLngZoom(DARWIN, 10f)); + } + + /** + * Move the camera to center on Adelaide. + */ + public void showAdelaide(View v) { + // Wait until map is ready + if (map == null) { + return; + } + + // Center camera on Adelaide marker + map.moveCamera(CameraUpdateFactory.newLatLngZoom(ADELAIDE, 10f)); + } + + /** + * Move the camera to show all of Australia. + * Construct a {@link com.google.android.gms.maps.model.LatLngBounds} from markers positions, + * then move the camera. + */ + public void showAustralia(View v) { + // Wait until map is ready + if (map == null) { + return; + } + + // Create bounds that include all locations of the map + LatLngBounds.Builder boundsBuilder = LatLngBounds.builder() + .include(PERTH) + .include(ADELAIDE) + .include(MELBOURNE) + .include(SYDNEY) + .include(DARWIN) + .include(BRISBANE); + + // Move camera to show all markers and locations + map.moveCamera(CameraUpdateFactory.newLatLngBounds(boundsBuilder.build(), 20)); + } + + /** + * Called when the map is ready to add all markers and objects to the map. + */ + @Override + public void onMapReady(GoogleMap googleMap) { + map = googleMap; + addMarkers(); + addPolyObjects(); + showAustralia(null); + } + + /** + * Add a Polyline and a Polygon to the map. + * The Polyline connects Melbourne, Adelaide and Perth. The Polygon is located in the Northern + * Territory (Australia). + */ + private void addPolyObjects() { + map.addPolyline((new PolylineOptions()) + .add(MELBOURNE, ADELAIDE, PERTH) + .color(Color.GREEN) + .width(5f)); + + map.addPolygon(new PolygonOptions() + .add(POLYGON) + .fillColor(Color.CYAN) + .strokeColor(Color.BLUE) + .strokeWidth(5)); + } + + /** + * Add Markers with default info windows to the map. + */ + private void addMarkers() { + map.addMarker(new MarkerOptions() + .position(BRISBANE) + .title("Brisbane")); + + map.addMarker(new MarkerOptions() + .position(MELBOURNE) + .title("Melbourne") + .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE))); + + map.addMarker(new MarkerOptions() + .position(SYDNEY) + .title("Sydney") + .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED))); + + map.addMarker(new MarkerOptions() + .position(ADELAIDE) + .title("Adelaide") + .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_YELLOW))); + + map.addMarker(new MarkerOptions() + .position(PERTH) + .title("Perth") + .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_MAGENTA))); + } +} diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/LiteListDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/LiteListDemoActivity.java new file mode 100755 index 000000000..28cfd3c3c --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/LiteListDemoActivity.java @@ -0,0 +1,266 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +package com.example.mapdemo; + +import com.google.android.gms.maps.CameraUpdateFactory; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.MapView; +import com.google.android.gms.maps.MapsInitializer; +import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.MarkerOptions; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +/** + * This shows to include a map in lite mode in a ListView. + * Note the use of the view holder pattern with the + * {@link com.google.android.gms.maps.OnMapReadyCallback}. + */ +public class LiteListDemoActivity extends AppCompatActivity { + + private RecyclerView mRecyclerView; + + private LinearLayoutManager mLinearLayoutManager; + private GridLayoutManager mGridLayoutManager; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.lite_list_demo); + + mGridLayoutManager = new GridLayoutManager(this, 2); + mLinearLayoutManager = new LinearLayoutManager(this); + + // Set up the RecyclerView + mRecyclerView = findViewById(R.id.recycler_view); + mRecyclerView.setHasFixedSize(true); + mRecyclerView.setLayoutManager(mLinearLayoutManager); + mRecyclerView.setAdapter(new MapAdapter(LIST_LOCATIONS)); + mRecyclerView.setRecyclerListener(mRecycleListener); + } + + /** Create a menu to switch between Linear and Grid LayoutManager. */ + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.lite_list_menu, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + if (id == R.id.layout_linear) { + mRecyclerView.setLayoutManager(mLinearLayoutManager); + } else if (id == R.id.layout_grid) { + mRecyclerView.setLayoutManager(mGridLayoutManager); + } + return true; + } + + /** + * Adapter that displays a title and {@link com.google.android.gms.maps.MapView} for each item. + * The layout is defined in lite_list_demo_row.xml. It contains a MapView + * that is programatically initialised in + * {@link #(int, android.view.View, android.view.ViewGroup)} + */ + private class MapAdapter extends RecyclerView.Adapter { + + private NamedLocation[] namedLocations; + + private MapAdapter(NamedLocation[] locations) { + super(); + namedLocations = locations; + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new ViewHolder(LayoutInflater.from(parent.getContext()) + .inflate(R.layout.lite_list_demo_row, parent, false)); + } + + /** + * This function is called when the user scrolls through the screen and a new item needs + * to be shown. So we will need to bind the holder with the details of the next item. + */ + @Override + public void onBindViewHolder(ViewHolder holder, int position) { + if (holder == null) { + return; + } + holder.bindView(position); + } + + @Override + public int getItemCount() { + return namedLocations.length; + } + + /** + * Holder for Views used in the {@link LiteListDemoActivity.MapAdapter}. + * Once the the map field is set, otherwise it is null. + * When the {@link #onMapReady(com.google.android.gms.maps.GoogleMap)} callback is received and + * the {@link com.google.android.gms.maps.GoogleMap} is ready, it stored in the {@link #map} + * field. The map is then initialised with the NamedLocation that is stored as the tag of the + * MapView. This ensures that the map is initialised with the latest data that it should + * display. + */ + class ViewHolder extends RecyclerView.ViewHolder implements OnMapReadyCallback { + + MapView mapView; + TextView title; + GoogleMap map; + View layout; + + private ViewHolder(View itemView) { + super(itemView); + layout = itemView; + mapView = layout.findViewById(R.id.lite_listrow_map); + title = layout.findViewById(R.id.lite_listrow_text); + if (mapView != null) { + // Initialise the MapView + mapView.onCreate(null); + // Set the map ready callback to receive the GoogleMap object + mapView.getMapAsync(this); + } + } + + @Override + public void onMapReady(GoogleMap googleMap) { + MapsInitializer.initialize(getApplicationContext()); + map = googleMap; + setMapLocation(); + } + + /** + * Displays a {@link LiteListDemoActivity.NamedLocation} on a + * {@link com.google.android.gms.maps.GoogleMap}. + * Adds a marker and centers the camera on the NamedLocation with the normal map type. + */ + private void setMapLocation() { + if (map == null) return; + + NamedLocation data = (NamedLocation) mapView.getTag(); + if (data == null) return; + + // Add a marker for this item and set the camera + map.moveCamera(CameraUpdateFactory.newLatLngZoom(data.location, 13f)); + map.addMarker(new MarkerOptions().position(data.location)); + + // Set the map type back to normal. + map.setMapType(GoogleMap.MAP_TYPE_NORMAL); + } + + private void bindView(int pos) { + NamedLocation item = namedLocations[pos]; + // Store a reference of the ViewHolder object in the layout. + layout.setTag(this); + // Store a reference to the item in the mapView's tag. We use it to get the + // coordinate of a location, when setting the map location. + mapView.setTag(item); + setMapLocation(); + title.setText(item.name); + } + } + } + + /** + * RecycleListener that completely clears the {@link com.google.android.gms.maps.GoogleMap} + * attached to a row in the RecyclerView. + * Sets the map type to {@link com.google.android.gms.maps.GoogleMap#MAP_TYPE_NONE} and clears + * the map. + */ + private RecyclerView.RecyclerListener mRecycleListener = new RecyclerView.RecyclerListener() { + + @Override + public void onViewRecycled(RecyclerView.ViewHolder holder) { + MapAdapter.ViewHolder mapHolder = (MapAdapter.ViewHolder) holder; + if (mapHolder != null && mapHolder.map != null) { + // Clear the map and free up resources by changing the map type to none. + // Also reset the map when it gets reattached to layout, so the previous map would + // not be displayed. + mapHolder.map.clear(); + mapHolder.map.setMapType(GoogleMap.MAP_TYPE_NONE); + } + } + }; + + /** + * Location represented by a position ({@link com.google.android.gms.maps.model.LatLng} and a + * name ({@link java.lang.String}). + */ + private static class NamedLocation { + + public final String name; + public final LatLng location; + + NamedLocation(String name, LatLng location) { + this.name = name; + this.location = location; + } + } + + /** + * A list of locations to show in this ListView. + */ + private static final NamedLocation[] LIST_LOCATIONS = new NamedLocation[]{ + new NamedLocation("Cape Town", new LatLng(-33.920455, 18.466941)), + new NamedLocation("Beijing", new LatLng(39.937795, 116.387224)), + new NamedLocation("Bern", new LatLng(46.948020, 7.448206)), + new NamedLocation("Breda", new LatLng(51.589256, 4.774396)), + new NamedLocation("Brussels", new LatLng(50.854509, 4.376678)), + new NamedLocation("Copenhagen", new LatLng(55.679423, 12.577114)), + new NamedLocation("Hannover", new LatLng(52.372026, 9.735672)), + new NamedLocation("Helsinki", new LatLng(60.169653, 24.939480)), + new NamedLocation("Hong Kong", new LatLng(22.325862, 114.165532)), + new NamedLocation("Istanbul", new LatLng(41.034435, 28.977556)), + new NamedLocation("Johannesburg", new LatLng(-26.202886, 28.039753)), + new NamedLocation("Lisbon", new LatLng(38.707163, -9.135517)), + new NamedLocation("London", new LatLng(51.500208, -0.126729)), + new NamedLocation("Madrid", new LatLng(40.420006, -3.709924)), + new NamedLocation("Mexico City", new LatLng(19.427050, -99.127571)), + new NamedLocation("Moscow", new LatLng(55.750449, 37.621136)), + new NamedLocation("New York", new LatLng(40.750580, -73.993584)), + new NamedLocation("Oslo", new LatLng(59.910761, 10.749092)), + new NamedLocation("Paris", new LatLng(48.859972, 2.340260)), + new NamedLocation("Prague", new LatLng(50.087811, 14.420460)), + new NamedLocation("Rio de Janeiro", new LatLng(-22.90187, -43.232437)), + new NamedLocation("Rome", new LatLng(41.889998, 12.500162)), + new NamedLocation("Sao Paolo", new LatLng(-22.863878, -43.244097)), + new NamedLocation("Seoul", new LatLng(37.560908, 126.987705)), + new NamedLocation("Stockholm", new LatLng(59.330650, 18.067360)), + new NamedLocation("Sydney", new LatLng(-33.873651, 151.2068896)), + new NamedLocation("Taipei", new LatLng(25.022112, 121.478019)), + new NamedLocation("Tokyo", new LatLng(35.670267, 139.769955)), + new NamedLocation("Tulsa Oklahoma", new LatLng(36.149777, -95.993398)), + new NamedLocation("Vaduz", new LatLng(47.141076, 9.521482)), + new NamedLocation("Vienna", new LatLng(48.209206, 16.372778)), + new NamedLocation("Warsaw", new LatLng(52.235474, 21.004057)), + new NamedLocation("Wellington", new LatLng(-41.286480, 174.776217)), + new NamedLocation("Winnipeg", new LatLng(49.875832, -97.150726)) + }; + +} diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/LocationSourceDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/LocationSourceDemoActivity.java new file mode 100644 index 000000000..6986f9476 --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/LocationSourceDemoActivity.java @@ -0,0 +1,126 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +package com.example.mapdemo; + +import android.Manifest.permission; +import android.annotation.SuppressLint; +import android.content.pm.PackageManager; +import androidx.core.app.ActivityCompat; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.GoogleMap.OnMapLongClickListener; +import com.google.android.gms.maps.LocationSource; +import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.model.LatLng; + +import android.location.Location; +import android.os.Bundle; + +import androidx.appcompat.app.AppCompatActivity; + +/** + * This shows how to use a custom location source. + */ +public class LocationSourceDemoActivity extends AppCompatActivity implements OnMapReadyCallback { + + /** + * A {@link LocationSource} which reports a new location whenever a user long presses the map + * at + * the point at which a user long pressed the map. + */ + private static class LongPressLocationSource implements LocationSource, OnMapLongClickListener { + + private OnLocationChangedListener mListener; + + /** + * Flag to keep track of the activity's lifecycle. This is not strictly necessary in this + * case because onMapLongPress events don't occur while the activity containing the map is + * paused but is included to demonstrate best practices (e.g., if a background service were + * to be used). + */ + private boolean mPaused; + + @Override + public void activate(OnLocationChangedListener listener) { + mListener = listener; + } + + @Override + public void deactivate() { + mListener = null; + } + + @Override + public void onMapLongClick(LatLng point) { + if (mListener != null && !mPaused) { + Location location = new Location("LongPressLocationProvider"); + location.setLatitude(point.latitude); + location.setLongitude(point.longitude); + location.setAccuracy(100); + mListener.onLocationChanged(location); + } + } + + public void onPause() { + mPaused = true; + } + + public void onResume() { + mPaused = false; + } + } + + private LongPressLocationSource mLocationSource; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.basic_demo); + + mLocationSource = new LongPressLocationSource(); + + SupportMapFragment mapFragment = + (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map); + mapFragment.getMapAsync(this); + } + + @Override + protected void onResume() { + super.onResume(); + mLocationSource.onResume(); + } + + @Override + protected void onPause() { + super.onPause(); + mLocationSource.onPause(); + } + + @SuppressLint("MissingPermission") + @Override + public void onMapReady(GoogleMap map) { + map.setLocationSource(mLocationSource); + map.setOnMapLongClickListener(mLocationSource); + + if (ActivityCompat.checkSelfPermission(this, permission.ACCESS_FINE_LOCATION) + != PackageManager.PERMISSION_GRANTED + && ActivityCompat.checkSelfPermission(this, permission.ACCESS_COARSE_LOCATION) + != PackageManager.PERMISSION_GRANTED) { + return; + } + map.setMyLocationEnabled(true); + } +} diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/MainActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/MainActivity.java new file mode 100644 index 000000000..25be799fd --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/MainActivity.java @@ -0,0 +1,90 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.mapdemo; + +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.os.Bundle; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ListAdapter; +import android.widget.ListView; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; + +/** + * The main activity of the API library demo gallery. + * + *

The main layout lists the demonstrated features, with buttons to launch them. + */ +public final class MainActivity extends AppCompatActivity { + + private static final String TAG = MainActivity.class.getSimpleName(); + + /** A custom array adapter that shows a {@link FeatureView} containing details about the demo. */ + private static class CustomArrayAdapter extends ArrayAdapter { + + /** @param demos An array containing the details of the demos to be displayed. */ + public CustomArrayAdapter(Context context, DemoDetails[] demos) { + super(context, R.layout.feature, R.id.title, demos); + } + + @NonNull + @Override + public View getView(int position, View convertView, @NonNull ViewGroup parent) { + FeatureView featureView; + if (convertView instanceof FeatureView) { + featureView = (FeatureView) convertView; + } else { + featureView = new FeatureView(getContext()); + } + + DemoDetails demo = getItem(position); + + featureView.setTitleId(demo.titleId); + featureView.setDescriptionId(demo.descriptionId); + + Resources resources = getContext().getResources(); + String title = resources.getString(demo.titleId); + String description = resources.getString(demo.descriptionId); + featureView.setContentDescription(title + ". " + description); + + return featureView; + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main); + + ListAdapter adapter = new CustomArrayAdapter(this, DemoDetailsList.DEMOS); + + ListView demoListView = findViewById(R.id.list); + if (demoListView != null) { + demoListView.setAdapter(adapter); + demoListView.setOnItemClickListener( + (parent, view, position, id) -> { + DemoDetails demo = (DemoDetails) parent.getItemAtPosition(position); + startActivity(new Intent(view.getContext(), demo.activityClass)); + }); + } + } +} diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/MapColorSchemeActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/MapColorSchemeActivity.java new file mode 100755 index 000000000..7b9fa92e6 --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/MapColorSchemeActivity.java @@ -0,0 +1,60 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.mapdemo; + +import android.os.Bundle; +import android.widget.Button; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; + +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.model.MapColorScheme; + +public class MapColorSchemeActivity extends AppCompatActivity implements OnMapReadyCallback { + + private Button buttonLight; + private Button buttonDark; + private Button buttonFollowSystem; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.map_color_scheme_demo); + SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map); + if (mapFragment != null) { + mapFragment.getMapAsync(this); + } + buttonLight = findViewById(R.id.map_color_light_mode); + buttonDark = findViewById(R.id.map_color_dark_mode); + buttonFollowSystem = findViewById(R.id.map_color_follow_system_mode); + } + + @Override + public void onMapReady(@NonNull GoogleMap googleMap) { + + googleMap.setMapColorScheme(MapColorScheme.DARK); + + buttonLight.setOnClickListener(view -> googleMap.setMapColorScheme(MapColorScheme.LIGHT)); + + buttonDark.setOnClickListener(view -> googleMap.setMapColorScheme(MapColorScheme.DARK)); + + buttonFollowSystem.setOnClickListener(view -> googleMap.setMapColorScheme(MapColorScheme.FOLLOW_SYSTEM)); + } +} diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/MapInPagerDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/MapInPagerDemoActivity.java new file mode 100755 index 000000000..db3365cee --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/MapInPagerDemoActivity.java @@ -0,0 +1,85 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +package com.example.mapdemo; + +import com.google.android.gms.maps.SupportMapFragment; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentPagerAdapter; +import androidx.viewpager.widget.ViewPager; +/** + * This shows how to add a map to a ViewPager. Note the use of + * {@link ViewGroup#requestTransparentRegion(View)} to reduce jankiness. + */ +public class MapInPagerDemoActivity extends AppCompatActivity { + + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.map_in_pager_demo); + MyAdapter adapter = new MyAdapter(getSupportFragmentManager()); + + ViewPager pager = findViewById(R.id.pager); + pager.setAdapter(adapter); + + // This is required to avoid a black flash when the map is loaded. The flash is due + // to the use of a SurfaceView as the underlying view of the map. + pager.requestTransparentRegion(pager); + } + + /** A simple fragment that displays a TextView. */ + public static class TextFragment extends Fragment { + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) { + return inflater.inflate(R.layout.text_fragment, container, false); + } + } + + /** A simple FragmentPagerAdapter that returns two TextFragment and a SupportMapFragment. */ + public static class MyAdapter extends FragmentPagerAdapter { + + public MyAdapter(FragmentManager fm) { + super(fm, FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); + } + + @Override + public int getCount() { + return 3; + } + + @Override + public Fragment getItem(int position) { + switch (position) { + case 0: + case 1: + return new TextFragment(); + case 2: + return SupportMapFragment.newInstance(); + default: + return null; + } + } + } +} diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/MarkerCloseInfoWindowOnRetapDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/MarkerCloseInfoWindowOnRetapDemoActivity.java new file mode 100644 index 000000000..7177f39b7 --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/MarkerCloseInfoWindowOnRetapDemoActivity.java @@ -0,0 +1,147 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +package com.example.mapdemo; + +import com.google.android.gms.maps.CameraUpdateFactory; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.GoogleMap.OnMapClickListener; +import com.google.android.gms.maps.GoogleMap.OnMarkerClickListener; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.LatLngBounds; +import com.google.android.gms.maps.model.Marker; +import com.google.android.gms.maps.model.MarkerOptions; + +import android.os.Bundle; + +import androidx.appcompat.app.AppCompatActivity; + +/** + * This shows how to close the info window when the currently selected marker is re-tapped. + */ +public class MarkerCloseInfoWindowOnRetapDemoActivity extends AppCompatActivity implements + OnMarkerClickListener, + OnMapClickListener, + OnMapAndViewReadyListener.OnGlobalLayoutAndMapReadyListener { + + private static final LatLng BRISBANE = new LatLng(-27.47093, 153.0235); + private static final LatLng MELBOURNE = new LatLng(-37.81319, 144.96298); + private static final LatLng SYDNEY = new LatLng(-33.87365, 151.20689); + private static final LatLng ADELAIDE = new LatLng(-34.92873, 138.59995); + private static final LatLng PERTH = new LatLng(-31.952854, 115.857342); + + private GoogleMap mMap = null; + + /** + * Keeps track of the selected marker. + */ + private Marker mSelectedMarker; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.marker_close_info_window_on_retap_demo); + + SupportMapFragment mapFragment = + (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map); + new OnMapAndViewReadyListener(mapFragment, this); + } + + @Override + public void onMapReady(GoogleMap map) { + mMap = map; + + // Hide the zoom controls. + mMap.getUiSettings().setZoomControlsEnabled(false); + + // Add lots of markers to the map. + addMarkersToMap(); + + // Set listener for marker click event. See the bottom of this class for its behavior. + mMap.setOnMarkerClickListener(this); + + // Set listener for map click event. See the bottom of this class for its behavior. + mMap.setOnMapClickListener(this); + + // Override the default content description on the view, for accessibility mode. + // Ideally this string would be localized. + map.setContentDescription("Demo showing how to close the info window when the currently" + + " selected marker is re-tapped."); + + LatLngBounds bounds = new LatLngBounds.Builder() + .include(PERTH) + .include(SYDNEY) + .include(ADELAIDE) + .include(BRISBANE) + .include(MELBOURNE) + .build(); + mMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 50)); + } + + private void addMarkersToMap() { + mMap.addMarker(new MarkerOptions() + .position(BRISBANE) + .title("Brisbane") + .snippet("Population: 2,074,200")); + + mMap.addMarker(new MarkerOptions() + .position(SYDNEY) + .title("Sydney") + .snippet("Population: 4,627,300")); + + mMap.addMarker(new MarkerOptions() + .position(MELBOURNE) + .title("Melbourne") + .snippet("Population: 4,137,400")); + + mMap.addMarker(new MarkerOptions() + .position(PERTH) + .title("Perth") + .snippet("Population: 1,738,800")); + + mMap.addMarker(new MarkerOptions() + .position(ADELAIDE) + .title("Adelaide") + .snippet("Population: 1,213,000")); + } + + @Override + public void onMapClick(final LatLng point) { + // Any showing info window closes when the map is clicked. + // Clear the currently selected marker. + mSelectedMarker = null; + } + + @Override + public boolean onMarkerClick(final Marker marker) { + // The user has re-tapped on the marker which was already showing an info window. + if (marker.equals(mSelectedMarker)) { + // The showing info window has already been closed - that's the first thing to happen + // when any marker is clicked. + // Return true to indicate we have consumed the event and that we do not want the + // the default behavior to occur (which is for the camera to move such that the + // marker is centered and for the marker's info window to open, if it has one). + mSelectedMarker = null; + return true; + } + + mSelectedMarker = marker; + + // Return false to indicate that we have not consumed the event and that we wish + // for the default behavior to occur. + return false; + } +} diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/MarkerDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/MarkerDemoActivity.java new file mode 100644 index 000000000..2d0e5a55a --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/MarkerDemoActivity.java @@ -0,0 +1,509 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.example.mapdemo; + +import com.google.android.gms.maps.CameraUpdateFactory; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.GoogleMap.InfoWindowAdapter; +import com.google.android.gms.maps.GoogleMap.OnInfoWindowClickListener; +import com.google.android.gms.maps.GoogleMap.OnInfoWindowCloseListener; +import com.google.android.gms.maps.GoogleMap.OnInfoWindowLongClickListener; +import com.google.android.gms.maps.GoogleMap.OnMarkerClickListener; +import com.google.android.gms.maps.GoogleMap.OnMarkerDragListener; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.model.BitmapDescriptor; +import com.google.android.gms.maps.model.BitmapDescriptorFactory; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.LatLngBounds; +import com.google.android.gms.maps.model.Marker; +import com.google.android.gms.maps.model.MarkerOptions; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.Handler; +import android.os.SystemClock; + +import androidx.annotation.ColorInt; +import androidx.annotation.DrawableRes; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.content.res.ResourcesCompat; +import androidx.core.graphics.drawable.DrawableCompat; +import android.text.SpannableString; +import android.text.style.ForegroundColorSpan; +import android.view.View; +import android.view.animation.BounceInterpolator; +import android.view.animation.Interpolator; +import android.widget.CheckBox; +import android.widget.ImageView; +import android.widget.RadioGroup; +import android.widget.RadioGroup.OnCheckedChangeListener; +import android.widget.SeekBar; +import android.widget.SeekBar.OnSeekBarChangeListener; +import android.widget.TextView; +import android.widget.Toast; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +/** + * This shows how to place markers on a map. + */ +// [START maps_android_sample_marker] +public class MarkerDemoActivity extends AppCompatActivity implements + OnMarkerClickListener, + OnInfoWindowClickListener, + OnMarkerDragListener, + OnSeekBarChangeListener, + OnInfoWindowLongClickListener, + OnInfoWindowCloseListener, + OnMapAndViewReadyListener.OnGlobalLayoutAndMapReadyListener { + + private static final LatLng BRISBANE = new LatLng(-27.47093, 153.0235); + + private static final LatLng MELBOURNE = new LatLng(-37.81319, 144.96298); + + private static final LatLng DARWIN = new LatLng(-12.4634, 130.8456); + + private static final LatLng SYDNEY = new LatLng(-33.87365, 151.20689); + + private static final LatLng ADELAIDE = new LatLng(-34.92873, 138.59995); + + private static final LatLng PERTH = new LatLng(-31.952854, 115.857342); + + private static final LatLng ALICE_SPRINGS = new LatLng(-24.6980, 133.8807); + + /** Demonstrates customizing the info window and/or its contents. */ + class CustomInfoWindowAdapter implements InfoWindowAdapter { + + // These are both viewgroups containing an ImageView with id "badge" and two TextViews with id + // "title" and "snippet". + private final View mWindow; + + private final View mContents; + + CustomInfoWindowAdapter() { + mWindow = getLayoutInflater().inflate(R.layout.custom_info_window, null); + mContents = getLayoutInflater().inflate(R.layout.custom_info_contents, null); + } + + @Override + public View getInfoWindow(Marker marker) { + if (mOptions.getCheckedRadioButtonId() != R.id.custom_info_window) { + // This means that getInfoContents will be called. + return null; + } + render(marker, mWindow); + return mWindow; + } + + @Override + public View getInfoContents(Marker marker) { + if (mOptions.getCheckedRadioButtonId() != R.id.custom_info_contents) { + // This means that the default info contents will be used. + return null; + } + render(marker, mContents); + return mContents; + } + + private void render(Marker marker, View view) { + int badge; + // Use the equals() method on a Marker to check for equals. Do not use ==. + if (marker.equals(mBrisbane)) { + badge = R.drawable.badge_qld; + } else if (marker.equals(mAdelaide)) { + badge = R.drawable.badge_sa; + } else if (marker.equals(mSydney)) { + badge = R.drawable.badge_nsw; + } else if (marker.equals(mMelbourne)) { + badge = R.drawable.badge_victoria; + } else if (marker.equals(mPerth)) { + badge = R.drawable.badge_wa; + } else if (marker.equals(mDarwin1)) { + badge = R.drawable.badge_nt; + } else if (marker.equals(mDarwin2)) { + badge = R.drawable.badge_nt; + } else if (marker.equals(mDarwin3)) { + badge = R.drawable.badge_nt; + } else if (marker.equals(mDarwin4)) { + badge = R.drawable.badge_nt; + } else { + // Passing 0 to setImageResource will clear the image view. + badge = 0; + } + ((ImageView) view.findViewById(R.id.badge)).setImageResource(badge); + + String title = marker.getTitle(); + TextView titleUi = ((TextView) view.findViewById(R.id.title)); + if (title != null) { + // Spannable string allows us to edit the formatting of the text. + SpannableString titleText = new SpannableString(title); + titleText.setSpan(new ForegroundColorSpan(Color.RED), 0, titleText.length(), 0); + titleUi.setText(titleText); + } else { + titleUi.setText(""); + } + + String snippet = marker.getSnippet(); + TextView snippetUi = ((TextView) view.findViewById(R.id.snippet)); + if (snippet != null && snippet.length() > 12) { + SpannableString snippetText = new SpannableString(snippet); + snippetText.setSpan(new ForegroundColorSpan(Color.MAGENTA), 0, 10, 0); + snippetText.setSpan(new ForegroundColorSpan(Color.BLUE), 12, snippet.length(), 0); + snippetUi.setText(snippetText); + } else { + snippetUi.setText(""); + } + } + } + + private GoogleMap mMap; + + private Marker mPerth; + + private Marker mSydney; + + private Marker mBrisbane; + + private Marker mAdelaide; + + private Marker mMelbourne; + + private Marker mDarwin1; + private Marker mDarwin2; + private Marker mDarwin3; + private Marker mDarwin4; + + + /** + * Keeps track of the last selected marker (though it may no longer be selected). This is + * useful for refreshing the info window. + */ + private Marker mLastSelectedMarker; + + private final List mMarkerRainbow = new ArrayList(); + + private TextView mTopText; + + private SeekBar mRotationBar; + + private CheckBox mFlatBox; + + private RadioGroup mOptions; + + private final Random mRandom = new Random(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.marker_demo); + + mTopText = (TextView) findViewById(R.id.top_text); + + mRotationBar = (SeekBar) findViewById(R.id.rotationSeekBar); + mRotationBar.setMax(360); + mRotationBar.setOnSeekBarChangeListener(this); + + mFlatBox = (CheckBox) findViewById(R.id.flat); + + mOptions = (RadioGroup) findViewById(R.id.custom_info_window_options); + mOptions.setOnCheckedChangeListener(new OnCheckedChangeListener() { + @Override + public void onCheckedChanged(RadioGroup group, int checkedId) { + if (mLastSelectedMarker != null && mLastSelectedMarker.isInfoWindowShown()) { + // Refresh the info window when the info window's content has changed. + mLastSelectedMarker.showInfoWindow(); + } + } + }); + + SupportMapFragment mapFragment = + (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map); + new OnMapAndViewReadyListener(mapFragment, this); + } + + @Override + public void onMapReady(GoogleMap map) { + mMap = map; + + // Hide the zoom controls as the button panel will cover it. + mMap.getUiSettings().setZoomControlsEnabled(false); + + // Add lots of markers to the map. + addMarkersToMap(); + + // Setting an info window adapter allows us to change the both the contents and look of the + // info window. + mMap.setInfoWindowAdapter(new CustomInfoWindowAdapter()); + + // Set listeners for marker events. See the bottom of this class for their behavior. + mMap.setOnMarkerClickListener(this); + mMap.setOnInfoWindowClickListener(this); + mMap.setOnMarkerDragListener(this); + mMap.setOnInfoWindowCloseListener(this); + mMap.setOnInfoWindowLongClickListener(this); + + // Override the default content description on the view, for accessibility mode. + // Ideally this string would be localised. + mMap.setContentDescription("Map with lots of markers."); + + LatLngBounds bounds = new LatLngBounds.Builder() + .include(PERTH) + .include(SYDNEY) + .include(ADELAIDE) + .include(BRISBANE) + .include(MELBOURNE) + .include(DARWIN) + .build(); + mMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 50)); + } + + private void addMarkersToMap() { + // Uses a colored icon. + mBrisbane = mMap.addMarker(new MarkerOptions() + .position(BRISBANE) + .title("Brisbane") + .snippet("Population: 2,074,200") + .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE))); + + // Uses a custom icon with the info window popping out of the center of the icon. + mSydney = mMap.addMarker(new MarkerOptions() + .position(SYDNEY) + .title("Sydney") + .snippet("Population: 4,627,300") + .icon(BitmapDescriptorFactory.fromResource(R.drawable.arrow)) + .infoWindowAnchor(0.5f, 0.5f)); + + // Creates a draggable marker. Long press to drag. + mMelbourne = mMap.addMarker(new MarkerOptions() + .position(MELBOURNE) + .title("Melbourne") + .snippet("Population: 4,137,400") + .draggable(true)); + + // Place four markers on top of each other with differing z-indexes. + mDarwin1 = mMap.addMarker(new MarkerOptions() + .position(DARWIN) + .title("Darwin Marker 1") + .snippet("z-index 1") + .zIndex(1)); + mDarwin2 = mMap.addMarker(new MarkerOptions() + .position(DARWIN) + .title("Darwin Marker 2") + .snippet("z-index 2") + .zIndex(2)); + mDarwin3 = mMap.addMarker(new MarkerOptions() + .position(DARWIN) + .title("Darwin Marker 3") + .snippet("z-index 3") + .zIndex(3)); + mDarwin4 = mMap.addMarker(new MarkerOptions() + .position(DARWIN) + .title("Darwin Marker 4") + .snippet("z-index 4") + .zIndex(4)); + + + // A few more markers for good measure. + mPerth = mMap.addMarker(new MarkerOptions() + .position(PERTH) + .title("Perth") + .snippet("Population: 1,738,800")); + mAdelaide = mMap.addMarker(new MarkerOptions() + .position(ADELAIDE) + .title("Adelaide") + .snippet("Population: 1,213,000")); + + // Vector drawable resource as a marker icon. + mMap.addMarker(new MarkerOptions() + .position(ALICE_SPRINGS) + .icon(vectorToBitmap(R.drawable.ic_android, Color.parseColor("#A4C639"))) + .title("Alice Springs")); + + // Creates a marker rainbow demonstrating how to create default marker icons of different + // hues (colors). + float rotation = mRotationBar.getProgress(); + boolean flat = mFlatBox.isChecked(); + + int numMarkersInRainbow = 12; + for (int i = 0; i < numMarkersInRainbow; i++) { + Marker marker = mMap.addMarker(new MarkerOptions() + .position(new LatLng( + -30 + 10 * Math.sin(i * Math.PI / (numMarkersInRainbow - 1)), + 135 - 10 * Math.cos(i * Math.PI / (numMarkersInRainbow - 1)))) + .title("Marker " + i) + .icon(BitmapDescriptorFactory.defaultMarker(i * 360 / numMarkersInRainbow)) + .flat(flat) + .rotation(rotation)); + mMarkerRainbow.add(marker); + } + } + + /** + * Demonstrates converting a {@link Drawable} to a {@link BitmapDescriptor}, + * for use as a marker icon. + */ + private BitmapDescriptor vectorToBitmap(@DrawableRes int id, @ColorInt int color) { + Drawable vectorDrawable = ResourcesCompat.getDrawable(getResources(), id, null); + Bitmap bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(), + vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + vectorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + DrawableCompat.setTint(vectorDrawable, color); + vectorDrawable.draw(canvas); + return BitmapDescriptorFactory.fromBitmap(bitmap); + } + + private boolean checkReady() { + if (mMap == null) { + Toast.makeText(this, R.string.map_not_ready, Toast.LENGTH_SHORT).show(); + return false; + } + return true; + } + + /** Called when the Clear button is clicked. */ + public void onClearMap(View view) { + if (!checkReady()) { + return; + } + mMap.clear(); + } + + /** Called when the Reset button is clicked. */ + public void onResetMap(View view) { + if (!checkReady()) { + return; + } + // Clear the map because we don't want duplicates of the markers. + mMap.clear(); + addMarkersToMap(); + } + + /** Called when the Reset button is clicked. */ + public void onToggleFlat(View view) { + if (!checkReady()) { + return; + } + boolean flat = mFlatBox.isChecked(); + for (Marker marker : mMarkerRainbow) { + marker.setFlat(flat); + } + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (!checkReady()) { + return; + } + float rotation = seekBar.getProgress(); + for (Marker marker : mMarkerRainbow) { + marker.setRotation(rotation); + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + // Do nothing. + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + // Do nothing. + } + + // + // Marker related listeners. + // + + @Override + public boolean onMarkerClick(final Marker marker) { + if (marker.equals(mPerth)) { + // This causes the marker at Perth to bounce into position when it is clicked. + final Handler handler = new Handler(); + final long start = SystemClock.uptimeMillis(); + final long duration = 1500; + + final Interpolator interpolator = new BounceInterpolator(); + + handler.post(new Runnable() { + @Override + public void run() { + long elapsed = SystemClock.uptimeMillis() - start; + float t = Math.max( + 1 - interpolator.getInterpolation((float) elapsed / duration), 0); + marker.setAnchor(0.5f, 1.0f + 2 * t); + + if (t > 0.0) { + // Post again 16ms later. + handler.postDelayed(this, 16); + } + } + }); + } else if (marker.equals(mAdelaide)) { + // This causes the marker at Adelaide to change color and alpha. + marker.setIcon(BitmapDescriptorFactory.defaultMarker(mRandom.nextFloat() * 360)); + marker.setAlpha(mRandom.nextFloat()); + } + + // Markers have a z-index that is settable and gettable. + float zIndex = marker.getZIndex() + 1.0f; + marker.setZIndex(zIndex); + Toast.makeText(this, marker.getTitle() + " z-index set to " + zIndex, + Toast.LENGTH_SHORT).show(); + + mLastSelectedMarker = marker; + // We return false to indicate that we have not consumed the event and that we wish + // for the default behavior to occur (which is for the camera to move such that the + // marker is centered and for the marker's info window to open, if it has one). + return false; + } + + @Override + public void onInfoWindowClick(Marker marker) { + Toast.makeText(this, "Click Info Window", Toast.LENGTH_SHORT).show(); + } + + @Override + public void onInfoWindowClose(Marker marker) { + //Toast.makeText(this, "Close Info Window", Toast.LENGTH_SHORT).show(); + } + + @Override + public void onInfoWindowLongClick(Marker marker) { + Toast.makeText(this, "Info Window long click", Toast.LENGTH_SHORT).show(); + } + + @Override + public void onMarkerDragStart(Marker marker) { + mTopText.setText("onMarkerDragStart"); + } + + @Override + public void onMarkerDragEnd(Marker marker) { + mTopText.setText("onMarkerDragEnd"); + } + + @Override + public void onMarkerDrag(Marker marker) { + mTopText.setText("onMarkerDrag. Current Position: " + marker.getPosition()); + } + +} +// [END maps_android_sample_marker] diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/MultiMapDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/MultiMapDemoActivity.java new file mode 100644 index 000000000..ea192b8b0 --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/MultiMapDemoActivity.java @@ -0,0 +1,32 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +package com.example.mapdemo; + +import android.os.Bundle; + +import androidx.appcompat.app.AppCompatActivity; + +/** + * This shows how to create a simple activity with multiple maps on screen. + */ +public class MultiMapDemoActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.multimap_demo); + } +} diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/MyLocationDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/MyLocationDemoActivity.java new file mode 100755 index 000000000..500c6b5f7 --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/MyLocationDemoActivity.java @@ -0,0 +1,162 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +package com.example.mapdemo; + +import android.Manifest.permission; +import android.annotation.SuppressLint; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.GoogleMap.OnMyLocationButtonClickListener; +import com.google.android.gms.maps.GoogleMap.OnMyLocationClickListener; +import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.SupportMapFragment; + +import android.Manifest; +import android.content.pm.PackageManager; +import android.location.Location; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; +import android.widget.Toast; + +/** + * This demo shows how GMS Location can be used to check for changes to the users location. The "My + * Location" button uses GMS Location to set the blue dot representing the users location. + * Permission for {@link android.Manifest.permission#ACCESS_FINE_LOCATION} and {@link + * android.Manifest.permission#ACCESS_COARSE_LOCATION} are requested at run time. If either + * permission is not granted, the Activity is finished with an error message. + */ +// [START maps_android_sample_my_location] +public class MyLocationDemoActivity extends AppCompatActivity + implements + OnMyLocationButtonClickListener, + OnMyLocationClickListener, + OnMapReadyCallback, + ActivityCompat.OnRequestPermissionsResultCallback { + + /** + * Request code for location permission request. + * + * @see #onRequestPermissionsResult(int, String[], int[]) + */ + private static final int LOCATION_PERMISSION_REQUEST_CODE = 1; + + /** + * Flag indicating whether a requested permission has been denied after returning in {@link + * #onRequestPermissionsResult(int, String[], int[])}. + */ + private boolean permissionDenied = false; + + private GoogleMap map; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.my_location_demo); + + SupportMapFragment mapFragment = + (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map); + mapFragment.getMapAsync(this); + } + + @Override + public void onMapReady(@NonNull GoogleMap googleMap) { + map = googleMap; + map.setOnMyLocationButtonClickListener(this); + map.setOnMyLocationClickListener(this); + enableMyLocation(); + } + + /** + * Enables the My Location layer if the fine location permission has been granted. + */ + @SuppressLint("MissingPermission") + private void enableMyLocation() { + // [START maps_check_location_permission] + // 1. Check if permissions are granted, if so, enable the my location layer + if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) + == PackageManager.PERMISSION_GRANTED + || ContextCompat.checkSelfPermission(this, permission.ACCESS_COARSE_LOCATION) + == PackageManager.PERMISSION_GRANTED) { + map.setMyLocationEnabled(true); + return; + } + + // 2. Otherwise, request location permissions from the user. + PermissionUtils.requestLocationPermissions(this, LOCATION_PERMISSION_REQUEST_CODE, true); + // [END maps_check_location_permission] + } + + @Override + public boolean onMyLocationButtonClick() { + Toast.makeText(this, "MyLocation button clicked", Toast.LENGTH_SHORT).show(); + // Return false so that we don't consume the event and the default behavior still occurs + // (the camera animates to the user's current position). + return false; + } + + @Override + public void onMyLocationClick(@NonNull Location location) { + Toast.makeText(this, "Current location:\n" + location, Toast.LENGTH_LONG).show(); + } + + // [START maps_check_location_permission_result] + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, + @NonNull int[] grantResults) { + if (requestCode != LOCATION_PERMISSION_REQUEST_CODE) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + return; + } + + if (PermissionUtils.isPermissionGranted(permissions, grantResults, + Manifest.permission.ACCESS_FINE_LOCATION) || PermissionUtils + .isPermissionGranted(permissions, grantResults, + Manifest.permission.ACCESS_COARSE_LOCATION)) { + // Enable the my location layer if the permission has been granted. + enableMyLocation(); + } else { + // Permission was denied. Display an error message + // [START_EXCLUDE] + // Display the missing permission error dialog when the fragments resume. + permissionDenied = true; + // [END_EXCLUDE] + } + } + // [END maps_check_location_permission_result] + + @Override + protected void onResumeFragments() { + super.onResumeFragments(); + if (permissionDenied) { + // Permission was not granted, display error dialog. + showMissingPermissionError(); + permissionDenied = false; + } + } + + /** + * Displays a dialog with error message explaining that the location permission is missing. + */ + private void showMissingPermissionError() { + PermissionUtils.PermissionDeniedDialog + .newInstance(true).show(getSupportFragmentManager(), "dialog"); + } + +} +// [END maps_android_sample_my_location] \ No newline at end of file diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/OnMapAndViewReadyListener.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/OnMapAndViewReadyListener.java new file mode 100644 index 000000000..c843b51c0 --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/OnMapAndViewReadyListener.java @@ -0,0 +1,100 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +package com.example.mapdemo; + +import android.annotation.SuppressLint; +import android.os.Build; +import android.view.View; +import android.view.ViewTreeObserver.OnGlobalLayoutListener; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.SupportMapFragment; + +/** + * Helper class that will delay triggering the OnMapReady callback until both the GoogleMap and the + * View having completed initialization. This is only necessary if a developer wishes to immediately + * invoke any method on the GoogleMap that also requires the View to have finished layout + * (ie. anything that needs to know the View's true size like snapshotting). + */ +public class OnMapAndViewReadyListener implements OnGlobalLayoutListener, OnMapReadyCallback { + + /** A listener that needs to wait for both the GoogleMap and the View to be initialized. */ + public interface OnGlobalLayoutAndMapReadyListener { + void onMapReady(GoogleMap googleMap); + } + + private final SupportMapFragment mapFragment; + private final View mapView; + private final OnGlobalLayoutAndMapReadyListener devCallback; + + private boolean isViewReady; + private boolean isMapReady; + private GoogleMap googleMap; + + public OnMapAndViewReadyListener( + SupportMapFragment mapFragment, OnGlobalLayoutAndMapReadyListener devCallback) { + this.mapFragment = mapFragment; + mapView = mapFragment.getView(); + this.devCallback = devCallback; + isViewReady = false; + isMapReady = false; + googleMap = null; + + registerListeners(); + } + + private void registerListeners() { + // View layout. + if ((mapView.getWidth() != 0) && (mapView.getHeight() != 0)) { + // View has already completed layout. + isViewReady = true; + } else { + // Map has not undergone layout, register a View observer. + mapView.getViewTreeObserver().addOnGlobalLayoutListener(this); + } + + // GoogleMap. Note if the GoogleMap is already ready it will still fire the callback later. + mapFragment.getMapAsync(this); + } + + @Override + public void onMapReady(GoogleMap googleMap) { + // NOTE: The GoogleMap API specifies the listener is removed just prior to invocation. + this.googleMap = googleMap; + isMapReady = true; + fireCallbackIfReady(); + } + + @SuppressWarnings("deprecation") // We use the new method when supported + @SuppressLint("NewApi") // We check which build version we are using. + @Override + public void onGlobalLayout() { + // Remove our listener. + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { + mapView.getViewTreeObserver().removeGlobalOnLayoutListener(this); + } else { + mapView.getViewTreeObserver().removeOnGlobalLayoutListener(this); + } + isViewReady = true; + fireCallbackIfReady(); + } + + private void fireCallbackIfReady() { + if (isViewReady && isMapReady) { + devCallback.onMapReady(googleMap); + } + } +} diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/OptionsDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/OptionsDemoActivity.java new file mode 100644 index 000000000..8b88b1d0a --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/OptionsDemoActivity.java @@ -0,0 +1,32 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +package com.example.mapdemo; + +import android.os.Bundle; + +import androidx.appcompat.app.AppCompatActivity; + +/** + * An activity that creates a map with some initial options. + */ +public final class OptionsDemoActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.options_demo); + } +} diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/PermissionUtils.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/PermissionUtils.java new file mode 100755 index 000000000..25c5d1aff --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/PermissionUtils.java @@ -0,0 +1,186 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +package com.example.mapdemo; + +import android.Manifest; +import android.Manifest.permission; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.content.pm.PackageManager; +import android.os.Bundle; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; +import androidx.fragment.app.DialogFragment; +import android.widget.Toast; + +/** + * Utility class for access to runtime permissions. + */ +public abstract class PermissionUtils { + + /** + * Requests the fine and coarse location permissions. If a rationale with an additional + * explanation should be shown to the user, displays a dialog that triggers the request. + */ + public static void requestLocationPermissions(AppCompatActivity activity, int requestId, + boolean finishActivity) { + if (ActivityCompat + .shouldShowRequestPermissionRationale(activity, permission.ACCESS_FINE_LOCATION) || + ActivityCompat.shouldShowRequestPermissionRationale(activity, + permission.ACCESS_COARSE_LOCATION)) { + // Display a dialog with rationale. + PermissionUtils.RationaleDialog.newInstance(requestId, finishActivity) + .show(activity.getSupportFragmentManager(), "dialog"); + } else { + // Location permission has not been granted yet, request it. + ActivityCompat.requestPermissions(activity, + new String[]{permission.ACCESS_FINE_LOCATION, permission.ACCESS_COARSE_LOCATION}, + requestId); + } + } + + /** + * Checks if the result contains a {@link PackageManager#PERMISSION_GRANTED} result for a + * permission from a runtime permissions request. + * + * @see androidx.core.app.ActivityCompat.OnRequestPermissionsResultCallback + */ + public static boolean isPermissionGranted(String[] grantPermissions, int[] grantResults, + String permission) { + for (int i = 0; i < grantPermissions.length; i++) { + if (permission.equals(grantPermissions[i])) { + return grantResults[i] == PackageManager.PERMISSION_GRANTED; + } + } + return false; + } + + /** + * A dialog that displays a permission denied message. + */ + public static class PermissionDeniedDialog extends DialogFragment { + + private static final String ARGUMENT_FINISH_ACTIVITY = "finish"; + + private boolean finishActivity = false; + + /** + * Creates a new instance of this dialog and optionally finishes the calling Activity when + * the 'Ok' button is clicked. + */ + public static PermissionDeniedDialog newInstance(boolean finishActivity) { + Bundle arguments = new Bundle(); + arguments.putBoolean(ARGUMENT_FINISH_ACTIVITY, finishActivity); + + PermissionDeniedDialog dialog = new PermissionDeniedDialog(); + dialog.setArguments(arguments); + return dialog; + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + finishActivity = getArguments().getBoolean(ARGUMENT_FINISH_ACTIVITY); + + return new AlertDialog.Builder(getActivity()) + .setMessage(R.string.location_permission_denied) + .setPositiveButton(android.R.string.ok, null) + .create(); + } + + @Override + public void onDismiss(DialogInterface dialog) { + super.onDismiss(dialog); + if (finishActivity) { + Toast.makeText(getActivity(), R.string.permission_required_toast, + Toast.LENGTH_SHORT).show(); + getActivity().finish(); + } + } + } + + /** + * A dialog that explains the use of the location permission and requests the necessary + * permission. + *

+ * The activity should implement {@link androidx.core.app.ActivityCompat.OnRequestPermissionsResultCallback} + * to handle permit or denial of this permission request. + */ + public static class RationaleDialog extends DialogFragment { + + private static final String ARGUMENT_PERMISSION_REQUEST_CODE = "requestCode"; + + private static final String ARGUMENT_FINISH_ACTIVITY = "finish"; + + private boolean finishActivity = false; + + /** + * Creates a new instance of a dialog displaying the rationale for the use of the location + * permission. + *

+ * The permission is requested after clicking 'ok'. + * + * @param requestCode Id of the request that is used to request the permission. It is + * returned to the {@link androidx.core.app.ActivityCompat.OnRequestPermissionsResultCallback}. + * @param finishActivity Whether the calling Activity should be finished if the dialog is + * cancelled. + */ + public static RationaleDialog newInstance(int requestCode, boolean finishActivity) { + Bundle arguments = new Bundle(); + arguments.putInt(ARGUMENT_PERMISSION_REQUEST_CODE, requestCode); + arguments.putBoolean(ARGUMENT_FINISH_ACTIVITY, finishActivity); + RationaleDialog dialog = new RationaleDialog(); + dialog.setArguments(arguments); + return dialog; + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + Bundle arguments = getArguments(); + final int requestCode = arguments.getInt(ARGUMENT_PERMISSION_REQUEST_CODE); + finishActivity = arguments.getBoolean(ARGUMENT_FINISH_ACTIVITY); + + return new AlertDialog.Builder(getActivity()) + .setMessage(R.string.permission_rationale_location) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // After click on Ok, request the permission. + ActivityCompat.requestPermissions(getActivity(), + new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, + requestCode); + // Do not finish the Activity while requesting permission. + finishActivity = false; + } + }) + .setNegativeButton(android.R.string.cancel, null) + .create(); + } + + @Override + public void onDismiss(DialogInterface dialog) { + super.onDismiss(dialog); + if (finishActivity) { + Toast.makeText(getActivity(), + R.string.permission_required_toast, + Toast.LENGTH_SHORT) + .show(); + getActivity().finish(); + } + } + } +} diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/PolygonDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/PolygonDemoActivity.java new file mode 100644 index 000000000..4be71e21a --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/PolygonDemoActivity.java @@ -0,0 +1,294 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +package com.example.mapdemo; + +import android.graphics.Color; +import android.os.Bundle; +import android.view.View; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.ArrayAdapter; +import android.widget.CheckBox; +import android.widget.SeekBar; +import android.widget.SeekBar.OnSeekBarChangeListener; +import android.widget.Spinner; + +import androidx.appcompat.app.AppCompatActivity; + +import com.google.android.gms.maps.CameraUpdateFactory; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.model.Dash; +import com.google.android.gms.maps.model.Dot; +import com.google.android.gms.maps.model.Gap; +import com.google.android.gms.maps.model.JointType; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.PatternItem; +import com.google.android.gms.maps.model.Polygon; +import com.google.android.gms.maps.model.PolygonOptions; + +import java.util.Arrays; +import java.util.List; + +/** + * This shows how to draw polygons on a map. + */ +// [START maps_android_sample_polygons] +public class PolygonDemoActivity extends AppCompatActivity + implements OnSeekBarChangeListener, OnItemSelectedListener, OnMapReadyCallback { + + private static final LatLng CENTER = new LatLng(-20, 130); + private static final int MAX_WIDTH_PX = 100; + private static final int MAX_HUE_DEGREES = 360; + private static final int MAX_ALPHA = 255; + + private static final int PATTERN_DASH_LENGTH_PX = 50; + private static final int PATTERN_GAP_LENGTH_PX = 10; + private static final Dot DOT = new Dot(); + private static final Dash DASH = new Dash(PATTERN_DASH_LENGTH_PX); + private static final Gap GAP = new Gap(PATTERN_GAP_LENGTH_PX); + private static final List PATTERN_DOTTED = Arrays.asList(DOT, GAP); + private static final List PATTERN_DASHED = Arrays.asList(DASH, GAP); + private static final List PATTERN_MIXED = Arrays.asList(DOT, GAP, DOT, DASH, GAP); + + private Polygon mutablePolygon; + private SeekBar fillHueBar; + private SeekBar fillAlphaBar; + private SeekBar strokeWidthBar; + private SeekBar strokeHueBar; + private SeekBar strokeAlphaBar; + private Spinner strokeJointTypeSpinner; + private Spinner strokePatternSpinner; + private CheckBox clickabilityCheckbox; + + // These are the options for polygon stroke joints and patterns. We use their + // string resource IDs as identifiers. + + private static final int[] JOINT_TYPE_NAME_RESOURCE_IDS = { + R.string.joint_type_default, // Default + R.string.joint_type_bevel, + R.string.joint_type_round, + }; + + private static final int[] PATTERN_TYPE_NAME_RESOURCE_IDS = { + R.string.pattern_solid, // Default + R.string.pattern_dashed, + R.string.pattern_dotted, + R.string.pattern_mixed, + }; + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.polygon_demo); + + fillHueBar = findViewById(R.id.fillHueSeekBar); + fillHueBar.setMax(MAX_HUE_DEGREES); + fillHueBar.setProgress(MAX_HUE_DEGREES / 2); + + fillAlphaBar = findViewById(R.id.fillAlphaSeekBar); + fillAlphaBar.setMax(MAX_ALPHA); + fillAlphaBar.setProgress(MAX_ALPHA / 2); + + strokeWidthBar = findViewById(R.id.strokeWidthSeekBar); + strokeWidthBar.setMax(MAX_WIDTH_PX); + strokeWidthBar.setProgress(MAX_WIDTH_PX / 3); + + strokeHueBar = findViewById(R.id.strokeHueSeekBar); + strokeHueBar.setMax(MAX_HUE_DEGREES); + strokeHueBar.setProgress(0); + + strokeAlphaBar = findViewById(R.id.strokeAlphaSeekBar); + strokeAlphaBar.setMax(MAX_ALPHA); + strokeAlphaBar.setProgress(MAX_ALPHA); + + strokeJointTypeSpinner = findViewById(R.id.strokeJointTypeSpinner); + strokeJointTypeSpinner.setAdapter(new ArrayAdapter<>( + this, android.R.layout.simple_spinner_item, + getResourceStrings(JOINT_TYPE_NAME_RESOURCE_IDS))); + + strokePatternSpinner = findViewById(R.id.strokePatternSpinner); + strokePatternSpinner.setAdapter(new ArrayAdapter<>( + this, android.R.layout.simple_spinner_item, + getResourceStrings(PATTERN_TYPE_NAME_RESOURCE_IDS))); + + clickabilityCheckbox = findViewById(R.id.toggleClickability); + + SupportMapFragment mapFragment = + (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map); + mapFragment.getMapAsync(this); + } + // [START_EXCLUDE silent] + + private String[] getResourceStrings(int[] resourceIds) { + String[] strings = new String[resourceIds.length]; + for (int i = 0; i < resourceIds.length; i++) { + strings[i] = getString(resourceIds[i]); + } + return strings; + } + // [END_EXCLUDE] + + @Override + public void onMapReady(GoogleMap map) { + // Override the default content description on the view, for accessibility mode. + map.setContentDescription(getString(R.string.polygon_demo_description)); + + int fillColorArgb = Color.HSVToColor( + fillAlphaBar.getProgress(), new float[]{fillHueBar.getProgress(), 1, 1}); + int strokeColorArgb = Color.HSVToColor( + strokeAlphaBar.getProgress(), new float[]{strokeHueBar.getProgress(), 1, 1}); + + // Create a rectangle with two rectangular holes. + mutablePolygon = map.addPolygon(new PolygonOptions() + .addAll(createRectangle(CENTER, 5, 5)) + .addHole(createRectangle(new LatLng(-22, 128), 1, 1)) + .addHole(createRectangle(new LatLng(-18, 133), 0.5, 1.5)) + .fillColor(fillColorArgb) + .strokeColor(strokeColorArgb) + .strokeWidth(strokeWidthBar.getProgress()) + .clickable(clickabilityCheckbox.isChecked())); + + fillHueBar.setOnSeekBarChangeListener(this); + fillAlphaBar.setOnSeekBarChangeListener(this); + + strokeWidthBar.setOnSeekBarChangeListener(this); + strokeHueBar.setOnSeekBarChangeListener(this); + strokeAlphaBar.setOnSeekBarChangeListener(this); + + strokeJointTypeSpinner.setOnItemSelectedListener(this); + strokePatternSpinner.setOnItemSelectedListener(this); + + mutablePolygon.setStrokeJointType(getSelectedJointType(strokeJointTypeSpinner.getSelectedItemPosition())); + mutablePolygon.setStrokePattern(getSelectedPattern(strokePatternSpinner.getSelectedItemPosition())); + + // Move the map so that it is centered on the mutable polygon. + map.moveCamera(CameraUpdateFactory.newLatLngZoom(CENTER, 4)); + + // Add a listener for polygon clicks that changes the clicked polygon's stroke color. + map.setOnPolygonClickListener(new GoogleMap.OnPolygonClickListener() { + @Override + public void onPolygonClick(Polygon polygon) { + // Flip the red, green and blue components of the polygon's stroke color. + polygon.setStrokeColor(polygon.getStrokeColor() ^ 0x00ffffff); + } + }); + } + + // [START_EXCLUDE silent] + /** + * Creates a List of LatLngs that form a rectangle with the given dimensions. + */ + private List createRectangle(LatLng center, double halfWidth, double halfHeight) { + return Arrays.asList(new LatLng(center.latitude - halfHeight, center.longitude - halfWidth), + new LatLng(center.latitude - halfHeight, center.longitude + halfWidth), + new LatLng(center.latitude + halfHeight, center.longitude + halfWidth), + new LatLng(center.latitude + halfHeight, center.longitude - halfWidth), + new LatLng(center.latitude - halfHeight, center.longitude - halfWidth)); + } + + private int getSelectedJointType(int pos) { + int id = JOINT_TYPE_NAME_RESOURCE_IDS[pos]; + if (id == R.string.joint_type_bevel) { + return JointType.BEVEL; + } else if (id == R.string.joint_type_round) { + return JointType.ROUND; + } else if (id == R.string.joint_type_default) { + return JointType.DEFAULT; + } + return 0; + } + + private List getSelectedPattern(int pos) { + int id = PATTERN_TYPE_NAME_RESOURCE_IDS[pos]; + if (id == R.string.pattern_solid) { + return null; + } else if (id == R.string.pattern_dotted) { + return PATTERN_DOTTED; + } else if (id == R.string.pattern_dashed) { + return PATTERN_DASHED; + } else if (id == R.string.pattern_mixed) { + return PATTERN_MIXED; + } else { + return null; + } + } + + @Override + public void onItemSelected(AdapterView parent, View view, int pos, long id) { + int parentId = parent.getId(); + if (parentId == R.id.strokeJointTypeSpinner) { + mutablePolygon.setStrokeJointType(getSelectedJointType(pos)); + } else if (parentId == R.id.strokePatternSpinner) { + mutablePolygon.setStrokePattern(getSelectedPattern(pos)); + } + } + + @Override + public void onNothingSelected(AdapterView parent) { + // Don't do anything here. + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + // Don't do anything here. + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + // Don't do anything here. + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (mutablePolygon == null) { + return; + } + + if (seekBar == fillHueBar) { + mutablePolygon.setFillColor(Color.HSVToColor( + Color.alpha(mutablePolygon.getFillColor()), new float[]{progress, 1, 1})); + } else if (seekBar == fillAlphaBar) { + int prevColor = mutablePolygon.getFillColor(); + mutablePolygon.setFillColor(Color.argb( + progress, Color.red(prevColor), Color.green(prevColor), + Color.blue(prevColor))); + } else if (seekBar == strokeHueBar) { + mutablePolygon.setStrokeColor(Color.HSVToColor( + Color.alpha(mutablePolygon.getStrokeColor()), new float[]{progress, 1, 1})); + } else if (seekBar == strokeAlphaBar) { + int prevColorArgb = mutablePolygon.getStrokeColor(); + mutablePolygon.setStrokeColor(Color.argb( + progress, Color.red(prevColorArgb), Color.green(prevColorArgb), + Color.blue(prevColorArgb))); + } else if (seekBar == strokeWidthBar) { + mutablePolygon.setStrokeWidth(progress); + } + } + + /** + * Toggles the clickability of the polygon based on the state of the View that triggered this + * call. + * This callback is defined on the CheckBox in the layout for this Activity. + */ + public void toggleClickability(View view) { + if (mutablePolygon != null) { + mutablePolygon.setClickable(((CheckBox) view).isChecked()); + } + } + // [END_EXCLUDE] +} +// [END maps_android_sample_polygons] \ No newline at end of file diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/PolylineDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/PolylineDemoActivity.java new file mode 100644 index 000000000..de634310b --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/PolylineDemoActivity.java @@ -0,0 +1,328 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +package com.example.mapdemo; + +import android.graphics.Color; +import android.os.Bundle; +import android.view.View; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.ArrayAdapter; +import android.widget.CheckBox; +import android.widget.SeekBar; +import android.widget.SeekBar.OnSeekBarChangeListener; +import android.widget.Spinner; + +import androidx.appcompat.app.AppCompatActivity; + +import com.example.mapdemo.R; +import com.google.android.gms.maps.CameraUpdateFactory; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.model.BitmapDescriptorFactory; +import com.google.android.gms.maps.model.ButtCap; +import com.google.android.gms.maps.model.Cap; +import com.google.android.gms.maps.model.CustomCap; +import com.google.android.gms.maps.model.Dash; +import com.google.android.gms.maps.model.Dot; +import com.google.android.gms.maps.model.Gap; +import com.google.android.gms.maps.model.JointType; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.PatternItem; +import com.google.android.gms.maps.model.Polyline; +import com.google.android.gms.maps.model.PolylineOptions; +import com.google.android.gms.maps.model.RoundCap; +import com.google.android.gms.maps.model.SquareCap; + +import java.util.Arrays; +import java.util.List; + +/** + * This shows how to draw polylines on a map. + */ +// [START maps_android_sample_polylines] +public class PolylineDemoActivity extends AppCompatActivity + implements OnSeekBarChangeListener, OnItemSelectedListener, OnMapReadyCallback { + + // City locations for mutable polyline. + private static final LatLng ADELAIDE = new LatLng(-34.92873, 138.59995); + private static final LatLng DARWIN = new LatLng(-12.4258647, 130.7932231); + private static final LatLng MELBOURNE = new LatLng(-37.81319, 144.96298); + private static final LatLng PERTH = new LatLng(-31.95285, 115.85734); + + // Airport locations for geodesic polyline. + private static final LatLng AKL = new LatLng(-37.006254, 174.783018); + private static final LatLng JFK = new LatLng(40.641051, -73.777485); + private static final LatLng LAX = new LatLng(33.936524, -118.377686); + private static final LatLng LHR = new LatLng(51.471547, -0.460052); + + private static final int MAX_WIDTH_PX = 100; + private static final int MAX_HUE_DEGREES = 360; + private static final int MAX_ALPHA = 255; + private static final int CUSTOM_CAP_IMAGE_REF_WIDTH_PX = 50; + private static final int INITIAL_STROKE_WIDTH_PX = 5; + + private static final int PATTERN_DASH_LENGTH_PX = 50; + private static final int PATTERN_GAP_LENGTH_PX = 20; + private static final Dot DOT = new Dot(); + private static final Dash DASH = new Dash(PATTERN_DASH_LENGTH_PX); + private static final Gap GAP = new Gap(PATTERN_GAP_LENGTH_PX); + private static final List PATTERN_DOTTED = Arrays.asList(DOT, GAP); + private static final List PATTERN_DASHED = Arrays.asList(DASH, GAP); + private static final List PATTERN_MIXED = Arrays.asList(DOT, GAP, DOT, DASH, GAP); + + private Polyline mutablePolyline; + private SeekBar hueBar; + private SeekBar alphaBar; + private SeekBar widthBar; + private Spinner startCapSpinner; + private Spinner endCapSpinner; + private Spinner jointTypeSpinner; + private Spinner patternSpinner; + private CheckBox clickabilityCheckbox; + + // These are the options for polyline caps, joints and patterns. We use their + // string resource IDs as identifiers. + + private static final int[] CAP_TYPE_NAME_RESOURCE_IDS = { + R.string.cap_butt, // Default + R.string.cap_round, + R.string.cap_square, + R.string.cap_image, + }; + + private static final int[] JOINT_TYPE_NAME_RESOURCE_IDS = { + R.string.joint_type_default, // Default + R.string.joint_type_bevel, + R.string.joint_type_round, + }; + + private static final int[] PATTERN_TYPE_NAME_RESOURCE_IDS = { + R.string.pattern_solid, // Default + R.string.pattern_dashed, + R.string.pattern_dotted, + R.string.pattern_mixed, + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.polyline_demo); + + hueBar = findViewById(R.id.hueSeekBar); + hueBar.setMax(MAX_HUE_DEGREES); + hueBar.setProgress(0); + + alphaBar = findViewById(R.id.alphaSeekBar); + alphaBar.setMax(MAX_ALPHA); + alphaBar.setProgress(MAX_ALPHA); + + widthBar = findViewById(R.id.widthSeekBar); + widthBar.setMax(MAX_WIDTH_PX); + widthBar.setProgress(MAX_WIDTH_PX / 2); + + startCapSpinner = findViewById(R.id.startCapSpinner); + startCapSpinner.setAdapter(new ArrayAdapter<>( + this, android.R.layout.simple_spinner_item, + getResourceStrings(CAP_TYPE_NAME_RESOURCE_IDS))); + + endCapSpinner = findViewById(R.id.endCapSpinner); + endCapSpinner.setAdapter(new ArrayAdapter<>( + this, android.R.layout.simple_spinner_item, + getResourceStrings(CAP_TYPE_NAME_RESOURCE_IDS))); + + jointTypeSpinner = findViewById(R.id.jointTypeSpinner); + jointTypeSpinner.setAdapter(new ArrayAdapter<>( + this, android.R.layout.simple_spinner_item, + getResourceStrings(JOINT_TYPE_NAME_RESOURCE_IDS))); + + patternSpinner = findViewById(R.id.patternSpinner); + patternSpinner.setAdapter(new ArrayAdapter<>( + this, android.R.layout.simple_spinner_item, + getResourceStrings(PATTERN_TYPE_NAME_RESOURCE_IDS))); + + clickabilityCheckbox = findViewById(R.id.toggleClickability); + + SupportMapFragment mapFragment = + (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map); + mapFragment.getMapAsync(this); + } + // [START_EXCLUDE silent] + + private String[] getResourceStrings(int[] resourceIds) { + String[] strings = new String[resourceIds.length]; + for (int i = 0; i < resourceIds.length; i++) { + strings[i] = getString(resourceIds[i]); + } + return strings; + } + // [END_EXCLUDE] + + @Override + public void onMapReady(GoogleMap map) { + + // Override the default content description on the view, for accessibility mode. + map.setContentDescription(getString(R.string.polyline_demo_description)); + + // A geodesic polyline that goes around the world. + map.addPolyline(new PolylineOptions() + .add(LHR, AKL, LAX, JFK, LHR) + .width(INITIAL_STROKE_WIDTH_PX) + .color(Color.BLUE) + .geodesic(true) + .clickable(clickabilityCheckbox.isChecked())); + + // A simple polyline across Australia. This polyline will be mutable. + int color = Color.HSVToColor( + alphaBar.getProgress(), new float[]{hueBar.getProgress(), 1, 1}); + mutablePolyline = map.addPolyline(new PolylineOptions() + .color(color) + .width(widthBar.getProgress()) + .clickable(clickabilityCheckbox.isChecked()) + .add(MELBOURNE, ADELAIDE, PERTH, DARWIN)); + + hueBar.setOnSeekBarChangeListener(this); + alphaBar.setOnSeekBarChangeListener(this); + widthBar.setOnSeekBarChangeListener(this); + + startCapSpinner.setOnItemSelectedListener(this); + endCapSpinner.setOnItemSelectedListener(this); + jointTypeSpinner.setOnItemSelectedListener(this); + patternSpinner.setOnItemSelectedListener(this); + + mutablePolyline.setStartCap(getSelectedCap(startCapSpinner.getSelectedItemPosition())); + mutablePolyline.setEndCap(getSelectedCap(endCapSpinner.getSelectedItemPosition())); + mutablePolyline.setJointType(getSelectedJointType(jointTypeSpinner.getSelectedItemPosition())); + mutablePolyline.setPattern(getSelectedPattern(patternSpinner.getSelectedItemPosition())); + + // Move the map so that it is centered on the mutable polyline. + map.moveCamera(CameraUpdateFactory.newLatLngZoom(MELBOURNE, 3)); + + // Add a listener for polyline clicks that changes the clicked polyline's color. + map.setOnPolylineClickListener(new GoogleMap.OnPolylineClickListener() { + @Override + public void onPolylineClick(Polyline polyline) { + // Flip the values of the red, green and blue components of the polyline's color. + polyline.setColor(polyline.getColor() ^ 0x00ffffff); + } + }); + } + + // [START_EXCLUDE silent] + private Cap getSelectedCap(int pos) { + int id = CAP_TYPE_NAME_RESOURCE_IDS[pos]; + if (id == R.string.cap_butt) { + return new ButtCap(); + } else if (id == R.string.cap_square) { + return new SquareCap(); + } else if (id == R.string.cap_round) { + return new RoundCap(); + } else if (id == R.string.cap_image) { + return new CustomCap( + BitmapDescriptorFactory.fromResource(R.drawable.chevron), + CUSTOM_CAP_IMAGE_REF_WIDTH_PX); + } + return null; + } + + private int getSelectedJointType(int pos) { + int id = JOINT_TYPE_NAME_RESOURCE_IDS[pos]; + if (id == R.string.joint_type_bevel) { + return JointType.BEVEL; + } else if (id == R.string.joint_type_round) { + return JointType.ROUND; + } else if (id == R.string.joint_type_default) { + return JointType.DEFAULT; + } + return 0; + } + + private List getSelectedPattern(int pos) { + int id = PATTERN_TYPE_NAME_RESOURCE_IDS[pos]; + if (id == R.string.pattern_solid) { + return null; + } else if (id == R.string.pattern_dotted) { + return PATTERN_DOTTED; + } else if (id == R.string.pattern_dashed) { + return PATTERN_DASHED; + } else if (id == R.string.pattern_mixed) { + return PATTERN_MIXED; + } else { + return null; + } + } + + @Override + public void onItemSelected(AdapterView parent, View view, int pos, long id) { + int parentId = parent.getId(); + if (parentId == R.id.startCapSpinner) { + mutablePolyline.setStartCap(getSelectedCap(pos)); + } else if (parentId == R.id.endCapSpinner) { + mutablePolyline.setEndCap(getSelectedCap(pos)); + } else if (parentId == R.id.jointTypeSpinner) { + mutablePolyline.setJointType(getSelectedJointType(pos)); + } else if (parentId == R.id.patternSpinner) { + mutablePolyline.setPattern(getSelectedPattern(pos)); + } + } + + @Override + public void onNothingSelected(AdapterView parent) { + // Don't do anything here. + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + // Don't do anything here. + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + // Don't do anything here. + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (mutablePolyline == null) { + return; + } + + if (seekBar == hueBar) { + mutablePolyline.setColor(Color.HSVToColor( + Color.alpha(mutablePolyline.getColor()), new float[]{progress, 1, 1})); + } else if (seekBar == alphaBar) { + float[] prevHSV = new float[3]; + Color.colorToHSV(mutablePolyline.getColor(), prevHSV); + mutablePolyline.setColor(Color.HSVToColor(progress, prevHSV)); + } else if (seekBar == widthBar) { + mutablePolyline.setWidth(progress); + } + } + + /** + * Toggles the clickability of the polyline based on the state of the View that triggered this + * call. + * This callback is defined on the CheckBox in the layout for this Activity. + */ + public void toggleClickability(View view) { + if (mutablePolyline != null) { + mutablePolyline.setClickable(((CheckBox) view).isChecked()); + } + } + // [END_EXCLUDE] +} +// [END maps_android_sample_polylines] \ No newline at end of file diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/ProgrammaticDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/ProgrammaticDemoActivity.java new file mode 100644 index 000000000..558fdf6da --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/ProgrammaticDemoActivity.java @@ -0,0 +1,63 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +package com.example.mapdemo; + +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.MarkerOptions; + +import android.os.Bundle; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.fragment.app.FragmentTransaction; + +/** + * Demonstrates how to instantiate a SupportMapFragment programmatically and add a marker to it. + */ +public class ProgrammaticDemoActivity extends AppCompatActivity implements OnMapReadyCallback { + + private static final String MAP_FRAGMENT_TAG = "map"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // It isn't possible to set a fragment's id programmatically so we set a tag instead and + // search for it using that. + SupportMapFragment mapFragment = (SupportMapFragment) + getSupportFragmentManager().findFragmentByTag(MAP_FRAGMENT_TAG); + + // We only create a fragment if it doesn't already exist. + if (mapFragment == null) { + // To programmatically add the map, we first create a SupportMapFragment. + mapFragment = SupportMapFragment.newInstance(); + + // Then we add it using a FragmentTransaction. + FragmentTransaction fragmentTransaction = + getSupportFragmentManager().beginTransaction(); + fragmentTransaction.add(android.R.id.content, mapFragment, MAP_FRAGMENT_TAG); + fragmentTransaction.commit(); + } + mapFragment.getMapAsync(this); + } + + @Override + public void onMapReady(GoogleMap map) { + map.addMarker(new MarkerOptions().position(new LatLng(0, 0)).title("Marker")); + } +} diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/RawMapViewDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/RawMapViewDemoActivity.java new file mode 100644 index 000000000..d69a7f066 --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/RawMapViewDemoActivity.java @@ -0,0 +1,110 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +package com.example.mapdemo; + +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.MapView; +import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.MarkerOptions; + +import android.os.Bundle; + +import androidx.appcompat.app.AppCompatActivity; + +/** + * This shows how to create a simple activity with a raw MapView and add a marker to it. This + * requires forwarding all the important lifecycle methods onto MapView. + */ +public class RawMapViewDemoActivity extends AppCompatActivity implements OnMapReadyCallback { + + private MapView mMapView; + + private static final String MAPVIEW_BUNDLE_KEY = "MapViewBundleKey"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.raw_mapview_demo); + + // *** IMPORTANT *** + // MapView requires that the Bundle you pass contain _ONLY_ MapView SDK + // objects or sub-Bundles. + Bundle mapViewBundle = null; + if (savedInstanceState != null) { + mapViewBundle = savedInstanceState.getBundle(MAPVIEW_BUNDLE_KEY); + } + mMapView = (MapView) findViewById(R.id.map); + mMapView.onCreate(mapViewBundle); + + mMapView.getMapAsync(this); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + Bundle mapViewBundle = outState.getBundle(MAPVIEW_BUNDLE_KEY); + if (mapViewBundle == null) { + mapViewBundle = new Bundle(); + outState.putBundle(MAPVIEW_BUNDLE_KEY, mapViewBundle); + } + + mMapView.onSaveInstanceState(mapViewBundle); + } + + @Override + protected void onResume() { + super.onResume(); + mMapView.onResume(); + } + + @Override + protected void onStart() { + super.onStart(); + mMapView.onStart(); + } + + @Override + protected void onStop() { + super.onStop(); + mMapView.onStop(); + } + + @Override + public void onMapReady(GoogleMap map) { + map.addMarker(new MarkerOptions().position(new LatLng(0, 0)).title("Marker")); + } + + @Override + protected void onPause() { + mMapView.onPause(); + super.onPause(); + } + + @Override + protected void onDestroy() { + mMapView.onDestroy(); + super.onDestroy(); + } + + @Override + public void onLowMemory() { + super.onLowMemory(); + mMapView.onLowMemory(); + } + +} diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/RetainMapDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/RetainMapDemoActivity.java new file mode 100755 index 000000000..026396e83 --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/RetainMapDemoActivity.java @@ -0,0 +1,54 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +package com.example.mapdemo; + +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.MarkerOptions; + +import android.os.Bundle; + +import androidx.appcompat.app.AppCompatActivity; + +/** + * This shows how to retain a map across activity restarts (e.g., from screen rotations), which can + * be faster than relying on state serialization. + */ +public class RetainMapDemoActivity extends AppCompatActivity implements OnMapReadyCallback { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.basic_demo); + + SupportMapFragment mapFragment = + (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map); + + if (savedInstanceState == null) { + // First incarnation of this activity. + mapFragment.setRetainInstance(true); + } + + mapFragment.getMapAsync(this); + } + + @Override + public void onMapReady(GoogleMap map) { + map.addMarker(new MarkerOptions().position(new LatLng(0, 0)).title("Marker")); + } +} diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/SaveStateDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/SaveStateDemoActivity.java new file mode 100755 index 000000000..2c32b5e57 --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/SaveStateDemoActivity.java @@ -0,0 +1,220 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +package com.example.mapdemo; + +import com.google.android.gms.maps.CameraUpdateFactory; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.GoogleMap.OnMarkerClickListener; +import com.google.android.gms.maps.GoogleMap.OnMarkerDragListener; +import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.model.BitmapDescriptorFactory; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.Marker; +import com.google.android.gms.maps.model.MarkerOptions; + +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.appcompat.app.AppCompatActivity; + +import java.util.Random; + +/** + * This activity shows how to save the state of a MapFragment when the activity is recreated, like + * after rotation of the device. + */ +public class SaveStateDemoActivity extends AppCompatActivity { + + /** Default marker position when the activity is first created. */ + private static final LatLng DEFAULT_MARKER_POSITION = new LatLng(48.858179, 2.294576); + + /** List of hues to use for the marker */ + private static final float[] MARKER_HUES = new float[]{ + BitmapDescriptorFactory.HUE_RED, + BitmapDescriptorFactory.HUE_ORANGE, + BitmapDescriptorFactory.HUE_YELLOW, + BitmapDescriptorFactory.HUE_GREEN, + BitmapDescriptorFactory.HUE_CYAN, + BitmapDescriptorFactory.HUE_AZURE, + BitmapDescriptorFactory.HUE_BLUE, + BitmapDescriptorFactory.HUE_VIOLET, + BitmapDescriptorFactory.HUE_MAGENTA, + BitmapDescriptorFactory.HUE_ROSE, + }; + + // Bundle keys. + private static final String OTHER_OPTIONS = "options"; + + private static final String MARKER_POSITION = "markerPosition"; + + private static final String MARKER_INFO = "markerInfo"; + + /** + * Extra info about a marker. + */ + static class MarkerInfo implements Parcelable { + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public MarkerInfo createFromParcel(Parcel in) { + return new MarkerInfo(in); + } + + @Override + public MarkerInfo[] newArray(int size) { + return new MarkerInfo[size]; + } + }; + + float mHue; + + public MarkerInfo(float color) { + mHue = color; + } + + private MarkerInfo(Parcel in) { + mHue = in.readFloat(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeFloat(mHue); + } + } + + /** + * Example of a custom {@code MapFragment} showing how the position of a marker and other + * custom + * {@link Parcelable}s objects can be saved after rotation of the device. + *

+ * Storing custom {@link Parcelable} objects directly in the {@link Bundle} provided by the + * {@link #onActivityCreated(Bundle)} method will throw a {@code ClassNotFoundException}. This + * is due to the fact that this Bundle is parceled (thus losing its ClassLoader attribute at + * this moment) and unparceled later in a different ClassLoader. + *
+ * A workaround to store these objects is to wrap the custom {@link Parcelable} objects in a + * new + * {@link Bundle} object. + *

+ * However, note that it is safe to store {@link Parcelable} objects from the Maps API (eg. + * MarkerOptions, LatLng, etc.) directly in the Bundle provided by the + * {@link #onActivityCreated(Bundle)} method. + */ + public static class SaveStateMapFragment extends SupportMapFragment + implements OnMarkerClickListener, OnMarkerDragListener, OnMapReadyCallback { + + private LatLng mMarkerPosition; + + private MarkerInfo mMarkerInfo; + + private boolean mMoveCameraToMarker; + + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (savedInstanceState == null) { + // Activity created for the first time. + mMarkerPosition = DEFAULT_MARKER_POSITION; + mMarkerInfo = new MarkerInfo(BitmapDescriptorFactory.HUE_RED); + mMoveCameraToMarker = true; + } else { + // Extract the state of the MapFragment: + // - Objects from the API (eg. LatLng, MarkerOptions, etc.) were stored directly in + // the savedInsanceState Bundle. + // - Custom Parcelable objects were wrapped in another Bundle. + + mMarkerPosition = savedInstanceState.getParcelable(MARKER_POSITION); + + Bundle bundle = savedInstanceState.getBundle(OTHER_OPTIONS); + mMarkerInfo = bundle.getParcelable(MARKER_INFO); + + mMoveCameraToMarker = false; + } + + getMapAsync(this); + } + + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + // All Parcelable objects of the API (eg. LatLng, MarkerOptions, etc.) can be set + // directly in the given Bundle. + outState.putParcelable(MARKER_POSITION, mMarkerPosition); + + // All other custom Parcelable objects must be wrapped in another Bundle. Indeed, + // failing to do so would throw a ClassNotFoundException. This is due to the fact that + // this Bundle is being parceled (losing its ClassLoader at this time) and unparceled + // later in a different ClassLoader. + Bundle bundle = new Bundle(); + bundle.putParcelable(MARKER_INFO, mMarkerInfo); + outState.putBundle(OTHER_OPTIONS, bundle); + } + + @Override + public boolean onMarkerClick(Marker marker) { + float newHue = MARKER_HUES[new Random().nextInt(MARKER_HUES.length)]; + mMarkerInfo.mHue = newHue; + marker.setIcon(BitmapDescriptorFactory.defaultMarker(newHue)); + return true; + } + + @Override + public void onMapReady(GoogleMap map) { + MarkerOptions markerOptions = new MarkerOptions() + .position(mMarkerPosition) + .icon(BitmapDescriptorFactory.defaultMarker(mMarkerInfo.mHue)) + .draggable(true); + map.addMarker(markerOptions); + map.setOnMarkerDragListener(this); + map.setOnMarkerClickListener(this); + + if (mMoveCameraToMarker) { + map.animateCamera(CameraUpdateFactory.newLatLng(mMarkerPosition)); + } + } + + @Override + public void onMarkerDragStart(Marker marker) { + } + + @Override + public void onMarkerDrag(Marker marker) { + } + + @Override + public void onMarkerDragEnd(Marker marker) { + mMarkerPosition = marker.getPosition(); + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.save_state_demo); + } +} diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/SnapshotDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/SnapshotDemoActivity.java new file mode 100755 index 000000000..198a44b56 --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/SnapshotDemoActivity.java @@ -0,0 +1,101 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +package com.example.mapdemo; + +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.GoogleMap.OnMapLoadedCallback; +import com.google.android.gms.maps.GoogleMap.SnapshotReadyCallback; +import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.SupportMapFragment; + +import android.graphics.Bitmap; +import android.os.Bundle; +import android.view.View; +import android.widget.CheckBox; +import android.widget.ImageView; + +import androidx.appcompat.app.AppCompatActivity; + +/** + * This shows how to take a snapshot of the map. + */ +public class SnapshotDemoActivity extends AppCompatActivity implements OnMapReadyCallback { + + /** + * Note that this may be null if the Google Play services APK is not available. + */ + private GoogleMap mMap; + + private CheckBox mWaitForMapLoadCheckBox; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.snapshot_demo); + mWaitForMapLoadCheckBox = (CheckBox) findViewById(R.id.wait_for_map_load); + + SupportMapFragment mapFragment = + (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map); + mapFragment.getMapAsync(this); + } + + @Override + public void onMapReady(GoogleMap map) { + mMap = map; + } + + /** + * Called when the snapshot button is clicked. + */ + public void onScreenshot(View view) { + takeSnapshot(); + } + + private void takeSnapshot() { + if (mMap == null) { + return; + } + + final ImageView snapshotHolder = (ImageView) findViewById(R.id.snapshot_holder); + + final SnapshotReadyCallback callback = new SnapshotReadyCallback() { + @Override + public void onSnapshotReady(Bitmap snapshot) { + // Callback is called from the main thread, so we can modify the ImageView safely. + snapshotHolder.setImageBitmap(snapshot); + } + }; + + if (mWaitForMapLoadCheckBox.isChecked()) { + mMap.setOnMapLoadedCallback(new OnMapLoadedCallback() { + @Override + public void onMapLoaded() { + mMap.snapshot(callback); + } + }); + } else { + mMap.snapshot(callback); + } + } + + /** + * Called when the clear button is clicked. + */ + public void onClearScreenshot(View view) { + ImageView snapshotHolder = (ImageView) findViewById(R.id.snapshot_holder); + snapshotHolder.setImageDrawable(null); + } +} diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/SplitStreetViewPanoramaAndMapDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/SplitStreetViewPanoramaAndMapDemoActivity.java new file mode 100755 index 000000000..7118ee6a8 --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/SplitStreetViewPanoramaAndMapDemoActivity.java @@ -0,0 +1,121 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +package com.example.mapdemo; + +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.GoogleMap.OnMarkerDragListener; +import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.OnStreetViewPanoramaReadyCallback; +import com.google.android.gms.maps.StreetViewPanorama; +import com.google.android.gms.maps.StreetViewPanorama.OnStreetViewPanoramaChangeListener; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.SupportStreetViewPanoramaFragment; +import com.google.android.gms.maps.model.BitmapDescriptorFactory; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.Marker; +import com.google.android.gms.maps.model.MarkerOptions; +import com.google.android.gms.maps.model.StreetViewPanoramaLocation; + +import android.os.Bundle; + +import androidx.appcompat.app.AppCompatActivity; + +/** + * This shows how to create a simple activity with streetview and a map + */ +public class SplitStreetViewPanoramaAndMapDemoActivity extends AppCompatActivity + implements OnMarkerDragListener, OnStreetViewPanoramaChangeListener { + + private static final String MARKER_POSITION_KEY = "MarkerPosition"; + + // George St, Sydney + private static final LatLng SYDNEY = new LatLng(-33.87365, 151.20689); + + private StreetViewPanorama streetViewPanorama; + + private Marker marker; + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.split_street_view_panorama_and_map_demo); + + final LatLng markerPosition; + if (savedInstanceState == null) { + markerPosition = SYDNEY; + } else { + markerPosition = savedInstanceState.getParcelable(MARKER_POSITION_KEY); + } + + SupportStreetViewPanoramaFragment streetViewPanoramaFragment = + (SupportStreetViewPanoramaFragment) + getSupportFragmentManager().findFragmentById(R.id.streetviewpanorama); + streetViewPanoramaFragment.getStreetViewPanoramaAsync( + new OnStreetViewPanoramaReadyCallback() { + @Override + public void onStreetViewPanoramaReady(StreetViewPanorama panorama) { + streetViewPanorama = panorama; + streetViewPanorama.setOnStreetViewPanoramaChangeListener( + SplitStreetViewPanoramaAndMapDemoActivity.this); + // Only need to set the position once as the streetview fragment will maintain + // its state. + if (savedInstanceState == null) { + streetViewPanorama.setPosition(SYDNEY); + } + } + }); + + SupportMapFragment mapFragment = + (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map); + mapFragment.getMapAsync(new OnMapReadyCallback() { + @Override + public void onMapReady(GoogleMap map) { + map.setOnMarkerDragListener(SplitStreetViewPanoramaAndMapDemoActivity.this); + // Creates a draggable marker. Long press to drag. + marker = map.addMarker(new MarkerOptions() + .position(markerPosition) + .icon(BitmapDescriptorFactory.fromResource(R.drawable.pegman)) + .draggable(true)); + } + }); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putParcelable(MARKER_POSITION_KEY, marker.getPosition()); + } + + @Override + public void onStreetViewPanoramaChange(StreetViewPanoramaLocation location) { + if (location != null) { + marker.setPosition(location.position); + } + } + + @Override + public void onMarkerDragStart(Marker marker) { + } + + @Override + public void onMarkerDragEnd(Marker marker) { + streetViewPanorama.setPosition(marker.getPosition(), 150); + } + + @Override + public void onMarkerDrag(Marker marker) { + } +} diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/StreetViewPanoramaBasicDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/StreetViewPanoramaBasicDemoActivity.java new file mode 100755 index 000000000..41d72fd3e --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/StreetViewPanoramaBasicDemoActivity.java @@ -0,0 +1,55 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +package com.example.mapdemo; + +import com.google.android.gms.maps.OnStreetViewPanoramaReadyCallback; +import com.google.android.gms.maps.StreetViewPanorama; +import com.google.android.gms.maps.SupportStreetViewPanoramaFragment; +import com.google.android.gms.maps.model.LatLng; + +import android.os.Bundle; + +import androidx.appcompat.app.AppCompatActivity; + +/** + * This shows how to create a simple activity with streetview + */ +public class StreetViewPanoramaBasicDemoActivity extends AppCompatActivity { + + // George St, Sydney + private static final LatLng SYDNEY = new LatLng(-33.87365, 151.20689); + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.street_view_panorama_basic_demo); + + SupportStreetViewPanoramaFragment streetViewPanoramaFragment = + (SupportStreetViewPanoramaFragment) + getSupportFragmentManager().findFragmentById(R.id.streetviewpanorama); + streetViewPanoramaFragment.getStreetViewPanoramaAsync( + new OnStreetViewPanoramaReadyCallback() { + @Override + public void onStreetViewPanoramaReady(StreetViewPanorama panorama) { + // Only set the panorama to SYDNEY on startup (when no panoramas have been + // loaded which is when the savedInstanceState is null). + if (savedInstanceState == null) { + panorama.setPosition(SYDNEY); + } + } + }); + } +} diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/StreetViewPanoramaEventsDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/StreetViewPanoramaEventsDemoActivity.java new file mode 100755 index 000000000..17ffa6e03 --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/StreetViewPanoramaEventsDemoActivity.java @@ -0,0 +1,132 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +package com.example.mapdemo; + +import android.graphics.Point; +import android.os.Bundle; +import android.widget.TextView; + +import androidx.appcompat.app.AppCompatActivity; + +import com.google.android.gms.maps.StreetViewPanorama; +import com.google.android.gms.maps.StreetViewPanorama.OnStreetViewPanoramaCameraChangeListener; +import com.google.android.gms.maps.StreetViewPanorama.OnStreetViewPanoramaChangeListener; +import com.google.android.gms.maps.StreetViewPanorama.OnStreetViewPanoramaClickListener; +import com.google.android.gms.maps.StreetViewPanorama.OnStreetViewPanoramaLongClickListener; +import com.google.android.gms.maps.SupportStreetViewPanoramaFragment; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.StreetViewPanoramaCamera; +import com.google.android.gms.maps.model.StreetViewPanoramaLocation; +import com.google.android.gms.maps.model.StreetViewPanoramaOrientation; + +/** + * This shows how to listen to some {@link StreetViewPanorama} events. + */ +public class StreetViewPanoramaEventsDemoActivity extends AppCompatActivity + implements OnStreetViewPanoramaChangeListener, OnStreetViewPanoramaCameraChangeListener, + OnStreetViewPanoramaClickListener, OnStreetViewPanoramaLongClickListener { + + // George St, Sydney + private static final LatLng SYDNEY = new LatLng(-33.87365, 151.20689); + + private StreetViewPanorama streetViewPanorama; + + private TextView panoChangeTimesTextView; + + private TextView panoCameraChangeTextView; + + private TextView panoClickTextView; + + private TextView panoLongClickTextView; + + private int panoChangeTimes = 0; + + private int panoCameraChangeTimes = 0; + + private int panoClickTimes = 0; + + private int panoLongClickTimes = 0; + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.street_view_panorama_events_demo); + + panoChangeTimesTextView = findViewById(R.id.change_pano); + panoCameraChangeTextView = findViewById(R.id.change_camera); + panoClickTextView = findViewById(R.id.click_pano); + panoLongClickTextView = findViewById(R.id.long_click_pano); + + SupportStreetViewPanoramaFragment streetViewPanoramaFragment = + (SupportStreetViewPanoramaFragment) + getSupportFragmentManager().findFragmentById(R.id.streetviewpanorama); + streetViewPanoramaFragment.getStreetViewPanoramaAsync( + panorama -> { + streetViewPanorama = panorama; + streetViewPanorama.setOnStreetViewPanoramaChangeListener( + StreetViewPanoramaEventsDemoActivity.this); + streetViewPanorama.setOnStreetViewPanoramaCameraChangeListener( + StreetViewPanoramaEventsDemoActivity.this); + streetViewPanorama.setOnStreetViewPanoramaClickListener( + StreetViewPanoramaEventsDemoActivity.this); + streetViewPanorama.setOnStreetViewPanoramaLongClickListener( + StreetViewPanoramaEventsDemoActivity.this); + + // Only set the panorama to SYDNEY on startup (when no panoramas have been + // loaded which is when the savedInstanceState is null). + if (savedInstanceState == null) { + streetViewPanorama.setPosition(SYDNEY); + } + }); + } + + @Override + public void onStreetViewPanoramaChange(StreetViewPanoramaLocation location) { + if (location != null) { + panoChangeTimesTextView.setText("Times panorama changed=" + ++panoChangeTimes); + } + } + + @Override + public void onStreetViewPanoramaCameraChange(StreetViewPanoramaCamera camera) { + panoCameraChangeTextView.setText("Times camera changed=" + ++panoCameraChangeTimes); + } + + @Override + public void onStreetViewPanoramaClick(StreetViewPanoramaOrientation orientation) { + Point point = streetViewPanorama.orientationToPoint(orientation); + if (point != null) { + panoClickTimes++; + panoClickTextView.setText( + "Times clicked=" + panoClickTimes + " : " + point.toString()); + streetViewPanorama.animateTo( + new StreetViewPanoramaCamera.Builder() + .orientation(orientation) + .zoom(streetViewPanorama.getPanoramaCamera().zoom) + .build(), 1000); + } + } + + @Override + public void onStreetViewPanoramaLongClick(StreetViewPanoramaOrientation orientation) { + Point point = streetViewPanorama.orientationToPoint(orientation); + if (point != null) { + panoLongClickTimes++; + panoLongClickTextView.setText( + "Times long clicked=" + panoLongClickTimes + " : " + point.toString()); + } + } +} diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/StreetViewPanoramaNavigationDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/StreetViewPanoramaNavigationDemoActivity.java new file mode 100755 index 000000000..e0f8b847d --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/StreetViewPanoramaNavigationDemoActivity.java @@ -0,0 +1,270 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +package com.example.mapdemo; + +import com.google.android.gms.maps.OnStreetViewPanoramaReadyCallback; +import com.google.android.gms.maps.StreetViewPanorama; +import com.google.android.gms.maps.SupportStreetViewPanoramaFragment; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.StreetViewPanoramaCamera; +import com.google.android.gms.maps.model.StreetViewPanoramaLink; +import com.google.android.gms.maps.model.StreetViewPanoramaLocation; + +import android.os.Bundle; +import android.view.View; +import android.widget.SeekBar; +import android.widget.Toast; + +import androidx.appcompat.app.AppCompatActivity; + + +/** + * This shows how to create an activity with access to all the options in Panorama + * which can be adjusted dynamically + */ + +public class StreetViewPanoramaNavigationDemoActivity extends AppCompatActivity { + + // George St, Sydney + private static final LatLng SYDNEY = new LatLng(-33.87365, 151.20689); + + // Cole St, San Fran + private static final LatLng SAN_FRAN = new LatLng(37.769263, -122.450727); + + // Santorini, Greece + private static final String SANTORINI = "WddsUw1geEoAAAQIt9RnsQ"; + + // LatLng with no panorama + private static final LatLng INVALID = new LatLng(-45.125783, 151.276417); + + /** + * The amount in degrees by which to scroll the camera + */ + private static final int PAN_BY_DEG = 30; + + private static final float ZOOM_BY = 0.5f; + + private StreetViewPanorama mStreetViewPanorama; + + private SeekBar mCustomDurationBar; + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.street_view_panorama_navigation_demo); + + SupportStreetViewPanoramaFragment streetViewPanoramaFragment = + (SupportStreetViewPanoramaFragment) + getSupportFragmentManager().findFragmentById(R.id.streetviewpanorama); + streetViewPanoramaFragment.getStreetViewPanoramaAsync( + new OnStreetViewPanoramaReadyCallback() { + @Override + public void onStreetViewPanoramaReady(StreetViewPanorama panorama) { + mStreetViewPanorama = panorama; + // Only set the panorama to SYDNEY on startup (when no panoramas have been + // loaded which is when the savedInstanceState is null). + if (savedInstanceState == null) { + mStreetViewPanorama.setPosition(SYDNEY); + } + } + }); + mCustomDurationBar = (SeekBar) findViewById(R.id.duration_bar); + } + + /** + * When the panorama is not ready the PanoramaView cannot be used. This should be called on + * all entry points that call methods on the Panorama API. + */ + private boolean checkReady() { + if (mStreetViewPanorama == null) { + Toast.makeText(this, R.string.panorama_not_ready, Toast.LENGTH_SHORT).show(); + return false; + } + return true; + } + + /** + * Called when the Go To San Fran button is clicked. + */ + public void onGoToSanFran(View view) { + if (!checkReady()) { + return; + } + mStreetViewPanorama.setPosition(SAN_FRAN, 30); + } + + /** + * Called when the Animate To Sydney button is clicked. + */ + public void onGoToSydney(View view) { + if (!checkReady()) { + return; + } + mStreetViewPanorama.setPosition(SYDNEY); + } + + /** + * Called when the Animate To Santorini button is clicked. + */ + public void onGoToSantorini(View view) { + if (!checkReady()) { + return; + } + mStreetViewPanorama.setPosition(SANTORINI); + } + + /** + * Called when the Animate To Invalid button is clicked. + */ + public void onGoToInvalid(View view) { + if (!checkReady()) { + return; + } + mStreetViewPanorama.setPosition(INVALID); + } + + public void onZoomIn(View view) { + if (!checkReady()) { + return; + } + + mStreetViewPanorama.animateTo( + new StreetViewPanoramaCamera.Builder().zoom( + mStreetViewPanorama.getPanoramaCamera().zoom + ZOOM_BY) + .tilt(mStreetViewPanorama.getPanoramaCamera().tilt) + .bearing(mStreetViewPanorama.getPanoramaCamera().bearing) + .build(), getDuration()); + } + + public void onZoomOut(View view) { + if (!checkReady()) { + return; + } + + mStreetViewPanorama.animateTo( + new StreetViewPanoramaCamera.Builder().zoom( + mStreetViewPanorama.getPanoramaCamera().zoom - ZOOM_BY) + .tilt(mStreetViewPanorama.getPanoramaCamera().tilt) + .bearing(mStreetViewPanorama.getPanoramaCamera().bearing) + .build(), getDuration()); + } + + public void onPanLeft(View view) { + if (!checkReady()) { + return; + } + + mStreetViewPanorama.animateTo( + new StreetViewPanoramaCamera.Builder().zoom( + mStreetViewPanorama.getPanoramaCamera().zoom) + .tilt(mStreetViewPanorama.getPanoramaCamera().tilt) + .bearing(mStreetViewPanorama.getPanoramaCamera().bearing - PAN_BY_DEG) + .build(), getDuration()); + } + + public void onPanRight(View view) { + if (!checkReady()) { + return; + } + + mStreetViewPanorama.animateTo( + new StreetViewPanoramaCamera.Builder().zoom( + mStreetViewPanorama.getPanoramaCamera().zoom) + .tilt(mStreetViewPanorama.getPanoramaCamera().tilt) + .bearing(mStreetViewPanorama.getPanoramaCamera().bearing + PAN_BY_DEG) + .build(), getDuration()); + + } + + public void onPanUp(View view) { + if (!checkReady()) { + return; + } + + float currentTilt = mStreetViewPanorama.getPanoramaCamera().tilt; + float newTilt = currentTilt + PAN_BY_DEG; + + newTilt = (newTilt > 90) ? 90 : newTilt; + + mStreetViewPanorama.animateTo( + new StreetViewPanoramaCamera.Builder() + .zoom(mStreetViewPanorama.getPanoramaCamera().zoom) + .tilt(newTilt) + .bearing(mStreetViewPanorama.getPanoramaCamera().bearing) + .build(), getDuration()); + } + + public void onPanDown(View view) { + if (!checkReady()) { + return; + } + + float currentTilt = mStreetViewPanorama.getPanoramaCamera().tilt; + float newTilt = currentTilt - PAN_BY_DEG; + + newTilt = (newTilt < -90) ? -90 : newTilt; + + mStreetViewPanorama.animateTo( + new StreetViewPanoramaCamera.Builder() + .zoom(mStreetViewPanorama.getPanoramaCamera().zoom) + .tilt(newTilt) + .bearing(mStreetViewPanorama.getPanoramaCamera().bearing) + .build(), getDuration()); + } + + public void onRequestPosition(View view) { + if (!checkReady()) { + return; + } + if (mStreetViewPanorama.getLocation() != null) { + Toast.makeText(view.getContext(), mStreetViewPanorama.getLocation().position.toString(), + Toast.LENGTH_SHORT).show(); + } + } + + public void onMovePosition(View view) { + StreetViewPanoramaLocation location = mStreetViewPanorama.getLocation(); + StreetViewPanoramaCamera camera = mStreetViewPanorama.getPanoramaCamera(); + if (location != null && location.links != null) { + StreetViewPanoramaLink link = findClosestLinkToBearing(location.links, camera.bearing); + mStreetViewPanorama.setPosition(link.panoId); + } + } + + public static StreetViewPanoramaLink findClosestLinkToBearing(StreetViewPanoramaLink[] links, + float bearing) { + float minBearingDiff = 360; + StreetViewPanoramaLink closestLink = links[0]; + for (StreetViewPanoramaLink link : links) { + if (minBearingDiff > findNormalizedDifference(bearing, link.bearing)) { + minBearingDiff = findNormalizedDifference(bearing, link.bearing); + closestLink = link; + } + } + return closestLink; + } + + // Find the difference between angle a and b as a value between 0 and 180 + public static float findNormalizedDifference(float a, float b) { + float diff = a - b; + float normalizedDiff = diff - (float) (360 * Math.floor(diff / 360.0f)); + return (normalizedDiff < 180.0f) ? normalizedDiff : 360.0f - normalizedDiff; + } + + private long getDuration() { + return mCustomDurationBar.getProgress(); + } +} diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/StreetViewPanoramaOptionsDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/StreetViewPanoramaOptionsDemoActivity.java new file mode 100755 index 000000000..e8916817e --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/StreetViewPanoramaOptionsDemoActivity.java @@ -0,0 +1,132 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +package com.example.mapdemo; + +import android.os.Bundle; +import android.view.View; +import android.widget.CheckBox; +import android.widget.Toast; + +import androidx.appcompat.app.AppCompatActivity; + +import com.google.android.gms.maps.StreetViewPanorama; +import com.google.android.gms.maps.SupportStreetViewPanoramaFragment; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.StreetViewSource; + +/** + * This shows how to create an activity with static streetview (all options have been switched off) + */ +public class StreetViewPanoramaOptionsDemoActivity extends AppCompatActivity { + + // Cole St, San Fran + private static final LatLng SAN_FRAN = new LatLng(37.765927, -122.449972); + + private static int RADIUS = 20; + + private StreetViewPanorama streetViewPanorama; + + private CheckBox streetNameCheckbox; + + private CheckBox navigationCheckbox; + + private CheckBox zoomCheckbox; + + private CheckBox panningCheckbox; + + private CheckBox outdoorCheckbox; + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.street_view_panorama_options_demo); + + streetNameCheckbox = findViewById(R.id.streetnames); + navigationCheckbox = findViewById(R.id.navigation); + zoomCheckbox = findViewById(R.id.zoom); + panningCheckbox = findViewById(R.id.panning); + outdoorCheckbox = findViewById(R.id.outdoor); + + SupportStreetViewPanoramaFragment streetViewPanoramaFragment = + (SupportStreetViewPanoramaFragment) + getSupportFragmentManager().findFragmentById(R.id.streetviewpanorama); + streetViewPanoramaFragment.getStreetViewPanoramaAsync( + panorama -> { + streetViewPanorama = panorama; + panorama.setStreetNamesEnabled(streetNameCheckbox.isChecked()); + panorama.setUserNavigationEnabled(navigationCheckbox.isChecked()); + panorama.setZoomGesturesEnabled(zoomCheckbox.isChecked()); + panorama.setPanningGesturesEnabled(panningCheckbox.isChecked()); + + // Only set the panorama to SAN_FRAN on startup (when no panoramas have been + // loaded which is when the savedInstanceState is null). + if (savedInstanceState == null) { + setPosition(); + } + }); + } + + private void setPosition() { + streetViewPanorama.setPosition( + SAN_FRAN, + RADIUS, + outdoorCheckbox.isChecked() ? StreetViewSource.OUTDOOR : StreetViewSource.DEFAULT + ); + } + + private boolean checkReady() { + if (streetViewPanorama == null) { + Toast.makeText(this, R.string.map_not_ready, Toast.LENGTH_SHORT).show(); + return false; + } + return true; + } + + public void onStreetNamesToggled(View view) { + if (!checkReady()) { + return; + } + streetViewPanorama.setStreetNamesEnabled(streetNameCheckbox.isChecked()); + } + + public void onNavigationToggled(View view) { + if (!checkReady()) { + return; + } + streetViewPanorama.setUserNavigationEnabled(navigationCheckbox.isChecked()); + } + + public void onZoomToggled(View view) { + if (!checkReady()) { + return; + } + streetViewPanorama.setZoomGesturesEnabled(zoomCheckbox.isChecked()); + } + + public void onPanningToggled(View view) { + if (!checkReady()) { + return; + } + streetViewPanorama.setPanningGesturesEnabled(panningCheckbox.isChecked()); + } + + public void onOutdoorToggled(View view) { + if (!checkReady()) { + return; + } + setPosition(); + } +} diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/StreetViewPanoramaViewDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/StreetViewPanoramaViewDemoActivity.java new file mode 100755 index 000000000..b242a8f94 --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/StreetViewPanoramaViewDemoActivity.java @@ -0,0 +1,93 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +package com.example.mapdemo; + +import com.google.android.gms.maps.StreetViewPanoramaOptions; +import com.google.android.gms.maps.StreetViewPanoramaView; +import com.google.android.gms.maps.model.LatLng; + +import android.os.Bundle; +import android.view.ViewGroup.LayoutParams; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; + +/** + * This shows how to create a simple activity with streetview + */ +public class StreetViewPanoramaViewDemoActivity extends AppCompatActivity { + + // George St, Sydney + private static final LatLng SYDNEY = new LatLng(-33.87365, 151.20689); + + private StreetViewPanoramaView streetViewPanoramaView; + + private static final String STREETVIEW_BUNDLE_KEY = "StreetViewBundleKey"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + StreetViewPanoramaOptions options = new StreetViewPanoramaOptions(); + if (savedInstanceState == null) { + options.position(SYDNEY); + } + + streetViewPanoramaView = new StreetViewPanoramaView(this, options); + addContentView(streetViewPanoramaView, + new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); + + // *** IMPORTANT *** + // StreetViewPanoramaView requires that the Bundle you pass contain _ONLY_ + // StreetViewPanoramaView SDK objects or sub-Bundles. + Bundle streetViewBundle = null; + if (savedInstanceState != null) { + streetViewBundle = savedInstanceState.getBundle(STREETVIEW_BUNDLE_KEY); + } + streetViewPanoramaView.onCreate(streetViewBundle); + } + + @Override + protected void onResume() { + streetViewPanoramaView.onResume(); + super.onResume(); + } + + @Override + protected void onPause() { + streetViewPanoramaView.onPause(); + super.onPause(); + } + + @Override + protected void onDestroy() { + streetViewPanoramaView.onDestroy(); + super.onDestroy(); + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + + Bundle streetViewBundle = outState.getBundle(STREETVIEW_BUNDLE_KEY); + if (streetViewBundle == null) { + streetViewBundle = new Bundle(); + outState.putBundle(STREETVIEW_BUNDLE_KEY, streetViewBundle); + } + + streetViewPanoramaView.onSaveInstanceState(streetViewBundle); + } +} diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/StyledMapDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/StyledMapDemoActivity.java new file mode 100644 index 000000000..933824778 --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/StyledMapDemoActivity.java @@ -0,0 +1,184 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +package com.example.mapdemo; + +import com.google.android.gms.maps.CameraUpdateFactory; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.MapStyleOptions; + +import android.content.DialogInterface; +import android.os.Bundle; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.widget.Toast; + +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; + +import java.util.ArrayList; +import java.util.List; + +/** + * This shows how to style a map with JSON. + */ +public class StyledMapDemoActivity extends AppCompatActivity implements OnMapReadyCallback { + + private GoogleMap mMap = null; + + private static final String TAG = StyledMapDemoActivity.class.getSimpleName(); + + private static final String SELECTED_STYLE = "selected_style"; + + // Stores the ID of the currently selected style, so that we can re-apply it when + // the activity restores state, for example when the device changes orientation. + private int mSelectedStyleId = R.string.style_label_default; + + // These are simply the string resource IDs for each of the style names. We use them + // as identifiers when choosing which style to apply. + private int mStyleIds[] = { + R.string.style_label_retro, + R.string.style_label_night, + R.string.style_label_grayscale, + R.string.style_label_no_pois_no_transit, + R.string.style_label_default, + }; + + private static final LatLng SYDNEY = new LatLng(-33.8688, 151.2093); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (savedInstanceState != null) { + mSelectedStyleId = savedInstanceState.getInt(SELECTED_STYLE); + } + setContentView(R.layout.styled_map_demo); + + SupportMapFragment mapFragment = + (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map); + mapFragment.getMapAsync(this); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + // Store the selected map style, so we can assign it when the activity resumes. + outState.putInt(SELECTED_STYLE, mSelectedStyleId); + super.onSaveInstanceState(outState); + } + + @Override + public void onMapReady(GoogleMap map) { + mMap = map; + mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(SYDNEY, 14)); + setSelectedStyle(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.styled_map, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.menu_style_choose) { + showStylesDialog(); + } + return true; + } + + /** + * Shows a dialog listing the styles to choose from, and applies the selected + * style when chosen. + */ + private void showStylesDialog() { + // mStyleIds stores each style's resource ID, and we extract the names here, rather + // than using an XML array resource which AlertDialog.Builder.setItems() can also + // accept. We do this since using an array resource would mean we would not have + // constant values we can switch/case on, when choosing which style to apply. + List styleNames = new ArrayList<>(); + for (int style : mStyleIds) { + styleNames.add(getString(style)); + } + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(getString(R.string.style_choose)); + builder.setItems(styleNames.toArray(new CharSequence[styleNames.size()]), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mSelectedStyleId = mStyleIds[which]; + String msg = getString(R.string.style_set_to, getString(mSelectedStyleId)); + Toast.makeText(getBaseContext(), msg, Toast.LENGTH_SHORT).show(); + Log.d(TAG, msg); + setSelectedStyle(); + } + }); + builder.show(); + } + + /** + * Creates a {@link MapStyleOptions} object via loadRawResourceStyle() (or via the + * constructor with a JSON String), then sets it on the {@link GoogleMap} instance, + * via the setMapStyle() method. + */ + private void setSelectedStyle() { + MapStyleOptions style; + int id = mSelectedStyleId; + if (id == R.string.style_label_retro) { + // Sets the retro style via raw resource JSON. + style = MapStyleOptions.loadRawResourceStyle(this, R.raw.mapstyle_retro); + } else if (id == R.string.style_label_night) { + // Sets the night style via raw resource JSON. + style = MapStyleOptions.loadRawResourceStyle(this, R.raw.mapstyle_night); + } else if (id == R.string.style_label_grayscale) { + // Sets the grayscale style via raw resource JSON. + style = MapStyleOptions.loadRawResourceStyle(this, R.raw.mapstyle_grayscale); + } else if (id == R.string.style_label_no_pois_no_transit) { + // Sets the no POIs or transit style via JSON string. + style = new MapStyleOptions("[" + + " {" + + " \"featureType\":\"poi.business\"," + + " \"elementType\":\"all\"," + + " \"stylers\":[" + + " {" + + " \"visibility\":\"off\"" + + " }" + + " ]" + + " }," + + " {" + + " \"featureType\":\"transit\"," + + " \"elementType\":\"all\"," + + " \"stylers\":[" + + " {" + + " \"visibility\":\"off\"" + + " }" + + " ]" + + " }" + + "]"); + } else if (id == R.string.style_label_default) { + // Removes previously set style, by setting it to null. + style = null; + } else { + return; + } + mMap.setMapStyle(style); + } + +} \ No newline at end of file diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/TagsDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/TagsDemoActivity.java new file mode 100644 index 000000000..ef4ebf5f5 --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/TagsDemoActivity.java @@ -0,0 +1,227 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +package com.example.mapdemo; + +import android.graphics.Color; +import android.os.Bundle; +import android.widget.TextView; + +import androidx.appcompat.app.AppCompatActivity; + +import com.google.android.gms.maps.CameraUpdateFactory; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.GoogleMap.OnCircleClickListener; +import com.google.android.gms.maps.GoogleMap.OnGroundOverlayClickListener; +import com.google.android.gms.maps.GoogleMap.OnMarkerClickListener; +import com.google.android.gms.maps.GoogleMap.OnPolygonClickListener; +import com.google.android.gms.maps.GoogleMap.OnPolylineClickListener; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.UiSettings; +import com.google.android.gms.maps.model.BitmapDescriptorFactory; +import com.google.android.gms.maps.model.Circle; +import com.google.android.gms.maps.model.CircleOptions; +import com.google.android.gms.maps.model.GroundOverlay; +import com.google.android.gms.maps.model.GroundOverlayOptions; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.LatLngBounds; +import com.google.android.gms.maps.model.Marker; +import com.google.android.gms.maps.model.MarkerOptions; +import com.google.android.gms.maps.model.Polygon; +import com.google.android.gms.maps.model.PolygonOptions; +import com.google.android.gms.maps.model.Polyline; +import com.google.android.gms.maps.model.PolylineOptions; + +/** + * This shows how to use setTag/getTag on API objects. + */ +public class TagsDemoActivity extends AppCompatActivity implements + OnCircleClickListener, + OnGroundOverlayClickListener, + OnMarkerClickListener, + OnMapAndViewReadyListener.OnGlobalLayoutAndMapReadyListener, + OnPolygonClickListener, + OnPolylineClickListener { + + private static final LatLng ADELAIDE = new LatLng(-34.92873, 138.59995); + private static final LatLng BRISBANE = new LatLng(-27.47093, 153.0235); + private static final LatLng DARWIN = new LatLng(-12.425892, 130.86327); + private static final LatLng HOBART = new LatLng(-42.8823388, 147.311042); + private static final LatLng PERTH = new LatLng(-31.952854, 115.857342); + private static final LatLng SYDNEY = new LatLng(-33.87365, 151.20689); + + private static class CustomTag { + private final String description; + private int clickCount; + + public CustomTag(String description) { + this.description = description; + clickCount = 0; + } + + public void incrementClickCount() { + clickCount++; + } + + @Override + public String toString() { + return "The " + description + " has been clicked " + clickCount + " times."; + } + } + + private GoogleMap mMap = null; + + private Circle mAdelaideCircle; + private GroundOverlay mSydneyGroundOverlay; + private Marker mHobartMarker; + private Polygon mDarwinPolygon; + private Polyline mPolyline; + + private TextView mTagText; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.tags_demo); + + mTagText = (TextView) findViewById(R.id.tag_text); + + SupportMapFragment mapFragment = + (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map); + new OnMapAndViewReadyListener(mapFragment, this); + } + + @Override + public void onMapReady(GoogleMap map) { + mMap = map; + + UiSettings mUiSettings = mMap.getUiSettings(); + + // Turn off the map toolbar. + mUiSettings.setMapToolbarEnabled(false); + + // Disable interaction with the map - other than clicking. + mUiSettings.setZoomControlsEnabled(false); + mUiSettings.setScrollGesturesEnabled(false); + mUiSettings.setZoomGesturesEnabled(false); + mUiSettings.setTiltGesturesEnabled(false); + mUiSettings.setRotateGesturesEnabled(false); + + // Add a circle, a ground overlay, a marker, a polygon and a polyline to the map. + addObjectsToMap(); + + // Set listeners for click events. See the bottom of this class for their behavior. + mMap.setOnCircleClickListener(this); + mMap.setOnGroundOverlayClickListener(this); + mMap.setOnMarkerClickListener(this); + mMap.setOnPolygonClickListener(this); + mMap.setOnPolylineClickListener(this); + + // Override the default content description on the view, for accessibility mode. + // Ideally this string would be localised. + map.setContentDescription(getString(R.string.tags_demo_map_description)); + + // Create bounds that include all locations of the map. + LatLngBounds bounds = new LatLngBounds.Builder() + .include(ADELAIDE) + .include(BRISBANE) + .include(DARWIN) + .include(HOBART) + .include(PERTH) + .include(SYDNEY) + .build(); + mMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 100)); + } + + private void addObjectsToMap() { + // A circle centered on Adelaide. + mAdelaideCircle = mMap.addCircle(new CircleOptions() + .center(ADELAIDE) + .radius(500000) + .fillColor(Color.argb(150, 66, 173, 244)) + .strokeColor(Color.rgb(66, 173, 244)) + .clickable(true)); + mAdelaideCircle.setTag(new CustomTag("Adelaide circle")); + + // A ground overlay at Sydney. + mSydneyGroundOverlay = mMap.addGroundOverlay(new GroundOverlayOptions() + .image(BitmapDescriptorFactory.fromResource(R.drawable.harbour_bridge)) + .position(SYDNEY, 700000) + .clickable(true)); + mSydneyGroundOverlay.setTag(new CustomTag("Sydney ground overlay")); + + // A marker at Hobart. + mHobartMarker = mMap.addMarker(new MarkerOptions().position(HOBART)); + mHobartMarker.setTag(new CustomTag("Hobart marker")); + + // A polygon centered at Darwin. + mDarwinPolygon = mMap.addPolygon(new PolygonOptions() + .add( + new LatLng(DARWIN.latitude + 3, DARWIN.longitude - 3), + new LatLng(DARWIN.latitude + 3, DARWIN.longitude + 3), + new LatLng(DARWIN.latitude - 3, DARWIN.longitude + 3), + new LatLng(DARWIN.latitude - 3, DARWIN.longitude - 3)) + .fillColor(Color.argb(150, 34, 173, 24)) + .strokeColor(Color.rgb(34, 173, 24)) + .clickable(true)); + mDarwinPolygon.setTag(new CustomTag("Darwin polygon")); + + // A polyline from Perth to Brisbane. + mPolyline = mMap.addPolyline(new PolylineOptions() + .add(PERTH, BRISBANE) + .color(Color.rgb(103, 24, 173)) + .width(30) + .clickable(true)); + mPolyline.setTag(new CustomTag("Perth to Brisbane polyline")); + } + + // + // Click event listeners. + // + + private void onClick(CustomTag tag) { + tag.incrementClickCount(); + mTagText.setText(tag.toString()); + } + + @Override + public void onCircleClick(Circle circle) { + onClick((CustomTag) circle.getTag()); + } + + @Override + public void onGroundOverlayClick(GroundOverlay groundOverlay) { + onClick((CustomTag) groundOverlay.getTag()); + } + + @Override + public boolean onMarkerClick(final Marker marker) { + onClick((CustomTag) marker.getTag()); + // We return true to indicate that we have consumed the event and that we do not wish + // for the default behavior to occur (which is for the camera to move such that the + // marker is centered and for the marker's info window to open, if it has one). + return true; + } + + @Override + public void onPolygonClick(Polygon polygon) { + onClick((CustomTag) polygon.getTag()); + } + + @Override + public void onPolylineClick(Polyline polyline) { + onClick((CustomTag) polyline.getTag()); + } +} \ No newline at end of file diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/TileCoordinateDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/TileCoordinateDemoActivity.java new file mode 100755 index 000000000..72ed124b1 --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/TileCoordinateDemoActivity.java @@ -0,0 +1,107 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +package com.example.mapdemo; + +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.model.Tile; +import com.google.android.gms.maps.model.TileOverlayOptions; +import com.google.android.gms.maps.model.TileProvider; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.os.Bundle; + +import androidx.appcompat.app.AppCompatActivity; + +import java.io.ByteArrayOutputStream; + +/** + * This demonstrates tile overlay coordinates. + */ +public class TileCoordinateDemoActivity extends AppCompatActivity implements OnMapReadyCallback { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.tile_coordinate_demo); + + SupportMapFragment mapFragment = + (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map); + mapFragment.getMapAsync(this); + } + + @Override + public void onMapReady(GoogleMap map) { + TileProvider coordTileProvider = new CoordTileProvider(this.getApplicationContext()); + map.addTileOverlay(new TileOverlayOptions().tileProvider(coordTileProvider)); + } + + private static class CoordTileProvider implements TileProvider { + + private static final int TILE_SIZE_DP = 256; + + private final float scaleFactor; + + private final Bitmap borderTile; + + public CoordTileProvider(Context context) { + /* Scale factor based on density, with a 0.6 multiplier to increase tile generation + * speed */ + scaleFactor = context.getResources().getDisplayMetrics().density * 0.6f; + Paint borderPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + borderPaint.setStyle(Paint.Style.STROKE); + borderTile = Bitmap.createBitmap((int) (TILE_SIZE_DP * scaleFactor), + (int) (TILE_SIZE_DP * scaleFactor), android.graphics.Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(borderTile); + canvas.drawRect(0, 0, TILE_SIZE_DP * scaleFactor, TILE_SIZE_DP * scaleFactor, + borderPaint); + } + + @Override + public Tile getTile(int x, int y, int zoom) { + Bitmap coordTile = drawTileCoords(x, y, zoom); + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + coordTile.compress(Bitmap.CompressFormat.PNG, 0, stream); + byte[] bitmapData = stream.toByteArray(); + return new Tile((int) (TILE_SIZE_DP * scaleFactor), + (int) (TILE_SIZE_DP * scaleFactor), bitmapData); + } + + private Bitmap drawTileCoords(int x, int y, int zoom) { + // Synchronize copying the bitmap to avoid a race condition in some devices. + Bitmap copy = null; + synchronized (borderTile) { + copy = borderTile.copy(android.graphics.Bitmap.Config.ARGB_8888, true); + } + Canvas canvas = new Canvas(copy); + String tileCoords = "(" + x + ", " + y + ")"; + String zoomLevel = "zoom = " + zoom; + /* Paint is not thread safe. */ + Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mTextPaint.setTextAlign(Paint.Align.CENTER); + mTextPaint.setTextSize(18 * scaleFactor); + canvas.drawText(tileCoords, TILE_SIZE_DP * scaleFactor / 2, + TILE_SIZE_DP * scaleFactor / 2, mTextPaint); + canvas.drawText(zoomLevel, TILE_SIZE_DP * scaleFactor / 2, + TILE_SIZE_DP * scaleFactor * 2 / 3, mTextPaint); + return copy; + } + } +} diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/TileOverlayDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/TileOverlayDemoActivity.java new file mode 100644 index 000000000..695905675 --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/TileOverlayDemoActivity.java @@ -0,0 +1,112 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +package com.example.mapdemo; + +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.model.TileOverlay; +import com.google.android.gms.maps.model.TileOverlayOptions; +import com.google.android.gms.maps.model.TileProvider; +import com.google.android.gms.maps.model.UrlTileProvider; + +import android.os.Bundle; +import android.view.View; +import android.widget.CheckBox; +import android.widget.SeekBar; +import android.widget.SeekBar.OnSeekBarChangeListener; + +import androidx.appcompat.app.AppCompatActivity; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Locale; + +/** + * This demonstrates how to add a tile overlay to a map. + */ +public class TileOverlayDemoActivity extends AppCompatActivity + implements OnSeekBarChangeListener, OnMapReadyCallback { + + private static final int TRANSPARENCY_MAX = 100; + + /** This returns moon tiles. */ + private static final String MOON_MAP_URL_FORMAT = + "https://mw1.google.com/mw-planetary/lunar/lunarmaps_v1/clem_bw/%d/%d/%d.jpg"; + + private TileOverlay moonTiles; + private SeekBar transparencyBar; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.tile_overlay_demo); + + transparencyBar = (SeekBar) findViewById(R.id.transparencySeekBar); + transparencyBar.setMax(TRANSPARENCY_MAX); + transparencyBar.setProgress(0); + + SupportMapFragment mapFragment = + (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map); + mapFragment.getMapAsync(this); + } + + @Override + public void onMapReady(GoogleMap map) { + map.setMapType(GoogleMap.MAP_TYPE_NONE); + + TileProvider tileProvider = new UrlTileProvider(256, 256) { + @Override + public synchronized URL getTileUrl(int x, int y, int zoom) { + // The moon tile coordinate system is reversed. This is not normal. + int reversedY = (1 << zoom) - y - 1; + String s = String.format(Locale.US, MOON_MAP_URL_FORMAT, zoom, x, reversedY); + URL url = null; + try { + url = new URL(s); + } catch (MalformedURLException e) { + throw new AssertionError(e); + } + return url; + } + }; + + moonTiles = map.addTileOverlay(new TileOverlayOptions().tileProvider(tileProvider)); + transparencyBar.setOnSeekBarChangeListener(this); + } + + public void setFadeIn(View v) { + if (moonTiles == null) { + return; + } + moonTiles.setFadeIn(((CheckBox) v).isChecked()); + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (moonTiles != null) { + moonTiles.setTransparency((float) progress / (float) TRANSPARENCY_MAX); + } + } +} diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/UiSettingsDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/UiSettingsDemoActivity.java new file mode 100755 index 000000000..efa445a12 --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/UiSettingsDemoActivity.java @@ -0,0 +1,251 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.example.mapdemo; + +import android.Manifest.permission; +import android.annotation.SuppressLint; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.UiSettings; + +import android.Manifest; +import android.content.pm.PackageManager; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; +import android.view.View; +import android.widget.CheckBox; +import android.widget.Toast; + +/** + * This shows how UI settings can be toggled. + */ +public class UiSettingsDemoActivity extends AppCompatActivity implements OnMapReadyCallback { + + private GoogleMap mMap; + + private UiSettings mUiSettings; + + private CheckBox mMyLocationButtonCheckbox; + + private CheckBox mMyLocationLayerCheckbox; + + private static final int MY_LOCATION_PERMISSION_REQUEST_CODE = 1; + + private static final int LOCATION_LAYER_PERMISSION_REQUEST_CODE = 2; + + /** + * Flag indicating whether a requested permission has been denied after returning in {@link + * #onRequestPermissionsResult(int, String[], int[])}. + */ + private boolean mLocationPermissionDenied = false; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.ui_settings_demo); + + mMyLocationButtonCheckbox = (CheckBox) findViewById(R.id.mylocationbutton_toggle); + mMyLocationLayerCheckbox = (CheckBox) findViewById(R.id.mylocationlayer_toggle); + + SupportMapFragment mapFragment = + (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map); + mapFragment.getMapAsync(this); + } + + /** + * Returns whether the checkbox with the given id is checked. + */ + private boolean isChecked(int id) { + return ((CheckBox) findViewById(id)).isChecked(); + } + + @SuppressLint("MissingPermission") + @Override + public void onMapReady(GoogleMap map) { + mMap = map; + + mUiSettings = mMap.getUiSettings(); + + // Keep the UI Settings state in sync with the checkboxes. + mUiSettings.setZoomControlsEnabled(isChecked(R.id.zoom_buttons_toggle)); + mUiSettings.setCompassEnabled(isChecked(R.id.compass_toggle)); + mUiSettings.setMyLocationButtonEnabled(isChecked(R.id.mylocationbutton_toggle)); + mUiSettings.setScrollGesturesEnabled(isChecked(R.id.scroll_toggle)); + mUiSettings.setZoomGesturesEnabled(isChecked(R.id.zoom_gestures_toggle)); + mUiSettings.setTiltGesturesEnabled(isChecked(R.id.tilt_toggle)); + mUiSettings.setRotateGesturesEnabled(isChecked(R.id.rotate_toggle)); + + if (ActivityCompat.checkSelfPermission(this, permission.ACCESS_FINE_LOCATION) + != PackageManager.PERMISSION_GRANTED + && ActivityCompat.checkSelfPermission(this, permission.ACCESS_COARSE_LOCATION) + != PackageManager.PERMISSION_GRANTED) { + return; + } + mMap.setMyLocationEnabled(isChecked(R.id.mylocationlayer_toggle)); + } + + /** + * Checks if the map is ready (which depends on whether the Google Play services APK is + * available. This should be called prior to calling any methods on GoogleMap. + */ + private boolean checkReady() { + if (mMap == null) { + Toast.makeText(this, R.string.map_not_ready, Toast.LENGTH_SHORT).show(); + return false; + } + return true; + } + + public void setZoomButtonsEnabled(View v) { + if (!checkReady()) { + return; + } + // Enables/disables the zoom controls (+/- buttons in the bottom-right of the map for LTR + // locale or bottom-left for RTL locale). + mUiSettings.setZoomControlsEnabled(((CheckBox) v).isChecked()); + } + + public void setCompassEnabled(View v) { + if (!checkReady()) { + return; + } + // Enables/disables the compass (icon in the top-left for LTR locale or top-right for RTL + // locale that indicates the orientation of the map). + mUiSettings.setCompassEnabled(((CheckBox) v).isChecked()); + } + + public void setMyLocationButtonEnabled(View v) { + if (!checkReady()) { + return; + } + // Enables/disables the my location button (this DOES NOT enable/disable the my location + // dot/chevron on the map). The my location button will never appear if the my location + // layer is not enabled. + // First verify that the location permission has been granted. + if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) + == PackageManager.PERMISSION_GRANTED + || ContextCompat.checkSelfPermission(this, permission.ACCESS_COARSE_LOCATION) + == PackageManager.PERMISSION_GRANTED) { + mUiSettings.setMyLocationButtonEnabled(mMyLocationButtonCheckbox.isChecked()); + } else { + // Uncheck the box and request missing location permission. + mMyLocationButtonCheckbox.setChecked(false); + PermissionUtils + .requestLocationPermissions(this, MY_LOCATION_PERMISSION_REQUEST_CODE, false); + } + } + + @SuppressLint("MissingPermission") + public void setMyLocationLayerEnabled(View v) { + if (!checkReady()) { + return; + } + // Enables/disables the my location layer (i.e., the dot/chevron on the map). If enabled, it + // will also cause the my location button to show (if it is enabled); if disabled, the my + // location button will never show. + if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) + == PackageManager.PERMISSION_GRANTED || + ContextCompat.checkSelfPermission(this, permission.ACCESS_COARSE_LOCATION) + == PackageManager.PERMISSION_GRANTED) { + mMap.setMyLocationEnabled(mMyLocationLayerCheckbox.isChecked()); + } else { + // Uncheck the box and request missing location permission. + mMyLocationLayerCheckbox.setChecked(false); + PermissionUtils + .requestLocationPermissions(this, LOCATION_LAYER_PERMISSION_REQUEST_CODE, false); + } + } + + public void setScrollGesturesEnabled(View v) { + if (!checkReady()) { + return; + } + // Enables/disables scroll gestures (i.e. panning the map). + mUiSettings.setScrollGesturesEnabled(((CheckBox) v).isChecked()); + } + + public void setZoomGesturesEnabled(View v) { + if (!checkReady()) { + return; + } + // Enables/disables zoom gestures (i.e., double tap, pinch & stretch). + mUiSettings.setZoomGesturesEnabled(((CheckBox) v).isChecked()); + } + + public void setTiltGesturesEnabled(View v) { + if (!checkReady()) { + return; + } + // Enables/disables tilt gestures. + mUiSettings.setTiltGesturesEnabled(((CheckBox) v).isChecked()); + } + + public void setRotateGesturesEnabled(View v) { + if (!checkReady()) { + return; + } + // Enables/disables rotate gestures. + mUiSettings.setRotateGesturesEnabled(((CheckBox) v).isChecked()); + } + + @SuppressLint("MissingPermission") + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, + @NonNull int[] grantResults) { + if (requestCode == MY_LOCATION_PERMISSION_REQUEST_CODE) { + // Enable the My Location button if the permission has been granted. + if (PermissionUtils.isPermissionGranted(permissions, grantResults, + Manifest.permission.ACCESS_FINE_LOCATION) || + PermissionUtils.isPermissionGranted(permissions, grantResults, + permission.ACCESS_COARSE_LOCATION) + ) { + mUiSettings.setMyLocationButtonEnabled(true); + mMyLocationButtonCheckbox.setChecked(true); + } else { + mLocationPermissionDenied = true; + } + return; + } else if (requestCode == LOCATION_LAYER_PERMISSION_REQUEST_CODE) { + // Enable the My Location layer if the permission has been granted. + if (PermissionUtils.isPermissionGranted(permissions, grantResults, + Manifest.permission.ACCESS_FINE_LOCATION) || + PermissionUtils.isPermissionGranted(permissions, grantResults, + permission.ACCESS_COARSE_LOCATION) + ) { + mMap.setMyLocationEnabled(true); + mMyLocationLayerCheckbox.setChecked(true); + } else { + mLocationPermissionDenied = true; + } + return; + } + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + + @Override + protected void onResumeFragments() { + super.onResumeFragments(); + if (mLocationPermissionDenied) { + PermissionUtils.PermissionDeniedDialog + .newInstance(false).show(getSupportFragmentManager(), "dialog"); + mLocationPermissionDenied = false; + } + } +} diff --git a/ApiDemos/java/app/src/gms/java/com/example/mapdemo/VisibleRegionDemoActivity.java b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/VisibleRegionDemoActivity.java new file mode 100755 index 000000000..4b25d759b --- /dev/null +++ b/ApiDemos/java/app/src/gms/java/com/example/mapdemo/VisibleRegionDemoActivity.java @@ -0,0 +1,186 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.example.mapdemo; + +import com.google.android.gms.maps.CameraUpdateFactory; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.GoogleMap.OnCameraIdleListener; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.LatLngBounds; +import com.google.android.gms.maps.model.MarkerOptions; + +import android.os.Bundle; +import android.os.Handler; +import android.os.SystemClock; +import android.view.View; +import android.view.animation.Interpolator; +import android.view.animation.OvershootInterpolator; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.appcompat.app.AppCompatActivity; + +/** + * This shows how to use setPadding to allow overlays that obscure part of the map without + * obscuring the map UI or copyright notices. + */ +public class VisibleRegionDemoActivity extends AppCompatActivity implements + OnMapAndViewReadyListener.OnGlobalLayoutAndMapReadyListener { + + /** + * Note that this may be null if the Google Play services APK is not available. + */ + private GoogleMap mMap; + + private static final LatLng SOH = new LatLng(-33.85704, 151.21522); + + private static final LatLng SFO = new LatLng(37.614631, -122.385153); + + private static final LatLngBounds AUS = new LatLngBounds( + new LatLng(-44, 113), new LatLng(-10, 154)); + + private TextView mMessageView; + + /** Keep track of current values for padding, so we can animate from them. */ + int currentLeft = 150; + + int currentTop = 0; + + int currentRight = 0; + + int currentBottom = 0; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.visible_region_demo); + mMessageView = (TextView) findViewById(R.id.message_text); + + SupportMapFragment mapFragment = + (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map); + new OnMapAndViewReadyListener(mapFragment, this); + } + + @Override + public void onMapReady(GoogleMap map) { + mMap = map; + + // Move to a place with indoor (SFO airport). + mMap.setPadding(currentLeft, currentTop, currentRight, currentBottom); + mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(SFO, 18)); + // Add a marker to the Opera House. + mMap.addMarker(new MarkerOptions().position(SOH).title("Sydney Opera House")); + // Add a camera idle listener. + mMap.setOnCameraIdleListener(new OnCameraIdleListener() { + @Override + public void onCameraIdle() { + mMessageView.setText("CameraChangeListener: " + mMap.getCameraPosition()); + } + }); + } + + /** + * Checks if the map is ready (which depends on whether the Google Play services APK is + * available. This should be called prior to calling any methods on GoogleMap. + */ + private boolean checkReady() { + if (mMap == null) { + Toast.makeText(this, R.string.map_not_ready, Toast.LENGTH_SHORT).show(); + return false; + } + return true; + } + + public void moveToOperaHouse(View view) { + if (!checkReady()) { + return; + } + mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(SOH, 16)); + } + + public void moveToSFO(View view) { + if (!checkReady()) { + return; + } + mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(SFO, 18)); + } + + public void moveToAUS(View view) { + if (!checkReady()) { + return; + } + mMap.moveCamera(CameraUpdateFactory.newLatLngBounds(AUS, 0)); + } + + public void setNoPadding(View view) { + if (!checkReady()) { + return; + } + animatePadding(150, 0, 0, 0); + } + + public void setMorePadding(View view) { + if (!checkReady()) { + return; + } + View mapView = (getSupportFragmentManager().findFragmentById(R.id.map)).getView(); + int left = 150; + int top = 0; + int right = mapView.getWidth() / 3; + int bottom = mapView.getHeight() / 4; + animatePadding(left, top, right, bottom); + } + + public void animatePadding( + final int toLeft, final int toTop, final int toRight, final int toBottom) { + + final Handler handler = new Handler(); + final long start = SystemClock.uptimeMillis(); + final long duration = 1000; + + final Interpolator interpolator = new OvershootInterpolator(); + + final int startLeft = currentLeft; + final int startTop = currentTop; + final int startRight = currentRight; + final int startBottom = currentBottom; + + currentLeft = toLeft; + currentTop = toTop; + currentRight = toRight; + currentBottom = toBottom; + + handler.post(new Runnable() { + @Override + public void run() { + long elapsed = SystemClock.uptimeMillis() - start; + float t = interpolator.getInterpolation((float) elapsed / duration); + + int left = (int) (startLeft + ((toLeft - startLeft) * t)); + int top = (int) (startTop + ((toTop - startTop) * t)); + int right = (int) (startRight + ((toRight - startRight) * t)); + int bottom = (int) (startBottom + ((toBottom - startBottom) * t)); + + mMap.setPadding(left, top, right, bottom); + + if (elapsed < duration) { + // Post again 16ms later. + handler.postDelayed(this, 16); + } + } + }); + } +} diff --git a/ApiDemos/java/app/src/gms/res/layout-land/snapshot_demo.xml b/ApiDemos/java/app/src/gms/res/layout-land/snapshot_demo.xml new file mode 100755 index 000000000..09a39ca04 --- /dev/null +++ b/ApiDemos/java/app/src/gms/res/layout-land/snapshot_demo.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + +