From c8a399aa2f96d9f460b5b36bcd8767a2c5ea7a5f Mon Sep 17 00:00:00 2001 From: Brad Leege Date: Fri, 7 Aug 2015 16:33:17 -0500 Subject: [PATCH 01/11] #2014 - Commenting out setElevation() --- .../src/main/java/com/mapbox/mapboxgl/views/MapView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java index 953bd6ae0..cd7e84cee 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java @@ -309,7 +309,7 @@ public class MapView extends FrameLayout implements LocationListener { trackingModeButton = new FloatingActionButton(mContext); trackingModeButton.setContentDescription(getResources().getString(R.string.trackingModeButtonContentDescription)); trackingModeButton.setBackgroundTintList(ColorStateList.valueOf(getResources().getColor(R.color.background_material_light))); - trackingModeButton.setElevation(4); +// trackingModeButton.setElevation(4); trackingModeButton.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.ic_my_location_black_24dp)); LayoutParams lp2 = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); lp2.gravity = Gravity.BOTTOM | Gravity.END; From 76c937f119bdc673fef2675a4db22731cde85dfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Thu, 6 Aug 2015 12:41:51 +0200 Subject: [PATCH 02/11] set thread priority on Linux --- platform/default/thread.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/platform/default/thread.cpp b/platform/default/thread.cpp index c0a1069b9..12d1802c2 100644 --- a/platform/default/thread.cpp +++ b/platform/default/thread.cpp @@ -1,11 +1,23 @@ #include +#include + +#include +#include + namespace mbgl { namespace platform { void makeThreadLowPriority() { - // no-op +#ifdef SCHED_IDLE + struct sched_param param; + param.sched_priority = 0; + int status = sched_setscheduler(0, SCHED_IDLE, ¶m); + if (status != 0) { + Log::Warning(Event::General, "Couldn't set thread scheduling policy"); + } +#endif } -} -} +} // namespace platform +} // namespace mbgl From 0a527f623ed88ef46c104e97a86239b012141074 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Mon, 10 Aug 2015 22:39:08 +0300 Subject: [PATCH 03/11] don't constantly repaint by setting needsRepaint to false fix by @brunoabinader --- src/mbgl/map/map_context.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/mbgl/map/map_context.cpp b/src/mbgl/map/map_context.cpp index 33e4e6d18..c14a61b8b 100644 --- a/src/mbgl/map/map_context.cpp +++ b/src/mbgl/map/map_context.cpp @@ -345,12 +345,8 @@ bool MapContext::renderSync(const TransformState& state, const FrameData& frame) } view.swap(); - viewInvalidated = false; - - if (style->hasTransitions() || painter->needsAnimation()) { - data.setNeedsRepaint(true); - } + data.setNeedsRepaint(style->hasTransitions() || painter->needsAnimation()); return isLoaded(); } From 26d300b4f435519b73743b36b48bf5cf71a678a4 Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Mon, 6 Apr 2015 21:37:58 -0700 Subject: [PATCH 04/11] Refactor user location annotation Adds: - Accuracy ring - Heading indicator Fixes: - Pulse sync - Visual appearance (removes slight aliasing) Closes #1222 (squashed) --- LICENSE.md | 10 + platform/ios/MGLMapView.mm | 1 + platform/ios/MGLUserLocationAnnotationView.m | 334 +++++++++++++------ 3 files changed, 242 insertions(+), 103 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index e5ed29c9a..6de1da495 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -53,3 +53,13 @@ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=========================================================================== + +Mapbox GL uses portions of SVPulsingAnnotationView. + +Copyright (c) 2013, Sam Vermette + +Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm index 81338f288..3a517ddef 100644 --- a/platform/ios/MGLMapView.mm +++ b/platform/ios/MGLMapView.mm @@ -737,6 +737,7 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) { [self updateHeadingForDeviceOrientation]; [self updateCompass]; + [self updateUserLocationAnnotationView]; } } diff --git a/platform/ios/MGLUserLocationAnnotationView.m b/platform/ios/MGLUserLocationAnnotationView.m index 67f360f68..a6310a82c 100644 --- a/platform/ios/MGLUserLocationAnnotationView.m +++ b/platform/ios/MGLUserLocationAnnotationView.m @@ -5,7 +5,8 @@ #import "MGLAnnotation.h" #import "MGLMapView.h" -const CGFloat MGLTrackingDotRingWidth = 24.0; +const CGFloat MGLUserLocationAnnotationDotSize = 22.0; +const CGFloat MGLUserLocationAnnotationHaloSize = 115.0; @interface MGLUserLocationAnnotationView () @@ -17,9 +18,15 @@ const CGFloat MGLTrackingDotRingWidth = 24.0; @implementation MGLUserLocationAnnotationView { + CALayer *_headingIndicatorLayer; + CAShapeLayer *_headingIndicatorMaskLayer; CALayer *_accuracyRingLayer; CALayer *_dotBorderLayer; CALayer *_dotLayer; + + double _oldHeadingAccuracy; + CLLocationAccuracy _oldHorizontalAccuracy; + double _oldZoom; } - (instancetype)initWithFrame:(CGRect)frame @@ -30,7 +37,7 @@ const CGFloat MGLTrackingDotRingWidth = 24.0; - (instancetype)initInMapView:(MGLMapView *)mapView { - if (self = [super initWithFrame:CGRectMake(0, 0, MGLTrackingDotRingWidth, MGLTrackingDotRingWidth)]) + if (self = [super initWithFrame:CGRectMake(0, 0, MGLUserLocationAnnotationDotSize, MGLUserLocationAnnotationDotSize)]) { self.annotation = [[MGLUserLocation alloc] initWithMapView:mapView]; _mapView = mapView; @@ -48,164 +55,285 @@ const CGFloat MGLTrackingDotRingWidth = 24.0; - (void)setTintColor:(UIColor *)tintColor { - UIImage *trackingDotHaloImage = [self trackingDotHaloImage]; - _haloLayer.bounds = CGRectMake(0, 0, trackingDotHaloImage.size.width, trackingDotHaloImage.size.height); - _haloLayer.contents = (__bridge id)[trackingDotHaloImage CGImage]; + if (_accuracyRingLayer) + { + _accuracyRingLayer.backgroundColor = [tintColor CGColor]; + } - UIImage *dotImage = [self dotImage]; - _dotLayer.bounds = CGRectMake(0, 0, dotImage.size.width, dotImage.size.height); - _dotLayer.contents = (__bridge id)[dotImage CGImage]; + _haloLayer.backgroundColor = [tintColor CGColor]; + _dotLayer.backgroundColor = [tintColor CGColor]; + + _headingIndicatorLayer.contents = (__bridge id)[[self headingIndicatorTintedGradientImage] CGImage]; } - (void)setupLayers { if (CLLocationCoordinate2DIsValid(self.annotation.coordinate)) { + // update heading indicator + // + if (_headingIndicatorLayer) + { + _headingIndicatorLayer.hidden = (_mapView.userTrackingMode == MGLUserTrackingModeFollowWithHeading) ? NO : YES; + + if (_oldHeadingAccuracy != self.annotation.heading.headingAccuracy) + { + // recalculate the clipping mask based on updated accuracy + _headingIndicatorMaskLayer.path = [[self headingIndicatorClippingMask] CGPath]; + + _oldHeadingAccuracy = self.annotation.heading.headingAccuracy; + } + } + + // heading indicator (tinted, semi-circle) + // + if ( ! _headingIndicatorLayer && self.annotation.heading.headingAccuracy) + { + CGFloat headingIndicatorSize = MGLUserLocationAnnotationHaloSize; + + _headingIndicatorLayer = [CALayer layer]; + _headingIndicatorLayer.bounds = CGRectMake(0, 0, headingIndicatorSize, headingIndicatorSize); + _headingIndicatorLayer.position = CGPointMake(super.bounds.size.width / 2.0, super.bounds.size.height / 2.0); + _headingIndicatorLayer.contents = (__bridge id)[[self headingIndicatorTintedGradientImage] CGImage]; + _headingIndicatorLayer.contentsGravity = kCAGravityBottom; + _headingIndicatorLayer.contentsScale = [UIScreen mainScreen].scale; + _headingIndicatorLayer.opacity = 0.4; + _headingIndicatorLayer.shouldRasterize = YES; + _headingIndicatorLayer.rasterizationScale = [UIScreen mainScreen].scale; + _headingIndicatorLayer.drawsAsynchronously = YES; + + [self.layer insertSublayer:_headingIndicatorLayer below:_dotBorderLayer]; + } + + // heading indicator accuracy mask (fan-shaped) + // + if ( ! _headingIndicatorMaskLayer && self.annotation.heading.headingAccuracy) + { + _headingIndicatorMaskLayer = [CAShapeLayer layer]; + _headingIndicatorMaskLayer.frame = _headingIndicatorLayer.bounds; + _headingIndicatorMaskLayer.path = [[self headingIndicatorClippingMask] CGPath]; + + // apply the mask to the halo-radius-sized gradient layer + _headingIndicatorLayer.mask = _headingIndicatorMaskLayer; + + _oldHeadingAccuracy = self.annotation.heading.headingAccuracy; + } + + // update accuracy ring + // + if (_accuracyRingLayer) + { + // FIX: This stops EVERYTHING... and that isn't necessarily necessary, now is it? + if (_oldZoom == self.mapView.zoomLevel && _oldHorizontalAccuracy == self.annotation.location.horizontalAccuracy) + { + return; + } + + CGFloat accuracyRingSize = [self calculateAccuracyRingSize]; + + // only show the accuracy ring if it won't be obscured by the location dot + if (accuracyRingSize > MGLUserLocationAnnotationDotSize + 15) + { + _accuracyRingLayer.hidden = NO; + _accuracyRingLayer.bounds = CGRectMake(0, 0, accuracyRingSize, accuracyRingSize); + _accuracyRingLayer.cornerRadius = accuracyRingSize / 2; + + // match the halo to the accuracy ring + _haloLayer.bounds = _accuracyRingLayer.bounds; + _haloLayer.cornerRadius = _accuracyRingLayer.cornerRadius; + _haloLayer.shouldRasterize = NO; + } + else + { + _accuracyRingLayer.hidden = YES; + + _haloLayer.bounds = CGRectMake(0, 0, MGLUserLocationAnnotationHaloSize, MGLUserLocationAnnotationHaloSize); + _haloLayer.cornerRadius = MGLUserLocationAnnotationHaloSize / 2.0; + _haloLayer.shouldRasterize = YES; + _haloLayer.rasterizationScale = [UIScreen mainScreen].scale; + } + + // store accuracy and zoom so we're not redrawing unchanged location updates + _oldHorizontalAccuracy = self.annotation.location.horizontalAccuracy; + _oldZoom = self.mapView.zoomLevel; + } + + // accuracy ring (circular, tinted, mostly-transparent) + // if ( ! _accuracyRingLayer && self.annotation.location.horizontalAccuracy) { - UIImage *accuracyRingImage = [self accuracyRingImage]; - _accuracyRingLayer = [CALayer layer]; - _haloLayer.bounds = CGRectMake(0, 0, accuracyRingImage.size.width, accuracyRingImage.size.height); - _haloLayer.contents = (__bridge id)[accuracyRingImage CGImage]; - _haloLayer.position = CGPointMake(super.layer.bounds.size.width / 2.0, super.layer.bounds.size.height / 2.0); + CGFloat accuracyRingSize = [self calculateAccuracyRingSize]; + _accuracyRingLayer = [self circleLayerWithSize:accuracyRingSize]; + _accuracyRingLayer.backgroundColor = [_mapView.tintColor CGColor]; + _accuracyRingLayer.opacity = 0.1; + _accuracyRingLayer.shouldRasterize = NO; + _accuracyRingLayer.allowsGroupOpacity = NO; [self.layer addSublayer:_accuracyRingLayer]; } + // expanding sonar-like pulse (circular, tinted, fades out) + // if ( ! _haloLayer) { - UIImage *haloImage = [self trackingDotHaloImage]; - _haloLayer = [CALayer layer]; - _haloLayer.bounds = CGRectMake(0, 0, haloImage.size.width, haloImage.size.height); - _haloLayer.contents = (__bridge id)[haloImage CGImage]; - _haloLayer.position = CGPointMake(super.layer.bounds.size.width / 2.0, super.layer.bounds.size.height / 2.0); + _haloLayer = [self circleLayerWithSize:MGLUserLocationAnnotationHaloSize]; + _haloLayer.backgroundColor = [_mapView.tintColor CGColor]; + _haloLayer.allowsGroupOpacity = NO; - [CATransaction begin]; + // set defaults for the animations + CAAnimationGroup *animationGroup = [self loopingAnimationGroupWithDuration:3.0]; - [CATransaction setAnimationDuration:3.5]; - [CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]]; + // scale out radially with initial acceleration + CAKeyframeAnimation *boundsAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale.xy"]; + boundsAnimation.values = @[@0, @0.35, @1]; + boundsAnimation.keyTimes = @[@0, @0.2, @1]; - // scale out radially - // - CABasicAnimation *boundsAnimation = [CABasicAnimation animationWithKeyPath:@"transform"]; - boundsAnimation.repeatCount = MAXFLOAT; - boundsAnimation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.1, 0.1, 1.0)]; - boundsAnimation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(2.0, 2.0, 1.0)]; - boundsAnimation.removedOnCompletion = NO; + // go transparent as scaled out, start semi-opaque + CAKeyframeAnimation *opacityAnimation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"]; + opacityAnimation.values = @[@0.4, @0.4, @0]; + opacityAnimation.keyTimes = @[@0, @0.2, @1]; - [_haloLayer addAnimation:boundsAnimation forKey:@"animateScale"]; + animationGroup.animations = @[boundsAnimation, opacityAnimation]; - // go transparent as scaled out - // - CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"]; - opacityAnimation.repeatCount = MAXFLOAT; - opacityAnimation.fromValue = [NSNumber numberWithFloat:1.0]; - opacityAnimation.toValue = [NSNumber numberWithFloat:-1.0]; - opacityAnimation.removedOnCompletion = NO; - - [_haloLayer addAnimation:opacityAnimation forKey:@"animateOpacity"]; - - [CATransaction commit]; + [_haloLayer addAnimation:animationGroup forKey:@"animateTransformAndOpacity"]; [self.layer addSublayer:_haloLayer]; } - // white dot background with shadow + // background dot (white with black shadow) // if ( ! _dotBorderLayer) { - CGRect rect = CGRectMake(0, 0, MGLTrackingDotRingWidth * 1.5, MGLTrackingDotRingWidth * 1.5); + _dotBorderLayer = [self circleLayerWithSize:MGLUserLocationAnnotationDotSize]; + _dotBorderLayer.backgroundColor = [[UIColor whiteColor] CGColor]; + _dotBorderLayer.shadowColor = [[UIColor blackColor] CGColor]; + _dotBorderLayer.shadowOffset = CGSizeMake(0, 0); + _dotBorderLayer.shadowRadius = 3; + _dotBorderLayer.shadowOpacity = 0.25; - UIGraphicsBeginImageContextWithOptions(rect.size, NO, [[UIScreen mainScreen] scale]); - CGContextRef context = UIGraphicsGetCurrentContext(); - - CGContextSetShadow(context, CGSizeMake(0, 0), MGLTrackingDotRingWidth / 4.0); - - CGContextSetFillColorWithColor(context, [[UIColor whiteColor] CGColor]); - CGContextFillEllipseInRect(context, CGRectMake((rect.size.width - MGLTrackingDotRingWidth) / 2.0, (rect.size.height - MGLTrackingDotRingWidth) / 2.0, MGLTrackingDotRingWidth, MGLTrackingDotRingWidth)); - - UIImage *whiteBackground = UIGraphicsGetImageFromCurrentImageContext(); - - UIGraphicsEndImageContext(); - - _dotBorderLayer = [CALayer layer]; - _dotBorderLayer.bounds = CGRectMake(0, 0, whiteBackground.size.width, whiteBackground.size.height); - _dotBorderLayer.contents = (__bridge id)[whiteBackground CGImage]; - _dotBorderLayer.position = CGPointMake(super.layer.bounds.size.width / 2.0, super.layer.bounds.size.height / 2.0); [self.layer addSublayer:_dotBorderLayer]; } - - // pulsing, tinted dot sublayer + + // inner dot (pulsing, tinted) // if ( ! _dotLayer) { - UIImage *dotImage = [self dotImage]; - _dotLayer = [CALayer layer]; - _dotLayer.bounds = CGRectMake(0, 0, dotImage.size.width, dotImage.size.height); - _dotLayer.contents = (__bridge id)[dotImage CGImage]; - _dotLayer.position = CGPointMake(super.layer.bounds.size.width / 2.0, super.layer.bounds.size.height / 2.0); + _dotLayer = [self circleLayerWithSize:MGLUserLocationAnnotationDotSize * 0.75]; + _dotLayer.backgroundColor = [_mapView.tintColor CGColor]; + _dotLayer.shouldRasterize = NO; - CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform"]; - animation.repeatCount = MAXFLOAT; - animation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.0, 1.0, 1.0)]; - animation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.8, 0.8, 1.0)]; - animation.removedOnCompletion = NO; - animation.autoreverses = YES; - animation.duration = 1.5; - animation.beginTime = CACurrentMediaTime() + 1.0; - animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]; + // set defaults for the animations + CAAnimationGroup *animationGroup = [self loopingAnimationGroupWithDuration:1.5]; + animationGroup.autoreverses = YES; + animationGroup.fillMode = kCAFillModeBoth; - [_dotLayer addAnimation:animation forKey:@"animateTransform"]; + // scale the dot up and down + CABasicAnimation *pulseAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale.xy"]; + pulseAnimation.fromValue = @0.8; + pulseAnimation.toValue = @1; + + // fade opacity in and out, subtly + CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"]; + opacityAnimation.fromValue = @0.8; + opacityAnimation.toValue = @1; + + animationGroup.animations = @[pulseAnimation, opacityAnimation]; + + [_dotLayer addAnimation:animationGroup forKey:@"animateTransformAndOpacity"]; [self.layer addSublayer:_dotLayer]; } } } -- (UIImage *)accuracyRingImage +- (CALayer *)circleLayerWithSize:(CGFloat)layerSize +{ + CALayer *circleLayer = [CALayer layer]; + circleLayer.bounds = CGRectMake(0, 0, layerSize, layerSize); + circleLayer.position = CGPointMake(super.bounds.size.width / 2.0, super.bounds.size.height / 2.0); + circleLayer.cornerRadius = layerSize / 2.0; + circleLayer.shouldRasterize = YES; + circleLayer.rasterizationScale = [UIScreen mainScreen].scale; + circleLayer.drawsAsynchronously = YES; + + return circleLayer; +} + +- (CAAnimationGroup *)loopingAnimationGroupWithDuration:(CGFloat)animationDuration +{ + CAAnimationGroup *animationGroup = [CAAnimationGroup animation]; + animationGroup.duration = animationDuration; + animationGroup.repeatCount = INFINITY; + animationGroup.removedOnCompletion = NO; + animationGroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]; + + return animationGroup; +} + +- (CGFloat)calculateAccuracyRingSize { CGFloat latRadians = self.annotation.coordinate.latitude * M_PI / 180.0f; CGFloat pixelRadius = self.annotation.location.horizontalAccuracy / cos(latRadians) / [self.mapView metersPerPixelAtLatitude:self.annotation.coordinate.latitude]; - UIGraphicsBeginImageContextWithOptions(CGSizeMake(pixelRadius * 2, pixelRadius * 2), NO, [[UIScreen mainScreen] scale]); - CGContextSetStrokeColorWithColor(UIGraphicsGetCurrentContext(), [[UIColor colorWithRed:0.378 green:0.552 blue:0.827 alpha:0.7] CGColor]); - CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), [[UIColor colorWithRed:0.378 green:0.552 blue:0.827 alpha:0.15] CGColor]); - CGContextSetLineWidth(UIGraphicsGetCurrentContext(), 2.0); - CGContextStrokeEllipseInRect(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, pixelRadius * 2, pixelRadius * 2)); - - UIImage *finalImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - return finalImage; + return pixelRadius * 2; } -- (UIImage *)trackingDotHaloImage +- (UIImage *)headingIndicatorTintedGradientImage { - UIGraphicsBeginImageContextWithOptions(CGSizeMake(100, 100), NO, [[UIScreen mainScreen] scale]); - CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), [[_mapView.tintColor colorWithAlphaComponent:0.75] CGColor]); - CGContextFillEllipseInRect(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, 100, 100)); - UIImage *finalImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); + UIImage *image; - return finalImage; -} - -- (UIImage *)dotImage -{ - CGFloat tintedWidth = MGLTrackingDotRingWidth * 0.7; + CGFloat haloRadius = MGLUserLocationAnnotationHaloSize / 2.0; - CGRect rect = CGRectMake(0, 0, tintedWidth, tintedWidth); + UIGraphicsBeginImageContextWithOptions(CGSizeMake(MGLUserLocationAnnotationHaloSize, haloRadius), NO, 0); - UIGraphicsBeginImageContextWithOptions(rect.size, NO, [[UIScreen mainScreen] scale]); + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef context = UIGraphicsGetCurrentContext(); - CGContextSetFillColorWithColor(context, [_mapView.tintColor CGColor]); - CGContextFillEllipseInRect(context, CGRectMake((rect.size.width - tintedWidth) / 2.0, (rect.size.height - tintedWidth) / 2.0, tintedWidth, tintedWidth)); + // gradient from the tint color to no-alpha tint color + CGFloat gradientLocations[] = {0.0, 1.0}; + CGGradientRef gradient = CGGradientCreateWithColors( + colorSpace, (__bridge CFArrayRef)@[(id)[_mapView.tintColor CGColor], + (id)[[_mapView.tintColor colorWithAlphaComponent:0] CGColor]], gradientLocations); - UIImage *tintedForeground = UIGraphicsGetImageFromCurrentImageContext(); + // draw the gradient from the center point to the edge (full halo radius) + CGPoint centerPoint = CGPointMake(haloRadius, haloRadius); + CGContextDrawRadialGradient(context, gradient, + centerPoint, 0.0, + centerPoint, haloRadius, + nil); + image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); - return tintedForeground; + CGGradientRelease(gradient); + CGColorSpaceRelease(colorSpace); + + return image; +} + +- (UIBezierPath *)headingIndicatorClippingMask +{ + CGFloat accuracy = self.annotation.heading.headingAccuracy; + + // size the mask using exagerated accuracy, but keep within a good display range + CGFloat clippingDegrees = 90 - (accuracy * 1.5); + clippingDegrees = fmin(clippingDegrees, 55); + clippingDegrees = fmax(clippingDegrees, 10); + + CGRect ovalRect = CGRectMake(0, 0, MGLUserLocationAnnotationHaloSize, MGLUserLocationAnnotationHaloSize); + UIBezierPath *ovalPath = UIBezierPath.bezierPath; + + // clip the oval to ± incoming accuracy degrees (converted to radians), from the top + [ovalPath addArcWithCenter:CGPointMake(CGRectGetMidX(ovalRect), CGRectGetMidY(ovalRect)) + radius:CGRectGetWidth(ovalRect) / 2.0 + startAngle:(-180 + clippingDegrees) * M_PI / 180 + endAngle:-clippingDegrees * M_PI / 180 + clockwise:YES]; + + [ovalPath addLineToPoint:CGPointMake(CGRectGetMidX(ovalRect), CGRectGetMidY(ovalRect))]; + [ovalPath closePath]; + + return ovalPath; } @end From dc11b8132ba7ccc58c280e580526425e7389df86 Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Mon, 10 Aug 2015 14:14:58 -0400 Subject: [PATCH 05/11] Improve accuracy ring update checks --- platform/ios/MGLUserLocationAnnotationView.m | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/platform/ios/MGLUserLocationAnnotationView.m b/platform/ios/MGLUserLocationAnnotationView.m index a6310a82c..536326d1f 100644 --- a/platform/ios/MGLUserLocationAnnotationView.m +++ b/platform/ios/MGLUserLocationAnnotationView.m @@ -119,16 +119,10 @@ const CGFloat MGLUserLocationAnnotationHaloSize = 115.0; _oldHeadingAccuracy = self.annotation.heading.headingAccuracy; } - // update accuracy ring + // update accuracy ring (if zoom or horizontal accuracy have changed) // - if (_accuracyRingLayer) + if (_accuracyRingLayer && (_oldZoom != self.mapView.zoomLevel || _oldHorizontalAccuracy != self.annotation.location.horizontalAccuracy)) { - // FIX: This stops EVERYTHING... and that isn't necessarily necessary, now is it? - if (_oldZoom == self.mapView.zoomLevel && _oldHorizontalAccuracy == self.annotation.location.horizontalAccuracy) - { - return; - } - CGFloat accuracyRingSize = [self calculateAccuracyRingSize]; // only show the accuracy ring if it won't be obscured by the location dot From 7325000edb13526e7ab694aef573a6717ef26884 Mon Sep 17 00:00:00 2001 From: Leith Bade Date: Tue, 11 Aug 2015 09:44:49 +1000 Subject: [PATCH 06/11] Check API level for totalMemory --- .../main/java/com/mapbox/mapboxgl/views/MapView.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java index cd7e84cee..3b34fcca0 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java @@ -192,6 +192,7 @@ public class MapView extends FrameLayout implements LocationListener { // // Common initialization code goes here + @TargetApi(16) private void initialize(Context context, AttributeSet attrs) { // Save the context @@ -218,9 +219,12 @@ public class MapView extends FrameLayout implements LocationListener { ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo(); ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); activityManager.getMemoryInfo(memoryInfo); - long totalMemory = memoryInfo.totalMem; - mNativeMapView = new NativeMapView(this, cachePath, dataPath, apkPath, mScreenDensity,availableProcessors, totalMemory); - + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + long totalMemory = memoryInfo.totalMem; + mNativeMapView = new NativeMapView(this, cachePath, dataPath, apkPath, mScreenDensity, availableProcessors, totalMemory); + } else { + throw new RuntimeException("Need to implement totalMemory on pre-Jelly Bean devices."); + } // Load the attributes TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MapView, 0, 0); try { @@ -685,7 +689,7 @@ public class MapView extends FrameLayout implements LocationListener { } public void onSizeChanged(int width, int height, int oldw, int oldh) { - mNativeMapView.resizeView((int)(width / mScreenDensity), (int)(height / mScreenDensity)); + mNativeMapView.resizeView((int) (width / mScreenDensity), (int) (height / mScreenDensity)); } // This class handles SurfaceHolder callbacks From 0cbe7e07f5ada444de45b3936f7378dd4f3b9d6b Mon Sep 17 00:00:00 2001 From: Leith Bade Date: Fri, 31 Jul 2015 13:00:52 +1000 Subject: [PATCH 07/11] Use OkHTTP to implement HTTPContext on Android. Closes #823 --- android/cpp/jni.cpp | 142 ++++++- android/cpp/native_map_view.cpp | 81 +--- .../com/mapbox/mapboxgl/http/HTTPContext.java | 93 ++++ .../mapbox/mapboxgl/util/JavaFileSource.java | 51 --- android/mapboxgl-app.gypi | 5 - gyp/http-android.gypi | 70 +++ include/mbgl/android/jni.hpp | 29 ++ mbgl.gyp | 1 + platform/android/http_request_android.cpp | 399 ++++++++++++++++++ platform/default/http_request_curl.cpp | 109 ----- scripts/android/configure.sh | 4 +- scripts/android/defaults.mk | 2 +- 12 files changed, 734 insertions(+), 252 deletions(-) create mode 100644 android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/http/HTTPContext.java delete mode 100644 android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/util/JavaFileSource.java create mode 100644 gyp/http-android.gypi create mode 100644 platform/android/http_request_android.cpp diff --git a/android/cpp/jni.cpp b/android/cpp/jni.cpp index 6d7def861..f1e9072cb 100644 --- a/android/cpp/jni.cpp +++ b/android/cpp/jni.cpp @@ -28,6 +28,8 @@ namespace mbgl { namespace android { +JavaVM* theJVM; + std::string cachePath; std::string dataPath; std::string apkPath; @@ -90,7 +92,15 @@ jmethodID pointFConstructorId = nullptr; jfieldID pointFXId = nullptr; jfieldID pointFYId = nullptr; -bool throw_error(JNIEnv *env, const char *msg) { +jclass httpContextClass = nullptr; +jmethodID httpContextGetInstanceId = nullptr; +jmethodID httpContextCreateRequestId = nullptr; + +jclass httpRequestClass = nullptr; +jmethodID httpRequestStartId = nullptr; +jmethodID httpRequestCancelId = nullptr; + +bool throw_jni_error(JNIEnv *env, const char *msg) { if (env->ThrowNew(runtimeExceptionClass, msg) < 0) { env->ExceptionDescribe(); return false; @@ -99,6 +109,41 @@ bool throw_error(JNIEnv *env, const char *msg) { return true; } +bool attach_jni_thread(JavaVM* vm, JNIEnv** env, std::string threadName) { + JavaVMAttachArgs args = {JNI_VERSION_1_2, threadName.c_str(), NULL}; + + jint ret; + *env = nullptr; + bool detach = false; + ret = vm->GetEnv(reinterpret_cast(env), JNI_VERSION_1_6); + if (ret != JNI_OK) { + if (ret != JNI_EDETACHED) { + mbgl::Log::Error(mbgl::Event::JNI, "GetEnv() failed with %i", ret); + throw new std::runtime_error("GetEnv() failed"); + } else { + ret = vm->AttachCurrentThread(env, &args); + if (ret != JNI_OK) { + mbgl::Log::Error(mbgl::Event::JNI, "AttachCurrentThread() failed with %i", ret); + throw new std::runtime_error("AttachCurrentThread() failed"); + } + detach = true; + } + } + + return detach; +} + +void detach_jni_thread(JavaVM* vm, JNIEnv** env, bool detach) { + if (detach) { + jint ret; + if ((ret = vm->DetachCurrentThread()) != JNI_OK) { + mbgl::Log::Error(mbgl::Event::JNI, "DetachCurrentThread() failed with %i", ret); + throw new std::runtime_error("DetachCurrentThread() failed"); + } + } + *env = nullptr; +} + std::string std_string_from_jstring(JNIEnv *env, jstring jstr) { std::string str; @@ -342,7 +387,7 @@ void JNICALL nativeInitializeDisplay(JNIEnv *env, jobject obj, jlong nativeMapVi { nativeMapView->initializeDisplay(); } catch(const std::exception& e) { - throw_error(env, "Unable to initialize GL display."); + throw_jni_error(env, "Unable to initialize GL display."); } } @@ -361,7 +406,7 @@ void JNICALL nativeInitializeContext(JNIEnv *env, jobject obj, jlong nativeMapVi try { nativeMapView->initializeContext(); } catch(const std::exception& e) { - throw_error(env, "Unable to initialize GL context."); + throw_jni_error(env, "Unable to initialize GL context."); } } @@ -380,7 +425,7 @@ void JNICALL nativeCreateSurface(JNIEnv *env, jobject obj, jlong nativeMapViewPt try { nativeMapView->createSurface(ANativeWindow_fromSurface(env, surface)); } catch(const std::exception& e) { - throw_error(env, "Unable to create GL surface."); + throw_jni_error(env, "Unable to create GL surface."); } } @@ -1131,6 +1176,8 @@ extern "C" { extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { mbgl::Log::Debug(mbgl::Event::JNI, "JNI_OnLoad"); + theJVM = vm; + JNIEnv *env = nullptr; jint ret = vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); if (ret != JNI_OK) { @@ -1426,6 +1473,36 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { return JNI_ERR; } + httpContextClass = env->FindClass("com/mapbox/mapboxgl/http/HTTPContext"); + if (httpContextClass == nullptr) { + env->ExceptionDescribe(); + } + + httpContextGetInstanceId = env->GetStaticMethodID(httpContextClass, "getInstance", "()Lcom/mapbox/mapboxgl/http/HTTPContext;"); + if (httpContextGetInstanceId == nullptr) { + env->ExceptionDescribe(); + } + + httpContextCreateRequestId = env->GetMethodID(httpContextClass, "createRequest", "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/mapbox/mapboxgl/http/HTTPContext$HTTPRequest;"); + if (httpContextCreateRequestId == nullptr) { + env->ExceptionDescribe(); + } + + httpRequestClass = env->FindClass("com/mapbox/mapboxgl/http/HTTPContext$HTTPRequest"); + if (httpRequestClass == nullptr) { + env->ExceptionDescribe(); + } + + httpRequestStartId = env->GetMethodID(httpRequestClass, "start", "()V"); + if (httpRequestStartId == nullptr) { + env->ExceptionDescribe(); + } + + httpRequestCancelId = env->GetMethodID(httpRequestClass, "cancel", "()V"); + if (httpRequestCancelId == nullptr) { + env->ExceptionDescribe(); + } + const std::vector methods = { {"nativeCreate", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;FIJ)J", reinterpret_cast(&nativeCreate)}, @@ -1524,10 +1601,16 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { //{"nativeGetWorldBoundsMeters", "(J)V", reinterpret_cast(&nativeGetWorldBoundsMeters)}, //{"nativeGetWorldBoundsLatLng", "(J)V", reinterpret_cast(&nativeGetWorldBoundsLatLng)}, {"nativeGetMetersPerPixelAtLatitude", "(JDD)D", reinterpret_cast(&nativeGetMetersPerPixelAtLatitude)}, - {"nativeProjectedMetersForLatLng", "(JLcom/mapbox/mapboxgl/geometry/LatLng;)Lcom/mapbox/mapboxgl/geometry/ProjectedMeters;", reinterpret_cast(&nativeProjectedMetersForLatLng)}, - {"nativeLatLngForProjectedMeters", "(JLcom/mapbox/mapboxgl/geometry/ProjectedMeters;)Lcom/mapbox/mapboxgl/geometry/LatLng;", reinterpret_cast(&nativeLatLngForProjectedMeters)}, - {"nativePixelForLatLng", "(JLcom/mapbox/mapboxgl/geometry/LatLng;)Landroid/graphics/PointF;", reinterpret_cast(&nativePixelForLatLng)}, - {"nativeLatLngForPixel", "(JLandroid/graphics/PointF;)Lcom/mapbox/mapboxgl/geometry/LatLng;", reinterpret_cast(&nativeLatLngForPixel)}, + {"nativeProjectedMetersForLatLng", + "(JLcom/mapbox/mapboxgl/geometry/LatLng;)Lcom/mapbox/mapboxgl/geometry/ProjectedMeters;", + reinterpret_cast(&nativeProjectedMetersForLatLng)}, + {"nativeLatLngForProjectedMeters", + "(JLcom/mapbox/mapboxgl/geometry/ProjectedMeters;)Lcom/mapbox/mapboxgl/geometry/LatLng;", + reinterpret_cast(&nativeLatLngForProjectedMeters)}, + {"nativePixelForLatLng", "(JLcom/mapbox/mapboxgl/geometry/LatLng;)Landroid/graphics/PointF;", + reinterpret_cast(&nativePixelForLatLng)}, + {"nativeLatLngForPixel", "(JLandroid/graphics/PointF;)Lcom/mapbox/mapboxgl/geometry/LatLng;", + reinterpret_cast(&nativeLatLngForPixel)}, }; if (env->RegisterNatives(nativeMapViewClass, methods.data(), methods.size()) < 0) { @@ -1641,6 +1724,37 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { return JNI_ERR; } + httpContextClass = reinterpret_cast(env->NewGlobalRef(httpContextClass)); + if (httpContextClass == nullptr) { + env->ExceptionDescribe(); + env->DeleteGlobalRef(latLngClass); + env->DeleteGlobalRef(markerClass); + env->DeleteGlobalRef(latLngZoomClass); + env->DeleteGlobalRef(polylineClass); + env->DeleteGlobalRef(polygonClass); + env->DeleteGlobalRef(runtimeExceptionClass); + env->DeleteGlobalRef(nullPointerExceptionClass); + env->DeleteGlobalRef(arrayListClass); + env->DeleteGlobalRef(projectedMetersClass); + env->DeleteGlobalRef(pointFClass); + } + + httpRequestClass = reinterpret_cast(env->NewGlobalRef(httpRequestClass)); + if (httpRequestClass == nullptr) { + env->ExceptionDescribe(); + env->DeleteGlobalRef(latLngClass); + env->DeleteGlobalRef(markerClass); + env->DeleteGlobalRef(latLngZoomClass); + env->DeleteGlobalRef(polylineClass); + env->DeleteGlobalRef(polygonClass); + env->DeleteGlobalRef(runtimeExceptionClass); + env->DeleteGlobalRef(nullPointerExceptionClass); + env->DeleteGlobalRef(arrayListClass); + env->DeleteGlobalRef(projectedMetersClass); + env->DeleteGlobalRef(pointFClass); + env->DeleteGlobalRef(httpContextClass); + } + char release[PROP_VALUE_MAX] = ""; __system_property_get("ro.build.version.release", release); androidRelease = std::string(release); @@ -1651,6 +1765,8 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { extern "C" JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) { mbgl::Log::Debug(mbgl::Event::JNI, "JNI_OnUnload"); + theJVM = vm; + JNIEnv *env = nullptr; jint ret = vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); if (ret != JNI_OK) { @@ -1725,5 +1841,15 @@ extern "C" JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) { pointFConstructorId = nullptr; pointFXId = nullptr; pointFYId = nullptr; + + env->DeleteGlobalRef(httpContextClass); + httpContextGetInstanceId = nullptr; + httpContextCreateRequestId = nullptr; + + env->DeleteGlobalRef(httpRequestClass); + httpRequestStartId = nullptr; + httpRequestCancelId = nullptr; + + theJVM = nullptr; } } diff --git a/android/cpp/native_map_view.cpp b/android/cpp/native_map_view.cpp index d0581212d..d7c40ba5a 100644 --- a/android/cpp/native_map_view.cpp +++ b/android/cpp/native_map_view.cpp @@ -148,38 +148,15 @@ void NativeMapView::invalidate() { assert(vm != nullptr); assert(obj != nullptr); - JavaVMAttachArgs args = {JNI_VERSION_1_2, "NativeMapView::invalidate()", NULL}; - - jint ret; JNIEnv *env = nullptr; - bool detach = false; - ret = vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); - if (ret != JNI_OK) { - if (ret != JNI_EDETACHED) { - mbgl::Log::Error(mbgl::Event::JNI, "GetEnv() failed with %i", ret); - throw new std::runtime_error("GetEnv() failed"); - } else { - ret = vm->AttachCurrentThread(&env, &args); - if (ret != JNI_OK) { - mbgl::Log::Error(mbgl::Event::JNI, "AttachCurrentThread() failed with %i", ret); - throw new std::runtime_error("AttachCurrentThread() failed"); - } - detach = true; - } - } + bool detach = attach_jni_thread(vm, &env, "NativeMapView::invalidate()"); env->CallVoidMethod(obj, onInvalidateId); if (env->ExceptionCheck()) { env->ExceptionDescribe(); } - if (detach) { - if ((ret = vm->DetachCurrentThread()) != JNI_OK) { - mbgl::Log::Error(mbgl::Event::JNI, "DetachCurrentThread() failed with %i", ret); - throw new std::runtime_error("DetachCurrentThread() failed"); - } - } - env = nullptr; + detach_jni_thread(vm, &env, detach); } void NativeMapView::swap() { @@ -662,38 +639,15 @@ void NativeMapView::notifyMapChange(mbgl::MapChange) { assert(vm != nullptr); assert(obj != nullptr); - JavaVMAttachArgs args = {JNI_VERSION_1_2, "NativeMapView::notifyMapChange()", NULL}; - - jint ret; JNIEnv *env = nullptr; - bool detach = false; - ret = vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); - if (ret != JNI_OK) { - if (ret != JNI_EDETACHED) { - mbgl::Log::Error(mbgl::Event::JNI, "GetEnv() failed with %i", ret); - throw new std::runtime_error("GetEnv() failed"); - } else { - ret = vm->AttachCurrentThread(&env, &args); - if (ret != JNI_OK) { - mbgl::Log::Error(mbgl::Event::JNI, "AttachCurrentThread() failed with %i", ret); - throw new std::runtime_error("AttachCurrentThread() failed"); - } - detach = true; - } - } + bool detach = attach_jni_thread(vm, &env, "NativeMapView::notifyMapChange()"); env->CallVoidMethod(obj, onMapChangedId); if (env->ExceptionCheck()) { env->ExceptionDescribe(); } - if (detach) { - if ((ret = vm->DetachCurrentThread()) != JNI_OK) { - mbgl::Log::Error(mbgl::Event::JNI, "DetachCurrentThread() failed with %i", ret); - throw new std::runtime_error("DetachCurrentThread() failed"); - } - } - env = nullptr; + detach_jni_thread(vm, &env, detach); } void NativeMapView::enableFps(bool enable) { @@ -727,38 +681,15 @@ void NativeMapView::updateFps() { assert(vm != nullptr); assert(obj != nullptr); - JavaVMAttachArgs args = {JNI_VERSION_1_2, "NativeMapView::updateFps()", NULL}; - - jint ret; JNIEnv *env = nullptr; - bool detach = false; - ret = vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); - if (ret != JNI_OK) { - if (ret != JNI_EDETACHED) { - mbgl::Log::Error(mbgl::Event::JNI, "GetEnv() failed with %i", ret); - throw new std::runtime_error("GetEnv() failed"); - } else { - ret = vm->AttachCurrentThread(&env, &args); - if (ret != JNI_OK) { - mbgl::Log::Error(mbgl::Event::JNI, "AttachCurrentThread() failed with %i", ret); - throw new std::runtime_error("AttachCurrentThread() failed"); - } - detach = true; - } - } + bool detach = attach_jni_thread(vm, &env, "NativeMapView::updateFps()"); env->CallVoidMethod(obj, onFpsChangedId, fps); if (env->ExceptionCheck()) { env->ExceptionDescribe(); } - if (detach) { - if ((ret = vm->DetachCurrentThread()) != JNI_OK) { - mbgl::Log::Error(mbgl::Event::JNI, "DetachCurrentThread() failed with %i", ret); - throw new std::runtime_error("DetachCurrentThread() failed"); - } - } - env = nullptr; + detach_jni_thread(vm, &env, detach); } void NativeMapView::onInvalidate() { diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/http/HTTPContext.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/http/HTTPContext.java new file mode 100644 index 000000000..509c79a4a --- /dev/null +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/http/HTTPContext.java @@ -0,0 +1,93 @@ +package com.mapbox.mapboxgl.http; + +import com.mapbox.mapboxgl.constants.MapboxConstants; +import com.squareup.okhttp.Call; +import com.squareup.okhttp.Callback; +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.Response; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.ProtocolException; +import java.net.SocketException; +import java.net.UnknownHostException; + +import javax.net.ssl.SSLException; + +class HTTPContext { + + private static final int CONNECTION_ERROR = 0; + private static final int TEMPORARY_ERROR = 1; + private static final int PERMANENT_ERROR = 2; + + private static HTTPContext mInstance = null; + + private OkHttpClient mClient; + + private HTTPContext() { + super(); + mClient = new OkHttpClient(); + } + + public static HTTPContext getInstance() { + if (mInstance == null) { + mInstance = new HTTPContext(); + } + + return mInstance; + } + + public HTTPRequest createRequest(long nativePtr, String resourceUrl, String userAgent, String etag, String modified) { + return new HTTPRequest(nativePtr, resourceUrl, userAgent, etag, modified); + } + + public class HTTPRequest implements Callback { + private long mNativePtr = 0; + + private Call mCall; + private Request mRequest; + + private native void nativeOnFailure(long nativePtr, int type, String message); + private native void nativeOnResponse(long nativePtr, int code, String message, String etag, String modified, String cacheControl, String expires, byte[] body); + + private HTTPRequest(long nativePtr, String resourceUrl, String userAgent, String etag, String modified) { + mNativePtr = nativePtr; + Request.Builder builder = new Request.Builder().url(resourceUrl).tag(resourceUrl.toLowerCase(MapboxConstants.MAPBOX_LOCALE)).addHeader("User-Agent", userAgent); + if (etag.length() > 0) { + builder = builder.addHeader("If-None-Match", etag); + } else if (modified.length() > 0) { + builder = builder.addHeader("If-Modified-Since", modified); + } + mRequest = builder.build(); + } + + public void start() { + mCall = HTTPContext.getInstance().mClient.newCall(mRequest); + mCall.enqueue(this); + } + + public void cancel() { + mCall.cancel(); + } + + @Override + public void onFailure(Request request, IOException e) { + int type = PERMANENT_ERROR; + if ((e instanceof UnknownHostException) || (e instanceof SocketException) || (e instanceof ProtocolException) || (e instanceof SSLException)) { + type = CONNECTION_ERROR; + } else if ((e instanceof InterruptedIOException)) { + type = TEMPORARY_ERROR; + } + nativeOnFailure(mNativePtr, type, e.getMessage()); + } + + @Override + public void onResponse(Response response) throws IOException { + byte[] body = response.body().bytes(); + response.body().close(); + + nativeOnResponse(mNativePtr, response.code(), response.message(), response.header("ETag"), response.header("Last-Modified"), response.header("Cache-Control"), response.header("Expires"), body); + } + } +} diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/util/JavaFileSource.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/util/JavaFileSource.java deleted file mode 100644 index bd8624188..000000000 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/util/JavaFileSource.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.mapbox.mapboxgl.util; - -import com.mapbox.mapboxgl.constants.MapboxConstants; -import com.squareup.okhttp.Callback; -import com.squareup.okhttp.OkHttpClient; -import com.squareup.okhttp.Request; - -public class JavaFileSource { - - private static JavaFileSource instance = null; - - // Single reference to OkHttp for performance gains - private OkHttpClient client; - - /** - * Private Constructor to support Singleton pattern - */ - private JavaFileSource() { - super(); - client = new OkHttpClient(); - } - - /** - * Get the singleton instance of JavaFileSource - * @return Reference to the Singleton Instance of JavaFileSource - */ - public static JavaFileSource getInstance() { - if (instance == null) { - instance = new JavaFileSource(); - } - return instance; - } - - /** - * Make an HTTP Request - * @param resourceUrl URL to resource - * @param callback Callback class - */ - public void request(final String resourceUrl, final Callback callback) { - Request request = new Request.Builder().url(resourceUrl).tag(resourceUrl.toLowerCase(MapboxConstants.MAPBOX_LOCALE)).build(); - client.newCall(request).enqueue(callback); - } - - /** - * Attempt to cancel HTTP Request made - * @param resourceUrl URL of request to cancel - */ - public void cancel(final String resourceUrl) { - client.cancel(resourceUrl.toLowerCase(MapboxConstants.MAPBOX_LOCALE)); - } -} diff --git a/android/mapboxgl-app.gypi b/android/mapboxgl-app.gypi index e32775ff7..e3db1269d 100644 --- a/android/mapboxgl-app.gypi +++ b/android/mapboxgl-app.gypi @@ -25,8 +25,6 @@ '<@(boost_cflags)', ], 'libraries': [ - '<@(openssl_static_libs)', - '<@(libcurl_static_libs)', '<@(libpng_static_libs)', '<@(jpeg_static_libs)', '<@(sqlite_static_libs)', @@ -45,8 +43,6 @@ '<@(libpng_ldflags)', '<@(jpeg_ldflags)', '<@(sqlite_ldflags)', - '<@(openssl_ldflags)', - '<@(libcurl_ldflags)', '<@(zlib_ldflags)', '<@(libzip_ldflags)', ], @@ -74,7 +70,6 @@ 'copies': [ { 'files': [ - '../common/ca-bundle.crt', '../styles/styles' ], 'destination': '<(pwd)/../android/java/MapboxGLAndroidSDK/src/main/assets' diff --git a/gyp/http-android.gypi b/gyp/http-android.gypi new file mode 100644 index 000000000..c210db0fb --- /dev/null +++ b/gyp/http-android.gypi @@ -0,0 +1,70 @@ +{ + 'targets': [ + { 'target_name': 'http-android', + 'product_name': 'mbgl-http-android', + 'type': 'static_library', + 'standalone_static_library': 1, + 'hard_dependency': 1, + + 'sources': [ + '../platform/android/http_request_android.cpp', + ], + + 'include_dirs': [ + '../include', + '../src', + ], + + 'variables': { + 'cflags_cc': [ + '<@(libuv_cflags)', + '<@(boost_cflags)', + ], + 'ldflags': [ + '<@(libuv_ldflags)', + ], + 'libraries': [ + '<@(libuv_static_libs)', + ], + 'defines': [ + '-DMBGL_HTTP_ANDROID' + ], + }, + + 'conditions': [ + ['OS == "mac"', { + 'xcode_settings': { + 'OTHER_CPLUSPLUSFLAGS': [ '<@(cflags_cc)' ], + }, + }, { + 'cflags_cc': [ '<@(cflags_cc)' ], + }], + ], + + 'direct_dependent_settings': { + 'conditions': [ + ['OS == "mac"', { + 'xcode_settings': { + 'OTHER_CFLAGS': [ '<@(defines)' ], + 'OTHER_CPLUSPLUSFLAGS': [ '<@(defines)' ], + } + }, { + 'cflags': [ '<@(defines)' ], + 'cflags_cc': [ '<@(defines)' ], + }] + ], + }, + + 'link_settings': { + 'conditions': [ + ['OS == "mac"', { + 'libraries': [ '<@(libraries)' ], + 'xcode_settings': { 'OTHER_LDFLAGS': [ '<@(ldflags)' ] } + }, { + 'libraries': [ '<@(libraries)', '<@(ldflags)' ], + }] + ], + }, + }, + ], +} diff --git a/include/mbgl/android/jni.hpp b/include/mbgl/android/jni.hpp index f27c8c844..62cb399d3 100644 --- a/include/mbgl/android/jni.hpp +++ b/include/mbgl/android/jni.hpp @@ -2,15 +2,27 @@ #define MBGL_ANDROID_JNI #include +#include // Forward definition of JNI types typedef class _jclass* jclass; +typedef class _jstring* jstring; +typedef class _jobject* jobject; +typedef class _jlongArray* jlongArray; typedef struct _jmethodID* jmethodID; typedef struct _jfieldID* jfieldID; +struct _JavaVM; +typedef _JavaVM JavaVM; + +struct _JNIEnv; +typedef _JNIEnv JNIEnv; + namespace mbgl { namespace android { +extern JavaVM* theJVM; + extern std::string cachePath; extern std::string dataPath; extern std::string apkPath; @@ -73,6 +85,23 @@ extern jmethodID pointFConstructorId; extern jfieldID pointFXId; extern jfieldID pointFYId; +extern jclass httpContextClass; +extern jmethodID httpContextGetInstanceId; +extern jmethodID httpContextCreateRequestId; + +extern jclass httpRequestClass; +extern jmethodID httpRequestStartId; +extern jmethodID httpRequestCancelId; + +extern bool throw_jni_error(JNIEnv *env, const char *msg); +extern bool attach_jni_thread(JavaVM* vm, JNIEnv** env, std::string threadName); +extern void detach_jni_thread(JavaVM* vm, JNIEnv** env, bool detach); +extern std::string std_string_from_jstring(JNIEnv *env, jstring jstr); +extern jstring std_string_to_jstring(JNIEnv *env, std::string str); +extern std::vector std_vector_string_from_jobject(JNIEnv *env, jobject jlist); +extern jobject std_vector_string_to_jobject(JNIEnv *env, std::vector vector); +extern jlongArray std_vector_uint_to_jobject(JNIEnv *env, std::vector vector); + } } diff --git a/mbgl.gyp b/mbgl.gyp index c5ede2823..959a9729d 100644 --- a/mbgl.gyp +++ b/mbgl.gyp @@ -18,6 +18,7 @@ ['platform_lib == "android" and host == "android"', { 'includes': [ './gyp/platform-android.gypi' ] } ], ['http_lib == "curl"', { 'includes': [ './gyp/http-curl.gypi' ] } ], ['http_lib == "nsurl" and (host == "osx" or host == "ios")', { 'includes': [ './gyp/http-nsurl.gypi' ] } ], + ['http_lib == "android" and host == "android"', { 'includes': [ './gyp/http-android.gypi' ] } ], ['asset_lib == "fs"', { 'includes': [ './gyp/asset-fs.gypi' ] } ], ['asset_lib == "zip"', { 'includes': [ './gyp/asset-zip.gypi' ] } ], ['cache_lib == "sqlite"', { 'includes': [ './gyp/cache-sqlite.gypi' ] } ], diff --git a/platform/android/http_request_android.cpp b/platform/android/http_request_android.cpp new file mode 100644 index 000000000..044f77262 --- /dev/null +++ b/platform/android/http_request_android.cpp @@ -0,0 +1,399 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +namespace mbgl { + +void JNICALL nativeOnFailure(JNIEnv *env, jobject obj, jlong nativePtr, jint type, jstring message); +void JNICALL nativeOnResponse(JNIEnv *env, jobject obj, jlong nativePtr, jint code, jstring message, jstring etag, jstring modified, jstring cacheControl, jstring expires, jbyteArray body); + +class HTTPAndroidRequest; + +class HTTPAndroidContext : public HTTPContextBase { +public: + explicit HTTPAndroidContext(uv_loop_t *loop); + ~HTTPAndroidContext(); + + HTTPRequestBase* createRequest(const Resource&, + RequestBase::Callback, + uv_loop_t*, + std::shared_ptr) final; + + uv_loop_t *loop = nullptr; + + JavaVM *vm = nullptr; + jobject obj = nullptr; +}; + +class HTTPAndroidRequest : public HTTPRequestBase { +public: + HTTPAndroidRequest(HTTPAndroidContext*, + const Resource&, + Callback, + uv_loop_t*, + std::shared_ptr); + ~HTTPAndroidRequest(); + + void cancel() final; + void retry() final; + + void onFailure(int type, std::string message); + void onResponse(int code, std::string message, std::string etag, std::string modified, std::string cacheControl, std::string expires, std::string body); + +private: + void retry(uint64_t timeout) final; +#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10 + static void restart(uv_timer_t *timer, int); +#else + static void restart(uv_timer_t *timer); +#endif + void finish(ResponseStatus status); + void start(); + + HTTPAndroidContext *context = nullptr; + + bool cancelled = false; + + std::unique_ptr response; + const std::shared_ptr existingResponse; + + jobject obj = nullptr; + + uv_timer_t *timer = nullptr; + enum : bool { PreemptImmediately, ExponentialBackoff } strategy = PreemptImmediately; + int attempts = 0; + + static const int maxAttempts = 4; + + static const int connectionError = 0; + static const int temporaryError = 1; + static const int permanentError = 1; +}; + +// ------------------------------------------------------------------------------------------------- + +HTTPAndroidContext::HTTPAndroidContext(uv_loop_t *loop_) + : HTTPContextBase(loop_), + loop(loop_), + vm(mbgl::android::theJVM) { + + JNIEnv *env = nullptr; + bool detach = mbgl::android::attach_jni_thread(vm, &env, "HTTPAndroidContext::HTTPAndroidContext()"); + + const std::vector methods = { + {"nativeOnFailure", "(JILjava/lang/String;)V", reinterpret_cast(&nativeOnFailure)}, + {"nativeOnResponse", + "(JILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[B)V", + reinterpret_cast(&nativeOnResponse)} + }; + + if (env->RegisterNatives(mbgl::android::httpRequestClass, methods.data(), methods.size()) < 0) { + env->ExceptionDescribe(); + } + + obj = env->CallStaticObjectMethod(mbgl::android::httpContextClass, mbgl::android::httpContextGetInstanceId); + if (env->ExceptionCheck() || (obj == nullptr)) { + env->ExceptionDescribe(); + } + + obj = env->NewGlobalRef(obj); + if (obj == nullptr) { + env->ExceptionDescribe(); + } + + mbgl::android::detach_jni_thread(vm, &env, detach); +} + +HTTPAndroidContext::~HTTPAndroidContext() { + JNIEnv *env = nullptr; + bool detach = mbgl::android::attach_jni_thread(vm, &env, "HTTPAndroidContext::~HTTPAndroidContext()"); + + env->DeleteGlobalRef(obj); + obj = nullptr; + + mbgl::android::detach_jni_thread(vm, &env, detach); + + vm = nullptr; +} + +HTTPRequestBase* HTTPAndroidContext::createRequest(const Resource& resource, + RequestBase::Callback callback, + uv_loop_t* loop_, + std::shared_ptr response) { + return new HTTPAndroidRequest(this, resource, callback, loop_, response); +} + +HTTPAndroidRequest::HTTPAndroidRequest(HTTPAndroidContext* context_, const Resource& resource_, Callback callback_, uv_loop_t*, std::shared_ptr response_) + : HTTPRequestBase(resource_, callback_), + context(context_), + existingResponse(response_) { + + std::string etagStr; + std::string modifiedStr; + if (existingResponse) { + if (!existingResponse->etag.empty()) { + etagStr = existingResponse->etag; + } else if (existingResponse->modified) { + modifiedStr = util::rfc1123(existingResponse->modified); + } + } + + JNIEnv *env = nullptr; + bool detach = mbgl::android::attach_jni_thread(context->vm, &env, "HTTPAndroidContext::HTTPAndroidRequest()"); + + jstring resourceUrl = mbgl::android::std_string_to_jstring(env, resource.url); + jstring userAgent = mbgl::android::std_string_to_jstring(env, "MapboxGL/1.0"); + jstring etag = mbgl::android::std_string_to_jstring(env, etagStr); + jstring modified = mbgl::android::std_string_to_jstring(env, modifiedStr); + obj = env->CallObjectMethod(context->obj, mbgl::android::httpContextCreateRequestId, reinterpret_cast(this), resourceUrl, userAgent, etag, modified); + if (env->ExceptionCheck() || (obj == nullptr)) { + env->ExceptionDescribe(); + } + + obj = env->NewGlobalRef(obj); + if (obj == nullptr) { + env->ExceptionDescribe(); + } + + mbgl::android::detach_jni_thread(context->vm, &env, detach); + + context->addRequest(this); + start(); +} + +HTTPAndroidRequest::~HTTPAndroidRequest() { + context->removeRequest(this); + + JNIEnv *env = nullptr; + bool detach = mbgl::android::attach_jni_thread(context->vm, &env, "HTTPAndroidContext::~HTTPAndroidRequest()"); + + env->DeleteGlobalRef(obj); + obj = nullptr; + + mbgl::android::detach_jni_thread(context->vm, &env, detach); + + if (timer) { + uv_timer_stop(timer); + uv::close(timer); + timer = nullptr; + } +} + +void HTTPAndroidRequest::cancel() { + cancelled = true; + + JNIEnv *env = nullptr; + bool detach = mbgl::android::attach_jni_thread(context->vm, &env, "HTTPAndroidContext::cancel()"); + + env->CallVoidMethod(obj, mbgl::android::httpRequestCancelId); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + } + + mbgl::android::detach_jni_thread(context->vm, &env, detach); +} + +void HTTPAndroidRequest::start() { + attempts++; + + JNIEnv *env = nullptr; + bool detach = mbgl::android::attach_jni_thread(context->vm, &env, "HTTPAndroidContext::start()"); + + env->CallVoidMethod(obj, mbgl::android::httpRequestStartId); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + } + + mbgl::android::detach_jni_thread(context->vm, &env, detach); +} + +void HTTPAndroidRequest::retry(uint64_t timeout) { + response.reset(); + + assert(!timer); + timer = new uv_timer_t; + timer->data = this; + uv_timer_init(context->loop, timer); + uv_timer_start(timer, restart, timeout, 0); +} + +void HTTPAndroidRequest::retry() { + if (timer && strategy == PreemptImmediately) { + uv_timer_stop(timer); + uv_timer_start(timer, restart, 0, 0); + } +} + +#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10 +void HTTPAndroidRequest::restart(uv_timer_t *timer, int) { +#else +void HTTPAndroidRequest::restart(uv_timer_t *timer) { +#endif + auto baton = reinterpret_cast(timer->data); + + baton->timer = nullptr; + uv::close(timer); + + baton->start(); +} + +void HTTPAndroidRequest::finish(ResponseStatus status) { + if (status == ResponseStatus::TemporaryError && attempts < maxAttempts) { + strategy = ExponentialBackoff; + return retry((1 << (attempts - 1)) * 1000); + } else if (status == ResponseStatus::ConnectionError && attempts < maxAttempts) { + strategy = PreemptImmediately; + return retry(30000); + } + + if (status == ResponseStatus::NotModified) { + notify(std::move(response), FileCache::Hint::Refresh); + } else { + notify(std::move(response), FileCache::Hint::Full); + } + + delete this; +} + +int64_t parseCacheControl(const char *value) { + if (value) { + unsigned long long seconds = 0; + // TODO: cache-control may contain other information as well: + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9 + if (std::sscanf(value, "max-age=%llu", &seconds) == 1) { + return std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()).count() + + seconds; + } + } + + return 0; +} + +void HTTPAndroidRequest::onResponse(int code, std::string message, std::string etag, std::string modified, std::string cacheControl, std::string expires, std::string body) { + if (cancelled) { + delete this; + return; + } + + if (!response) { + response = std::make_unique(); + } + + response->message = message; + response->modified = parse_date(modified.c_str()); + response->etag = etag; + response->expires = parseCacheControl(cacheControl.c_str()); + if (!expires.empty()) { + response->expires = parse_date(expires.c_str()); + } + response->data = body; + + if (code == 304) { + if (existingResponse) { + response->status = existingResponse->status; + response->message = existingResponse->message; + response->modified = existingResponse->modified; + response->etag = existingResponse->etag; + response->data = existingResponse->data; + return finish(ResponseStatus::NotModified); + } else { + response->status = Response::Successful; + return finish(ResponseStatus::Successful); + } + } else if (code == 200) { + response->status = Response::Successful; + return finish(ResponseStatus::Successful); + } else if (code >= 500 && code < 600) { + response->status = Response::Error; + response->message = "HTTP status code " + util::toString(code); + return finish(ResponseStatus::TemporaryError); + } else { + response->status = Response::Error; + response->message = "HTTP status code " + util::toString(code); + return finish(ResponseStatus::PermanentError); + } + + throw std::runtime_error("Response hasn't been handled"); +} + +void HTTPAndroidRequest::onFailure(int type, std::string message) { + if (cancelled) { + delete this; + return; + } + + if (!response) { + response = std::make_unique(); + } + + response->status = Response::Error; + response->message = message; + + switch (type) { + case connectionError: + return finish(ResponseStatus::ConnectionError); + + case temporaryError: + return finish(ResponseStatus::TemporaryError); + + default: + return finish(ResponseStatus::PermanentError); + } + + throw std::runtime_error("Response hasn't been handled"); +} + +std::unique_ptr HTTPContextBase::createContext(uv_loop_t* loop) { + return std::make_unique(loop); +} + +#pragma clang diagnostic ignored "-Wunused-parameter" + +void JNICALL nativeOnFailure(JNIEnv *env, jobject obj, jlong nativePtr, jint type, jstring message) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeOnFailure"); + assert(nativePtr != 0); + HTTPAndroidRequest *request = reinterpret_cast(nativePtr); + std::string messageStr = mbgl::android::std_string_from_jstring(env, message); + return request->onFailure(type, messageStr); +} + +void JNICALL nativeOnResponse(JNIEnv *env, jobject obj, jlong nativePtr, jint code, jstring message, jstring etag, jstring modified, jstring cacheControl, jstring expires, jbyteArray body) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeOnResponse"); + assert(nativePtr != 0); + HTTPAndroidRequest *request = reinterpret_cast(nativePtr); + std::string messageStr = mbgl::android::std_string_from_jstring(env, message); + std::string etagStr; + if (etag != nullptr) { + etagStr = mbgl::android::std_string_from_jstring(env, etag); + } + std::string modifiedStr; + if (modified != nullptr) { + modifiedStr = mbgl::android::std_string_from_jstring(env, modified); + } + std::string cacheControlStr; + if (cacheControl != nullptr) { + cacheControlStr = mbgl::android::std_string_from_jstring(env, cacheControl); + } + std::string expiresStr; + if (expires != nullptr) { + expiresStr = mbgl::android::std_string_from_jstring(env, expires); + } + jbyte* bodyData = env->GetByteArrayElements(body, nullptr); + std::string bodyStr(reinterpret_cast(bodyData), env->GetArrayLength(body)); + env->ReleaseByteArrayElements(body, bodyData, JNI_ABORT); + return request->onResponse(code, messageStr, etagStr, modifiedStr, cacheControlStr, expiresStr, bodyStr); +} + +} diff --git a/platform/default/http_request_curl.cpp b/platform/default/http_request_curl.cpp index 0f7f8c0ac..e416034b4 100644 --- a/platform/default/http_request_curl.cpp +++ b/platform/default/http_request_curl.cpp @@ -11,12 +11,6 @@ #include -#ifdef __ANDROID__ -#include -#include -#include -#endif - #include #include #include @@ -331,104 +325,6 @@ int HTTPCURLContext::startTimeout(CURLM * /* multi */, long timeout_ms, void *us // ------------------------------------------------------------------------------------------------- -#ifdef __ANDROID__ - -// This function is called to load the CA bundle -// from http://curl.haxx.se/libcurl/c/cacertinmem.html¯ -static CURLcode sslctx_function(CURL * /* curl */, void *sslctx, void * /* parm */) { - - int error = 0; - struct zip *apk = zip_open(mbgl::android::apkPath.c_str(), 0, &error); - if (apk == nullptr) { - return CURLE_SSL_CACERT_BADFILE; - } - - struct zip_file *apkFile = zip_fopen(apk, "assets/ca-bundle.crt", ZIP_FL_NOCASE); - if (apkFile == nullptr) { - zip_close(apk); - apk = nullptr; - return CURLE_SSL_CACERT_BADFILE; - } - - struct zip_stat stat; - if (zip_stat(apk, "assets/ca-bundle.crt", ZIP_FL_NOCASE, &stat) != 0) { - zip_fclose(apkFile); - apkFile = nullptr; - zip_close(apk); - apk = nullptr; - return CURLE_SSL_CACERT_BADFILE; - } - - if (stat.size > std::numeric_limits::max()) { - zip_fclose(apkFile); - apkFile = nullptr; - zip_close(apk); - apk = nullptr; - return CURLE_SSL_CACERT_BADFILE; - } - - const auto pem = std::make_unique(stat.size); - - if (static_cast(zip_fread(apkFile, reinterpret_cast(pem.get()), stat.size)) != stat.size) { - zip_fclose(apkFile); - apkFile = nullptr; - zip_close(apk); - apk = nullptr; - return CURLE_SSL_CACERT_BADFILE; - } - - // get a pointer to the X509 certificate store (which may be empty!) - X509_STORE *store = SSL_CTX_get_cert_store((SSL_CTX *)sslctx); - if (store == nullptr) { - return CURLE_SSL_CACERT_BADFILE; - } - - // get a BIO - BIO *bio = BIO_new_mem_buf(pem.get(), static_cast(stat.size)); - if (bio == nullptr) { - store = nullptr; - return CURLE_SSL_CACERT_BADFILE; - } - - // use it to read the PEM formatted certificate from memory into an X509 - // structure that SSL can use - X509 *cert = nullptr; - while (PEM_read_bio_X509(bio, &cert, 0, nullptr) != nullptr) { - if (cert == nullptr) { - BIO_free(bio); - bio = nullptr; - store = nullptr; - return CURLE_SSL_CACERT_BADFILE; - } - - // add our certificate to this store - if (X509_STORE_add_cert(store, cert) == 0) { - X509_free(cert); - cert = nullptr; - BIO_free(bio); - bio = nullptr; - store = nullptr; - return CURLE_SSL_CACERT_BADFILE; - } - - X509_free(cert); - cert = nullptr; - } - - // decrease reference counts - BIO_free(bio); - bio = nullptr; - - zip_fclose(apkFile); - apkFile = nullptr; - zip_close(apk); - apk = nullptr; - - // all set to go - return CURLE_OK; -} -#endif - HTTPCURLRequest::HTTPCURLRequest(HTTPCURLContext* context_, const Resource& resource_, Callback callback_, uv_loop_t*, std::shared_ptr response_) : HTTPRequestBase(resource_, callback_), context(context_), @@ -458,12 +354,7 @@ HTTPCURLRequest::HTTPCURLRequest(HTTPCURLContext* context_, const Resource& reso handleError(curl_easy_setopt(handle, CURLOPT_PRIVATE, this)); handleError(curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, error)); -#ifdef __ANDROID__ - handleError(curl_easy_setopt(handle, CURLOPT_SSLCERTTYPE, "PEM")); - handleError(curl_easy_setopt(handle, CURLOPT_SSL_CTX_FUNCTION, sslctx_function)); -#else handleError(curl_easy_setopt(handle, CURLOPT_CAINFO, "ca-bundle.crt")); -#endif handleError(curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1)); handleError(curl_easy_setopt(handle, CURLOPT_URL, resource.url.c_str())); handleError(curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, writeCallback)); diff --git a/scripts/android/configure.sh b/scripts/android/configure.sh index 667ea6891..70c8973a8 100644 --- a/scripts/android/configure.sh +++ b/scripts/android/configure.sh @@ -1,8 +1,6 @@ #!/usr/bin/env bash BOOST_VERSION=1.57.0 -LIBCURL_VERSION=7.40.0 -OPENSSL_VERSION=1.0.1l LIBPNG_VERSION=1.6.16 JPEG_VERSION=v9a SQLITE_VERSION=3.8.8.1 @@ -11,4 +9,4 @@ ZLIB_VERSION=system NUNICODE_VERSION=1.5.1 LIBZIP_VERSION=0.11.2 -export MASON_ANDROID_ABI=${MASON_PLATFORM_VERSION} \ No newline at end of file +export MASON_ANDROID_ABI=${MASON_PLATFORM_VERSION} diff --git a/scripts/android/defaults.mk b/scripts/android/defaults.mk index 436f9db71..29f047c88 100644 --- a/scripts/android/defaults.mk +++ b/scripts/android/defaults.mk @@ -1,7 +1,7 @@ HEADLESS ?= none PLATFORM ?= android ASSET ?= zip -HTTP ?= curl +HTTP ?= android CACHE ?= sqlite GYP_FLAVOR_SUFFIX=-android From 21ec5056489f53601b3d45a0b1c79a85add42b0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Mon, 10 Aug 2015 17:14:00 +0300 Subject: [PATCH 08/11] add benchmark mode that shows the real frame time rather than the vsync time --- include/mbgl/map/map.hpp | 1 + include/mbgl/platform/default/glfw_view.hpp | 10 +++- linux/main.cpp | 13 ++++- macosx/main.mm | 53 +++++++++++++++++++-- macosx/mapboxgl-app.gypi | 1 + platform/default/glfw_view.cpp | 34 ++++++++----- src/mbgl/map/map.cpp | 4 ++ 7 files changed, 96 insertions(+), 20 deletions(-) diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp index ae43dc8a8..ebd79620d 100644 --- a/include/mbgl/map/map.hpp +++ b/include/mbgl/map/map.hpp @@ -164,6 +164,7 @@ public: void setDebug(bool value); void toggleDebug(); bool getDebug() const; + void setNeedsRepaint(); void setCollisionDebug(bool value); void toggleCollisionDebug(); bool getCollisionDebug() const; diff --git a/include/mbgl/platform/default/glfw_view.hpp b/include/mbgl/platform/default/glfw_view.hpp index 1bc210b29..ec9908073 100644 --- a/include/mbgl/platform/default/glfw_view.hpp +++ b/include/mbgl/platform/default/glfw_view.hpp @@ -12,7 +12,7 @@ class GLFWView : public mbgl::View { public: - GLFWView(bool fullscreen = false); + GLFWView(bool fullscreen = false, bool benchmark = false); ~GLFWView(); float getPixelRatio() const override; @@ -42,7 +42,7 @@ public: void setWindowTitle(const std::string&); void run(); - void fps(); + void report(float duration); private: mbgl::LatLng makeRandomPoint() const; @@ -61,9 +61,15 @@ private: private: bool fullscreen = false; + const bool benchmark = false; bool tracking = false; bool rotating = false; + // Frame timer + int frames = 0; + float frameTime = 0; + double lastReported = 0; + int width = 1024; int height = 768; int fbWidth; diff --git a/linux/main.cpp b/linux/main.cpp index c21d7446e..0892ce8f1 100644 --- a/linux/main.cpp +++ b/linux/main.cpp @@ -32,17 +32,19 @@ void quit_handler(int) { int main(int argc, char *argv[]) { bool fullscreen = false; + bool benchmark = false; std::string style; const struct option long_options[] = { {"fullscreen", no_argument, 0, 'f'}, + {"benchmark", no_argument, 0, 'b'}, {"style", required_argument, 0, 's'}, {0, 0, 0, 0} }; while (true) { int option_index = 0; - int opt = getopt_long(argc, argv, "fs:", long_options, &option_index); + int opt = getopt_long(argc, argv, "fbs:", long_options, &option_index); if (opt == -1) break; switch (opt) { @@ -52,6 +54,9 @@ int main(int argc, char *argv[]) { case 'f': fullscreen = true; break; + case 'b': + benchmark = true; + break; case 's': style = std::string("asset://") + std::string(optarg); default: @@ -67,7 +72,11 @@ int main(int argc, char *argv[]) { sigIntHandler.sa_flags = 0; sigaction(SIGINT, &sigIntHandler, NULL); - view = std::make_unique(fullscreen); + if (benchmark) { + mbgl::Log::Info(mbgl::Event::General, "BENCHMARK MODE: Some optimizations are disabled."); + } + + view = std::make_unique(fullscreen, benchmark); mbgl::SQLiteCache cache("/tmp/mbgl-cache.db"); mbgl::DefaultFileSource fileSource(&cache); diff --git a/macosx/main.mm b/macosx/main.mm index 4a786f577..24251e6d4 100644 --- a/macosx/main.mm +++ b/macosx/main.mm @@ -12,6 +12,19 @@ #import +#pragma GCC diagnostic push +#ifndef __clang__ +#pragma GCC diagnostic ignored "-Wunused-local-typedefs" +#pragma GCC diagnostic ignored "-Wshadow" +#endif +#include +#pragma GCC diagnostic pop + +#include + +namespace po = boost::program_options; + + @interface URLHandler : NSObject @property (nonatomic) mbgl::Map *map; @@ -101,8 +114,33 @@ const std::string &defaultCacheDatabase() { return path; } -int main() { - GLFWView view; +int main(int argc, char* argv[]) { + bool fullscreen = false; + bool benchmark = false; + std::string style; + + po::options_description desc("Allowed options"); + desc.add_options() + ("fullscreen,f", po::bool_switch(&fullscreen)->default_value(fullscreen), "Fullscreen mode") + ("style,s", po::value(&style)->value_name("json"), "Map stylesheet") + ("benchmark,b", po::bool_switch(&benchmark)->default_value(benchmark), "Benchmark mode") + ; + + try { + auto parsed = po::command_line_parser(argc, argv).options(desc).allow_unregistered().run(); + po::variables_map vm; + po::store(parsed, vm); + po::notify(vm); + } catch(std::exception& e) { + std::cout << "Error: " << e.what() << std::endl << desc; + exit(1); + } + + if (benchmark) { + mbgl::Log::Info(mbgl::Event::General, "BENCHMARK MODE: Some optimizations are disabled."); + } + + GLFWView view(fullscreen, benchmark); mbgl::SQLiteCache cache(defaultCacheDatabase()); mbgl::DefaultFileSource fileSource(&cache); @@ -146,10 +184,15 @@ int main() { mbgl::Log::Info(mbgl::Event::Setup, std::string("Changed style to: ") + newStyle.first); }); + // Load style - const auto& newStyle = mbgl::util::defaultStyles.front(); - map.setStyleURL(newStyle.first); - view.setWindowTitle(newStyle.second); + if (style.empty()) { + const auto& newStyle = mbgl::util::defaultStyles.front(); + style = newStyle.first; + view.setWindowTitle(newStyle.second); + } + + map.setStyleURL(style); view.run(); diff --git a/macosx/mapboxgl-app.gypi b/macosx/mapboxgl-app.gypi index 589ae29bd..e1d9476e1 100644 --- a/macosx/mapboxgl-app.gypi +++ b/macosx/mapboxgl-app.gypi @@ -43,6 +43,7 @@ ], 'libraries': [ '<@(glfw_static_libs)', + '<@(boost_libprogram_options_static_libs)' ], }, diff --git a/platform/default/glfw_view.cpp b/platform/default/glfw_view.cpp index 078c26fee..3ac60621e 100644 --- a/platform/default/glfw_view.cpp +++ b/platform/default/glfw_view.cpp @@ -15,7 +15,8 @@ void glfwError(int error, const char *description) { assert(false); } -GLFWView::GLFWView(bool fullscreen_) : fullscreen(fullscreen_) { +GLFWView::GLFWView(bool fullscreen_, bool benchmark_) + : fullscreen(fullscreen_), benchmark(benchmark_) { glfwSetErrorCallback(glfwError); std::srand(std::time(0)); @@ -56,7 +57,13 @@ GLFWView::GLFWView(bool fullscreen_) : fullscreen(fullscreen_) { glfwSetWindowUserPointer(window, this); glfwMakeContextCurrent(window); - glfwSwapInterval(1); + if (benchmark) { + // Disables vsync on platforms that support it. + glfwSwapInterval(0); + } else { + glfwSwapInterval(1); + } + glfwSetCursorPosCallback(window, onMouseMove); glfwSetMouseButtonCallback(window, onMouseClick); @@ -336,7 +343,12 @@ void GLFWView::run() { glfwWaitEvents(); const bool dirty = !clean.test_and_set(); if (dirty) { + const double started = glfwGetTime(); map->renderSync(); + report(1000 * (glfwGetTime() - started)); + if (benchmark) { + map->setNeedsRepaint(); + } map->nudgeTransitions(); } } @@ -373,20 +385,20 @@ void GLFWView::invalidate() { void GLFWView::swap() { glfwSwapBuffers(window); - fps(); } -void GLFWView::fps() { - static int frames = 0; - static double timeElapsed = 0; - +void GLFWView::report(float duration) { frames++; - double currentTime = glfwGetTime(); + frameTime += duration; - if (currentTime - timeElapsed >= 1) { - mbgl::Log::Info(mbgl::Event::OpenGL, "FPS: %4.2f", frames / (currentTime - timeElapsed)); - timeElapsed = currentTime; + const double currentTime = glfwGetTime(); + if (currentTime - lastReported >= 1) { + frameTime /= frames; + mbgl::Log::Info(mbgl::Event::OpenGL, "Frame time: %6.2fms (%6.2f fps)", frameTime, + 1000 / frameTime); frames = 0; + frameTime = 0; + lastReported = currentTime; } } diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp index fadd4c48d..a7015695c 100644 --- a/src/mbgl/map/map.cpp +++ b/src/mbgl/map/map.cpp @@ -375,6 +375,10 @@ bool Map::getDebug() const { return data->getDebug(); } +void Map::setNeedsRepaint() { + data->setNeedsRepaint(true); +} + void Map::setCollisionDebug(bool value) { data->setCollisionDebug(value); update(Update::Repaint); From 5a79bd832da2c32f715870a653475dc96fea3f20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Tue, 11 Aug 2015 16:17:15 +0300 Subject: [PATCH 09/11] allow setting a location and print the location on exit --- linux/main.cpp | 44 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/linux/main.cpp b/linux/main.cpp index 0892ce8f1..6bd2f7c43 100644 --- a/linux/main.cpp +++ b/linux/main.cpp @@ -34,11 +34,18 @@ int main(int argc, char *argv[]) { bool fullscreen = false; bool benchmark = false; std::string style; + double latitude = 0, longitude = 0; + double bearing = 0, zoom = 1; + bool skipConfig = false; const struct option long_options[] = { {"fullscreen", no_argument, 0, 'f'}, {"benchmark", no_argument, 0, 'b'}, {"style", required_argument, 0, 's'}, + {"lon", required_argument, 0, 'x'}, + {"lat", required_argument, 0, 'y'}, + {"zoom", required_argument, 0, 'z'}, + {"bearing", required_argument, 0, 'r'}, {0, 0, 0, 0} }; @@ -59,6 +66,23 @@ int main(int argc, char *argv[]) { break; case 's': style = std::string("asset://") + std::string(optarg); + break; + case 'x': + longitude = atof(optarg); + skipConfig = true; + break; + case 'y': + latitude = atof(optarg); + skipConfig = true; + break; + case 'z': + zoom = atof(optarg); + skipConfig = true; + break; + case 'r': + bearing = atof(optarg); + skipConfig = true; + break; default: break; } @@ -93,9 +117,16 @@ int main(int argc, char *argv[]) { // Load settings mbgl::Settings_JSON settings; - map.setLatLngZoom(mbgl::LatLng(settings.latitude, settings.longitude), settings.zoom); - map.setBearing(settings.bearing); - map.setDebug(settings.debug); + + if (skipConfig) { + map.setLatLngZoom(mbgl::LatLng(latitude, longitude), zoom); + map.setBearing(bearing); + mbgl::Log::Info(mbgl::Event::General, "Location: %f/%f (z%.2f, %.2f deg)", latitude, longitude, zoom, bearing); + } else { + map.setLatLngZoom(mbgl::LatLng(settings.latitude, settings.longitude), settings.zoom); + map.setBearing(settings.bearing); + map.setDebug(settings.debug); + } view->setChangeStyleCallback([&map] () { static uint8_t currentStyleIndex; @@ -129,7 +160,12 @@ int main(int argc, char *argv[]) { settings.zoom = map.getZoom(); settings.bearing = map.getBearing(); settings.debug = map.getDebug(); - settings.save(); + if (!skipConfig) { + settings.save(); + } + mbgl::Log::Info(mbgl::Event::General, + "Exit location: --lat=\"%f\" --lon=\"%f\" --zoom=\"%f\" --bearing \"%f\"", + settings.latitude, settings.longitude, settings.zoom, settings.bearing); return 0; } From aa06a9d58748b5ef9e9e979891d197f5a26c9fa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Tue, 11 Aug 2015 16:39:52 +0300 Subject: [PATCH 10/11] query the monitor's resolution instead of forcing 1024x768 --- platform/default/glfw_view.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/platform/default/glfw_view.cpp b/platform/default/glfw_view.cpp index 3ac60621e..8453845d1 100644 --- a/platform/default/glfw_view.cpp +++ b/platform/default/glfw_view.cpp @@ -29,6 +29,9 @@ GLFWView::GLFWView(bool fullscreen_, bool benchmark_) GLFWmonitor *monitor = nullptr; if (fullscreen) { monitor = glfwGetPrimaryMonitor(); + auto videoMode = glfwGetVideoMode(monitor); + width = videoMode->width; + height = videoMode->height; } #ifdef DEBUG From 01b5636df0f7fe885176ad4918d18c5aa12de11d Mon Sep 17 00:00:00 2001 From: "Justin R. Miller" Date: Tue, 11 Aug 2015 15:10:55 -0700 Subject: [PATCH 11/11] fixes #1951: check for support of title before querying --- platform/ios/MGLMapView.mm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm index 3a517ddef..895066355 100644 --- a/platform/ios/MGLMapView.mm +++ b/platform/ios/MGLMapView.mm @@ -2047,7 +2047,9 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) self.selectedAnnotation = annotation; - if (annotation.title && [self.delegate respondsToSelector:@selector(mapView:annotationCanShowCallout:)] && + if ([annotation respondsToSelector:@selector(title)] && + annotation.title && + [self.delegate respondsToSelector:@selector(mapView:annotationCanShowCallout:)] && [self.delegate mapView:self annotationCanShowCallout:annotation]) { // build the callout