Java tutorial
/* * Copyright (C) 2015 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.codetroopers.shakemytours.ui.activity; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; import android.location.Location; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.view.MenuItemCompat; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.CardView; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.ShareActionProvider; import android.support.v7.widget.Toolbar; import android.util.SparseArray; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import com.codetroopers.shakemytours.R; import com.codetroopers.shakemytours.core.entities.Travel; import com.codetroopers.shakemytours.service.JsonDirectionResponse; import com.codetroopers.shakemytours.util.PermissionUtils; import com.codetroopers.shakemytours.util.Strings; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.location.LocationServices; 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.MapView; import com.google.android.gms.maps.MapsInitializer; 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.PolylineOptions; import com.google.common.collect.Lists; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.lang.reflect.Type; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.List; import butterknife.Bind; import butterknife.ButterKnife; import timber.log.Timber; public class TripActivity extends AppCompatActivity implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener { public static final String PARAM_TRAVELS = "PARAM_TRAVELS"; public static final int PERMISSION_REQUEST_CODE = 12; private SparseArray<Bitmap> mMarkerList; private SparseArray<Integer> mAvailableColors; private ShareActionProvider mShareActionProvider; private boolean mLocationAllowed; public static Intent newIntent(Context context, ArrayList<Travel> selectedTravels) { Intent intent = new Intent(context, TripActivity.class); intent.putParcelableArrayListExtra(PARAM_TRAVELS, selectedTravels); return intent; } @Bind(R.id.trip_activity_mapview) MapView mapView; @Bind(R.id.trip_activity_recyclerview) RecyclerView mRecyclerView; int primaryColor; @Nullable private GoogleMap map; @Nullable private LatLng mLastKnownLatLng; private GoogleApiClient mGoogleApiClient; private ArrayList<Travel> mTravels; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_trip); ButterKnife.bind(this); mTravels = getIntent().getParcelableArrayListExtra(PARAM_TRAVELS); final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); getSupportActionBar().setDisplayHomeAsUpEnabled(true); initAvailableColors(); generateMarkers(mTravels.size()); primaryColor = getResources().getColor(R.color.colorPrimary); mapView.onCreate(savedInstanceState); setupRecyclerView(); buildGoogleApiClient(); } private void initAvailableColors() { mAvailableColors = new SparseArray<>(); mAvailableColors.put(0, getResources().getColor(R.color.map_marker_0)); mAvailableColors.put(1, getResources().getColor(R.color.map_marker_1)); mAvailableColors.put(2, getResources().getColor(R.color.map_marker_2)); mAvailableColors.put(3, getResources().getColor(R.color.map_marker_3)); mAvailableColors.put(4, getResources().getColor(R.color.map_marker_4)); } @Override protected void onStop() { if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) { mGoogleApiClient.disconnect(); } super.onStop(); } private void initMap() { String mapKey = getString(R.string.google_api_key); String orgineCoord = "origin=" + mLastKnownLatLng.latitude + "," + mLastKnownLatLng.longitude; String destCoord = "destination=" + mTravels.get(mTravels.size() - 1).latitude + "," + mTravels.get(mTravels.size() - 1).longitude; String waypoints = "waypoints="; for (int i = 0; i < mTravels.size() - 1; i++) { waypoints += mTravels.get(i).latitude + "," + mTravels.get(i).longitude + "|"; } URL url = null; try { url = new URL("https://maps.googleapis.com/maps/api/directions/json?" + orgineCoord + "&" + destCoord + "&" + waypoints + "&key=" + mapKey); } catch (MalformedURLException e) { e.printStackTrace(); } final URL finalUrl = url; new AsyncTask<Void, Void, JsonDirectionResponse>() { @Override protected JsonDirectionResponse doInBackground(Void... params) { JsonDirectionResponse response = null; HttpURLConnection request = null; try { request = (HttpURLConnection) finalUrl.openConnection(); } catch (IOException e) { e.printStackTrace(); } BufferedReader reader = null; Gson gson = new GsonBuilder().create(); try { reader = new BufferedReader(new InputStreamReader(request.getInputStream())); final Type result = new TypeToken<JsonDirectionResponse>() { }.getType(); response = gson.fromJson(reader, result); } catch (Exception e) { Timber.e(e, ""); } finally { if (null != reader) { try { reader.close(); } catch (IOException e) { Timber.e(e, ""); } } } return response; } @Override protected void onPostExecute(JsonDirectionResponse jsonDirectionResponse) { super.onPostExecute(jsonDirectionResponse); if (!TripActivity.this.isFinishing()) { showMapWithPoints(jsonDirectionResponse); } } }.execute(); } private List<LatLng> decodePoly(String encoded) { List<LatLng> poly = new ArrayList<LatLng>(); int index = 0, len = encoded.length(); int lat = 0, lng = 0; while (index < len) { int b, shift = 0, result = 0; do { b = encoded.charAt(index++) - 63; result |= (b & 0x1f) << shift; shift += 5; } while (b >= 0x20); int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1)); lat += dlat; shift = 0; result = 0; do { b = encoded.charAt(index++) - 63; result |= (b & 0x1f) << shift; shift += 5; } while (b >= 0x20); int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1)); lng += dlng; LatLng p = new LatLng((((double) lat / 1E5)), (((double) lng / 1E5))); poly.add(p); } return poly; } private void showMapWithPoints(JsonDirectionResponse jsonDirectionResponse) { MapsInitializer.initialize(this); List<MarkerOptions> mapMarkerOptionsList = new ArrayList<>(); List<PolylineOptions> mapPolylineOptionsList = new ArrayList<>(); final LatLngBounds.Builder boundsBuilder = new LatLngBounds.Builder(); for (int y = 0; y < mTravels.size(); y++) { Travel currentTravel = mTravels.get(y); //We dont add last because last have special marker mapMarkerOptionsList.add(new MarkerOptions().position(currentTravel.toLatLng()) .title(currentTravel.name).icon(BitmapDescriptorFactory.fromBitmap(mMarkerList.get(y)))); } ArrayList<LatLng> commuteLatLngPoints = new ArrayList<LatLng>(); PolylineOptions polyLineOptions = new PolylineOptions(); commuteLatLngPoints.add(mLastKnownLatLng); boundsBuilder.include(mLastKnownLatLng); List<LatLng> latLngs = decodePoly(jsonDirectionResponse.getEncodedPolyline()); for (LatLng point : latLngs) { //add point on cummute line commuteLatLngPoints.add(point); //add point in bound to perfect zoom map boundsBuilder.include(point); } //FIXME change color polyLineOptions.width(getResources().getDimensionPixelSize(R.dimen.map_polyline_walk_width)); polyLineOptions.color(primaryColor); polyLineOptions.addAll(commuteLatLngPoints); mapPolylineOptionsList.add(polyLineOptions); LatLngBounds mapBounds = boundsBuilder.build(); // Gets to GoogleMap from the MapView and does initialization stuff map = mapView.getMap(); if (map != null) { if (mLocationAllowed) { map.setMyLocationEnabled(true); } map.getUiSettings().setMyLocationButtonEnabled(true); for (MarkerOptions marker : mapMarkerOptionsList) { map.addMarker(marker); } for (PolylineOptions line : mapPolylineOptionsList) { map.addPolyline(line); } //init map position to start, so animate will be nice ///FIXME use my position a start stop // CameraUpdate cameraInit = CameraUpdateFactory.newLatLngZoom(currentTravel.startStation.coords.toLatLng(), getInitZoom()); // map.moveCamera(cameraInit); final CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngBounds(mapBounds, 15); map.setOnMapLoadedCallback(new GoogleMap.OnMapLoadedCallback() { @Override public void onMapLoaded() { //Bounds can only be done after map calculation map.animateCamera(cameraUpdate); } }); } } private void generateMarkers(int count) { mMarkerList = new SparseArray<>(); for (int i = 0; i < count; i++) { Bitmap intermediateMarker = createIntermediateMarker(i); mMarkerList.put(i, intermediateMarker); } } private Bitmap createIntermediateMarker(int position) { int accentColor = mAvailableColors.get(position); return createMarker(getResources().getDimensionPixelSize(R.dimen.map_marker_radius), getResources().getDimensionPixelSize(R.dimen.map_marker_stroke), null, Color.WHITE, accentColor); } private Bitmap createBigMarker(String letter) { return createMarker(getResources().getDimensionPixelSize(R.dimen.map_big_marker_radius), getResources().getDimensionPixelSize(R.dimen.map_big_marker_stroke), letter, Color.WHITE, primaryColor); } private Bitmap createMarker(int radius, int strokeWidth, String letter, int strokeColor, int backgroundColor) { int width = (radius * 2) + (strokeWidth * 2); Bitmap marker = Bitmap.createBitmap(width, width, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(marker); Paint strokePaint = new Paint(Paint.ANTI_ALIAS_FLAG); strokePaint.setColor(strokeColor); strokePaint.setShadowLayer(strokeWidth, 1.0f, 1.0f, Color.BLACK); canvas.drawCircle(width / 2, width / 2, radius, strokePaint); Paint backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); backgroundPaint.setColor(backgroundColor); canvas.drawCircle(width / 2, width / 2, radius - strokeWidth, backgroundPaint); if (letter != null) { Rect result = new Rect(); Paint textPaint = new Paint(Paint.ANTI_ALIAS_FLAG); textPaint.setTextAlign(Paint.Align.CENTER); textPaint.setTextSize(getResources().getDimensionPixelSize(R.dimen.map_marker_text_size)); textPaint.setColor(strokeColor); textPaint.getTextBounds(letter, 0, letter.length(), result); int yOffset = result.height() / 2; canvas.drawText(letter, width / 2, (width / 2) + yOffset, textPaint); } return marker; } @Override public void onResume() { if (mapView != null) { mapView.onResume(); } super.onResume(); } @Override public void onDestroy() { super.onDestroy(); if (mapView != null) { mapView.onDestroy(); } } @Override public void onLowMemory() { super.onLowMemory(); if (mapView != null) { mapView.onLowMemory(); } } @Override public void onPause() { if (mapView != null) { mapView.onPause(); } super.onPause(); } @Override public void onConnected(Bundle bundle) { Location mLastLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient); if (mLastLocation == null) { mLastKnownLatLng = new LatLng(47.400542, 0.685327); } else { mLastKnownLatLng = new LatLng(mLastLocation.getLatitude(), mLastLocation.getLongitude()); } initMap(); } protected synchronized void buildGoogleApiClient() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (PermissionUtils.hasNotLocation(this)) { PermissionUtils.askLocation(this, PERMISSION_REQUEST_CODE); } else { connectWithPermission(); } } else { connectWithPermission(); } } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == PERMISSION_REQUEST_CODE) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { connectWithPermission(); } else { mLastKnownLatLng = new LatLng(47.400542, 0.685327); initMap(); } } } private void connectWithPermission() { mGoogleApiClient = new GoogleApiClient.Builder(this).addConnectionCallbacks(this) .addOnConnectionFailedListener(this).addApi(LocationServices.API).build(); mGoogleApiClient.connect(); mLocationAllowed = true; } @Override public void onConnectionSuspended(int i) { } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate menu resource file. getMenuInflater().inflate(R.menu.menu_shake, menu); // Locate MenuItem with ShareActionProvider MenuItem item = menu.findItem(R.id.action_share); // Fetch and store ShareActionProvider mShareActionProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(item); mShareActionProvider.setShareIntent(createShareIntent()); // Return true to display menu return true; } private Intent createShareIntent() { Intent shareIntent = new Intent(Intent.ACTION_SEND); shareIntent.setType("text/plain"); List<String> names = Lists.newArrayList(); for (Travel travel : mTravels) { names.add(travel.name); } String textToShare = "This is a great shake (" + Strings.join("->", names) + "), SHAKE it out !"; shareIntent.putExtra(Intent.EXTRA_TEXT, textToShare); return shareIntent; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: finish(); return true; } return super.onOptionsItemSelected(item); } @Override public void onConnectionFailed(ConnectionResult connectionResult) { } private void setupRecyclerView() { mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); TravelTripRecyclerViewAdapter mTravelAdapter = new TravelTripRecyclerViewAdapter(); mRecyclerView.setAdapter(mTravelAdapter); mRecyclerView.setHasFixedSize(true); } public class TravelTripRecyclerViewAdapter extends RecyclerView.Adapter<TravelTripViewHolder> { public TravelTripRecyclerViewAdapter() { } @Override public TravelTripViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.trip_list_item, parent, false); return new TravelTripViewHolder(v); } @Override public void onBindViewHolder(final TravelTripViewHolder holder, int position) { Travel currentItem = mTravels.get(position); holder.setTravel(currentItem); holder.mTravelDestinationName.setText(currentItem.name); holder.mMarkerImage.setImageBitmap(mMarkerList.get(position)); } @Override public int getItemCount() { return mTravels.size(); } } class TravelTripViewHolder extends RecyclerView.ViewHolder { public final View mView; @Bind(R.id.trip_list_item_name) TextView mTravelDestinationName; @Bind(R.id.trip_list_item_marker) ImageView mMarkerImage; @Bind(R.id.trip_list_item_detail_layout) LinearLayout mLayoutDetail; @Bind(R.id.trip_list_item_detail_image) ImageView mImageDetail; @Bind(R.id.trip_list_item_cardview) CardView card; private Travel travel; public TravelTripViewHolder(View itemView) { super(itemView); ButterKnife.bind(this, itemView); mView = itemView; mImageDetail.setColorFilter(new PorterDuffColorFilter(primaryColor, PorterDuff.Mode.MULTIPLY)); View.OnClickListener onClickDetailListener = new View.OnClickListener() { @Override public void onClick(View v) { Intent startIntent = TravelDetailActivity.newIntent(TripActivity.this, travel); startActivity(startIntent); } }; mImageDetail.setOnClickListener(onClickDetailListener); mLayoutDetail.setOnClickListener(onClickDetailListener); card.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { CameraUpdate cameraInit = CameraUpdateFactory.newLatLngZoom(travel.toLatLng(), 15); if (map != null) { map.animateCamera(cameraInit); } } }); } public void setTravel(Travel travel) { this.travel = travel; } public Travel getTravel() { return travel; } } }