Compare commits

...

35 commits
master ... r3.4

Author SHA1 Message Date
max-klaus
fa5425c8fe Fix huawei build 2019-09-12 19:13:58 +03:00
max-klaus
8352fa4d84 Revert huawei build 2019-09-11 21:59:22 +03:00
max-klaus
ad4fa9c500 Try to fix build 2019-09-11 21:20:38 +03:00
max-klaus
fae21cbdf3 Try to fix build 2019-09-11 21:13:56 +03:00
max-klaus
784ca0df66 Try to fix build 2019-09-11 21:11:40 +03:00
max-klaus
d2ea48e468 Fix huawei build 2019-09-11 20:27:45 +03:00
max-klaus
98162d6b8f Fixed Huawei build 2019-09-11 19:30:36 +03:00
max-klaus
1505b77fea Fix build 2019-09-11 19:30:26 +03:00
Nazar
184c7d3b90 huawei drm integration p.6
remove drm-strings
2019-09-11 19:30:03 +03:00
Nazar
556d75c6f2 huawei drm integration p.5 2019-09-11 19:29:33 +03:00
Nazar
5bc0da0efb huawei drm integration p.4 2019-09-11 19:29:23 +03:00
Nazar
cd4bb2667c huawei drm integration p.3 2019-09-11 19:29:12 +03:00
Nazar
dd296864f4 huawei drm integration p.2 2019-09-11 19:29:00 +03:00
Nazar
1f04ce2ce2 huawei drm integration 2019-09-11 19:28:34 +03:00
max-klaus
3a7232c8b4 Fix #7468 #7460 2019-09-05 11:45:54 +03:00
max-klaus
53acf00f1a Fix #7491 2019-09-01 10:23:31 +03:00
max-klaus
8235f31f7d Fix BillingManager crash 2019-08-20 10:45:57 +03:00
max-klaus
4353251067 Fix billing and choose plan UI 2019-08-18 22:12:05 +03:00
max-klaus
30056e5dea Fix merge 2019-08-18 19:41:44 +03:00
max-klaus
ef52f119d7 Drop billing aidl 2019-08-18 19:39:25 +03:00
max-klaus
222b9e410b Move purchases to google billing library 2019-08-18 19:39:07 +03:00
max-klaus
0808d1a3b8 Added occitan language (configure map) 2019-08-18 19:38:44 +03:00
max-klaus
5eac1ac31d Added occitan language 2019-08-18 19:38:31 +03:00
max-klaus
4e5d5f700f Fix annual monthly price 2019-08-18 19:36:49 +03:00
max-klaus
f0eae1515a Move OLC lib to gradle 2019-08-18 19:35:40 +03:00
max-klaus
1ae1893105 Fix sensor for turn screen on 2019-08-14 15:58:20 +03:00
max-klaus
ceff1bef41 Fix turn screen on and notifications flickering 2019-08-14 15:58:09 +03:00
max-klaus
e04a63987c Fix route simulation in background 2019-08-14 15:57:51 +03:00
max-klaus
e11ee96e6a Fix turn screen on flickering 2019-08-14 15:57:31 +03:00
max-klaus
1d316087b6 Fix context menu point animation 2019-08-13 10:23:18 +03:00
max-klaus
f0dde03f19 Fix turn screen on/off 2019-08-09 12:35:10 +03:00
max-klaus
ba6d3c1c85 Fix jumping animation 2019-08-09 12:33:54 +03:00
max-klaus
5e591194c0 Fix route details dismiss on tap 2019-08-03 22:17:31 +03:00
max-klaus
46b88489a3 Fix android 4 padding 2019-08-03 19:34:13 +03:00
max-klaus
e86bde9c61 Fix transport stop context menu. Fix top route badge press. 2019-08-03 19:18:21 +03:00
48 changed files with 1743 additions and 2558 deletions

View file

@ -103,6 +103,7 @@ dependencies {
implementation 'org.apache.commons:commons-compress:1.17' implementation 'org.apache.commons:commons-compress:1.17'
implementation 'com.moparisthebest:junidecode:0.1.1' implementation 'com.moparisthebest:junidecode:0.1.1'
implementation 'com.vividsolutions:jts-core:1.14.0' implementation 'com.vividsolutions:jts-core:1.14.0'
implementation 'com.google.openlocationcode:openlocationcode:1.0.4'
// turn off for now // turn off for now
//implementation 'com.atilika.kuromoji:kuromoji-ipadic:0.9.0' //implementation 'com.atilika.kuromoji:kuromoji-ipadic:0.9.0'
implementation 'net.sf.kxml:kxml2:2.1.8' implementation 'net.sf.kxml:kxml2:2.1.8'

View file

@ -1,618 +0,0 @@
// Copyright 2014 Google Inc. All rights reserved.
//
// 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.google.openlocationcode;
import java.math.BigDecimal;
/**
* Convert locations to and from convenient short codes.
*
* Open Location Codes are short, ~10 character codes that can be used instead of street
* addresses. The codes can be generated and decoded offline, and use a reduced character set that
* minimises the chance of codes including words.
*
* This provides both object and static methods.
*
* Create an object with:
* OpenLocationCode code = new OpenLocationCode("7JVW52GR+2V");
* OpenLocationCode code = new OpenLocationCode("52GR+2V");
* OpenLocationCode code = new OpenLocationCode(27.175063, 78.042188);
* OpenLocationCode code = new OpenLocationCode(27.175063, 78.042188, 11);
*
* Once you have a code object, you can apply the other methods to it, such as to shorten:
* code.shorten(27.176, 78.05)
*
* Recover the nearest match (if the code was a short code):
* code.recover(27.176, 78.05)
*
* Or decode a code into its coordinates, returning a CodeArea object.
* code.decode()
*
* @author Jiri Semecky
* @author Doug Rinckes
*/
public final class OpenLocationCode {
// Provides a normal precision code, approximately 14x14 meters.
public static final int CODE_PRECISION_NORMAL = 10;
// The character set used to encode the values.
public static final String CODE_ALPHABET = "23456789CFGHJMPQRVWX";
// A separator used to break the code into two parts to aid memorability.
public static final char SEPARATOR = '+';
// The character used to pad codes.
public static final char PADDING_CHARACTER = '0';
// The number of characters to place before the separator.
private static final int SEPARATOR_POSITION = 8;
// The max number of digits to process in a plus code.
public static final int MAX_DIGIT_COUNT = 15;
// Note: The double type can't be used because of the rounding arithmetic due to floating point
// implementation. Eg. "8.95 - 8" can give result 0.9499999999999 instead of 0.95 which
// incorrectly classify the points on the border of a cell. Therefore all the calculation is done
// using BigDecimal.
// The base to use to convert numbers to/from.
private static final BigDecimal ENCODING_BASE = new BigDecimal(CODE_ALPHABET.length());
// The maximum value for latitude in degrees.
private static final BigDecimal LATITUDE_MAX = new BigDecimal(90);
// The maximum value for longitude in degrees.
private static final BigDecimal LONGITUDE_MAX = new BigDecimal(180);
// Maximum code length using just lat/lng pair encoding.
private static final int PAIR_CODE_LENGTH = 10;
// Number of columns in the grid refinement method.
private static final BigDecimal GRID_COLUMNS = new BigDecimal(4);
// Number of rows in the grid refinement method.
private static final BigDecimal GRID_ROWS = new BigDecimal(5);
/**
* Coordinates of a decoded Open Location Code.
*
* <p>The coordinates include the latitude and longitude of the lower left and upper right corners
* and the center of the bounding box for the area the code represents.
*/
public static class CodeArea {
private final BigDecimal southLatitude;
private final BigDecimal westLongitude;
private final BigDecimal northLatitude;
private final BigDecimal eastLongitude;
public CodeArea(
BigDecimal southLatitude,
BigDecimal westLongitude,
BigDecimal northLatitude,
BigDecimal eastLongitude) {
this.southLatitude = southLatitude;
this.westLongitude = westLongitude;
this.northLatitude = northLatitude;
this.eastLongitude = eastLongitude;
}
public double getSouthLatitude() {
return southLatitude.doubleValue();
}
public double getWestLongitude() {
return westLongitude.doubleValue();
}
public double getLatitudeHeight() {
return northLatitude.subtract(southLatitude).doubleValue();
}
public double getLongitudeWidth() {
return eastLongitude.subtract(westLongitude).doubleValue();
}
public double getCenterLatitude() {
return southLatitude.add(northLatitude).doubleValue() / 2;
}
public double getCenterLongitude() {
return westLongitude.add(eastLongitude).doubleValue() / 2;
}
public double getNorthLatitude() {
return northLatitude.doubleValue();
}
public double getEastLongitude() {
return eastLongitude.doubleValue();
}
}
/** The current code for objects. */
private final String code;
/**
* Creates Open Location Code object for the provided code.
* @param code A valid OLC code. Can be a full code or a shortened code.
* @throws IllegalArgumentException when the passed code is not valid.
*/
public OpenLocationCode(String code) {
if (!isValidCode(code.toUpperCase())) {
throw new IllegalArgumentException(
"The provided code '" + code + "' is not a valid Open Location Code.");
}
this.code = code.toUpperCase();
}
/**
* Creates Open Location Code.
* @param latitude The latitude in decimal degrees.
* @param longitude The longitude in decimal degrees.
* @param codeLength The desired number of digits in the code.
* @throws IllegalArgumentException if the code length is not valid.
*/
public OpenLocationCode(double latitude, double longitude, int codeLength) {
// Limit the maximum number of digits in the code.
codeLength = Math.min(codeLength, MAX_DIGIT_COUNT);
// Check that the code length requested is valid.
if (codeLength < 4 || (codeLength < PAIR_CODE_LENGTH && codeLength % 2 == 1)) {
throw new IllegalArgumentException("Illegal code length " + codeLength);
}
// Ensure that latitude and longitude are valid.
latitude = clipLatitude(latitude);
longitude = normalizeLongitude(longitude);
// Latitude 90 needs to be adjusted to be just less, so the returned code can also be decoded.
if (latitude == LATITUDE_MAX.doubleValue()) {
latitude = latitude - 0.9 * computeLatitudePrecision(codeLength);
}
// Adjust latitude and longitude to be in positive number ranges.
// We add the max values when creating the BigDecimal (as opposed to creating a BigDecimal and
// then adding the value) to be consistent with the other implementations.
BigDecimal remainingLatitude = new BigDecimal(latitude + LATITUDE_MAX.doubleValue());
BigDecimal remainingLongitude = new BigDecimal(longitude + LONGITUDE_MAX.doubleValue());
// Count how many digits have been created.
int generatedDigits = 0;
// Store the code.
StringBuilder codeBuilder = new StringBuilder();
// The precisions are initially set to ENCODING_BASE^2 because they will be immediately
// divided.
BigDecimal latPrecision = ENCODING_BASE.multiply(ENCODING_BASE);
BigDecimal lngPrecision = ENCODING_BASE.multiply(ENCODING_BASE);
while (generatedDigits < codeLength) {
if (generatedDigits < PAIR_CODE_LENGTH) {
// Use the normal algorithm for the first set of digits.
latPrecision = latPrecision.divide(ENCODING_BASE);
lngPrecision = lngPrecision.divide(ENCODING_BASE);
BigDecimal latDigit = remainingLatitude.divide(latPrecision, 0, BigDecimal.ROUND_FLOOR);
BigDecimal lngDigit = remainingLongitude.divide(lngPrecision, 0, BigDecimal.ROUND_FLOOR);
remainingLatitude = remainingLatitude.subtract(latPrecision.multiply(latDigit));
remainingLongitude = remainingLongitude.subtract(lngPrecision.multiply(lngDigit));
codeBuilder.append(CODE_ALPHABET.charAt(latDigit.intValue()));
codeBuilder.append(CODE_ALPHABET.charAt(lngDigit.intValue()));
generatedDigits += 2;
} else {
// Use the 4x5 grid for remaining digits.
latPrecision = latPrecision.divide(GRID_ROWS);
lngPrecision = lngPrecision.divide(GRID_COLUMNS);
BigDecimal row = remainingLatitude.divide(latPrecision, 0, BigDecimal.ROUND_FLOOR);
BigDecimal col = remainingLongitude.divide(lngPrecision, 0, BigDecimal.ROUND_FLOOR);
remainingLatitude = remainingLatitude.subtract(latPrecision.multiply(row));
remainingLongitude = remainingLongitude.subtract(lngPrecision.multiply(col));
codeBuilder.append(
CODE_ALPHABET.charAt(row.intValue() * GRID_COLUMNS.intValue() + col.intValue()));
generatedDigits += 1;
}
// If we are at the separator position, add the separator.
if (generatedDigits == SEPARATOR_POSITION) {
codeBuilder.append(SEPARATOR);
}
}
// If the generated code is shorter than the separator position, pad the code and add the
// separator.
if (generatedDigits < SEPARATOR_POSITION) {
for (; generatedDigits < SEPARATOR_POSITION; generatedDigits++) {
codeBuilder.append(PADDING_CHARACTER);
}
codeBuilder.append(SEPARATOR);
}
this.code = codeBuilder.toString();
}
/**
* Creates Open Location Code with the default precision length.
* @param latitude The latitude in decimal degrees.
* @param longitude The longitude in decimal degrees.
*/
public OpenLocationCode(double latitude, double longitude) {
this(latitude, longitude, CODE_PRECISION_NORMAL);
}
/**
* Returns the string representation of the code.
*/
public String getCode() {
return code;
}
/**
* Encodes latitude/longitude into 10 digit Open Location Code. This method is equivalent to
* creating the OpenLocationCode object and getting the code from it.
* @param latitude The latitude in decimal degrees.
* @param longitude The longitude in decimal degrees.
*/
public static String encode(double latitude, double longitude) {
return new OpenLocationCode(latitude, longitude).getCode();
}
/**
* Encodes latitude/longitude into Open Location Code of the provided length. This method is
* equivalent to creating the OpenLocationCode object and getting the code from it.
* @param latitude The latitude in decimal degrees.
* @param longitude The longitude in decimal degrees.
*/
public static String encode(double latitude, double longitude, int codeLength) {
return new OpenLocationCode(latitude, longitude, codeLength).getCode();
}
/**
* Decodes {@link OpenLocationCode} object into {@link CodeArea} object encapsulating
* latitude/longitude bounding box.
*/
public CodeArea decode() {
if (!isFullCode(code)) {
throw new IllegalStateException(
"Method decode() could only be called on valid full codes, code was " + code + ".");
}
// Strip padding and separator characters out of the code.
String decoded = code.replace(String.valueOf(SEPARATOR), "")
.replace(String.valueOf(PADDING_CHARACTER), "");
int digit = 0;
// The precisions are initially set to ENCODING_BASE^2 because they will be immediately
// divided.
BigDecimal latPrecision = ENCODING_BASE.multiply(ENCODING_BASE);
BigDecimal lngPrecision = ENCODING_BASE.multiply(ENCODING_BASE);
// Save the coordinates.
BigDecimal southLatitude = BigDecimal.ZERO;
BigDecimal westLongitude = BigDecimal.ZERO;
// Decode the digits.
while (digit < Math.min(decoded.length(), MAX_DIGIT_COUNT)) {
if (digit < PAIR_CODE_LENGTH) {
// Decode a pair of digits, the first being latitude and the second being longitude.
latPrecision = latPrecision.divide(ENCODING_BASE);
lngPrecision = lngPrecision.divide(ENCODING_BASE);
int digitVal = CODE_ALPHABET.indexOf(decoded.charAt(digit));
southLatitude = southLatitude.add(latPrecision.multiply(new BigDecimal(digitVal)));
digitVal = CODE_ALPHABET.indexOf(decoded.charAt(digit + 1));
westLongitude = westLongitude.add(lngPrecision.multiply(new BigDecimal(digitVal)));
digit += 2;
} else {
// Use the 4x5 grid for digits after 10.
int digitVal = CODE_ALPHABET.indexOf(decoded.charAt(digit));
int row = (int) (digitVal / GRID_COLUMNS.intValue());
int col = digitVal % GRID_COLUMNS.intValue();
latPrecision = latPrecision.divide(GRID_ROWS);
lngPrecision = lngPrecision.divide(GRID_COLUMNS);
southLatitude = southLatitude.add(latPrecision.multiply(new BigDecimal(row)));
westLongitude = westLongitude.add(lngPrecision.multiply(new BigDecimal(col)));
digit += 1;
}
}
return new CodeArea(
southLatitude.subtract(LATITUDE_MAX),
westLongitude.subtract(LONGITUDE_MAX),
southLatitude.subtract(LATITUDE_MAX).add(latPrecision),
westLongitude.subtract(LONGITUDE_MAX).add(lngPrecision));
}
/**
* Decodes code representing Open Location Code into {@link CodeArea} object encapsulating
* latitude/longitude bounding box.
*
* @param code Open Location Code to be decoded.
* @throws IllegalArgumentException if the provided code is not a valid Open Location Code.
*/
public static CodeArea decode(String code) throws IllegalArgumentException {
return new OpenLocationCode(code).decode();
}
/** Returns whether this {@link OpenLocationCode} is a full Open Location Code. */
public boolean isFull() {
return code.indexOf(SEPARATOR) == SEPARATOR_POSITION;
}
/** Returns whether the provided Open Location Code is a full Open Location Code. */
public static boolean isFull(String code) throws IllegalArgumentException {
return new OpenLocationCode(code).isFull();
}
/** Returns whether this {@link OpenLocationCode} is a short Open Location Code. */
public boolean isShort() {
return code.indexOf(SEPARATOR) >= 0 && code.indexOf(SEPARATOR) < SEPARATOR_POSITION;
}
/** Returns whether the provided Open Location Code is a short Open Location Code. */
public static boolean isShort(String code) throws IllegalArgumentException {
return new OpenLocationCode(code).isShort();
}
/**
* Returns whether this {@link OpenLocationCode} is a padded Open Location Code, meaning that it
* contains less than 8 valid digits.
*/
private boolean isPadded() {
return code.indexOf(PADDING_CHARACTER) >= 0;
}
/**
* Returns whether the provided Open Location Code is a padded Open Location Code, meaning that it
* contains less than 8 valid digits.
*/
public static boolean isPadded(String code) throws IllegalArgumentException {
return new OpenLocationCode(code).isPadded();
}
/**
* Returns short {@link OpenLocationCode} from the full Open Location Code created by removing
* four or six digits, depending on the provided reference point. It removes as many digits as
* possible.
*/
public OpenLocationCode shorten(double referenceLatitude, double referenceLongitude) {
if (!isFull()) {
throw new IllegalStateException("shorten() method could only be called on a full code.");
}
if (isPadded()) {
throw new IllegalStateException("shorten() method can not be called on a padded code.");
}
CodeArea codeArea = decode();
double range = Math.max(
Math.abs(referenceLatitude - codeArea.getCenterLatitude()),
Math.abs(referenceLongitude - codeArea.getCenterLongitude()));
// We are going to check to see if we can remove three pairs, two pairs or just one pair of
// digits from the code.
for (int i = 4; i >= 1; i--) {
// Check if we're close enough to shorten. The range must be less than 1/2
// the precision to shorten at all, and we want to allow some safety, so
// use 0.3 instead of 0.5 as a multiplier.
if (range < (computeLatitudePrecision(i * 2) * 0.3)) {
// We're done.
return new OpenLocationCode(code.substring(i * 2));
}
}
throw new IllegalArgumentException(
"Reference location is too far from the Open Location Code center.");
}
/**
* Returns an {@link OpenLocationCode} object representing a full Open Location Code from this
* (short) Open Location Code, given the reference location.
*/
public OpenLocationCode recover(double referenceLatitude, double referenceLongitude) {
if (isFull()) {
// Note: each code is either full xor short, no other option.
return this;
}
referenceLatitude = clipLatitude(referenceLatitude);
referenceLongitude = normalizeLongitude(referenceLongitude);
int digitsToRecover = SEPARATOR_POSITION - code.indexOf(SEPARATOR);
// The precision (height and width) of the missing prefix in degrees.
double prefixPrecision = Math.pow(ENCODING_BASE.intValue(), 2 - (digitsToRecover / 2));
// Use the reference location to generate the prefix.
String recoveredPrefix =
new OpenLocationCode(referenceLatitude, referenceLongitude)
.getCode()
.substring(0, digitsToRecover);
// Combine the prefix with the short code and decode it.
OpenLocationCode recovered = new OpenLocationCode(recoveredPrefix + code);
CodeArea recoveredCodeArea = recovered.decode();
// Work out whether the new code area is too far from the reference location. If it is, we
// move it. It can only be out by a single precision step.
double recoveredLatitude = recoveredCodeArea.getCenterLatitude();
double recoveredLongitude = recoveredCodeArea.getCenterLongitude();
// Move the recovered latitude by one precision up or down if it is too far from the reference,
// unless doing so would lead to an invalid latitude.
double latitudeDiff = recoveredLatitude - referenceLatitude;
if (latitudeDiff > prefixPrecision / 2
&& recoveredLatitude - prefixPrecision > -LATITUDE_MAX.intValue()) {
recoveredLatitude -= prefixPrecision;
} else if (latitudeDiff < -prefixPrecision / 2
&& recoveredLatitude + prefixPrecision < LATITUDE_MAX.intValue()) {
recoveredLatitude += prefixPrecision;
}
// Move the recovered longitude by one precision up or down if it is too far from the
// reference.
double longitudeDiff = recoveredCodeArea.getCenterLongitude() - referenceLongitude;
if (longitudeDiff > prefixPrecision / 2) {
recoveredLongitude -= prefixPrecision;
} else if (longitudeDiff < -prefixPrecision / 2) {
recoveredLongitude += prefixPrecision;
}
return new OpenLocationCode(
recoveredLatitude, recoveredLongitude, recovered.getCode().length() - 1);
}
/**
* Returns whether the bounding box specified by the Open Location Code contains provided point.
*/
public boolean contains(double latitude, double longitude) {
CodeArea codeArea = decode();
return codeArea.getSouthLatitude() <= latitude
&& latitude < codeArea.getNorthLatitude()
&& codeArea.getWestLongitude() <= longitude
&& longitude < codeArea.getEastLongitude();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
OpenLocationCode that = (OpenLocationCode) o;
return code == that.code || code != null && code.equals(that.code);
}
@Override
public int hashCode() {
return code != null ? code.hashCode() : 0;
}
@Override
public String toString() {
return getCode();
}
// Exposed static helper methods.
/** Returns whether the provided string is a valid Open Location code. */
public static boolean isValidCode(String code) {
if (code == null || code.length() < 2) {
return false;
}
code = code.toUpperCase();
// There must be exactly one separator.
int separatorPosition = code.indexOf(SEPARATOR);
if (separatorPosition == -1) {
return false;
}
if (separatorPosition != code.lastIndexOf(SEPARATOR)) {
return false;
}
if (separatorPosition % 2 != 0) {
return false;
}
// Check first two characters: only some values from the alphabet are permitted.
if (separatorPosition == 8) {
// First latitude character can only have first 9 values.
Integer index0 = CODE_ALPHABET.indexOf(code.charAt(0));
if (index0 == null || index0 > 8) {
return false;
}
// First longitude character can only have first 18 values.
Integer index1 = CODE_ALPHABET.indexOf(code.charAt(1));
if (index1 == null || index1 > 17) {
return false;
}
}
// Check the characters before the separator.
boolean paddingStarted = false;
for (int i = 0; i < separatorPosition; i++) {
if (paddingStarted) {
// Once padding starts, there must not be anything but padding.
if (code.charAt(i) != PADDING_CHARACTER) {
return false;
}
continue;
}
if (CODE_ALPHABET.indexOf(code.charAt(i)) != -1) {
continue;
}
if (PADDING_CHARACTER == code.charAt(i)) {
paddingStarted = true;
// Padding can start on even character: 2, 4 or 6.
if (i != 2 && i != 4 && i != 6) {
return false;
}
continue;
}
return false; // Illegal character.
}
// Check the characters after the separator.
if (code.length() > separatorPosition + 1) {
if (paddingStarted) {
return false;
}
// Only one character after separator is forbidden.
if (code.length() == separatorPosition + 2) {
return false;
}
for (int i = separatorPosition + 1; i < code.length(); i++) {
if (CODE_ALPHABET.indexOf(code.charAt(i)) == -1) {
return false;
}
}
}
return true;
}
/** Returns if the code is a valid full Open Location Code. */
public static boolean isFullCode(String code) {
try {
return new OpenLocationCode(code).isFull();
} catch (IllegalArgumentException e) {
return false;
}
}
/** Returns if the code is a valid short Open Location Code. */
public static boolean isShortCode(String code) {
try {
return new OpenLocationCode(code).isShort();
} catch (IllegalArgumentException e) {
return false;
}
}
// Private static methods.
private static double clipLatitude(double latitude) {
return Math.min(Math.max(latitude, -LATITUDE_MAX.intValue()), LATITUDE_MAX.intValue());
}
private static double normalizeLongitude(double longitude) {
while (longitude < -LONGITUDE_MAX.intValue()) {
longitude = longitude + LONGITUDE_MAX.intValue() * 2;
}
while (longitude >= LONGITUDE_MAX.intValue()) {
longitude = longitude - LONGITUDE_MAX.intValue() * 2;
}
return longitude;
}
/**
* Compute the latitude precision value for a given code length. Lengths <= 10 have the same
* precision for latitude and longitude, but lengths > 10 have different precisions due to the
* grid method having fewer columns than rows. Copied from the JS implementation.
*/
private static double computeLatitudePrecision(int codeLength) {
if (codeLength <= CODE_PRECISION_NORMAL) {
return Math.pow(ENCODING_BASE.intValue(), Math.floor(codeLength / -2 + 2));
}
return Math.pow(ENCODING_BASE.intValue(), -3)
/ Math.pow(GRID_ROWS.intValue(), codeLength - PAIR_CODE_LENGTH);
}
}

View file

@ -0,0 +1,171 @@
package net.osmand;
import java.text.ParseException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Period {
public enum PeriodUnit {
YEAR("Y"),
MONTH("M"),
WEEK("W"),
DAY("D");
private String unitStr;
PeriodUnit(String unitStr) {
this.unitStr = unitStr;
}
public String getUnitStr() {
return unitStr;
}
public double getMonthsValue() {
switch (this) {
case YEAR:
return 12d;
case MONTH:
return 1d;
case WEEK:
return 1/4d;
case DAY:
return 1/30d;
}
return 0d;
}
public static PeriodUnit parseUnit(String unitStr) {
for (PeriodUnit unit : values()) {
if (unit.unitStr.equals(unitStr)) {
return unit;
}
}
return null;
}
}
private static final Pattern PATTERN =
Pattern.compile("^P(?:([-+]?[0-9]+)([YMWD]))?$", Pattern.CASE_INSENSITIVE);
private PeriodUnit unit;
private final int numberOfUnits;
public static Period ofYears(int years) {
return new Period(PeriodUnit.YEAR, years);
}
public static Period ofMonths(int months) {
return new Period(PeriodUnit.MONTH, months);
}
public static Period ofWeeks(int weeks) {
return new Period(PeriodUnit.WEEK, weeks);
}
public static Period ofDays(int days) {
return new Period(PeriodUnit.DAY, days);
}
public PeriodUnit getUnit() {
return unit;
}
public int getNumberOfUnits() {
return numberOfUnits;
}
/**
* Obtains a {@code Period} from a text string such as {@code PnY PnM PnD PnW}.
* <p>
* This will parse the string produced by {@code toString()} which is
* based on the ISO-8601 period formats {@code PnY PnM PnD PnW}.
* <p>
* The string cannot start with negative sign.
* The ASCII letter "P" is next in upper or lower case.
* <p>
* For example, the following are valid inputs:
* <pre>
* "P2Y" -- Period.ofYears(2)
* "P3M" -- Period.ofMonths(3)
* "P4W" -- Period.ofWeeks(4)
* "P5D" -- Period.ofDays(5)
* </pre>
*
* @param text the text to parse, not null
* @return the parsed period, not null
* @throws ParseException if the text cannot be parsed to a period
*/
public static Period parse(CharSequence text) throws ParseException {
Matcher matcher = PATTERN.matcher(text);
if (matcher.matches()) {
String numberOfUnitsMatch = matcher.group(1);
String unitMatch = matcher.group(2);
if (numberOfUnitsMatch != null && unitMatch != null) {
try {
int numberOfUnits = parseNumber(numberOfUnitsMatch);
PeriodUnit unit = PeriodUnit.parseUnit(unitMatch);
return new Period(unit, numberOfUnits);
} catch (IllegalArgumentException ex) {
throw new ParseException("Text cannot be parsed to a Period: " + text, 0);
}
}
}
throw new ParseException("Text cannot be parsed to a Period: " + text, 0);
}
private static int parseNumber(String str) throws ParseException {
if (str == null) {
return 0;
}
return Integer.parseInt(str);
}
public Period(PeriodUnit unit, int numberOfUnits) {
if (unit == null) {
throw new IllegalArgumentException("PeriodUnit cannot be null");
}
this.unit = unit;
this.numberOfUnits = numberOfUnits;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof Period) {
Period other = (Period) obj;
return unit.ordinal() == other.unit.ordinal() && numberOfUnits == other.numberOfUnits;
}
return false;
}
@Override
public int hashCode() {
return unit.ordinal() + Integer.rotateLeft(numberOfUnits, 8);
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append('P').append(numberOfUnits);
switch (unit) {
case YEAR:
buf.append('Y');
break;
case MONTH:
buf.append('M');
break;
case WEEK:
buf.append('W');
break;
case DAY:
buf.append('D');
break;
}
return buf.toString();
}
}

View file

@ -8,7 +8,6 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map;
public class TransportStop extends MapObject { public class TransportStop extends MapObject {
@ -111,6 +110,10 @@ public class TransportStop extends MapObject {
return !isDeleted() && referencesToRoutes != null && referencesToRoutes.length > 0; return !isDeleted() && referencesToRoutes != null && referencesToRoutes.length > 0;
} }
public boolean hasReferencesToRoutesMap() {
return !isDeleted() && referencesToRoutesMap != null && !referencesToRoutesMap.isEmpty();
}
public Amenity getAmenity() { public Amenity getAmenity() {
if (transportStopAggregated != null) { if (transportStopAggregated != null) {
return transportStopAggregated.getAmenity(); return transportStopAggregated.getAmenity();

5
OsmAnd/.gitignore vendored
View file

@ -13,6 +13,11 @@ libs/it.unibo.alice.tuprolog-tuprolog-3.2.1.jar
libs/commons-codec-commons-codec-1.11.jar libs/commons-codec-commons-codec-1.11.jar
libs/OsmAndCore_android-0.1-SNAPSHOT.jar libs/OsmAndCore_android-0.1-SNAPSHOT.jar
libs/huawei-*.jar
huaweidrmlib/
HwDRM_SDK_*
drm_strings.xml
valgrind/ valgrind/
bin/ bin/
dist/ dist/

View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application>
<activity android:name="com.huawei.android.sdk.drm.DrmDialogActivity"
android:configChanges="screenSize|orientation|keyboardHidden"
android:exported="false"
android:theme="@android:style/Theme.Translucent">
<meta-data
android:name="hwc-theme"
android:value="androidhwext:style/Theme.Emui.Translucent" />
</activity>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="net.osmand.plus.huawei.fileprovider"
tools:replace="android:authorities" />
<service
android:name="net.osmand.plus.NavigationService"
android:process="net.osmand.plus.huawei"
tools:replace="android:process" />
</application>
</manifest>

View file

@ -17,7 +17,6 @@ apply plugin: 'com.android.application'
// </unzip> // </unzip>
// Less important // Less important
task printc { task printc {
configurations.each { if(it.isCanBeResolved()) println it.name } configurations.each { if(it.isCanBeResolved()) println it.name }
} }
@ -112,6 +111,9 @@ android {
freecustom { freecustom {
manifest.srcFile "AndroidManifest-freecustom.xml" manifest.srcFile "AndroidManifest-freecustom.xml"
} }
huawei {
manifest.srcFile "AndroidManifest-huawei.xml"
}
legacy { legacy {
jniLibs.srcDirs = ["libc++"] jniLibs.srcDirs = ["libc++"]
@ -175,6 +177,10 @@ android {
resConfig "en" resConfig "en"
//resConfigs "xxhdpi", "nodpi" //resConfigs "xxhdpi", "nodpi"
} }
huawei {
dimension "version"
applicationId "net.osmand.plus.huawei"
}
// CoreVersion // CoreVersion
legacy { legacy {
@ -251,6 +257,46 @@ task downloadWorldMiniBasemap {
} }
} }
task downloadHuaweiDrmZip {
doLast {
ant.get(src: 'https://obs.cn-north-2.myhwclouds.com/hms-ds-wf/sdk/HwDRM_SDK_2.5.2.300_ADT.zip', dest: 'HwDRM_SDK_2.5.2.300_ADT.zip', skipexisting: 'true')
ant.unzip(src: 'HwDRM_SDK_2.5.2.300_ADT.zip', dest: 'huaweidrmlib/')
}
}
task copyHuaweiDrmLibs(type: Copy) {
dependsOn downloadHuaweiDrmZip
from "huaweidrmlib/HwDRM_SDK_2.5.2.300_ADT/libs"
into "libs"
}
task copyHuaweiDrmValues(type: Copy) {
dependsOn downloadHuaweiDrmZip
from "huaweidrmlib/HwDRM_SDK_2.5.2.300_ADT/res"
into "res"
}
task downloadPrebuiltHuaweiDrm {
dependsOn copyHuaweiDrmLibs, copyHuaweiDrmValues
}
task cleanHuaweiDrmLibs(type: Delete) {
delete "huaweidrmlib"
delete fileTree("libs").matching {
include "**/huawei-*.jar"
}
}
task cleanHuaweiDrmValues(type: Delete) {
delete fileTree("res").matching {
include "**/drm_strings.xml"
}
}
task cleanPrebuiltHuaweiDrm {
dependsOn cleanHuaweiDrmLibs, cleanHuaweiDrmValues
}
task collectVoiceAssets(type: Sync) { task collectVoiceAssets(type: Sync) {
from "../../resources/voice" from "../../resources/voice"
into "assets/voice" into "assets/voice"
@ -317,6 +363,15 @@ task collectExternalResources {
validateTranslate, validateTranslate,
copyWidgetIcons, copyWidgetIcons,
downloadWorldMiniBasemap downloadWorldMiniBasemap
Gradle gradle = getGradle()
String tskReqStr = gradle.getStartParameter().getTaskRequests().toString().toLowerCase()
// Use Drm SDK only for huawei build
if (tskReqStr.contains("huawei")) {
dependsOn downloadPrebuiltHuaweiDrm
} else {
dependsOn cleanPrebuiltHuaweiDrm
}
} }
// Legacy core build // Legacy core build
@ -363,9 +418,9 @@ task cleanupDuplicatesInCore() {
} }
} }
afterEvaluate { afterEvaluate {
android.applicationVariants.all { variant -> android.applicationVariants.all { variant ->
variant.javaCompiler.dependsOn(collectExternalResources, buildOsmAndCore, cleanupDuplicatesInCore) variant.javaCompiler.dependsOn(collectExternalResources, buildOsmAndCore, cleanupDuplicatesInCore)
} }
} }
task appStart(type: Exec) { task appStart(type: Exec) {
@ -394,6 +449,8 @@ dependencies {
implementation 'com.moparisthebest:junidecode:0.1.1' implementation 'com.moparisthebest:junidecode:0.1.1'
implementation 'org.immutables:gson:2.5.0' implementation 'org.immutables:gson:2.5.0'
implementation 'com.vividsolutions:jts-core:1.14.0' implementation 'com.vividsolutions:jts-core:1.14.0'
implementation 'com.google.openlocationcode:openlocationcode:1.0.4'
implementation 'com.android.billingclient:billing:2.0.3'
// turn off for now // turn off for now
//implementation 'com.atilika.kuromoji:kuromoji-ipadic:0.9.0' //implementation 'com.atilika.kuromoji:kuromoji-ipadic:0.9.0'
implementation 'com.squareup.picasso:picasso:2.71828' implementation 'com.squareup.picasso:picasso:2.71828'
@ -419,4 +476,6 @@ dependencies {
implementation ("com.github.HITGIF:TextFieldBoxes:1.3.5"){ implementation ("com.github.HITGIF:TextFieldBoxes:1.3.5"){
exclude group: 'com.android.support' exclude group: 'com.android.support'
} }
huaweiImplementation files('libs/huawei-android-drm_v2.5.2.300.jar')
} }

View file

@ -29,7 +29,7 @@
<string name="osm_live_3_months_price">€3,99</string> <string name="osm_live_3_months_price">€3,99</string>
<string name="osm_live_3_months_monthly_price">€1,33</string> <string name="osm_live_3_months_monthly_price">€1,33</string>
<string name="osm_live_annual_price">€7,99</string> <string name="osm_live_annual_price">€7,99</string>
<string name="osm_live_annual_monthly_price">2,66</string> <string name="osm_live_annual_monthly_price">0,66</string>
<string name="twitter_address">https://twitter.com/osmandapp</string> <string name="twitter_address">https://twitter.com/osmandapp</string>
<string name="reddit_address">https://www.reddit.com/r/OsmAnd</string> <string name="reddit_address">https://www.reddit.com/r/OsmAnd</string>
<string name="facebook_address">https://www.facebook.com/osmandapp</string> <string name="facebook_address">https://www.facebook.com/osmandapp</string>

View file

@ -6,52 +6,51 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="1dp" android:layout_marginLeft="1dp"
android:layout_marginRight="1dp" android:layout_marginRight="1dp"
android:background="?attr/subscription_active_bg_color"
android:orientation="vertical"> android:orientation="vertical">
<View
android:id="@+id/div_top"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/subscription_active_div_color"
android:visibility="gone" />
<LinearLayout <LinearLayout
android:id="@+id/button_container" android:id="@+id/button_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:baselineAligned="false" android:baselineAligned="false"
android:minHeight="@dimen/dialog_button_ex_height" android:orientation="vertical"
android:orientation="horizontal"
android:paddingLeft="@dimen/card_padding" android:paddingLeft="@dimen/card_padding"
android:paddingRight="@dimen/card_padding"> android:paddingRight="@dimen/card_padding">
<LinearLayout <LinearLayout
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:layout_marginTop="@dimen/list_header_padding" android:layout_marginTop="@dimen/list_header_padding"
android:layout_marginEnd="@dimen/list_content_padding"
android:layout_marginRight="@dimen/list_content_padding"
android:layout_marginBottom="@dimen/list_header_padding" android:layout_marginBottom="@dimen/list_header_padding"
android:layout_weight="1" android:orientation="horizontal">
android:orientation="vertical">
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="?attr/dialog_title_color"
android:textSize="@dimen/default_list_text_size"
osmand:typeface="@string/font_roboto_regular"
tools:text="Pay monthly" />
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal"> android:layout_weight="1"
android:orientation="vertical">
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="?attr/dialog_title_color"
android:textSize="@dimen/default_list_text_size"
osmand:typeface="@string/font_roboto_regular"
tools:text="Pay monthly" />
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/description_contribute"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/osm_live_payment_contribute_descr"
android:textColor="?attr/card_description_text_color"
android:textSize="@dimen/default_desc_text_size"
android:visibility="gone"
osmand:typeface="@string/font_roboto_regular"
tools:visibility="visible" />
<net.osmand.plus.widgets.TextViewEx <net.osmand.plus.widgets.TextViewEx
android:id="@+id/description" android:id="@+id/description"
@ -64,11 +63,20 @@
</LinearLayout> </LinearLayout>
<android.support.v7.widget.AppCompatImageView
android:id="@+id/right_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="@dimen/list_header_padding"
android:layout_marginLeft="@dimen/list_header_padding"
android:src="@drawable/img_feature_purchased" />
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/button_view" android:id="@+id/button_view"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:background="?attr/wikivoyage_secondary_btn_bg"> android:background="?attr/wikivoyage_secondary_btn_bg">
@ -76,7 +84,7 @@
<LinearLayout <LinearLayout
android:id="@+id/button" android:id="@+id/button"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/dialog_button_height" android:layout_height="wrap_content"
android:gravity="center" android:gravity="center"
android:orientation="horizontal" android:orientation="horizontal"
android:padding="@dimen/list_header_padding"> android:padding="@dimen/list_header_padding">
@ -100,7 +108,7 @@
<LinearLayout <LinearLayout
android:id="@+id/button_cancel_view" android:id="@+id/button_cancel_view"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:background="?attr/wikivoyage_primary_btn_bg"> android:background="?attr/wikivoyage_primary_btn_bg">
@ -108,7 +116,8 @@
<LinearLayout <LinearLayout
android:id="@+id/button_cancel" android:id="@+id/button_cancel"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/dialog_button_height" android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:gravity="center" android:gravity="center"
android:orientation="horizontal" android:orientation="horizontal"
android:padding="@dimen/list_header_padding"> android:padding="@dimen/list_header_padding">
@ -121,7 +130,7 @@
android:letterSpacing="@dimen/text_button_letter_spacing" android:letterSpacing="@dimen/text_button_letter_spacing"
android:maxWidth="@dimen/dialog_button_ex_max_width" android:maxWidth="@dimen/dialog_button_ex_max_width"
android:minWidth="@dimen/dialog_button_ex_min_width" android:minWidth="@dimen/dialog_button_ex_min_width"
android:text="@string/shared_string_cancel" android:text="@string/cancel_subscription"
android:textColor="@color/color_white" android:textColor="@color/color_white"
android:textSize="@dimen/text_button_text_size" android:textSize="@dimen/text_button_text_size"
osmand:typeface="@string/font_roboto_medium" /> osmand:typeface="@string/font_roboto_medium" />
@ -136,16 +145,8 @@
android:id="@+id/div" android:id="@+id/div"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="1dp" android:layout_height="1dp"
android:layout_marginLeft="@dimen/card_padding" android:layout_marginTop="@dimen/content_padding_small"
android:layout_marginRight="@dimen/card_padding" android:background="?attr/wikivoyage_card_divider_color"
android:background="?attr/subscription_active_div_color"
android:visibility="gone" />
<View
android:id="@+id/div_bottom"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/subscription_active_div_color"
android:visibility="gone" /> android:visibility="gone" />
</LinearLayout> </LinearLayout>

View file

@ -12,82 +12,52 @@
android:id="@+id/button_container" android:id="@+id/button_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:baselineAligned="false" android:baselineAligned="false"
android:minHeight="@dimen/dialog_button_ex_height" android:orientation="vertical"
android:orientation="horizontal"
android:paddingLeft="@dimen/card_padding" android:paddingLeft="@dimen/card_padding"
android:paddingRight="@dimen/card_padding"> android:paddingRight="@dimen/card_padding">
<LinearLayout <LinearLayout
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="@dimen/list_header_padding" android:layout_marginTop="@dimen/list_header_padding"
android:layout_marginEnd="@dimen/list_content_padding"
android:layout_marginRight="@dimen/list_content_padding"
android:layout_marginBottom="@dimen/list_header_padding" android:layout_marginBottom="@dimen/list_header_padding"
android:layout_weight="1"
android:orientation="vertical"> android:orientation="vertical">
<net.osmand.plus.widgets.TextViewEx <net.osmand.plus.widgets.TextViewEx
android:id="@+id/title" android:id="@+id/title"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center"
android:textColor="?attr/dialog_title_color" android:textColor="?attr/dialog_title_color"
android:textSize="@dimen/default_list_text_size" android:textSize="@dimen/default_list_text_size"
osmand:typeface="@string/font_roboto_regular" osmand:typeface="@string/font_roboto_regular"
tools:text="Pay monthly" /> tools:text="Pay monthly" />
<LinearLayout <net.osmand.plus.widgets.TextViewEx
android:layout_width="wrap_content" android:id="@+id/description_contribute"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal"> android:text="@string/osm_live_payment_contribute_descr"
android:textColor="?attr/card_description_text_color"
android:textSize="@dimen/default_desc_text_size"
android:visibility="gone"
osmand:typeface="@string/font_roboto_regular"
tools:visibility="visible" />
<net.osmand.plus.widgets.TextViewEx <net.osmand.plus.widgets.TextViewEx
android:id="@+id/description" android:id="@+id/description"
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:textColor="?attr/card_description_text_color"
android:textColor="?attr/card_description_text_color" android:textSize="@dimen/default_desc_text_size"
android:textSize="@dimen/default_desc_text_size" osmand:typeface="@string/font_roboto_regular"
osmand:typeface="@string/font_roboto_regular" tools:text="$0.62 / month • Save 68%" />
tools:text="Monthly payment" />
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/discount_banner_regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginStart="@dimen/list_item_button_padding"
android:layout_marginLeft="@dimen/list_item_button_padding"
android:background="?attr/text_rounded_bg_regular"
android:textColor="?attr/card_description_text_color"
android:textSize="@dimen/default_desc_text_size"
osmand:typeface="@string/font_roboto_regular"
tools:text=" Save 20%! " />
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/discount_banner_active"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginStart="@dimen/list_item_button_padding"
android:layout_marginLeft="@dimen/list_item_button_padding"
android:background="?attr/text_rounded_bg_active"
android:textColor="@color/osmand_orange"
android:textSize="@dimen/default_desc_text_size"
osmand:typeface="@string/font_roboto_regular"
tools:text=" Save 20%! " />
</LinearLayout>
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/button_view" android:id="@+id/button_view"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:background="?attr/btn_round_border_2"> android:background="?attr/btn_round_border_2">
@ -95,7 +65,7 @@
<LinearLayout <LinearLayout
android:id="@+id/button" android:id="@+id/button"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/dialog_button_height" android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
android:gravity="center" android:gravity="center"
android:orientation="horizontal" android:orientation="horizontal"
@ -119,7 +89,7 @@
android:textColor="?attr/color_dialog_buttons" android:textColor="?attr/color_dialog_buttons"
android:textSize="@dimen/text_button_text_size" android:textSize="@dimen/text_button_text_size"
osmand:typeface="@string/font_roboto_medium" osmand:typeface="@string/font_roboto_medium"
tools:text="7,99€" /> tools:text="7,99€ / year" />
</LinearLayout> </LinearLayout>
@ -127,7 +97,7 @@
<LinearLayout <LinearLayout
android:id="@+id/button_ex_view" android:id="@+id/button_ex_view"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:background="?attr/wikivoyage_primary_btn_bg"> android:background="?attr/wikivoyage_primary_btn_bg">
@ -135,7 +105,7 @@
<LinearLayout <LinearLayout
android:id="@+id/button_ex" android:id="@+id/button_ex"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/dialog_button_height" android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
android:gravity="center" android:gravity="center"
android:orientation="horizontal" android:orientation="horizontal"
@ -152,7 +122,7 @@
android:textColor="@color/color_white" android:textColor="@color/color_white"
android:textSize="@dimen/text_button_text_size" android:textSize="@dimen/text_button_text_size"
osmand:typeface="@string/font_roboto_medium" osmand:typeface="@string/font_roboto_medium"
tools:text="7,99€" /> tools:text="$3.99 for six month\nthan $7.49 / year" />
</LinearLayout> </LinearLayout>
@ -164,8 +134,7 @@
android:id="@+id/div" android:id="@+id/div"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="1dp" android:layout_height="1dp"
android:layout_marginLeft="@dimen/card_padding" android:layout_marginTop="@dimen/content_padding_small"
android:layout_marginRight="@dimen/card_padding"
android:background="?attr/wikivoyage_card_divider_color" android:background="?attr/wikivoyage_card_divider_color"
android:visibility="gone" /> android:visibility="gone" />

View file

@ -64,6 +64,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="2dp" android:layout_marginBottom="2dp"
android:paddingBottom="@dimen/content_padding_small"
android:orientation="vertical" android:orientation="vertical"
android:visibility="gone" /> android:visibility="gone" />

View file

@ -1,5 +1,26 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="day">День</string>
<string name="days_2_4">Дня</string>
<string name="days_5">Дней</string>
<string name="week">Неделя</string>
<string name="weeks_2_4">Недели</string>
<string name="weeks_5">Недель</string>
<string name="month">Месяц</string>
<string name="months_2_4">Месяца</string>
<string name="months_5">Месяцев</string>
<string name="year">Год</string>
<string name="years_2_4">Года</string>
<string name="years_5">Лет</string>
<string name="months_3">Три месяца</string>
<string name="price_free">Бесплатно</string>
<string name="get_discount_title">Получите %1$d %2$s со скидкой %3$s.</string>
<string name="get_free_trial_title">Начать бесплатный период в %1$d %2$s.</string>
<string name="get_discount_first_part">%1$s за первый %2$s</string>
<string name="get_discount_first_few_part">%1$s за первые %2$s</string>
<string name="get_discount_second_part">затем %1$s</string>
<string name="cancel_subscription">Отменить подписку</string>
<string name="price_and_discount">%1$s • Экономия %2$s</string>
<string name="rendering_attr_winter_road_name">Автозимник</string> <string name="rendering_attr_winter_road_name">Автозимник</string>
<string name="rendering_attr_ice_road_name">Ледовый автозимник</string> <string name="rendering_attr_ice_road_name">Ледовый автозимник</string>
<string name="routeInfo_winter_ice_road_name">Зимники</string> <string name="routeInfo_winter_ice_road_name">Зимники</string>

View file

@ -11,6 +11,30 @@
Thx - Hardy Thx - Hardy
--> -->
<string name="day">Day</string>
<string name="days_2_4">Days</string>
<string name="days_5">Days</string>
<string name="week">Week</string>
<string name="weeks_2_4">Weeks</string>
<string name="weeks_5">Weeks</string>
<string name="month">Month</string>
<string name="months_2_4">Months</string>
<string name="months_5">Months</string>
<string name="year">Year</string>
<string name="years_2_4">Years</string>
<string name="years_5">Years</string>
<string name="months_3">Three months</string>
<string name="price_free">Free</string>
<string name="get_discount_title">Get %1$d %2$s at %3$s off.</string>
<string name="get_free_trial_title">Start your %1$d %2$s free trial.</string>
<string name="get_discount_first_part">%1$s for first %2$s</string>
<string name="get_discount_first_few_part">%1$s for first %2$s</string>
<string name="get_discount_second_part">then %1$s</string>
<string name="cancel_subscription">Cancel subscription</string>
<string name="price_and_discount">%1$s • Save %2$s</string>
<string name="app_mode_wagon">Wagon</string>
<string name="app_mode_pickup_truck">Pickup truck</string>
<string name="shared_string_default">Default</string>
<string name="gpx_join_gaps">Join gaps</string> <string name="gpx_join_gaps">Join gaps</string>
<string name="app_mode_camper">Camper</string> <string name="app_mode_camper">Camper</string>
<string name="app_mode_campervan">Campervan</string> <string name="app_mode_campervan">Campervan</string>
@ -1276,6 +1300,7 @@
<string name="lang_th">Thai</string> <string name="lang_th">Thai</string>
<string name="lang_te">Telugu</string> <string name="lang_te">Telugu</string>
<string name="lang_nn">Norwegian Nynorsk</string> <string name="lang_nn">Norwegian Nynorsk</string>
<string name="lang_oc">Occitan</string>
<string name="lang_new">Newar / Nepal Bhasa</string> <string name="lang_new">Newar / Nepal Bhasa</string>
<string name="lang_ms">Malaysian</string> <string name="lang_ms">Malaysian</string>
<string name="lang_ht">Haitian</string> <string name="lang_ht">Haitian</string>

View file

@ -1,144 +0,0 @@
/*
* Copyright (C) 2012 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.android.vending.billing;
import android.os.Bundle;
/**
* InAppBillingService is the service that provides in-app billing version 3 and beyond.
* This service provides the following features:
* 1. Provides a new API to get details of in-app items published for the app including
* price, type, title and description.
* 2. The purchase flow is synchronous and purchase information is available immediately
* after it completes.
* 3. Purchase information of in-app purchases is maintained within the Google Play system
* till the purchase is consumed.
* 4. An API to consume a purchase of an inapp item. All purchases of one-time
* in-app items are consumable and thereafter can be purchased again.
* 5. An API to get current purchases of the user immediately. This will not contain any
* consumed purchases.
*
* All calls will give a response code with the following possible values
* RESULT_OK = 0 - success
* RESULT_USER_CANCELED = 1 - user pressed back or canceled a dialog
* RESULT_BILLING_UNAVAILABLE = 3 - this billing API version is not supported for the type requested
* RESULT_ITEM_UNAVAILABLE = 4 - requested SKU is not available for purchase
* RESULT_DEVELOPER_ERROR = 5 - invalid arguments provided to the API
* RESULT_ERROR = 6 - Fatal error during the API action
* RESULT_ITEM_ALREADY_OWNED = 7 - Failure to purchase since item is already owned
* RESULT_ITEM_NOT_OWNED = 8 - Failure to consume since item is not owned
*/
interface IInAppBillingService {
/**
* Checks support for the requested billing API version, package and in-app type.
* Minimum API version supported by this interface is 3.
* @param apiVersion the billing version which the app is using
* @param packageName the package name of the calling app
* @param type type of the in-app item being purchased "inapp" for one-time purchases
* and "subs" for subscription.
* @return RESULT_OK(0) on success, corresponding result code on failures
*/
int isBillingSupported(int apiVersion, String packageName, String type);
/**
* Provides details of a list of SKUs
* Given a list of SKUs of a valid type in the skusBundle, this returns a bundle
* with a list JSON strings containing the productId, price, title and description.
* This API can be called with a maximum of 20 SKUs.
* @param apiVersion billing API version that the Third-party is using
* @param packageName the package name of the calling app
* @param skusBundle bundle containing a StringArrayList of SKUs with key "ITEM_ID_LIST"
* @return Bundle containing the following key-value pairs
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
* failure as listed above.
* "DETAILS_LIST" with a StringArrayList containing purchase information
* in JSON format similar to:
* '{ "productId" : "exampleSku", "type" : "inapp", "price" : "$5.00",
* "title : "Example Title", "description" : "This is an example description" }'
*/
Bundle getSkuDetails(int apiVersion, String packageName, String type, in Bundle skusBundle);
/**
* Returns a pending intent to launch the purchase flow for an in-app item by providing a SKU,
* the type, a unique purchase token and an optional developer payload.
* @param apiVersion billing API version that the app is using
* @param packageName package name of the calling app
* @param sku the SKU of the in-app item as published in the developer console
* @param type the type of the in-app item ("inapp" for one-time purchases
* and "subs" for subscription).
* @param developerPayload optional argument to be sent back with the purchase information
* @return Bundle containing the following key-value pairs
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
* failure as listed above.
* "BUY_INTENT" - PendingIntent to start the purchase flow
*
* The Pending intent should be launched with startIntentSenderForResult. When purchase flow
* has completed, the onActivityResult() will give a resultCode of OK or CANCELED.
* If the purchase is successful, the result data will contain the following key-value pairs
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
* failure as listed above.
* "INAPP_PURCHASE_DATA" - String in JSON format similar to
* '{"orderId":"12999763169054705758.1371079406387615",
* "packageName":"com.example.app",
* "productId":"exampleSku",
* "purchaseTime":1345678900000,
* "purchaseToken" : "122333444455555",
* "developerPayload":"example developer payload" }'
* "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that
* was signed with the private key of the developer
* TODO: change this to app-specific keys.
*/
Bundle getBuyIntent(int apiVersion, String packageName, String sku, String type,
String developerPayload);
/**
* Returns the current SKUs owned by the user of the type and package name specified along with
* purchase information and a signature of the data to be validated.
* This will return all SKUs that have been purchased in V3 and managed items purchased using
* V1 and V2 that have not been consumed.
* @param apiVersion billing API version that the app is using
* @param packageName package name of the calling app
* @param type the type of the in-app items being requested
* ("inapp" for one-time purchases and "subs" for subscription).
* @param continuationToken to be set as null for the first call, if the number of owned
* skus are too many, a continuationToken is returned in the response bundle.
* This method can be called again with the continuation token to get the next set of
* owned skus.
* @return Bundle containing the following key-value pairs
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
* failure as listed above.
* "INAPP_PURCHASE_ITEM_LIST" - StringArrayList containing the list of SKUs
* "INAPP_PURCHASE_DATA_LIST" - StringArrayList containing the purchase information
* "INAPP_DATA_SIGNATURE_LIST"- StringArrayList containing the signatures
* of the purchase information
* "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the
* next set of in-app purchases. Only set if the
* user has more owned skus than the current list.
*/
Bundle getPurchases(int apiVersion, String packageName, String type, String continuationToken);
/**
* Consume the last purchase of the given SKU. This will result in this item being removed
* from all subsequent responses to getPurchases() and allow re-purchase of this item.
* @param apiVersion billing API version that the app is using
* @param packageName package name of the calling app
* @param purchaseToken token in the purchase information JSON that identifies the purchase
* to be consumed
* @return 0 if consumption succeeded. Appropriate error values for failures.
*/
int consumePurchase(int apiVersion, String packageName, String purchaseToken);
}

View file

@ -30,6 +30,9 @@ import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar; import android.support.design.widget.Snackbar;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.support.v4.content.FileProvider; import android.support.v4.content.FileProvider;
import android.support.v4.text.TextUtilsCompat;
import android.support.v4.view.ViewCompat;
import android.text.ParcelableSpan;
import android.text.Spannable; import android.text.Spannable;
import android.text.SpannableString; import android.text.SpannableString;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
@ -60,6 +63,7 @@ import java.util.Date;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import static android.content.Context.POWER_SERVICE; import static android.content.Context.POWER_SERVICE;
@ -576,4 +580,8 @@ public class AndroidUtils {
return baseString; return baseString;
} }
} }
public static boolean isRTL() {
return TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) == ViewCompat.LAYOUT_DIRECTION_RTL;
}
} }

View file

@ -5,11 +5,7 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import com.google.openlocationcode.OpenLocationCode; import com.google.openlocationcode.OpenLocationCode;
import com.jwetherell.openmap.common.LatLonPoint;
import com.jwetherell.openmap.common.UTMPoint;
import java.util.LinkedHashMap;
import java.util.Map;
import net.osmand.LocationConvert; import net.osmand.LocationConvert;
import net.osmand.plus.OsmAndFormatter; import net.osmand.plus.OsmAndFormatter;
import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandApplication;
@ -18,6 +14,9 @@ import net.osmand.plus.R;
import net.osmand.plus.activities.MapActivity; import net.osmand.plus.activities.MapActivity;
import net.osmand.util.Algorithms; import net.osmand.util.Algorithms;
import java.util.LinkedHashMap;
import java.util.Map;
public class PointDescription { public class PointDescription {
private String type = ""; private String type = "";
private String name = ""; private String name = "";

View file

@ -0,0 +1,66 @@
package net.osmand.plus;
import android.app.Activity;
import android.util.Log;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class HuaweiDrmHelper {
private static final String TAG = HuaweiDrmHelper.class.getSimpleName();
//Copyright protection id
private static final String DRM_ID = "101117397";
//Copyright protection public key
private static final String DRM_PUBLIC_KEY = "9d6f861e7d46be167809a6a62302749a6753b3c1bd02c9729efb3973e268091d";
public static void check(Activity activity) {
boolean succeed = false;
try {
final WeakReference<Activity> activityRef = new WeakReference<>(activity);
Class<?> drmCheckCallbackClass = Class.forName("com.huawei.android.sdk.drm.DrmCheckCallback");
Object callback = java.lang.reflect.Proxy.newProxyInstance(
drmCheckCallbackClass.getClassLoader(),
new java.lang.Class[]{drmCheckCallbackClass},
new java.lang.reflect.InvocationHandler() {
@Override
public Object invoke(Object proxy, java.lang.reflect.Method method, Object[] args) {
Activity a = activityRef.get();
if (a != null && !a.isFinishing()) {
String method_name = method.getName();
if (method_name.equals("onCheckSuccess")) {
// skip now
} else if (method_name.equals("onCheckFailed")) {
closeApplication(a);
}
}
return null;
}
});
Class<?> drmClass = Class.forName("com.huawei.android.sdk.drm.Drm");
Class[] partypes = new Class[]{Activity.class, String.class, String.class, String.class, drmCheckCallbackClass};
Method check = drmClass.getMethod("check", partypes);
check.invoke(null, activity, activity.getPackageName(), DRM_ID, DRM_PUBLIC_KEY, callback);
succeed = true;
} catch (ClassNotFoundException e) {
Log.e(TAG, "check: ", e);
} catch (NoSuchMethodException e) {
Log.e(TAG, "check: ", e);
} catch (IllegalAccessException e) {
Log.e(TAG, "check: ", e);
} catch (InvocationTargetException e) {
Log.e(TAG, "check: ", e);
}
if (!succeed) {
closeApplication(activity);
}
}
private static void closeApplication(Activity activity) {
((OsmandApplication) activity.getApplication()).closeApplicationAnywayImpl(activity, true);
}
}

View file

@ -286,7 +286,7 @@ public class NavigationService extends Service implements LocationListener {
@Override @Override
public void onTaskRemoved(Intent rootIntent) { public void onTaskRemoved(Intent rootIntent) {
OsmandApplication app = ((OsmandApplication) getApplication()); OsmandApplication app = ((OsmandApplication) getApplication());
app.getNotificationHelper().removeNotifications(); app.getNotificationHelper().removeNotifications(false);
if (app.getNavigationService() != null && if (app.getNavigationService() != null &&
app.getSettings().DISABLE_RECORDING_ONCE_APP_KILLED.get()) { app.getSettings().DISABLE_RECORDING_ONCE_APP_KILLED.get()) {
NavigationService.this.stopSelf(); NavigationService.this.stopSelf();

View file

@ -155,9 +155,11 @@ public class NotificationHelper {
} }
} }
public void removeNotifications() { public void removeNotifications(boolean inactiveOnly) {
for (OsmandNotification notification : all) { for (OsmandNotification notification : all) {
notification.removeNotification(); if (!inactiveOnly || !notification.isActive()) {
notification.removeNotification();
}
} }
} }

View file

@ -761,6 +761,9 @@ public class OsmAndLocationProvider implements SensorEventListener {
public void setLocationFromService(net.osmand.Location location, boolean continuous) { public void setLocationFromService(net.osmand.Location location, boolean continuous) {
if (locationSimulation.isRouteAnimating()) {
return;
}
// if continuous notify about lost location // if continuous notify about lost location
if (continuous) { if (continuous) {
scheduleCheckIfGpsLost(location); scheduleCheckIfGpsLost(location);

View file

@ -242,7 +242,7 @@ public class OsmandApplication extends MultiDexApplication {
if(RateUsBottomSheetDialog.shouldShow(this)) { if(RateUsBottomSheetDialog.shouldShow(this)) {
osmandSettings.RATE_US_STATE.set(RateUsBottomSheetDialog.RateUsState.IGNORED); osmandSettings.RATE_US_STATE.set(RateUsBottomSheetDialog.RateUsState.IGNORED);
} }
getNotificationHelper().removeNotifications(); getNotificationHelper().removeNotifications(false);
} }
public RendererRegistry getRendererRegistry() { public RendererRegistry getRendererRegistry() {

View file

@ -25,6 +25,10 @@ public class Version {
return ctx.getString(R.string.versionFeatures).contains("+blackberry"); return ctx.getString(R.string.versionFeatures).contains("+blackberry");
} }
public static boolean isHuawei(OsmandApplication ctx) {
return ctx.getPackageName().endsWith(".huawei");
}
public static boolean isMarketEnabled(OsmandApplication ctx) { public static boolean isMarketEnabled(OsmandApplication ctx) {
return isGooglePlayEnabled(ctx) || isAmazonEnabled(ctx); return isGooglePlayEnabled(ctx) || isAmazonEnabled(ctx);
} }

View file

@ -64,6 +64,7 @@ import net.osmand.plus.AppInitializer.AppInitializeListener;
import net.osmand.plus.AppInitializer.InitEvents; import net.osmand.plus.AppInitializer.InitEvents;
import net.osmand.plus.ApplicationMode; import net.osmand.plus.ApplicationMode;
import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem; import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem;
import net.osmand.plus.HuaweiDrmHelper;
import net.osmand.plus.MapMarkersHelper; import net.osmand.plus.MapMarkersHelper;
import net.osmand.plus.MapMarkersHelper.MapMarker; import net.osmand.plus.MapMarkersHelper.MapMarker;
import net.osmand.plus.MapMarkersHelper.MapMarkerChangedListener; import net.osmand.plus.MapMarkersHelper.MapMarkerChangedListener;
@ -244,6 +245,10 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven
trackDetailsMenu.setMapActivity(this); trackDetailsMenu.setMapActivity(this);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if (Version.isHuawei(getMyApplication())) {
HuaweiDrmHelper.check(this);
}
// Full screen is not used here // Full screen is not used here
// getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); // getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.main); setContentView(R.layout.main);
@ -909,6 +914,13 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven
} }
} }
public void setKeepScreenOn(boolean keepScreenOn) {
View mainView = findViewById(R.id.MapViewWithLayers);
if (mainView != null) {
mainView.setKeepScreenOn(keepScreenOn);
}
}
private void clearIntent(Intent intent) { private void clearIntent(Intent intent) {
intent.setAction(null); intent.setAction(null);
intent.setData(null); intent.setData(null);
@ -1308,7 +1320,7 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven
@Override @Override
protected void onStop() { protected void onStop() {
getMyApplication().getNotificationHelper().removeNotifications(); getMyApplication().getNotificationHelper().removeNotifications(true);
if(pendingPause) { if(pendingPause) {
onPauseActivity(); onPauseActivity();
} }
@ -1397,6 +1409,7 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven
} }
public void updateApplicationModeSettings() { public void updateApplicationModeSettings() {
changeKeyguardFlags();
updateMapSettings(); updateMapSettings();
mapViewTrackingUtilities.updateSettings(); mapViewTrackingUtilities.updateSettings();
//app.getRoutingHelper().setAppMode(settings.getApplicationMode()); //app.getRoutingHelper().setAppMode(settings.getApplicationMode());
@ -1894,23 +1907,29 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven
return oldPoint.getLayerId().equals(layerId) && oldPoint.getId().equals(point.getId()); return oldPoint.getLayerId().equals(layerId) && oldPoint.getId().equals(point.getId());
} }
public void changeKeyguardFlags(final boolean enable) { public void changeKeyguardFlags() {
changeKeyguardFlags(settings.TURN_SCREEN_ON_TIME_INT.get() > 0, true);
}
private void changeKeyguardFlags(boolean enable, boolean forceKeepScreenOn) {
if (enable) { if (enable) {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED, getWindow().setFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED,
WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
setKeepScreenOn(true);
} else { } else {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
setKeepScreenOn(forceKeepScreenOn);
} }
} }
@Override @Override
public void lock() { public void lock() {
changeKeyguardFlags(false); changeKeyguardFlags(false, false);
} }
@Override @Override
public void unlock() { public void unlock() {
changeKeyguardFlags(true); changeKeyguardFlags(true, false);
} }
private class ScreenOffReceiver extends BroadcastReceiver { private class ScreenOffReceiver extends BroadcastReceiver {
@ -1976,12 +1995,14 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven
@Override @Override
public void routeWasCancelled() { public void routeWasCancelled() {
changeKeyguardFlags();
} }
@Override @Override
public void routeWasFinished() { public void routeWasFinished() {
if (!mIsDestroyed) { if (!mIsDestroyed) {
DestinationReachedMenu.show(this); DestinationReachedMenu.show(this);
changeKeyguardFlags();
} }
} }

View file

@ -54,17 +54,6 @@ public class OsmandInAppPurchaseActivity extends AppCompatActivity implements In
deinitInAppPurchaseHelper(); deinitInAppPurchaseHelper();
} }
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// Pass on the activity result to the helper for handling
if (purchaseHelper == null || !purchaseHelper.onActivityResultHandled(requestCode, resultCode, data)) {
// not handled, so handle it ourselves (here's where you'd
// perform any handling of activity results not related to in-app
// billing...
super.onActivityResult(requestCode, resultCode, data);
}
}
private void initInAppPurchaseHelper() { private void initInAppPurchaseHelper() {
deinitInAppPurchaseHelper(); deinitInAppPurchaseHelper();

View file

@ -277,6 +277,7 @@ public class SettingsGeneralActivity extends SettingsBaseActivity implements OnR
"nb", "nb",
"nl", "nl",
"nn", "nn",
"oc",
"pl", "pl",
"pt", "pt",
"pt_BR", "pt_BR",
@ -337,6 +338,7 @@ public class SettingsGeneralActivity extends SettingsBaseActivity implements OnR
getString(R.string.lang_nb), getString(R.string.lang_nb),
getString(R.string.lang_nl), getString(R.string.lang_nl),
getString(R.string.lang_nn) + incompleteSuffix, getString(R.string.lang_nn) + incompleteSuffix,
getString(R.string.lang_oc) + incompleteSuffix,
getString(R.string.lang_pl), getString(R.string.lang_pl),
getString(R.string.lang_pt), getString(R.string.lang_pt),
getString(R.string.lang_pt_br), getString(R.string.lang_pt_br),

View file

@ -45,6 +45,7 @@ import net.osmand.plus.inapp.InAppPurchaseHelper.InAppPurchaseListener;
import net.osmand.plus.inapp.InAppPurchaseHelper.InAppPurchaseTaskType; import net.osmand.plus.inapp.InAppPurchaseHelper.InAppPurchaseTaskType;
import net.osmand.plus.inapp.InAppPurchases.InAppPurchase; import net.osmand.plus.inapp.InAppPurchases.InAppPurchase;
import net.osmand.plus.inapp.InAppPurchases.InAppSubscription; import net.osmand.plus.inapp.InAppPurchases.InAppSubscription;
import net.osmand.plus.inapp.InAppPurchases.InAppSubscriptionIntroductoryInfo;
import net.osmand.plus.liveupdates.SubscriptionFragment; import net.osmand.plus.liveupdates.SubscriptionFragment;
import net.osmand.plus.srtmplugin.SRTMPlugin; import net.osmand.plus.srtmplugin.SRTMPlugin;
import net.osmand.plus.widgets.TextViewEx; import net.osmand.plus.widgets.TextViewEx;
@ -341,38 +342,41 @@ public abstract class ChoosePlanDialogFragment extends BaseOsmAndDialogFragment
} else if (osmLiveCardButtonsContainer != null) { } else if (osmLiveCardButtonsContainer != null) {
osmLiveCardButtonsContainer.removeAllViews(); osmLiveCardButtonsContainer.removeAllViews();
View lastBtn = null; View lastBtn = null;
InAppSubscription monthlyLiveUpdates = purchaseHelper.getMonthlyLiveUpdates();
double regularMonthlyPrice = monthlyLiveUpdates.getPriceValue();
List<InAppSubscription> visibleSubscriptions = purchaseHelper.getLiveUpdates().getVisibleSubscriptions(); List<InAppSubscription> visibleSubscriptions = purchaseHelper.getLiveUpdates().getVisibleSubscriptions();
boolean anyPurchased = false; boolean anyPurchasedOrIntroducted = false;
for (final InAppSubscription s : visibleSubscriptions) { for (final InAppSubscription s : visibleSubscriptions) {
if (s.isPurchased()) { if (s.isPurchased() || s.getIntroductoryInfo() != null) {
anyPurchased = true; anyPurchasedOrIntroducted = true;
break; break;
} }
} }
for (final InAppSubscription s : visibleSubscriptions) { for (final InAppSubscription s : visibleSubscriptions) {
InAppSubscriptionIntroductoryInfo introductoryInfo = s.getIntroductoryInfo();
boolean hasIntroductoryInfo = introductoryInfo != null;
CharSequence descriptionText = hasIntroductoryInfo ?
introductoryInfo.getDescriptionTitle(ctx) : s.getDescription(ctx, purchaseHelper.getMonthlyLiveUpdates());
if (s.isPurchased()) { if (s.isPurchased()) {
View buttonPurchased = inflate(R.layout.purchase_dialog_card_button_active_ex, osmLiveCardButtonsContainer); View buttonPurchased = inflate(R.layout.purchase_dialog_card_button_active_ex, osmLiveCardButtonsContainer);
View buttonContainer = buttonPurchased.findViewById(R.id.button_container);
TextViewEx title = (TextViewEx) buttonPurchased.findViewById(R.id.title); TextViewEx title = (TextViewEx) buttonPurchased.findViewById(R.id.title);
TextViewEx description = (TextViewEx) buttonPurchased.findViewById(R.id.description); TextViewEx description = (TextViewEx) buttonPurchased.findViewById(R.id.description);
TextViewEx descriptionContribute = (TextViewEx) buttonPurchased.findViewById(R.id.description_contribute);
descriptionContribute.setVisibility(s.isDonationSupported() ? View.VISIBLE : View.GONE);
TextViewEx buttonTitle = (TextViewEx) buttonPurchased.findViewById(R.id.button_title); TextViewEx buttonTitle = (TextViewEx) buttonPurchased.findViewById(R.id.button_title);
View buttonView = buttonPurchased.findViewById(R.id.button_view); View buttonView = buttonPurchased.findViewById(R.id.button_view);
View buttonCancelView = buttonPurchased.findViewById(R.id.button_cancel_view); View buttonCancelView = buttonPurchased.findViewById(R.id.button_cancel_view);
View divTop = buttonPurchased.findViewById(R.id.div_top);
View divBottom = buttonPurchased.findViewById(R.id.div_bottom);
View div = buttonPurchased.findViewById(R.id.div); View div = buttonPurchased.findViewById(R.id.div);
AppCompatImageView rightImage = (AppCompatImageView) buttonPurchased.findViewById(R.id.right_image);
CharSequence priceTitle = hasIntroductoryInfo ?
introductoryInfo.getFormattedDescription(ctx, buttonTitle.getCurrentTextColor()) : s.getPrice(ctx);
title.setText(s.getTitle(ctx)); title.setText(s.getTitle(ctx));
description.setText(s.getDescription(ctx)); description.setText(descriptionText);
buttonTitle.setText(s.getPrice(ctx)); buttonTitle.setText(priceTitle);
buttonView.setVisibility(View.VISIBLE); buttonView.setVisibility(View.VISIBLE);
buttonCancelView.setVisibility(View.GONE); buttonCancelView.setVisibility(View.GONE);
buttonPurchased.setOnClickListener(null); buttonPurchased.setOnClickListener(null);
divTop.setVisibility(View.VISIBLE); div.setVisibility(View.GONE);
div.setVisibility(View.VISIBLE); rightImage.setVisibility(View.GONE);
divBottom.setVisibility(View.GONE);
if (s.isDonationSupported()) { if (s.isDonationSupported()) {
buttonPurchased.setOnClickListener(new OnClickListener() { buttonPurchased.setOnClickListener(new OnClickListener() {
@Override @Override
@ -387,89 +391,52 @@ public abstract class ChoosePlanDialogFragment extends BaseOsmAndDialogFragment
osmLiveCardButtonsContainer.addView(buttonPurchased); osmLiveCardButtonsContainer.addView(buttonPurchased);
View buttonCancel = inflate(R.layout.purchase_dialog_card_button_active_ex, osmLiveCardButtonsContainer); View buttonCancel = inflate(R.layout.purchase_dialog_card_button_active_ex, osmLiveCardButtonsContainer);
buttonContainer = buttonCancel.findViewById(R.id.button_container);
title = (TextViewEx) buttonCancel.findViewById(R.id.title); title = (TextViewEx) buttonCancel.findViewById(R.id.title);
description = (TextViewEx) buttonCancel.findViewById(R.id.description); description = (TextViewEx) buttonCancel.findViewById(R.id.description);
buttonView = buttonCancel.findViewById(R.id.button_view); buttonView = buttonCancel.findViewById(R.id.button_view);
buttonCancelView = buttonCancel.findViewById(R.id.button_cancel_view); buttonCancelView = buttonCancel.findViewById(R.id.button_cancel_view);
divTop = buttonCancel.findViewById(R.id.div_top);
divBottom = buttonCancel.findViewById(R.id.div_bottom);
div = buttonCancel.findViewById(R.id.div); div = buttonCancel.findViewById(R.id.div);
rightImage = (AppCompatImageView) buttonCancel.findViewById(R.id.right_image);
title.setText(getString(R.string.osm_live_payment_current_subscription)); title.setText(getString(R.string.osm_live_payment_current_subscription));
description.setText(s.getRenewDescription(ctx)); description.setText(s.getRenewDescription(ctx));
buttonView.setVisibility(View.GONE); buttonView.setVisibility(View.GONE);
buttonCancelView.setVisibility(View.VISIBLE); buttonCancelView.setVisibility(View.VISIBLE);
buttonCancel.setOnClickListener(new OnClickListener() { buttonCancelView.setOnClickListener(new OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
manageSubscription(ctx, s.getSku()); manageSubscription(ctx, s.getSku());
} }
}); });
divTop.setVisibility(View.GONE); div.setVisibility(View.VISIBLE);
div.setVisibility(View.GONE); rightImage.setVisibility(View.VISIBLE);
divBottom.setVisibility(View.VISIBLE);
osmLiveCardButtonsContainer.addView(buttonCancel); osmLiveCardButtonsContainer.addView(buttonCancel);
if (lastBtn != null) {
View lastBtnDiv = lastBtn.findViewById(R.id.div);
if (lastBtnDiv != null) {
lastBtnDiv.setVisibility(View.GONE);
}
View lastBtnDivBottom = lastBtn.findViewById(R.id.div_bottom);
if (lastBtnDivBottom != null) {
lastBtnDivBottom.setVisibility(View.GONE);
}
}
lastBtn = buttonCancel; lastBtn = buttonCancel;
} else { } else {
View button = inflate(R.layout.purchase_dialog_card_button_ex, osmLiveCardButtonsContainer); View button = inflate(R.layout.purchase_dialog_card_button_ex, osmLiveCardButtonsContainer);
TextViewEx title = (TextViewEx) button.findViewById(R.id.title); TextViewEx title = (TextViewEx) button.findViewById(R.id.title);
TextViewEx description = (TextViewEx) button.findViewById(R.id.description); TextViewEx description = (TextViewEx) button.findViewById(R.id.description);
TextViewEx descriptionContribute = (TextViewEx) button.findViewById(R.id.description_contribute);
descriptionContribute.setVisibility(s.isDonationSupported() ? View.VISIBLE : View.GONE);
View buttonView = button.findViewById(R.id.button_view); View buttonView = button.findViewById(R.id.button_view);
View buttonExView = button.findViewById(R.id.button_ex_view); View buttonExView = button.findViewById(R.id.button_ex_view);
TextViewEx buttonTitle = (TextViewEx) button.findViewById(R.id.button_title); TextViewEx buttonTitle = (TextViewEx) button.findViewById(R.id.button_title);
TextViewEx buttonExTitle = (TextViewEx) button.findViewById(R.id.button_ex_title); TextViewEx buttonExTitle = (TextViewEx) button.findViewById(R.id.button_ex_title);
buttonView.setVisibility(anyPurchased ? View.VISIBLE : View.GONE); boolean showSolidButton = !anyPurchasedOrIntroducted || hasIntroductoryInfo;
buttonExView.setVisibility(!anyPurchased ? View.VISIBLE : View.GONE); buttonView.setVisibility(!showSolidButton ? View.VISIBLE : View.GONE);
buttonExView.setVisibility(showSolidButton ? View.VISIBLE : View.GONE);
TextViewEx discountRegular = (TextViewEx) button.findViewById(R.id.discount_banner_regular);
TextViewEx discountActive = (TextViewEx) button.findViewById(R.id.discount_banner_active);
View div = button.findViewById(R.id.div); View div = button.findViewById(R.id.div);
CharSequence priceTitle = hasIntroductoryInfo ?
introductoryInfo.getFormattedDescription(ctx, buttonExTitle.getCurrentTextColor()) : s.getPrice(ctx);
title.setText(s.getTitle(ctx)); title.setText(s.getTitle(ctx));
description.setText(s.getDescription(ctx)); description.setText(descriptionText);
buttonTitle.setText(s.getPrice(ctx)); buttonTitle.setText(priceTitle);
buttonExTitle.setText(s.getPrice(ctx)); buttonExTitle.setText(priceTitle);
if (regularMonthlyPrice > 0 && s.getMonthlyPriceValue() > 0 && s.getMonthlyPriceValue() < regularMonthlyPrice) { if (!showSolidButton) {
int discount = (int) ((1 - s.getMonthlyPriceValue() / regularMonthlyPrice) * 100d);
String discountStr = discount + "%";
if (discount > 50) {
discountActive.setText(String.format(" %s ", getString(R.string.osm_live_payment_discount_descr, discountStr)));
discountActive.setVisibility(View.VISIBLE);
discountRegular.setVisibility(View.GONE);
} else if (discount > 0) {
discountActive.setVisibility(View.GONE);
discountRegular.setText(String.format(" %s ", getString(R.string.osm_live_payment_discount_descr, discountStr)));
discountRegular.setVisibility(View.VISIBLE);
} else {
discountActive.setVisibility(View.GONE);
discountRegular.setVisibility(View.GONE);
}
} else {
discountActive.setVisibility(View.GONE);
discountRegular.setVisibility(View.GONE);
}
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
subscribe(s.getSku());
}
});
if (anyPurchased) {
buttonView.setOnClickListener(new OnClickListener() { buttonView.setOnClickListener(new OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -494,10 +461,6 @@ public abstract class ChoosePlanDialogFragment extends BaseOsmAndDialogFragment
if (div != null) { if (div != null) {
div.setVisibility(View.GONE); div.setVisibility(View.GONE);
} }
View divBottom = lastBtn.findViewById(R.id.div_bottom);
if (divBottom != null) {
divBottom.setVisibility(View.GONE);
}
} }
if (osmLiveCardProgress != null) { if (osmLiveCardProgress != null) {
osmLiveCardProgress.setVisibility(View.GONE); osmLiveCardProgress.setVisibility(View.GONE);

View file

@ -828,7 +828,7 @@ public class ConfigureMapMenu {
} }
} }
public static String[] mapNamesIds = new String[]{"", "en", "af", "als", "ar", "az", "be", "ber", "bg", "bn", "bpy", "br", "bs", "ca", "ceb", "cs", "cy", "da", "de", "el", "eo", "es", "et", "eu", "fa", "fi", "fr", "fy", "ga", "gl", "he", "hi", "hsb", "hr", "ht", "hu", "hy", "id", "is", "it", "ja", "ka", "kab", "ko", "ku", "la", "lb", "lo", "lt", "lv", "mk", "ml", "mr", "ms", "nds", "new", "nl", "nn", "no", "nv", "os", "pl", "pms", "pt", "ro", "ru", "sc", "sh", "sk", "sl", "sq", "sr", "sv", "sw", "ta", "te", "th", "tl", "tr", "uk", "vi", "vo", "zh"}; public static String[] mapNamesIds = new String[]{"", "en", "af", "als", "ar", "az", "be", "ber", "bg", "bn", "bpy", "br", "bs", "ca", "ceb", "cs", "cy", "da", "de", "el", "eo", "es", "et", "eu", "fa", "fi", "fr", "fy", "ga", "gl", "he", "hi", "hsb", "hr", "ht", "hu", "hy", "id", "is", "it", "ja", "ka", "kab", "ko", "ku", "la", "lb", "lo", "lt", "lv", "mk", "ml", "mr", "ms", "nds", "new", "nl", "nn", "no", "nv", "oc", "os", "pl", "pms", "pt", "ro", "ru", "sc", "sh", "sk", "sl", "sq", "sr", "sv", "sw", "ta", "te", "th", "tl", "tr", "uk", "vi", "vo", "zh"};
public static String[] getSortedMapNamesIds(Context ctx, String[] ids, String[] values) { public static String[] getSortedMapNamesIds(Context ctx, String[] ids, String[] values) {
final Map<String, String> mp = new HashMap<>(); final Map<String, String> mp = new HashMap<>();

View file

@ -1,5 +1,6 @@
package net.osmand.plus.helpers; package net.osmand.plus.helpers;
import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.hardware.Sensor; import android.hardware.Sensor;
@ -31,7 +32,6 @@ public class LockHelper implements SensorEventListener {
private CommonPreference<Integer> turnScreenOnTime; private CommonPreference<Integer> turnScreenOnTime;
private CommonPreference<Boolean> turnScreenOnSensor; private CommonPreference<Boolean> turnScreenOnSensor;
@Nullable @Nullable
private LockUIAdapter lockUIAdapter; private LockUIAdapter lockUIAdapter;
private Runnable lockRunnable; private Runnable lockRunnable;
@ -75,35 +75,42 @@ public class LockHelper implements SensorEventListener {
} }
} }
private void unlock(long timeInMills) { @SuppressLint("WakelockTimeout")
releaseWakeLocks(); private void unlock() {
if (lockUIAdapter != null) { if (lockUIAdapter != null) {
lockUIAdapter.unlock(); lockUIAdapter.unlock();
} }
PowerManager pm = (PowerManager) app.getSystemService(Context.POWER_SERVICE); PowerManager pm = (PowerManager) app.getSystemService(Context.POWER_SERVICE);
if (pm != null) { if (pm != null) {
wakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK wakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK
| PowerManager.ACQUIRE_CAUSES_WAKEUP, "tso:wakelocktag"); | PowerManager.ACQUIRE_CAUSES_WAKEUP, "OsmAnd:OnVoiceWakeupTag");
wakeLock.acquire(timeInMills); wakeLock.acquire();
} }
} }
private void lock() { private void lock() {
releaseWakeLocks(); releaseWakeLocks();
if (lockUIAdapter != null) { if (lockUIAdapter != null && isFollowingMode()) {
lockUIAdapter.lock(); lockUIAdapter.lock();
} }
} }
private boolean isFollowingMode() {
return app.getRoutingHelper().isFollowingMode();
}
private void timedUnlock(final long millis) { private void timedUnlock(final long millis) {
uiHandler.removeCallbacks(lockRunnable); uiHandler.removeCallbacks(lockRunnable);
uiHandler.post(new Runnable() { if (wakeLock == null) {
@Override uiHandler.post(new Runnable() {
public void run() { @Override
unlock(millis); public void run() {
} if (wakeLock == null) {
}); unlock();
}
}
});
}
uiHandler.postDelayed(lockRunnable, millis); uiHandler.postDelayed(lockRunnable, millis);
} }
@ -149,16 +156,15 @@ public class LockHelper implements SensorEventListener {
} }
private boolean isSensorEnabled() { private boolean isSensorEnabled() {
return turnScreenOnSensor.get() && app.getRoutingHelper().isFollowingMode(); return turnScreenOnSensor.get() && isFollowingMode();
} }
public void onStart(@NonNull Activity activity) { public void onStart(@NonNull Activity activity) {
if (wakeLock == null) { switchSensorOff();
switchSensorOff();
}
} }
public void onStop(@NonNull Activity activity) { public void onStop(@NonNull Activity activity) {
lock();
if (!activity.isFinishing() && isSensorEnabled()) { if (!activity.isFinishing() && isSensorEnabled()) {
switchSensorOn(); switchSensorOn();
} }

View file

@ -2,15 +2,22 @@ package net.osmand.plus.inapp;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.content.Intent;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import com.android.billingclient.api.BillingClient.BillingResponseCode;
import com.android.billingclient.api.BillingClient.SkuType;
import com.android.billingclient.api.BillingResult;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.SkuDetails;
import com.android.billingclient.api.SkuDetailsResponseListener;
import net.osmand.AndroidNetworkUtils; import net.osmand.AndroidNetworkUtils;
import net.osmand.AndroidNetworkUtils.OnRequestResultListener; import net.osmand.AndroidNetworkUtils.OnRequestResultListener;
import net.osmand.PlatformUtil;
import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandApplication;
import net.osmand.plus.OsmandSettings; import net.osmand.plus.OsmandSettings;
import net.osmand.plus.OsmandSettings.OsmandPreference; import net.osmand.plus.OsmandSettings.OsmandPreference;
@ -20,14 +27,10 @@ import net.osmand.plus.inapp.InAppPurchases.InAppPurchase;
import net.osmand.plus.inapp.InAppPurchases.InAppPurchase.PurchaseState; import net.osmand.plus.inapp.InAppPurchases.InAppPurchase.PurchaseState;
import net.osmand.plus.inapp.InAppPurchases.InAppPurchaseLiveUpdatesOldSubscription; import net.osmand.plus.inapp.InAppPurchases.InAppPurchaseLiveUpdatesOldSubscription;
import net.osmand.plus.inapp.InAppPurchases.InAppSubscription; import net.osmand.plus.inapp.InAppPurchases.InAppSubscription;
import net.osmand.plus.inapp.InAppPurchases.InAppSubscriptionIntroductoryInfo;
import net.osmand.plus.inapp.InAppPurchases.InAppSubscriptionList; import net.osmand.plus.inapp.InAppPurchases.InAppSubscriptionList;
import net.osmand.plus.inapp.util.IabHelper; import net.osmand.plus.inapp.util.BillingManager;
import net.osmand.plus.inapp.util.IabHelper.OnIabPurchaseFinishedListener; import net.osmand.plus.inapp.util.BillingManager.BillingUpdatesListener;
import net.osmand.plus.inapp.util.IabHelper.QueryInventoryFinishedListener;
import net.osmand.plus.inapp.util.IabResult;
import net.osmand.plus.inapp.util.Inventory;
import net.osmand.plus.inapp.util.Purchase;
import net.osmand.plus.inapp.util.SkuDetails;
import net.osmand.plus.liveupdates.CountrySelectionFragment; import net.osmand.plus.liveupdates.CountrySelectionFragment;
import net.osmand.plus.liveupdates.CountrySelectionFragment.CountryItem; import net.osmand.plus.liveupdates.CountrySelectionFragment.CountryItem;
import net.osmand.util.Algorithms; import net.osmand.util.Algorithms;
@ -37,6 +40,7 @@ import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.text.ParseException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -46,13 +50,11 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import static net.osmand.plus.inapp.util.IabHelper.IABHELPER_USER_CANCELLED;
import static net.osmand.plus.inapp.util.IabHelper.ITEM_TYPE_SUBS;
public class InAppPurchaseHelper { public class InAppPurchaseHelper {
// Debug tag, for logging // Debug tag, for logging
private static final org.apache.commons.logging.Log LOG = PlatformUtil.getLog(InAppPurchaseHelper.class);
private static final String TAG = InAppPurchaseHelper.class.getSimpleName(); private static final String TAG = InAppPurchaseHelper.class.getSimpleName();
private boolean mDebugLog = true; private boolean mDebugLog = false;
public static final long SUBSCRIPTION_HOLDING_TIME_MSEC = 1000 * 60 * 60 * 24 * 3; // 3 days public static final long SUBSCRIPTION_HOLDING_TIME_MSEC = 1000 * 60 * 60 * 24 * 3; // 3 days
@ -64,7 +66,9 @@ public class InAppPurchaseHelper {
private static final int RC_REQUEST = 10001; private static final int RC_REQUEST = 10001;
// The helper object // The helper object
private IabHelper mHelper; private BillingManager billingManager;
private List<SkuDetails> skuDetailsList;
private boolean isDeveloperVersion; private boolean isDeveloperVersion;
private String token = ""; private String token = "";
private InAppPurchaseTaskType activeTask; private InAppPurchaseTaskType activeTask;
@ -186,6 +190,10 @@ public class InAppPurchaseHelper {
return false; return false;
} }
private BillingManager getBillingManager() {
return billingManager;
}
private void exec(final @NonNull InAppPurchaseTaskType taskType, final @NonNull InAppRunnable runnable) { private void exec(final @NonNull InAppPurchaseTaskType taskType, final @NonNull InAppRunnable runnable) {
if (isDeveloperVersion || !Version.isGooglePlayEnabled(ctx)) { if (isDeveloperVersion || !Version.isGooglePlayEnabled(ctx)) {
return; return;
@ -201,10 +209,6 @@ public class InAppPurchaseHelper {
// Create the helper, passing it our context and the public key to verify signatures with // Create the helper, passing it our context and the public key to verify signatures with
logDebug("Creating InAppPurchaseHelper."); logDebug("Creating InAppPurchaseHelper.");
mHelper = new IabHelper(ctx, BASE64_ENCODED_PUBLIC_KEY);
// enable debug logging (for a production application, you should set this to false).
mHelper.enableDebugLogging(false);
// Start setup. This is asynchronous and the specified listener // Start setup. This is asynchronous and the specified listener
// will be called once setup completes. // will be called once setup completes.
@ -212,26 +216,110 @@ public class InAppPurchaseHelper {
try { try {
processingTask = true; processingTask = true;
activeTask = taskType; activeTask = taskType;
mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() { billingManager = new BillingManager(ctx, BASE64_ENCODED_PUBLIC_KEY, new BillingUpdatesListener() {
public void onIabSetupFinished(IabResult result) {
@Override
public void onBillingClientSetupFinished() {
logDebug("Setup finished."); logDebug("Setup finished.");
if (!result.isSuccess()) { BillingManager billingManager = getBillingManager();
// Oh noes, there was a problem. // Have we been disposed of in the meantime? If so, quit.
//complain("Problem setting up in-app billing: " + result); if (billingManager == null) {
notifyError(taskType, result.getMessage());
stop(true); stop(true);
return; return;
} }
// Have we been disposed of in the meantime? If so, quit. if (!billingManager.isServiceConnected()) {
if (mHelper == null) { // Oh noes, there was a problem.
//complain("Problem setting up in-app billing: " + result);
notifyError(taskType, billingManager.getBillingClientResponseMessage());
stop(true); stop(true);
return; return;
} }
processingTask = !runnable.run(InAppPurchaseHelper.this); processingTask = !runnable.run(InAppPurchaseHelper.this);
} }
@Override
public void onConsumeFinished(String token, BillingResult billingResult) {
}
@Override
public void onPurchasesUpdated(final List<Purchase> purchases) {
BillingManager billingManager = getBillingManager();
// Have we been disposed of in the meantime? If so, quit.
if (billingManager == null) {
stop(true);
return;
}
if (activeTask == InAppPurchaseTaskType.REQUEST_INVENTORY) {
List<String> skuInApps = new ArrayList<>();
for (InAppPurchase purchase : getInAppPurchases().getAllInAppPurchases(false)) {
skuInApps.add(purchase.getSku());
}
for (Purchase p : purchases) {
skuInApps.add(p.getSku());
}
billingManager.querySkuDetailsAsync(SkuType.INAPP, skuInApps, new SkuDetailsResponseListener() {
@Override
public void onSkuDetailsResponse(BillingResult billingResult, final List<SkuDetails> skuDetailsListInApps) {
// Is it a failure?
if (billingResult.getResponseCode() != BillingResponseCode.OK) {
logError("Failed to query inapps sku details: " + billingResult.getResponseCode());
notifyError(InAppPurchaseTaskType.REQUEST_INVENTORY, billingResult.getDebugMessage());
stop(true);
return;
}
List<String> skuSubscriptions = new ArrayList<>();
for (InAppSubscription subscription : getInAppPurchases().getAllInAppSubscriptions()) {
skuSubscriptions.add(subscription.getSku());
}
for (Purchase p : purchases) {
skuSubscriptions.add(p.getSku());
}
BillingManager billingManager = getBillingManager();
// Have we been disposed of in the meantime? If so, quit.
if (billingManager == null) {
stop(true);
return;
}
billingManager.querySkuDetailsAsync(SkuType.SUBS, skuSubscriptions, new SkuDetailsResponseListener() {
@Override
public void onSkuDetailsResponse(BillingResult billingResult, final List<SkuDetails> skuDetailsListSubscriptions) {
// Is it a failure?
if (billingResult.getResponseCode() != BillingResponseCode.OK) {
logError("Failed to query subscriptipons sku details: " + billingResult.getResponseCode());
notifyError(InAppPurchaseTaskType.REQUEST_INVENTORY, billingResult.getDebugMessage());
stop(true);
return;
}
List<SkuDetails> skuDetailsList = new ArrayList<>(skuDetailsListInApps);
skuDetailsList.addAll(skuDetailsListSubscriptions);
InAppPurchaseHelper.this.skuDetailsList = skuDetailsList;
mSkuDetailsResponseListener.onSkuDetailsResponse(billingResult, skuDetailsList);
}
});
}
});
}
for (Purchase purchase : purchases) {
if (!purchase.isAcknowledged()) {
onPurchaseFinished(purchase);
}
}
}
@Override
public void onPurchaseCanceled() {
stop(true);
}
}); });
} catch (Exception e) { } catch (Exception e) {
logError("exec Error", e); logError("exec Error", e);
@ -255,8 +343,16 @@ public class InAppPurchaseHelper {
@Override @Override
public boolean run(InAppPurchaseHelper helper) { public boolean run(InAppPurchaseHelper helper) {
try { try {
mHelper.launchPurchaseFlow(activity, SkuDetails skuDetails = getSkuDetails(getFullVersion().getSku());
getFullVersion().getSku(), RC_REQUEST, mPurchaseFinishedListener); if (skuDetails == null) {
throw new IllegalArgumentException("Cannot find sku details");
}
BillingManager billingManager = getBillingManager();
if (billingManager != null) {
billingManager.initiatePurchaseFlow(activity, skuDetails);
} else {
throw new IllegalStateException("BillingManager disposed");
}
return false; return false;
} catch (Exception e) { } catch (Exception e) {
complain("Cannot launch full version purchase!"); complain("Cannot launch full version purchase!");
@ -281,8 +377,16 @@ public class InAppPurchaseHelper {
@Override @Override
public boolean run(InAppPurchaseHelper helper) { public boolean run(InAppPurchaseHelper helper) {
try { try {
mHelper.launchPurchaseFlow(activity, SkuDetails skuDetails = getSkuDetails(getDepthContours().getSku());
getDepthContours().getSku(), RC_REQUEST, mPurchaseFinishedListener); if (skuDetails == null) {
throw new IllegalArgumentException("Cannot find sku details");
}
BillingManager billingManager = getBillingManager();
if (billingManager != null) {
billingManager.initiatePurchaseFlow(activity, skuDetails);
} else {
throw new IllegalStateException("BillingManager disposed");
}
return false; return false;
} catch (Exception e) { } catch (Exception e) {
complain("Cannot launch depth contours purchase!"); complain("Cannot launch depth contours purchase!");
@ -294,26 +398,76 @@ public class InAppPurchaseHelper {
}); });
} }
@Nullable
private SkuDetails getSkuDetails(@NonNull String sku) {
List<SkuDetails> skuDetailsList = this.skuDetailsList;
if (skuDetailsList != null) {
for (SkuDetails details : skuDetailsList) {
if (details.getSku().equals(sku)) {
return details;
}
}
}
return null;
}
private boolean hasDetails(@NonNull String sku) {
return getSkuDetails(sku) != null;
}
@Nullable
private Purchase getPurchase(@NonNull String sku) {
BillingManager billingManager = getBillingManager();
if (billingManager != null) {
List<Purchase> purchases = billingManager.getPurchases();
if (purchases != null) {
for (Purchase p : purchases) {
if (p.getSku().equals(sku)) {
return p;
}
}
}
}
return null;
}
// Listener that's called when we finish querying the items and subscriptions we own // Listener that's called when we finish querying the items and subscriptions we own
private QueryInventoryFinishedListener mGotInventoryListener = new QueryInventoryFinishedListener() { private SkuDetailsResponseListener mSkuDetailsResponseListener = new SkuDetailsResponseListener() {
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
logDebug("Query inventory finished."); @NonNull
private List<String> getAllOwnedSubscriptionSkus() {
List<String> result = new ArrayList<>();
BillingManager billingManager = getBillingManager();
if (billingManager != null) {
for (Purchase p : billingManager.getPurchases()) {
if (getInAppPurchases().getInAppSubscriptionBySku(p.getSku()) != null) {
result.add(p.getSku());
}
}
}
return result;
}
@Override
public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
logDebug("Query sku details finished.");
// Have we been disposed of in the meantime? If so, quit. // Have we been disposed of in the meantime? If so, quit.
if (mHelper == null) { if (getBillingManager() == null) {
stop(true); stop(true);
return; return;
} }
// Is it a failure? // Is it a failure?
if (result.isFailure()) { if (billingResult.getResponseCode() != BillingResponseCode.OK) {
logError("Failed to query inventory: " + result); logError("Failed to query inventory: " + billingResult.getResponseCode());
notifyError(InAppPurchaseTaskType.REQUEST_INVENTORY, result.getMessage()); notifyError(InAppPurchaseTaskType.REQUEST_INVENTORY, billingResult.getDebugMessage());
stop(true); stop(true);
return; return;
} }
logDebug("Query inventory was successful."); logDebug("Query sku details was successful.");
/* /*
* Check for items we own. Notice that for each purchase, we check * Check for items we own. Notice that for each purchase, we check
@ -321,54 +475,64 @@ public class InAppPurchaseHelper {
* verifyDeveloperPayload(). * verifyDeveloperPayload().
*/ */
List<String> allOwnedSubscriptionSkus = inventory.getAllOwnedSkus(ITEM_TYPE_SUBS); List<String> allOwnedSubscriptionSkus = getAllOwnedSubscriptionSkus();
for (InAppPurchase p : getLiveUpdates().getAllSubscriptions()) { for (InAppSubscription s : getLiveUpdates().getAllSubscriptions()) {
if (inventory.hasDetails(p.getSku())) { if (hasDetails(s.getSku())) {
Purchase purchase = inventory.getPurchase(p.getSku()); Purchase purchase = getPurchase(s.getSku());
SkuDetails liveUpdatesDetails = inventory.getSkuDetails(p.getSku()); SkuDetails liveUpdatesDetails = getSkuDetails(s.getSku());
fetchInAppPurchase(p, liveUpdatesDetails, purchase); if (liveUpdatesDetails != null) {
allOwnedSubscriptionSkus.remove(p.getSku()); fetchInAppPurchase(s, liveUpdatesDetails, purchase);
}
allOwnedSubscriptionSkus.remove(s.getSku());
} }
} }
for (String sku : allOwnedSubscriptionSkus) { for (String sku : allOwnedSubscriptionSkus) {
Purchase purchase = inventory.getPurchase(sku); Purchase purchase = getPurchase(sku);
SkuDetails liveUpdatesDetails = inventory.getSkuDetails(sku); SkuDetails liveUpdatesDetails = getSkuDetails(sku);
InAppSubscription s = getLiveUpdates().upgradeSubscription(sku); if (liveUpdatesDetails != null) {
if (s == null) { InAppSubscription s = getLiveUpdates().upgradeSubscription(sku);
s = new InAppPurchaseLiveUpdatesOldSubscription(liveUpdatesDetails); if (s == null) {
s = new InAppPurchaseLiveUpdatesOldSubscription(liveUpdatesDetails);
}
fetchInAppPurchase(s, liveUpdatesDetails, purchase);
} }
fetchInAppPurchase(s, liveUpdatesDetails, purchase);
} }
InAppPurchase fullVersion = getFullVersion(); InAppPurchase fullVersion = getFullVersion();
if (inventory.hasDetails(fullVersion.getSku())) { if (hasDetails(fullVersion.getSku())) {
Purchase purchase = inventory.getPurchase(fullVersion.getSku()); Purchase purchase = getPurchase(fullVersion.getSku());
SkuDetails fullPriceDetails = inventory.getSkuDetails(fullVersion.getSku()); SkuDetails fullPriceDetails = getSkuDetails(fullVersion.getSku());
fetchInAppPurchase(fullVersion, fullPriceDetails, purchase); if (fullPriceDetails != null) {
fetchInAppPurchase(fullVersion, fullPriceDetails, purchase);
}
} }
InAppPurchase depthContours = getDepthContours(); InAppPurchase depthContours = getDepthContours();
if (inventory.hasDetails(depthContours.getSku())) { if (hasDetails(depthContours.getSku())) {
Purchase purchase = inventory.getPurchase(depthContours.getSku()); Purchase purchase = getPurchase(depthContours.getSku());
SkuDetails depthContoursDetails = inventory.getSkuDetails(depthContours.getSku()); SkuDetails depthContoursDetails = getSkuDetails(depthContours.getSku());
fetchInAppPurchase(depthContours, depthContoursDetails, purchase); if (depthContoursDetails != null) {
fetchInAppPurchase(depthContours, depthContoursDetails, purchase);
}
} }
InAppPurchase contourLines = getContourLines(); InAppPurchase contourLines = getContourLines();
if (inventory.hasDetails(contourLines.getSku())) { if (hasDetails(contourLines.getSku())) {
Purchase purchase = inventory.getPurchase(contourLines.getSku()); Purchase purchase = getPurchase(contourLines.getSku());
SkuDetails contourLinesDetails = inventory.getSkuDetails(contourLines.getSku()); SkuDetails contourLinesDetails = getSkuDetails(contourLines.getSku());
fetchInAppPurchase(contourLines, contourLinesDetails, purchase); if (contourLinesDetails != null) {
fetchInAppPurchase(contourLines, contourLinesDetails, purchase);
}
} }
Purchase fullVersionPurchase = inventory.getPurchase(fullVersion.getSku()); Purchase fullVersionPurchase = getPurchase(fullVersion.getSku());
boolean fullVersionPurchased = (fullVersionPurchase != null && fullVersionPurchase.getPurchaseState() == 0); boolean fullVersionPurchased = fullVersionPurchase != null;
if (fullVersionPurchased) { if (fullVersionPurchased) {
ctx.getSettings().FULL_VERSION_PURCHASED.set(true); ctx.getSettings().FULL_VERSION_PURCHASED.set(true);
} }
Purchase depthContoursPurchase = inventory.getPurchase(depthContours.getSku()); Purchase depthContoursPurchase = getPurchase(depthContours.getSku());
boolean depthContoursPurchased = (depthContoursPurchase != null && depthContoursPurchase.getPurchaseState() == 0); boolean depthContoursPurchased = depthContoursPurchase != null;
if (depthContoursPurchased) { if (depthContoursPurchased) {
ctx.getSettings().DEPTH_CONTOURS_PURCHASED.set(true); ctx.getSettings().DEPTH_CONTOURS_PURCHASED.set(true);
} }
@ -377,10 +541,10 @@ public class InAppPurchaseHelper {
boolean subscribedToLiveUpdates = false; boolean subscribedToLiveUpdates = false;
List<Purchase> liveUpdatesPurchases = new ArrayList<>(); List<Purchase> liveUpdatesPurchases = new ArrayList<>();
for (InAppPurchase p : getLiveUpdates().getAllSubscriptions()) { for (InAppPurchase p : getLiveUpdates().getAllSubscriptions()) {
Purchase purchase = inventory.getPurchase(p.getSku()); Purchase purchase = getPurchase(p.getSku());
if (purchase != null) { if (purchase != null) {
liveUpdatesPurchases.add(purchase); liveUpdatesPurchases.add(purchase);
if (!subscribedToLiveUpdates && purchase.getPurchaseState() == 0) { if (!subscribedToLiveUpdates) {
subscribedToLiveUpdates = true; subscribedToLiveUpdates = true;
} }
} }
@ -453,8 +617,7 @@ public class InAppPurchaseHelper {
private void fetchInAppPurchase(@NonNull InAppPurchase inAppPurchase, @NonNull SkuDetails skuDetails, @Nullable Purchase purchase) { private void fetchInAppPurchase(@NonNull InAppPurchase inAppPurchase, @NonNull SkuDetails skuDetails, @Nullable Purchase purchase) {
if (purchase != null) { if (purchase != null) {
inAppPurchase.setPurchaseState(purchase.getPurchaseState() == 0 inAppPurchase.setPurchaseState(PurchaseState.PURCHASED);
? PurchaseState.PURCHASED : PurchaseState.NOT_PURCHASED);
inAppPurchase.setPurchaseTime(purchase.getPurchaseTime()); inAppPurchase.setPurchaseTime(purchase.getPurchaseTime());
} else { } else {
inAppPurchase.setPurchaseState(PurchaseState.NOT_PURCHASED); inAppPurchase.setPurchaseState(PurchaseState.NOT_PURCHASED);
@ -467,7 +630,26 @@ public class InAppPurchaseHelper {
String subscriptionPeriod = skuDetails.getSubscriptionPeriod(); String subscriptionPeriod = skuDetails.getSubscriptionPeriod();
if (!Algorithms.isEmpty(subscriptionPeriod)) { if (!Algorithms.isEmpty(subscriptionPeriod)) {
if (inAppPurchase instanceof InAppSubscription) { if (inAppPurchase instanceof InAppSubscription) {
((InAppSubscription) inAppPurchase).setSubscriptionPeriod(subscriptionPeriod); try {
((InAppSubscription) inAppPurchase).setSubscriptionPeriodString(subscriptionPeriod);
} catch (ParseException e) {
LOG.error(e);
}
}
}
if (inAppPurchase instanceof InAppSubscription) {
String introductoryPrice = skuDetails.getIntroductoryPrice();
String introductoryPricePeriod = skuDetails.getIntroductoryPricePeriod();
String introductoryPriceCycles = skuDetails.getIntroductoryPriceCycles();
long introductoryPriceAmountMicros = skuDetails.getIntroductoryPriceAmountMicros();
if (!Algorithms.isEmpty(introductoryPrice)) {
InAppSubscription s = (InAppSubscription) inAppPurchase;
try {
s.setIntroductoryInfo(new InAppSubscriptionIntroductoryInfo(s, introductoryPrice,
introductoryPriceAmountMicros, introductoryPricePeriod, introductoryPriceCycles));
} catch (ParseException e) {
LOG.error(e);
}
} }
} }
} }
@ -563,10 +745,15 @@ public class InAppPurchaseHelper {
public boolean run(InAppPurchaseHelper helper) { public boolean run(InAppPurchaseHelper helper) {
try { try {
Activity a = activity.get(); Activity a = activity.get();
if (a != null) { SkuDetails skuDetails = getSkuDetails(sku);
mHelper.launchPurchaseFlow(a, if (a != null && skuDetails != null) {
sku, ITEM_TYPE_SUBS, BillingManager billingManager = getBillingManager();
RC_REQUEST, mPurchaseFinishedListener, payload); if (billingManager != null) {
billingManager.setPayload(payload);
billingManager.initiatePurchaseFlow(a, skuDetails);
} else {
throw new IllegalStateException("BillingManager disposed");
}
return false; return false;
} else { } else {
stop(true); stop(true);
@ -585,28 +772,6 @@ public class InAppPurchaseHelper {
} }
} }
public boolean onActivityResultHandled(int requestCode, int resultCode, Intent data) {
logDebug("onActivityResult(" + requestCode + "," + resultCode + "," + data);
if (mHelper == null) return false;
try {
// Pass on the activity result to the helper for handling
if (!mHelper.handleActivityResult(requestCode, resultCode, data)) {
// not handled, so handle it ourselves (here's where you'd
// perform any handling of activity results not related to in-app
// billing...
//super.onActivityResult(requestCode, resultCode, data);
return false;
} else {
logDebug("onActivityResult handled by IABUtil.");
return true;
}
} catch (Exception e) {
logError("onActivityResultHandled", e);
return false;
}
}
@SuppressLint("StaticFieldLeak") @SuppressLint("StaticFieldLeak")
private class RequestInventoryTask extends AsyncTask<Void, Void, String> { private class RequestInventoryTask extends AsyncTask<Void, Void, String> {
@ -652,12 +817,13 @@ public class InAppPurchaseHelper {
@Override @Override
public boolean run(InAppPurchaseHelper helper) { public boolean run(InAppPurchaseHelper helper) {
logDebug("Setup successful. Querying inventory."); logDebug("Setup successful. Querying inventory.");
Set<String> skus = new HashSet<>();
for (InAppPurchase purchase : purchases.getAllInAppPurchases()) {
skus.add(purchase.getSku());
}
try { try {
mHelper.queryInventoryAsync(true, new ArrayList<>(skus), mGotInventoryListener); BillingManager billingManager = getBillingManager();
if (billingManager != null) {
billingManager.queryPurchases();
} else {
throw new IllegalStateException("BillingManager disposed");
}
return false; return false;
} catch (Exception e) { } catch (Exception e) {
logError("queryInventoryAsync Error", e); logError("queryInventoryAsync Error", e);
@ -679,81 +845,69 @@ public class InAppPurchaseHelper {
parameters.put("aid", ctx.getUserAndroidId()); parameters.put("aid", ctx.getUserAndroidId());
} }
// Callback for when a purchase is finished // Call when a purchase is finished
private OnIabPurchaseFinishedListener mPurchaseFinishedListener = new OnIabPurchaseFinishedListener() { private void onPurchaseFinished(Purchase purchase) {
public void onIabPurchaseFinished(IabResult result, Purchase purchase) { logDebug("Purchase finished: " + purchase);
logDebug("Purchase finished: " + result + ", purchase: " + purchase);
// if we were disposed of in the meantime, quit. // if we were disposed of in the meantime, quit.
if (mHelper == null) { if (getBillingManager() == null) {
stop(true); stop(true);
return; return;
}
if (result.isFailure()) {
if (result.getResponse() != IABHELPER_USER_CANCELLED) {
complain("Error purchasing: " + result);
}
notifyDismissProgress(activeTask);
notifyError(activeTask, "Error purchasing: " + result);
stop(true);
return;
}
logDebug("Purchase successful.");
InAppPurchase liveUpdatesPurchase = getLiveUpdates().getSubscriptionBySku(purchase.getSku());
if (liveUpdatesPurchase != null) {
// bought live updates
logDebug("Live updates subscription purchased.");
final String sku = liveUpdatesPurchase.getSku();
liveUpdatesPurchase.setPurchaseState(purchase.getPurchaseState() == 0 ? PurchaseState.PURCHASED : PurchaseState.NOT_PURCHASED);
sendTokens(Collections.singletonList(purchase), new OnRequestResultListener() {
@Override
public void onResult(String result) {
boolean active = ctx.getSettings().LIVE_UPDATES_PURCHASED.get();
ctx.getSettings().LIVE_UPDATES_PURCHASED.set(true);
ctx.getSettings().getCustomRenderBooleanProperty("depthContours").set(true);
ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_TIME.set(0L);
ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_FIRST_DLG_SHOWN.set(false);
ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_SECOND_DLG_SHOWN.set(false);
notifyDismissProgress(InAppPurchaseTaskType.PURCHASE_LIVE_UPDATES);
notifyItemPurchased(sku, active);
stop(true);
}
});
} else if (purchase.getSku().equals(getFullVersion().getSku())) {
// bought full version
getFullVersion().setPurchaseState(purchase.getPurchaseState() == 0 ? PurchaseState.PURCHASED : PurchaseState.NOT_PURCHASED);
logDebug("Full version purchased.");
showToast(ctx.getString(R.string.full_version_thanks));
ctx.getSettings().FULL_VERSION_PURCHASED.set(true);
notifyDismissProgress(InAppPurchaseTaskType.PURCHASE_FULL_VERSION);
notifyItemPurchased(getFullVersion().getSku(), false);
stop(true);
} else if (purchase.getSku().equals(getDepthContours().getSku())) {
// bought sea depth contours
getDepthContours().setPurchaseState(purchase.getPurchaseState() == 0 ? PurchaseState.PURCHASED : PurchaseState.NOT_PURCHASED);
logDebug("Sea depth contours purchased.");
showToast(ctx.getString(R.string.sea_depth_thanks));
ctx.getSettings().DEPTH_CONTOURS_PURCHASED.set(true);
ctx.getSettings().getCustomRenderBooleanProperty("depthContours").set(true);
notifyDismissProgress(InAppPurchaseTaskType.PURCHASE_DEPTH_CONTOURS);
notifyItemPurchased(getDepthContours().getSku(), false);
stop(true);
} else {
notifyDismissProgress(activeTask);
stop(true);
}
} }
};
logDebug("Purchase successful.");
InAppPurchase liveUpdatesPurchase = getLiveUpdates().getSubscriptionBySku(purchase.getSku());
if (liveUpdatesPurchase != null) {
// bought live updates
logDebug("Live updates subscription purchased.");
final String sku = liveUpdatesPurchase.getSku();
liveUpdatesPurchase.setPurchaseState(PurchaseState.PURCHASED);
sendTokens(Collections.singletonList(purchase), new OnRequestResultListener() {
@Override
public void onResult(String result) {
boolean active = ctx.getSettings().LIVE_UPDATES_PURCHASED.get();
ctx.getSettings().LIVE_UPDATES_PURCHASED.set(true);
ctx.getSettings().getCustomRenderBooleanProperty("depthContours").set(true);
ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_TIME.set(0L);
ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_FIRST_DLG_SHOWN.set(false);
ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_SECOND_DLG_SHOWN.set(false);
notifyDismissProgress(InAppPurchaseTaskType.PURCHASE_LIVE_UPDATES);
notifyItemPurchased(sku, active);
stop(true);
}
});
} else if (purchase.getSku().equals(getFullVersion().getSku())) {
// bought full version
getFullVersion().setPurchaseState(PurchaseState.PURCHASED);
logDebug("Full version purchased.");
showToast(ctx.getString(R.string.full_version_thanks));
ctx.getSettings().FULL_VERSION_PURCHASED.set(true);
notifyDismissProgress(InAppPurchaseTaskType.PURCHASE_FULL_VERSION);
notifyItemPurchased(getFullVersion().getSku(), false);
stop(true);
} else if (purchase.getSku().equals(getDepthContours().getSku())) {
// bought sea depth contours
getDepthContours().setPurchaseState(PurchaseState.PURCHASED);
logDebug("Sea depth contours purchased.");
showToast(ctx.getString(R.string.sea_depth_thanks));
ctx.getSettings().DEPTH_CONTOURS_PURCHASED.set(true);
ctx.getSettings().getCustomRenderBooleanProperty("depthContours").set(true);
notifyDismissProgress(InAppPurchaseTaskType.PURCHASE_DEPTH_CONTOURS);
notifyItemPurchased(getDepthContours().getSku(), false);
stop(true);
} else {
notifyDismissProgress(activeTask);
stop(true);
}
}
// Do not forget call stop() when helper is not needed anymore // Do not forget call stop() when helper is not needed anymore
public void stop() { public void stop() {
@ -762,14 +916,15 @@ public class InAppPurchaseHelper {
private void stop(boolean taskDone) { private void stop(boolean taskDone) {
logDebug("Destroying helper."); logDebug("Destroying helper.");
if (mHelper != null) { BillingManager billingManager = getBillingManager();
if (billingManager != null) {
if (taskDone) { if (taskDone) {
processingTask = false; processingTask = false;
} }
if (!processingTask) { if (!processingTask) {
activeTask = null; activeTask = null;
mHelper.dispose(); billingManager.destroy();
mHelper = null; this.billingManager = null;
} }
} else { } else {
processingTask = false; processingTask = false;
@ -793,7 +948,7 @@ public class InAppPurchaseHelper {
Map<String, String> parameters = new HashMap<>(); Map<String, String> parameters = new HashMap<>();
parameters.put("userid", userId); parameters.put("userid", userId);
parameters.put("sku", purchase.getSku()); parameters.put("sku", purchase.getSku());
parameters.put("purchaseToken", purchase.getToken()); parameters.put("purchaseToken", purchase.getPurchaseToken());
parameters.put("email", email); parameters.put("email", email);
parameters.put("token", token); parameters.put("token", token);
addUserInfo(parameters); addUserInfo(parameters);

View file

@ -1,19 +1,29 @@
package net.osmand.plus.inapp; package net.osmand.plus.inapp;
import android.content.Context; import android.content.Context;
import android.graphics.Color;
import android.graphics.Typeface;
import android.support.annotation.ColorInt;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.text.Spannable; import android.text.Spannable;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.style.StyleSpan; import android.text.style.ForegroundColorSpan;
import com.android.billingclient.api.SkuDetails;
import net.osmand.AndroidUtils;
import net.osmand.Period;
import net.osmand.Period.PeriodUnit;
import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R; import net.osmand.plus.R;
import net.osmand.plus.Version; import net.osmand.plus.Version;
import net.osmand.plus.inapp.util.SkuDetails; import net.osmand.plus.helpers.FontCache;
import net.osmand.plus.widgets.style.CustomTypefaceSpan;
import net.osmand.util.Algorithms; import net.osmand.util.Algorithms;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.text.ParseException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Currency; import java.util.Currency;
@ -122,15 +132,31 @@ public class InAppPurchases {
return liveUpdates; return liveUpdates;
} }
public List<InAppPurchase> getAllInAppPurchases() { public List<InAppPurchase> getAllInAppPurchases(boolean includeSubscriptions) {
List<InAppPurchase> purchases = new ArrayList<>(); List<InAppPurchase> purchases = new ArrayList<>();
purchases.add(fullVersion); purchases.add(fullVersion);
purchases.add(depthContours); purchases.add(depthContours);
purchases.add(contourLines); purchases.add(contourLines);
purchases.addAll(liveUpdates.getAllSubscriptions()); if (includeSubscriptions) {
purchases.addAll(liveUpdates.getAllSubscriptions());
}
return purchases; return purchases;
} }
public List<InAppSubscription> getAllInAppSubscriptions() {
return liveUpdates.getAllSubscriptions();
}
@Nullable
public InAppSubscription getInAppSubscriptionBySku(@NonNull String sku) {
for (InAppSubscription s : liveUpdates.getAllSubscriptions()) {
if (sku.startsWith(s.getSkuNoVersion())) {
return s;
}
}
return null;
}
public boolean isFullVersion(String sku) { public boolean isFullVersion(String sku) {
return FULL_VERSION.getSku().equals(sku); return FULL_VERSION.getSku().equals(sku);
} }
@ -162,7 +188,7 @@ public class InAppPurchases {
private List<InAppSubscription> subscriptions; private List<InAppSubscription> subscriptions;
InAppSubscriptionList(@NonNull InAppSubscription[] subscriptionsArray) { InAppSubscriptionList(@NonNull InAppSubscription[] subscriptionsArray) {
this.subscriptions = Arrays.asList(subscriptionsArray);; this.subscriptions = Arrays.asList(subscriptionsArray);
} }
private List<InAppSubscription> getSubscriptions() { private List<InAppSubscription> getSubscriptions() {
@ -420,13 +446,205 @@ public class InAppPurchases {
} }
} }
public static class InAppSubscriptionIntroductoryInfo {
private InAppSubscription subscription;
private String introductoryPrice;
private long introductoryPriceAmountMicros;
private String introductoryPeriodString;
private int introductoryCycles;
private double introductoryPriceValue;
private Period introductoryPeriod;
public InAppSubscriptionIntroductoryInfo(@NonNull InAppSubscription subscription,
String introductoryPrice,
long introductoryPriceAmountMicros,
String introductoryPeriodString,
String introductoryCycles) throws ParseException {
this.subscription = subscription;
this.introductoryPrice = introductoryPrice;
this.introductoryPriceAmountMicros = introductoryPriceAmountMicros;
this.introductoryPeriodString = introductoryPeriodString;
try {
this.introductoryCycles = Integer.parseInt(introductoryCycles);
} catch (NumberFormatException e) {
throw new ParseException("Cannot parse introductoryCycles = " + introductoryCycles, 0);
}
introductoryPriceValue = introductoryPriceAmountMicros / 1000000d;
introductoryPeriod = Period.parse(introductoryPeriodString);
}
public String getIntroductoryPrice() {
return introductoryPrice;
}
public long getIntroductoryPriceAmountMicros() {
return introductoryPriceAmountMicros;
}
public String getIntroductoryPeriodString() {
return introductoryPeriodString;
}
public int getIntroductoryCycles() {
return introductoryCycles;
}
public double getIntroductoryPriceValue() {
return introductoryPriceValue;
}
public double getIntroductoryMonthlyPriceValue() {
return introductoryPriceValue /
(introductoryPeriod.getUnit().getMonthsValue() * introductoryPeriod.getNumberOfUnits());
}
public Period getIntroductoryPeriod() {
return introductoryPeriod;
}
public long getTotalPeriods() {
return introductoryPeriod.getNumberOfUnits() * introductoryCycles;
}
private String getTotalUnitsString(@NonNull Context ctx, boolean original) {
String unitStr = "";
Period subscriptionPeriod = subscription.getSubscriptionPeriod();
PeriodUnit unit = original && subscriptionPeriod != null ? subscriptionPeriod.getUnit() : introductoryPeriod.getUnit();
long totalPeriods = original && subscriptionPeriod != null ? subscriptionPeriod.getNumberOfUnits() : getTotalPeriods();
switch (unit) {
case YEAR:
unitStr = ctx.getString(R.string.year);
break;
case MONTH:
if (totalPeriods == 1) {
unitStr = ctx.getString(R.string.month);
} else if (totalPeriods < 5) {
unitStr = ctx.getString(R.string.months_2_4);
} else {
unitStr = ctx.getString(R.string.months_5);
}
break;
case WEEK:
if (totalPeriods == 1) {
unitStr = ctx.getString(R.string.week);
} else if (totalPeriods < 5) {
unitStr = ctx.getString(R.string.weeks_2_4);
} else {
unitStr = ctx.getString(R.string.weeks_5);
}
break;
case DAY:
if (totalPeriods == 1) {
unitStr = ctx.getString(R.string.day);
} else if (totalPeriods < 5) {
unitStr = ctx.getString(R.string.days_2_4);
} else {
unitStr = ctx.getString(R.string.days_5);
}
break;
}
return unitStr;
}
private String getUnitString(@NonNull Context ctx) {
PeriodUnit unit = introductoryPeriod.getUnit();
switch (unit) {
case YEAR:
return ctx.getString(R.string.year);
case MONTH:
return ctx.getString(R.string.month);
case WEEK:
return ctx.getString(R.string.week);
case DAY:
return ctx.getString(R.string.day);
}
return "";
}
private String getDisountPeriodString(String unitStr, long totalPeriods) {
if (totalPeriods == 1)
return unitStr;
if (AndroidUtils.isRTL()) {
return unitStr + " " + totalPeriods;
} else {
return totalPeriods + " " + unitStr;
}
}
public CharSequence getDescriptionTitle(@NonNull Context ctx) {
long totalPeriods = getTotalPeriods();
String unitStr = getTotalUnitsString(ctx, false).toLowerCase();
int discountPercent = subscription.getDiscountPercent(null);
return ctx.getString(R.string.get_discount_title, totalPeriods, unitStr, discountPercent + "%");
}
public CharSequence getFormattedDescription(@NonNull Context ctx, @ColorInt int textColor) {
long totalPeriods = getTotalPeriods();
String singleUnitStr = getUnitString(ctx).toLowerCase();
String unitStr = getTotalUnitsString(ctx, false).toLowerCase();
long numberOfUnits = introductoryPeriod.getNumberOfUnits();
Period subscriptionPeriod = subscription.getSubscriptionPeriod();
long originalNumberOfUnits = subscriptionPeriod != null ? subscriptionPeriod.getNumberOfUnits() : 1;
String originalUnitsStr = getTotalUnitsString(ctx, true).toLowerCase();
String originalPriceStr = subscription.getPrice(ctx);
String priceStr = introductoryPrice;
String pricePeriod;
String originalPricePeriod;
if (AndroidUtils.isRTL()) {
pricePeriod = singleUnitStr + " / " + priceStr;
originalPricePeriod = originalUnitsStr + " / " + originalPriceStr;
if (numberOfUnits > 1) {
pricePeriod = unitStr + " " + numberOfUnits + " / " + priceStr;
}
if (originalNumberOfUnits == 3 && subscriptionPeriod.getUnit() == PeriodUnit.MONTH) {
originalPricePeriod = ctx.getString(R.string.months_3).toLowerCase() + " / " + originalPriceStr;
} else if (originalNumberOfUnits > 1) {
originalPricePeriod = originalUnitsStr + " " + originalNumberOfUnits + " / " + originalPriceStr;
}
} else {
pricePeriod = priceStr + " / " + singleUnitStr;
originalPricePeriod = originalPriceStr + " / " + originalUnitsStr;
if (numberOfUnits > 1) {
pricePeriod = priceStr + " / " + numberOfUnits + " " + unitStr;
}
if (originalNumberOfUnits == 3 && subscriptionPeriod.getUnit() == PeriodUnit.MONTH) {
originalPricePeriod = originalPriceStr + " / " + ctx.getString(R.string.months_3).toLowerCase();
} else if (originalNumberOfUnits > 1) {
originalPricePeriod = originalPriceStr + " / " + originalNumberOfUnits + " " + originalUnitsStr;
}
}
String periodPriceStr = introductoryCycles == 1 ? priceStr : pricePeriod;
int firstPartRes = totalPeriods == 1 ? R.string.get_discount_first_part : R.string.get_discount_first_few_part;
SpannableStringBuilder mainPart = new SpannableStringBuilder(ctx.getString(firstPartRes, periodPriceStr, getDisountPeriodString(unitStr, totalPeriods)));
SpannableStringBuilder thenPart = new SpannableStringBuilder(ctx.getString(R.string.get_discount_second_part, originalPricePeriod));
Typeface typefaceRegular = FontCache.getRobotoRegular(ctx);
Typeface typefaceBold = FontCache.getRobotoMedium(ctx);
mainPart.setSpan(new ForegroundColorSpan(textColor), 0, mainPart.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
mainPart.setSpan(new CustomTypefaceSpan(typefaceBold), 0, mainPart.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
int secondaryTextColor = Color.argb(128, Color.red(textColor), Color.green(textColor), Color.blue(textColor));
thenPart.setSpan(new ForegroundColorSpan(secondaryTextColor), 0, thenPart.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
thenPart.setSpan(new CustomTypefaceSpan(typefaceRegular), 0, thenPart.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return new SpannableStringBuilder(mainPart).append("\n").append(thenPart);
}
}
public static abstract class InAppSubscription extends InAppPurchase { public static abstract class InAppSubscription extends InAppPurchase {
private Map<String, InAppSubscription> upgrades = new ConcurrentHashMap<>(); private Map<String, InAppSubscription> upgrades = new ConcurrentHashMap<>();
private String skuNoVersion; private String skuNoVersion;
private String subscriptionPeriod; private String subscriptionPeriodString;
private Period subscriptionPeriod;
private boolean upgrade = false; private boolean upgrade = false;
private InAppSubscriptionIntroductoryInfo introductoryInfo;
InAppSubscription(@NonNull String skuNoVersion, int version) { InAppSubscription(@NonNull String skuNoVersion, int version) {
super(skuNoVersion + "_v" + version); super(skuNoVersion + "_v" + version);
this.skuNoVersion = skuNoVersion; this.skuNoVersion = skuNoVersion;
@ -475,12 +693,36 @@ public class InAppPurchases {
return skuNoVersion; return skuNoVersion;
} }
public String getSubscriptionPeriod() { @Nullable
public String getSubscriptionPeriodString() {
return subscriptionPeriodString;
}
@Nullable
public Period getSubscriptionPeriod() {
return subscriptionPeriod; return subscriptionPeriod;
} }
public void setSubscriptionPeriod(String subscriptionPeriod) { public void setSubscriptionPeriodString(String subscriptionPeriodString) throws ParseException {
this.subscriptionPeriod = subscriptionPeriod; this.subscriptionPeriodString = subscriptionPeriodString;
this.subscriptionPeriod = Period.parse(subscriptionPeriodString);
}
public InAppSubscriptionIntroductoryInfo getIntroductoryInfo() {
/*
try {
if (subscriptionPeriod != null && subscriptionPeriod.getUnit() == PeriodUnit.YEAR) {
introductoryInfo = new InAppSubscriptionIntroductoryInfo(this, "30 грн.", 30000000L, "P1Y", "1");
}
} catch (ParseException e) {
//
}
*/
return introductoryInfo;
}
public void setIntroductoryInfo(InAppSubscriptionIntroductoryInfo introductoryInfo) {
this.introductoryInfo = introductoryInfo;
} }
@Override @Override
@ -497,12 +739,34 @@ public class InAppPurchases {
} }
} }
public CharSequence getDescription(@NonNull Context ctx, @Nullable InAppSubscription monthlyLiveUpdates) {
CharSequence descr = getDescription(ctx);
int discountPercent = getDiscountPercent(monthlyLiveUpdates);
return discountPercent > 0 ? ctx.getString(R.string.price_and_discount, descr, discountPercent + "%") : descr;
}
public CharSequence getRenewDescription(@NonNull Context ctx) { public CharSequence getRenewDescription(@NonNull Context ctx) {
return ""; return "";
} }
@Nullable @Nullable
protected abstract InAppSubscription newInstance(@NonNull String sku); protected abstract InAppSubscription newInstance(@NonNull String sku);
public int getDiscountPercent(@Nullable InAppSubscription monthlyLiveUpdates) {
double monthlyPriceValue = getMonthlyPriceValue();
if (monthlyLiveUpdates != null) {
double regularMonthlyPrice = monthlyLiveUpdates.getPriceValue();
if (regularMonthlyPrice > 0 && monthlyPriceValue > 0 && monthlyPriceValue < regularMonthlyPrice) {
return (int) ((1 - monthlyPriceValue / regularMonthlyPrice) * 100d);
}
} else if (introductoryInfo != null) {
double introductoryMonthlyPrice = introductoryInfo.getIntroductoryMonthlyPriceValue();
if (introductoryMonthlyPrice > 0 && monthlyPriceValue > 0 && monthlyPriceValue > introductoryMonthlyPrice) {
return (int) ((1 - introductoryMonthlyPrice / monthlyPriceValue) * 100d);
}
}
return 0;
}
} }
public static class InAppPurchaseFullVersion extends InAppPurchase { public static class InAppPurchaseFullVersion extends InAppPurchase {
@ -616,14 +880,6 @@ public class InAppPurchases {
return ctx.getString(R.string.osm_live_payment_monthly_title); return ctx.getString(R.string.osm_live_payment_monthly_title);
} }
@Override
public CharSequence getDescription(@NonNull Context ctx) {
CharSequence descr = super.getDescription(ctx);
SpannableStringBuilder text = new SpannableStringBuilder(descr).append(". ").append(ctx.getString(R.string.osm_live_payment_contribute_descr));
text.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), descr.length() + 1, text.length() - 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return text;
}
@Override @Override
public CharSequence getRenewDescription(@NonNull Context ctx) { public CharSequence getRenewDescription(@NonNull Context ctx) {
return ctx.getString(R.string.osm_live_payment_renews_monthly); return ctx.getString(R.string.osm_live_payment_renews_monthly);

View file

@ -0,0 +1,420 @@
package net.osmand.plus.inapp.util;
import android.app.Activity;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.android.billingclient.api.AcknowledgePurchaseParams;
import com.android.billingclient.api.AcknowledgePurchaseResponseListener;
import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingClient.BillingResponseCode;
import com.android.billingclient.api.BillingClient.FeatureType;
import com.android.billingclient.api.BillingClient.SkuType;
import com.android.billingclient.api.BillingClientStateListener;
import com.android.billingclient.api.BillingFlowParams;
import com.android.billingclient.api.BillingResult;
import com.android.billingclient.api.ConsumeParams;
import com.android.billingclient.api.ConsumeResponseListener;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.Purchase.PurchasesResult;
import com.android.billingclient.api.PurchasesUpdatedListener;
import com.android.billingclient.api.SkuDetails;
import com.android.billingclient.api.SkuDetailsParams;
import com.android.billingclient.api.SkuDetailsResponseListener;
import net.osmand.PlatformUtil;
import net.osmand.util.Algorithms;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Handles all the interactions with Play Store (via Billing library), maintains connection to
* it through BillingClient and caches temporary states/data if needed
*/
public class BillingManager implements PurchasesUpdatedListener {
// Default value of mBillingClientResponseCode until BillingManager was not yeat initialized
public static final int BILLING_MANAGER_NOT_INITIALIZED = -1;
private static final org.apache.commons.logging.Log LOG = PlatformUtil.getLog(BillingManager.class);
private static final String TAG = "BillingManager";
/**
* A reference to BillingClient
**/
private BillingClient mBillingClient;
/**
* True if billing service is connected now.
*/
private boolean mIsServiceConnected;
private final Context mContext;
// Public key for verifying signature, in base64 encoding
private String mSignatureBase64;
private String mPayload;
private final BillingUpdatesListener mBillingUpdatesListener;
private final List<Purchase> mPurchases = new ArrayList<>();
private Set<String> mTokensToBeConsumed;
private int mBillingClientResponseCode = BILLING_MANAGER_NOT_INITIALIZED;
private String mBillingClientResponseMessage;
/**
* Listener to the updates that happen when purchases list was updated or consumption of the
* item was finished
*/
public interface BillingUpdatesListener {
void onBillingClientSetupFinished();
void onConsumeFinished(String token, BillingResult billingResult);
void onPurchasesUpdated(List<Purchase> purchases);
void onPurchaseCanceled();
}
/**
* Listener for the Billing client state to become connected
*/
public interface ServiceConnectedListener {
void onServiceConnected(BillingResult billingResult);
}
public BillingManager(@NonNull final Context context, @NonNull final String base64PublicKey,
@NonNull final BillingUpdatesListener updatesListener) {
LOG.debug("Creating Billing client.");
mContext = context;
mSignatureBase64 = base64PublicKey;
mBillingUpdatesListener = updatesListener;
mBillingClient = BillingClient.newBuilder(mContext)
.enablePendingPurchases()
.setListener(this)
.build();
LOG.debug("Starting setup.");
// Start setup. This is asynchronous and the specified listener will be called
// once setup completes.
// It also starts to report all the new purchases through onPurchasesUpdated() callback.
startServiceConnection(null);
}
/**
* Handle a callback that purchases were updated from the Billing library
*/
@Override
public void onPurchasesUpdated(BillingResult billingResult, @Nullable List<Purchase> purchases) {
int responseCode = billingResult.getResponseCode();
if (responseCode == BillingResponseCode.OK) {
if (purchases != null) {
for (Purchase purchase : purchases) {
handlePurchase(purchase);
}
} else {
LOG.info("onPurchasesUpdated() - no purchases");
}
mBillingUpdatesListener.onPurchasesUpdated(mPurchases);
} else if (responseCode == BillingResponseCode.USER_CANCELED) {
LOG.info("onPurchasesUpdated() - user cancelled the purchase flow - skipping");
mBillingUpdatesListener.onPurchaseCanceled();
} else {
LOG.warn("onPurchasesUpdated() got unknown responseCode: " + responseCode);
}
}
/**
* Start a purchase flow
*/
public void initiatePurchaseFlow(final Activity activity, final SkuDetails skuDetails) {
initiatePurchaseFlow(activity, skuDetails, null);
}
/**
* Start a purchase or subscription replace flow
*/
public void initiatePurchaseFlow(final Activity activity, final SkuDetails skuDetails, final String oldSku) {
Runnable purchaseFlowRequest = new Runnable() {
@Override
public void run() {
LOG.debug("Launching in-app purchase flow. Replace old SKU? " + (oldSku != null));
BillingFlowParams purchaseParams = BillingFlowParams.newBuilder().setSkuDetails(skuDetails).setOldSku(oldSku).build();
mBillingClient.launchBillingFlow(activity, purchaseParams);
}
};
executeServiceRequest(purchaseFlowRequest);
}
public Context getContext() {
return mContext;
}
/**
* Clear the resources
*/
public void destroy() {
LOG.debug("Destroying the manager.");
if (mBillingClient != null && mBillingClient.isReady()) {
mBillingClient.endConnection();
mBillingClient = null;
}
}
public void querySkuDetailsAsync(@SkuType final String itemType, final List<String> skuList,
final SkuDetailsResponseListener listener) {
// Creating a runnable from the request to use it inside our connection retry policy below
Runnable queryRequest = new Runnable() {
@Override
public void run() {
// Query the purchase async
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(skuList).setType(itemType);
mBillingClient.querySkuDetailsAsync(params.build(),
new SkuDetailsResponseListener() {
@Override
public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
listener.onSkuDetailsResponse(billingResult, skuDetailsList);
}
});
}
};
executeServiceRequest(queryRequest);
}
public void consumeAsync(final ConsumeParams consumeParams) {
// If we've already scheduled to consume this token - no action is needed (this could happen
// if you received the token when querying purchases inside onReceive() and later from
// onActivityResult()
final String purchaseToken = consumeParams.getPurchaseToken();
if (mTokensToBeConsumed == null) {
mTokensToBeConsumed = new HashSet<>();
} else if (mTokensToBeConsumed.contains(purchaseToken)) {
LOG.info("Token was already scheduled to be consumed - skipping...");
return;
}
mTokensToBeConsumed.add(purchaseToken);
// Generating Consume Response listener
final ConsumeResponseListener onConsumeListener = new ConsumeResponseListener() {
@Override
public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
// If billing service was disconnected, we try to reconnect 1 time
// (feel free to introduce your retry policy here).
mBillingUpdatesListener.onConsumeFinished(purchaseToken, billingResult);
}
};
// Creating a runnable from the request to use it inside our connection retry policy below
Runnable consumeRequest = new Runnable() {
@Override
public void run() {
// Consume the purchase async
mBillingClient.consumeAsync(consumeParams, onConsumeListener);
}
};
executeServiceRequest(consumeRequest);
}
public boolean isServiceConnected() {
return mIsServiceConnected;
}
/**
* Returns the value Billing client response code or BILLING_MANAGER_NOT_INITIALIZED if the
* client connection response was not received yet.
*/
public int getBillingClientResponseCode() {
return mBillingClientResponseCode;
}
public String getBillingClientResponseMessage() {
return mBillingClientResponseMessage;
}
public List<Purchase> getPurchases() {
return Collections.unmodifiableList(mPurchases);
}
public String getPayload() {
return mPayload;
}
public void setPayload(String payload) {
this.mPayload = payload;
}
/**
* Handles the purchase
* <p>Note: Notice that for each purchase, we check if signature is valid on the client.
* It's recommended to move this check into your backend.
* See {@link Security#verifyPurchase(String, String, String)}
* </p>
*
* @param purchase Purchase to be handled
*/
private void handlePurchase(final Purchase purchase) {
if (!verifyValidSignature(purchase.getOriginalJson(), purchase.getSignature())) {
LOG.info("Got a purchase: " + purchase + ", but signature is bad. Skipping...");
return;
}
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
// Acknowledge the purchase if it hasn't already been acknowledged.
if (!purchase.isAcknowledged()) {
AcknowledgePurchaseParams.Builder builder =
AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken());
if (!Algorithms.isEmpty(mPayload)) {
builder.setDeveloperPayload(mPayload);
}
AcknowledgePurchaseParams acknowledgePurchaseParams = builder.build();
mBillingClient.acknowledgePurchase(acknowledgePurchaseParams, new AcknowledgePurchaseResponseListener() {
@Override
public void onAcknowledgePurchaseResponse(BillingResult billingResult) {
if (billingResult.getResponseCode() != BillingResponseCode.OK) {
LOG.info("Acknowledge a purchase: " + purchase + " failed (" + billingResult.getResponseCode() + "). " + billingResult.getDebugMessage());
}
}
});
}
} else if (purchase.getPurchaseState() == Purchase.PurchaseState.PENDING) {
LOG.info("Got a purchase: " + purchase + ", but purchase state is pending. Skipping...");
return;
} else {
LOG.info("Got a purchase: " + purchase + ", but purchase state is " + purchase.getPurchaseState() + ". Skipping...");
return;
}
LOG.debug("Got a verified purchase: " + purchase);
mPurchases.add(purchase);
}
/**
* Handle a result from querying of purchases and report an updated list to the listener
*/
private void onQueryPurchasesFinished(PurchasesResult result) {
// Have we been disposed of in the meantime? If so, or bad result code, then quit
if (mBillingClient == null || result.getResponseCode() != BillingResponseCode.OK) {
LOG.warn("Billing client was null or result code (" + result.getResponseCode()
+ ") was bad - quitting");
return;
}
LOG.debug("Query inventory was successful.");
// Update the UI and purchases inventory with new list of purchases
mPurchases.clear();
onPurchasesUpdated(result.getBillingResult(), result.getPurchasesList());
}
/**
* Checks if subscriptions are supported for current client
* <p>Note: This method does not automatically retry for RESULT_SERVICE_DISCONNECTED.
* It is only used in unit tests and after queryPurchases execution, which already has
* a retry-mechanism implemented.
* </p>
*/
public boolean areSubscriptionsSupported() {
int responseCode = mBillingClient.isFeatureSupported(FeatureType.SUBSCRIPTIONS).getResponseCode();
if (responseCode != BillingResponseCode.OK) {
LOG.warn("areSubscriptionsSupported() got an error response: " + responseCode);
}
return responseCode == BillingResponseCode.OK;
}
/**
* Query purchases across various use cases and deliver the result in a formalized way through
* a listener
*/
public void queryPurchases() {
Runnable queryToExecute = new Runnable() {
@Override
public void run() {
long time = System.currentTimeMillis();
PurchasesResult purchasesResult = mBillingClient.queryPurchases(SkuType.INAPP);
LOG.info("Querying purchases elapsed time: " + (System.currentTimeMillis() - time)
+ "ms");
// If there are subscriptions supported, we add subscription rows as well
if (areSubscriptionsSupported()) {
PurchasesResult subscriptionResult = mBillingClient.queryPurchases(SkuType.SUBS);
LOG.info("Querying purchases and subscriptions elapsed time: "
+ (System.currentTimeMillis() - time) + "ms");
LOG.info("Querying subscriptions result code: "
+ subscriptionResult.getResponseCode()
+ " res: " + subscriptionResult.getPurchasesList().size());
if (subscriptionResult.getResponseCode() == BillingResponseCode.OK) {
purchasesResult.getPurchasesList().addAll(
subscriptionResult.getPurchasesList());
} else {
LOG.error("Got an error response trying to query subscription purchases");
}
} else if (purchasesResult.getResponseCode() == BillingResponseCode.OK) {
LOG.info("Skipped subscription purchases query since they are not supported");
} else {
LOG.warn("queryPurchases() got an error response code: "
+ purchasesResult.getResponseCode());
}
onQueryPurchasesFinished(purchasesResult);
}
};
executeServiceRequest(queryToExecute);
}
public void startServiceConnection(final Runnable executeOnSuccess) {
mBillingClient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingSetupFinished(BillingResult billingResult) {
int billingResponseCode = billingResult.getResponseCode();
LOG.debug("Setup finished. Response code: " + billingResponseCode);
mIsServiceConnected = billingResponseCode == BillingResponseCode.OK;
mBillingClientResponseCode = billingResponseCode;
mBillingClientResponseMessage = billingResult.getDebugMessage();
mBillingUpdatesListener.onBillingClientSetupFinished();
if (mIsServiceConnected) {
if (executeOnSuccess != null) {
executeOnSuccess.run();
}
}
}
@Override
public void onBillingServiceDisconnected() {
mIsServiceConnected = false;
mBillingUpdatesListener.onBillingClientSetupFinished();
}
});
}
private void executeServiceRequest(Runnable runnable) {
if (mIsServiceConnected) {
runnable.run();
} else {
// If billing service was disconnected, we try to reconnect 1 time.
startServiceConnection(runnable);
}
}
private boolean verifyValidSignature(String signedData, String signature) {
return Security.verifyPurchase(mSignatureBase64, signedData, signature);
}
}

View file

@ -1,43 +0,0 @@
/* Copyright (c) 2012 Google Inc.
*
* 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 net.osmand.plus.inapp.util;
/**
* Exception thrown when something went wrong with in-app billing.
* An IabException has an associated IabResult (an error).
* To get the IAB result that caused this exception to be thrown,
* call {@link #getResult()}.
*/
public class IabException extends Exception {
IabResult mResult;
public IabException(IabResult r) {
this(r, null);
}
public IabException(int response, String message) {
this(new IabResult(response, message));
}
public IabException(IabResult r, Exception cause) {
super(r.getMessage(), cause);
mResult = r;
}
public IabException(int response, String message, Exception cause) {
this(new IabResult(response, message), cause);
}
/** Returns the IAB result (error) that this exception signals. */
public IabResult getResult() { return mResult; }
}

File diff suppressed because it is too large Load diff

View file

@ -1,45 +0,0 @@
/* Copyright (c) 2012 Google Inc.
*
* 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 net.osmand.plus.inapp.util;
/**
* Represents the result of an in-app billing operation.
* A result is composed of a response code (an integer) and possibly a
* message (String). You can get those by calling
* {@link #getResponse} and {@link #getMessage()}, respectively. You
* can also inquire whether a result is a success or a failure by
* calling {@link #isSuccess()} and {@link #isFailure()}.
*/
public class IabResult {
int mResponse;
String mMessage;
public IabResult(int response, String message) {
mResponse = response;
if (message == null || message.trim().length() == 0) {
mMessage = IabHelper.getResponseDesc(response);
}
else {
mMessage = message + " (response: " + IabHelper.getResponseDesc(response) + ")";
}
}
public int getResponse() { return mResponse; }
public String getMessage() { return mMessage; }
public boolean isSuccess() { return mResponse == IabHelper.BILLING_RESPONSE_RESULT_OK; }
public boolean isFailure() { return !isSuccess(); }
public String toString() { return "IabResult: " + getMessage(); }
}

View file

@ -1,91 +0,0 @@
/* Copyright (c) 2012 Google Inc.
*
* 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 net.osmand.plus.inapp.util;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Represents a block of information about in-app items.
* An Inventory is returned by such methods as {@link IabHelper#queryInventory}.
*/
public class Inventory {
Map<String,SkuDetails> mSkuMap = new HashMap<String,SkuDetails>();
Map<String,Purchase> mPurchaseMap = new HashMap<String,Purchase>();
Inventory() { }
/** Returns the listing details for an in-app product. */
public SkuDetails getSkuDetails(String sku) {
return mSkuMap.get(sku);
}
/** Returns purchase information for a given product, or null if there is no purchase. */
public Purchase getPurchase(String sku) {
return mPurchaseMap.get(sku);
}
/** Returns whether or not there exists a purchase of the given product. */
public boolean hasPurchase(String sku) {
return mPurchaseMap.containsKey(sku);
}
/** Return whether or not details about the given product are available. */
public boolean hasDetails(String sku) {
return mSkuMap.containsKey(sku);
}
/**
* Erase a purchase (locally) from the inventory, given its product ID. This just
* modifies the Inventory object locally and has no effect on the server! This is
* useful when you have an existing Inventory object which you know to be up to date,
* and you have just consumed an item successfully, which means that erasing its
* purchase data from the Inventory you already have is quicker than querying for
* a new Inventory.
*/
public void erasePurchase(String sku) {
if (mPurchaseMap.containsKey(sku)) mPurchaseMap.remove(sku);
}
/** Returns a list of all owned product IDs. */
public List<String> getAllOwnedSkus() {
return new ArrayList<String>(mPurchaseMap.keySet());
}
/** Returns a list of all owned product IDs of a given type */
public List<String> getAllOwnedSkus(String itemType) {
List<String> result = new ArrayList<String>();
for (Purchase p : mPurchaseMap.values()) {
if (p.getItemType().equals(itemType)) result.add(p.getSku());
}
return result;
}
/** Returns a list of all purchases. */
List<Purchase> getAllPurchases() {
return new ArrayList<Purchase>(mPurchaseMap.values());
}
void addSkuDetails(SkuDetails d) {
mSkuMap.put(d.getSku(), d);
}
void addPurchase(Purchase p) {
mPurchaseMap.put(p.getSku(), p);
}
}

View file

@ -1,63 +0,0 @@
/* Copyright (c) 2012 Google Inc.
*
* 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 net.osmand.plus.inapp.util;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Represents an in-app billing purchase.
*/
public class Purchase {
String mItemType; // ITEM_TYPE_INAPP or ITEM_TYPE_SUBS
String mOrderId;
String mPackageName;
String mSku;
long mPurchaseTime;
int mPurchaseState;
String mDeveloperPayload;
String mToken;
String mOriginalJson;
String mSignature;
public Purchase(String itemType, String jsonPurchaseInfo, String signature) throws JSONException {
mItemType = itemType;
mOriginalJson = jsonPurchaseInfo;
JSONObject o = new JSONObject(mOriginalJson);
mOrderId = o.optString("orderId");
mPackageName = o.optString("packageName");
mSku = o.optString("productId");
mPurchaseTime = o.optLong("purchaseTime");
mPurchaseState = o.optInt("purchaseState");
mDeveloperPayload = o.optString("developerPayload");
mToken = o.optString("token", o.optString("purchaseToken"));
mSignature = signature;
}
public String getItemType() { return mItemType; }
public String getOrderId() { return mOrderId; }
public String getPackageName() { return mPackageName; }
public String getSku() { return mSku; }
public long getPurchaseTime() { return mPurchaseTime; }
public int getPurchaseState() { return mPurchaseState; }
public String getDeveloperPayload() { return mDeveloperPayload; }
public String getToken() { return mToken; }
public String getOriginalJson() { return mOriginalJson; }
public String getSignature() { return mSignature; }
@Override
public String toString() { return "PurchaseInfo(type:" + mItemType + "):" + mOriginalJson; }
}

View file

@ -18,10 +18,6 @@ package net.osmand.plus.inapp.util;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.KeyFactory; import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;

View file

@ -1,73 +0,0 @@
/* Copyright (c) 2012 Google Inc.
*
* 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 net.osmand.plus.inapp.util;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Represents an in-app product's listing details.
*/
public class SkuDetails {
String mItemType;
String mSku;
String mType;
String mPrice;
long mPriceAmountMicros;
String mPriceCurrencyCode;
String mSubscriptionPeriod;
String mTitle;
String mDescription;
String mJson;
public SkuDetails(String jsonSkuDetails) throws JSONException {
this(IabHelper.ITEM_TYPE_INAPP, jsonSkuDetails);
}
public SkuDetails(String itemType, String jsonSkuDetails) throws JSONException {
mItemType = itemType;
mJson = jsonSkuDetails;
JSONObject o = new JSONObject(mJson);
mSku = o.optString("productId");
mType = o.optString("type");
mPrice = o.optString("price");
mPriceAmountMicros = o.optLong("price_amount_micros");
mPriceCurrencyCode = o.optString("price_currency_code");
mSubscriptionPeriod = o.optString("subscriptionPeriod");
mTitle = o.optString("title");
mDescription = o.optString("description");
}
public String getSku() { return mSku; }
public String getType() { return mType; }
public String getPrice() { return mPrice; }
public long getPriceAmountMicros() {
return mPriceAmountMicros;
}
public String getPriceCurrencyCode() {
return mPriceCurrencyCode;
}
public String getSubscriptionPeriod() {
return mSubscriptionPeriod;
}
public String getTitle() { return mTitle; }
public String getDescription() { return mDescription; }
@Override
public String toString() {
return "SkuDetails:" + mJson;
}
}

View file

@ -88,6 +88,7 @@ public class MapContextMenu extends MenuTitleController implements StateChangedL
private LatLon mapCenter; private LatLon mapCenter;
private int mapPosition = 0; private int mapPosition = 0;
private boolean centerMarker; private boolean centerMarker;
private boolean zoomOutOnly;
private int mapZoom; private int mapZoom;
private boolean inLocationUpdate = false; private boolean inLocationUpdate = false;
@ -286,6 +287,14 @@ public class MapContextMenu extends MenuTitleController implements StateChangedL
this.centerMarker = centerMarker; this.centerMarker = centerMarker;
} }
public boolean isZoomOutOnly() {
return zoomOutOnly;
}
public void setZoomOutOnly(boolean zoomOutOnly) {
this.zoomOutOnly = zoomOutOnly;
}
public int getMapZoom() { public int getMapZoom() {
return mapZoom; return mapZoom;
} }

View file

@ -1698,7 +1698,7 @@ public class MapContextMenuFragment extends BaseOsmAndFragment implements Downlo
public void centerMarkerLocation() { public void centerMarkerLocation() {
centered = true; centered = true;
showOnMap(menu.getLatLon(), true, true, false, getZoom()); showOnMap(menu.getLatLon(), true, false, getZoom());
} }
private int getZoom() { private int getZoom() {
@ -1722,8 +1722,8 @@ public class MapContextMenuFragment extends BaseOsmAndFragment implements Downlo
cp.setCenterLocation(0.5f, map.getMapPosition() == OsmandSettings.BOTTOM_CONSTANT ? 0.15f : 0.5f); cp.setCenterLocation(0.5f, map.getMapPosition() == OsmandSettings.BOTTOM_CONSTANT ? 0.15f : 0.5f);
cp.setLatLonCenter(flat, flon); cp.setLatLonCenter(flat, flon);
cp.setZoom(zoom); cp.setZoom(zoom);
flat = cp.getLatFromPixel(cp.getPixWidth() / 2, cp.getPixHeight() / 2); flat = cp.getLatFromPixel(cp.getPixWidth() / 2f, cp.getPixHeight() / 2f);
flon = cp.getLonFromPixel(cp.getPixWidth() / 2, cp.getPixHeight() / 2); flon = cp.getLonFromPixel(cp.getPixWidth() / 2f, cp.getPixHeight() / 2f);
if (updateOrigXY) { if (updateOrigXY) {
origMarkerX = cp.getCenterPixelX(); origMarkerX = cp.getCenterPixelX();
@ -1732,21 +1732,22 @@ public class MapContextMenuFragment extends BaseOsmAndFragment implements Downlo
return new LatLon(flat, flon); return new LatLon(flat, flon);
} }
private void showOnMap(LatLon latLon, boolean updateCoords, boolean needMove, boolean alreadyAdjusted, int zoom) { private void showOnMap(LatLon latLon, boolean updateCoords, boolean alreadyAdjusted, int zoom) {
AnimateDraggingMapThread thread = map.getAnimatedDraggingThread(); AnimateDraggingMapThread thread = map.getAnimatedDraggingThread();
int calculatedZoom = menu.isZoomOutOnly() ? thread.calculateMoveZoom(null, latLon.getLatitude(), latLon.getLongitude(), null) : 0;
if (calculatedZoom > 0) {
zoom = Math.min(zoom, calculatedZoom);
}
menu.setZoomOutOnly(false);
LatLon calcLatLon = calculateCenterLatLon(latLon, zoom, updateCoords); LatLon calcLatLon = calculateCenterLatLon(latLon, zoom, updateCoords);
if (updateCoords) { if (updateCoords) {
mapCenter = calcLatLon; mapCenter = calcLatLon;
menu.setMapCenter(mapCenter); menu.setMapCenter(mapCenter);
} }
if (!alreadyAdjusted) { if (!alreadyAdjusted) {
calcLatLon = getAdjustedMarkerLocation(getPosY(), calcLatLon, true, zoom); calcLatLon = getAdjustedMarkerLocation(getPosY(), calcLatLon, true, zoom);
} }
thread.startMoving(calcLatLon.getLatitude(), calcLatLon.getLongitude(), zoom, true);
if (needMove) {
thread.startMoving(calcLatLon.getLatitude(), calcLatLon.getLongitude(), zoom, true);
}
} }
private void setAddressLocation() { private void setAddressLocation() {
@ -2021,7 +2022,7 @@ public class MapContextMenuFragment extends BaseOsmAndFragment implements Downlo
} }
if (animated) { if (animated) {
showOnMap(latlon, false, true, true, zoom); showOnMap(latlon, false, true, zoom);
} else { } else {
if (dZoom != 0) { if (dZoom != 0) {
map.setIntZoom(zoom); map.setIntZoom(zoom);

View file

@ -60,7 +60,7 @@ public class TransportRouteController extends MenuController {
public void buttonPressed() { public void buttonPressed() {
final int previousStop = getPreviousStop(); final int previousStop = getPreviousStop();
if (previousStop != -1) { if (previousStop != -1) {
showTransportStop(getTransportRoute().route.getForwardStops().get(previousStop)); showTransportStop(getTransportRoute().route.getForwardStops().get(previousStop), true);
} }
} }
}; };
@ -72,7 +72,7 @@ public class TransportRouteController extends MenuController {
public void buttonPressed() { public void buttonPressed() {
final int nextStop = getNextStop(); final int nextStop = getNextStop();
if (nextStop != -1) { if (nextStop != -1) {
showTransportStop(getTransportRoute().route.getForwardStops().get(nextStop)); showTransportStop(getTransportRoute().route.getForwardStops().get(nextStop), true);
} }
} }
}; };
@ -180,7 +180,7 @@ public class TransportRouteController extends MenuController {
} }
} }
private void showTransportStop(TransportStop stop) { private void showTransportStop(TransportStop stop, boolean movingBetweenStops) {
MapActivity mapActivity = getMapActivity(); MapActivity mapActivity = getMapActivity();
if (mapActivity != null && mapContextMenu != null) { if (mapActivity != null && mapContextMenu != null) {
transportRoute.stop = stop; transportRoute.stop = stop;
@ -198,6 +198,7 @@ public class TransportRouteController extends MenuController {
mapContextMenu.setMapPosition(getMapActivity().getMapView().getMapPosition()); mapContextMenu.setMapPosition(getMapActivity().getMapView().getMapPosition());
} }
mapContextMenu.setCenterMarker(true); mapContextMenu.setCenterMarker(true);
mapContextMenu.setZoomOutOnly(movingBetweenStops);
mapContextMenu.setMapZoom(15); mapContextMenu.setMapZoom(15);
mapContextMenu.showOrUpdate(stopLocation, pd, transportRoute); mapContextMenu.showOrUpdate(stopLocation, pd, transportRoute);
} }
@ -286,16 +287,7 @@ public class TransportRouteController extends MenuController {
@Override @Override
public void onClick(View arg0) { public void onClick(View arg0) {
showTransportStop(stop); showTransportStop(stop, false);
/*
PointDescription pd = new PointDescription(PointDescription.POINT_TYPE_TRANSPORT_STOP,
getMapActivity().getString(R.string.transport_Stop), name);
LatLon stopLocation = stop.getLocation();
getMapActivity().getMyApplication().getSettings()
.setMapLocationToShow(stopLocation.getLatitude(), stopLocation.getLongitude(),
15, pd, false, transportRoute);
MapActivity.launchMapActivityMoveToTop(getMapActivity());
*/
} }
}); });
} }

View file

@ -161,7 +161,7 @@ public class TransportStopController extends MenuController {
private void addTransportStopRoutes(OsmandApplication app, List<TransportStop> stops, List<TransportStopRoute> routes, boolean useEnglishNames) { private void addTransportStopRoutes(OsmandApplication app, List<TransportStop> stops, List<TransportStopRoute> routes, boolean useEnglishNames) {
for (TransportStop tstop : stops) { for (TransportStop tstop : stops) {
if (tstop.hasReferencesToRoutes()) { if (tstop.hasReferencesToRoutesMap()) {
addRoutes(app, routes, useEnglishNames, tstop, transportStop, (int) MapUtils.getDistance(tstop.getLocation(), transportStop.getLocation())); addRoutes(app, routes, useEnglishNames, tstop, transportStop, (int) MapUtils.getDistance(tstop.getLocation(), transportStop.getLocation()));
} }
} }
@ -262,15 +262,19 @@ public class TransportStopController extends MenuController {
private static void processTransportStopAggregated(OsmandApplication app, TransportStop transportStop) { private static void processTransportStopAggregated(OsmandApplication app, TransportStop transportStop) {
TransportStopAggregated stopAggregated = new TransportStopAggregated(); TransportStopAggregated stopAggregated = new TransportStopAggregated();
transportStop.setTransportStopAggregated(stopAggregated); transportStop.setTransportStopAggregated(stopAggregated);
stopAggregated.addLocalTransportStop(transportStop); TransportStop localStop = null;
LatLon loc = transportStop.getLocation(); LatLon loc = transportStop.getLocation();
List<TransportStop> transportStops = findTransportStopsAt(app, loc.getLatitude(), loc.getLongitude(), SHOW_STOPS_RADIUS_METERS); List<TransportStop> transportStops = findTransportStopsAt(app, loc.getLatitude(), loc.getLongitude(), SHOW_STOPS_RADIUS_METERS);
if (transportStops != null) { if (transportStops != null) {
for (TransportStop stop : transportStops) { for (TransportStop stop : transportStops) {
stopAggregated.addNearbyTransportStop(stop); if (localStop == null && transportStop.equals(stop)) {
localStop = stop;
} else {
stopAggregated.addNearbyTransportStop(stop);
}
} }
} }
stopAggregated.addLocalTransportStop(localStop == null ? transportStop : localStop);
} }
private static TransportStopAggregated processTransportStopsForAmenity(List<TransportStop> transportStops, Amenity amenity) { private static TransportStopAggregated processTransportStopsForAmenity(List<TransportStop> transportStops, Amenity amenity) {

View file

@ -26,7 +26,6 @@ public abstract class OsmandNotification {
public final static int WEAR_ERROR_NOTIFICATION_SERVICE_ID = 1007; public final static int WEAR_ERROR_NOTIFICATION_SERVICE_ID = 1007;
public final static int WEAR_DOWNLOAD_NOTIFICATION_SERVICE_ID = 1008; public final static int WEAR_DOWNLOAD_NOTIFICATION_SERVICE_ID = 1008;
protected OsmandApplication app; protected OsmandApplication app;
protected boolean ongoing = true; protected boolean ongoing = true;
protected int color; protected int color;
@ -35,6 +34,8 @@ public abstract class OsmandNotification {
private String groupName; private String groupName;
private Notification currentNotification;
public enum NotificationType { public enum NotificationType {
NAVIGATION, NAVIGATION,
GPX, GPX,
@ -125,7 +126,7 @@ public abstract class OsmandNotification {
if (isEnabled()) { if (isEnabled()) {
Builder notificationBuilder = buildNotification(false); Builder notificationBuilder = buildNotification(false);
if (notificationBuilder != null) { if (notificationBuilder != null) {
Notification notification = notificationBuilder.build(); Notification notification = getNotification(notificationBuilder);
setupNotification(notification); setupNotification(notification);
notificationManager.notify(top ? TOP_NOTIFICATION_SERVICE_ID : getOsmandNotificationId(), notification); notificationManager.notify(top ? TOP_NOTIFICATION_SERVICE_ID : getOsmandNotificationId(), notification);
notifyWearable(notificationManager); notifyWearable(notificationManager);
@ -140,10 +141,10 @@ public abstract class OsmandNotification {
if (isEnabled()) { if (isEnabled()) {
Builder notificationBuilder = buildNotification(false); Builder notificationBuilder = buildNotification(false);
if (notificationBuilder != null) { if (notificationBuilder != null) {
Notification notification = notificationBuilder.build(); Notification notification = getNotification(notificationBuilder);
setupNotification(notification); setupNotification(notification);
if (top) { if (top) {
notificationManager.cancel(getOsmandNotificationId()); //notificationManager.cancel(getOsmandNotificationId());
notificationManager.notify(TOP_NOTIFICATION_SERVICE_ID, notification); notificationManager.notify(TOP_NOTIFICATION_SERVICE_ID, notification);
} else { } else {
notificationManager.notify(getOsmandNotificationId(), notification); notificationManager.notify(getOsmandNotificationId(), notification);
@ -159,7 +160,17 @@ public abstract class OsmandNotification {
return false; return false;
} }
private Notification getNotification(Builder notificationBuilder) {
Notification notification = currentNotification;
if (notification == null) {
notification = notificationBuilder.build();
currentNotification = notification;
}
return notification;
}
public void removeNotification() { public void removeNotification() {
currentNotification = null;
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(app); NotificationManagerCompat notificationManager = NotificationManagerCompat.from(app);
notificationManager.cancel(getOsmandNotificationId()); notificationManager.cancel(getOsmandNotificationId());
notificationManager.cancel(getOsmandWearableNotificationId()); notificationManager.cancel(getOsmandWearableNotificationId());

View file

@ -99,6 +99,7 @@ public class ChooseRouteFragment extends BaseOsmAndFragment implements ContextMe
private boolean wasDrawerDisabled; private boolean wasDrawerDisabled;
private int currentMenuState; private int currentMenuState;
private int routesCount; private int routesCount;
private boolean paused;
private boolean publicTransportMode; private boolean publicTransportMode;
private boolean needAdjustMap; private boolean needAdjustMap;
@ -209,6 +210,7 @@ public class ChooseRouteFragment extends BaseOsmAndFragment implements ContextMe
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
paused = false;
MapActivity mapActivity = getMapActivity(); MapActivity mapActivity = getMapActivity();
if (mapActivity != null) { if (mapActivity != null) {
mapActivity.getMapLayers().getMapControlsLayer().showMapControlsIfHidden(); mapActivity.getMapLayers().getMapControlsLayer().showMapControlsIfHidden();
@ -223,6 +225,7 @@ public class ChooseRouteFragment extends BaseOsmAndFragment implements ContextMe
public void onPause() { public void onPause() {
super.onPause(); super.onPause();
paused = true;
MapRouteInfoMenu.chooseRoutesVisible = false; MapRouteInfoMenu.chooseRoutesVisible = false;
MapActivity mapActivity = getMapActivity(); MapActivity mapActivity = getMapActivity();
if (mapActivity != null) { if (mapActivity != null) {
@ -262,6 +265,10 @@ public class ChooseRouteFragment extends BaseOsmAndFragment implements ContextMe
return getIcon(id, nightMode ? R.color.icon_color_default_dark : R.color.icon_color_default_light); return getIcon(id, nightMode ? R.color.icon_color_default_dark : R.color.icon_color_default_light);
} }
public boolean isPaused() {
return paused;
}
public void analyseOnMap(LatLon location, GpxDisplayItem gpxItem) { public void analyseOnMap(LatLon location, GpxDisplayItem gpxItem) {
OsmandApplication app = requireMyApplication(); OsmandApplication app = requireMyApplication();
final OsmandSettings settings = app.getSettings(); final OsmandSettings settings = app.getSettings();

View file

@ -543,6 +543,7 @@ public class MapRouteInfoMenu implements IRouteInformationListener, CardListener
card.setRouteButtonsVisible(false); card.setRouteButtonsVisible(false);
card.setShowBottomShadow(false); card.setShowBottomShadow(false);
card.setShowTopShadow(false); card.setShowTopShadow(false);
card.setListener(this);
menuCards.add(card); menuCards.add(card);
hasTopCard = true; hasTopCard = true;
} }
@ -668,6 +669,10 @@ public class MapRouteInfoMenu implements IRouteInformationListener, CardListener
if (card instanceof SimpleRouteCard) { if (card instanceof SimpleRouteCard) {
hide(); hide();
ChooseRouteFragment.showInstance(mapActivity.getSupportFragmentManager(), 0, MenuState.FULL_SCREEN); ChooseRouteFragment.showInstance(mapActivity.getSupportFragmentManager(), 0, MenuState.FULL_SCREEN);
} else if (card instanceof PublicTransportCard) {
hide();
ChooseRouteFragment.showInstance(mapActivity.getSupportFragmentManager(),
((PublicTransportCard) card).getRouteId(), MenuState.FULL_SCREEN);
} }
} }
} }
@ -1885,6 +1890,7 @@ public class MapRouteInfoMenu implements IRouteInformationListener, CardListener
return false; return false;
} }
@Nullable
public WeakReference<MapRouteInfoMenuFragment> findMenuFragment() { public WeakReference<MapRouteInfoMenuFragment> findMenuFragment() {
MapActivity mapActivity = getMapActivity(); MapActivity mapActivity = getMapActivity();
if (mapActivity != null) { if (mapActivity != null) {
@ -1896,6 +1902,18 @@ public class MapRouteInfoMenu implements IRouteInformationListener, CardListener
return null; return null;
} }
@Nullable
public WeakReference<ChooseRouteFragment> findChooseRouteFragment() {
MapActivity mapActivity = getMapActivity();
if (mapActivity != null) {
Fragment fragment = mapActivity.getSupportFragmentManager().findFragmentByTag(ChooseRouteFragment.TAG);
if (fragment instanceof ChooseRouteFragment && !((ChooseRouteFragment) fragment).isPaused()) {
return new WeakReference<>((ChooseRouteFragment) fragment);
}
}
return null;
}
public static void showLocationOnMap(MapActivity mapActivity, double latitude, double longitude) { public static void showLocationOnMap(MapActivity mapActivity, double latitude, double longitude) {
RotatedTileBox tb = mapActivity.getMapView().getCurrentRotatedTileBox().copy(); RotatedTileBox tb = mapActivity.getMapView().getCurrentRotatedTileBox().copy();
int tileBoxWidthPx = 0; int tileBoxWidthPx = 0;

View file

@ -106,11 +106,33 @@ public class PublicTransportCard extends BaseCard {
@Override @Override
protected void updateContent() { protected void updateContent() {
List<TransportRouteResultSegment> segments = routeResult.getSegments(); List<TransportRouteResultSegment> segments = routeResult.getSegments();
createRouteBadges(segments); boolean badgesRowClickable = !routeInfoVisible && !routeButtonsVisible;
createRouteBadges(segments, badgesRowClickable);
view.findViewById(R.id.route_info).setVisibility(routeInfoVisible ? View.VISIBLE : View.GONE); view.findViewById(R.id.route_info).setVisibility(routeInfoVisible ? View.VISIBLE : View.GONE);
view.findViewById(R.id.route_buttons).setVisibility(routeButtonsVisible ? View.VISIBLE : View.GONE); view.findViewById(R.id.route_buttons).setVisibility(routeButtonsVisible ? View.VISIBLE : View.GONE);
view.findViewById(R.id.badges_padding).setVisibility(!routeInfoVisible && !routeButtonsVisible ? View.VISIBLE : View.GONE); if (badgesRowClickable) {
view.findViewById(R.id.badges_padding).setVisibility(View.VISIBLE);
view.setBackgroundResource(AndroidUtils.resolveAttribute(view.getContext(), R.attr.card_and_list_background_basic));
View info = view.findViewById(R.id.routes_info_container);
int paddingLeft = info.getPaddingLeft();
int paddingTop = info.getPaddingTop();
int paddingRight = info.getPaddingRight();
int paddingBottom = info.getPaddingBottom();
info.setBackgroundResource(AndroidUtils.resolveAttribute(view.getContext(), android.R.attr.selectableItemBackground));
info.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
info.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
CardListener listener = getListener();
if (listener != null) {
listener.onCardPressed(PublicTransportCard.this);
}
}
});
} else {
view.findViewById(R.id.badges_padding).setVisibility(View.GONE);
}
TextView fromLine = (TextView) view.findViewById(R.id.from_line); TextView fromLine = (TextView) view.findViewById(R.id.from_line);
TextView wayLine = (TextView) view.findViewById(R.id.way_line); TextView wayLine = (TextView) view.findViewById(R.id.way_line);
@ -256,7 +278,7 @@ public class PublicTransportCard extends BaseCard {
return secondLineDesc; return secondLineDesc;
} }
private void createRouteBadges(List<TransportRouteResultSegment> segments) { private void createRouteBadges(List<TransportRouteResultSegment> segments, boolean badgesRowClickable) {
int itemsSpacing = AndroidUtils.dpToPx(app, 6); int itemsSpacing = AndroidUtils.dpToPx(app, 6);
FlowLayout routesBadges = (FlowLayout) view.findViewById(R.id.routes_badges); FlowLayout routesBadges = (FlowLayout) view.findViewById(R.id.routes_badges);
routesBadges.removeAllViews(); routesBadges.removeAllViews();
@ -270,7 +292,7 @@ public class PublicTransportCard extends BaseCard {
if (walkingSegment != null) { if (walkingSegment != null) {
double walkTime = walkingSegment.getRoutingTime(); double walkTime = walkingSegment.getRoutingTime();
if (walkTime > MIN_WALK_TIME) { if (walkTime > MIN_WALK_TIME) {
routesBadges.addView(createWalkRouteBadge(walkingSegment), new FlowLayout.LayoutParams(itemsSpacing, itemsSpacing)); routesBadges.addView(createWalkRouteBadge(walkingSegment, badgesRowClickable), new FlowLayout.LayoutParams(itemsSpacing, itemsSpacing));
routesBadges.addView(createArrow(), new FlowLayout.LayoutParams(itemsSpacing, itemsSpacing)); routesBadges.addView(createArrow(), new FlowLayout.LayoutParams(itemsSpacing, itemsSpacing));
} }
} else if (s.walkDist > 0) { } else if (s.walkDist > 0) {
@ -283,11 +305,11 @@ public class PublicTransportCard extends BaseCard {
} else { } else {
start = this.startLocation; start = this.startLocation;
} }
routesBadges.addView(createWalkRouteBadge(walkTime, start, end), new FlowLayout.LayoutParams(itemsSpacing, itemsSpacing)); routesBadges.addView(createWalkRouteBadge(walkTime, start, end, badgesRowClickable), new FlowLayout.LayoutParams(itemsSpacing, itemsSpacing));
routesBadges.addView(createArrow(), new FlowLayout.LayoutParams(itemsSpacing, itemsSpacing)); routesBadges.addView(createArrow(), new FlowLayout.LayoutParams(itemsSpacing, itemsSpacing));
} }
} }
routesBadges.addView(createRouteBadge(s), new FlowLayout.LayoutParams(itemsSpacing, itemsSpacing)); routesBadges.addView(createRouteBadge(s, badgesRowClickable), new FlowLayout.LayoutParams(itemsSpacing, itemsSpacing));
if (iterator.hasNext()) { if (iterator.hasNext()) {
routesBadges.addView(createArrow(), new FlowLayout.LayoutParams(itemsSpacing, itemsSpacing)); routesBadges.addView(createArrow(), new FlowLayout.LayoutParams(itemsSpacing, itemsSpacing));
} else { } else {
@ -296,7 +318,7 @@ public class PublicTransportCard extends BaseCard {
double walkTime = walkingSegment.getRoutingTime(); double walkTime = walkingSegment.getRoutingTime();
if (walkTime > MIN_WALK_TIME) { if (walkTime > MIN_WALK_TIME) {
routesBadges.addView(createArrow(), new FlowLayout.LayoutParams(itemsSpacing, itemsSpacing)); routesBadges.addView(createArrow(), new FlowLayout.LayoutParams(itemsSpacing, itemsSpacing));
routesBadges.addView(createWalkRouteBadge(walkingSegment), new FlowLayout.LayoutParams(itemsSpacing, itemsSpacing)); routesBadges.addView(createWalkRouteBadge(walkingSegment, badgesRowClickable), new FlowLayout.LayoutParams(itemsSpacing, itemsSpacing));
} }
} else { } else {
double finishWalkDist = routeResult.getFinishWalkDist(); double finishWalkDist = routeResult.getFinishWalkDist();
@ -306,7 +328,7 @@ public class PublicTransportCard extends BaseCard {
LatLon start = s.getEnd().getLocation(); LatLon start = s.getEnd().getLocation();
LatLon end = this.endLocation; LatLon end = this.endLocation;
routesBadges.addView(createArrow(), new FlowLayout.LayoutParams(itemsSpacing, itemsSpacing)); routesBadges.addView(createArrow(), new FlowLayout.LayoutParams(itemsSpacing, itemsSpacing));
routesBadges.addView(createWalkRouteBadge(walkTime, start, end)); routesBadges.addView(createWalkRouteBadge(walkTime, start, end, badgesRowClickable));
} }
} }
} }
@ -315,7 +337,7 @@ public class PublicTransportCard extends BaseCard {
} }
} }
private View createRouteBadge(@NonNull final TransportRouteResultSegment segment) { private View createRouteBadge(@NonNull final TransportRouteResultSegment segment, boolean badgesRowClickable) {
LinearLayout bageView = (LinearLayout) mapActivity.getLayoutInflater().inflate(R.layout.transport_stop_route_item_with_icon, null, false); LinearLayout bageView = (LinearLayout) mapActivity.getLayoutInflater().inflate(R.layout.transport_stop_route_item_with_icon, null, false);
TransportRoute transportRoute = segment.route; TransportRoute transportRoute = segment.route;
TransportStopRoute transportStopRoute = TransportStopRoute.getTransportStopRoute(transportRoute, segment.getStart()); TransportStopRoute transportStopRoute = TransportStopRoute.getTransportStopRoute(transportRoute, segment.getStart());
@ -333,20 +355,22 @@ public class PublicTransportCard extends BaseCard {
gradientDrawableBg.setColor(bgColor); gradientDrawableBg.setColor(bgColor);
transportStopRouteTextView.setTextColor(UiUtilities.getContrastColor(app, bgColor, true)); transportStopRouteTextView.setTextColor(UiUtilities.getContrastColor(app, bgColor, true));
if (transportCardListener != null) { if (transportCardListener != null && !badgesRowClickable) {
bageView.findViewById(R.id.button).setOnClickListener(new View.OnClickListener() { bageView.findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
transportCardListener.onPublicTransportCardBadgePressed(PublicTransportCard.this, segment); transportCardListener.onPublicTransportCardBadgePressed(PublicTransportCard.this, segment);
} }
}); });
} else {
bageView.findViewById(R.id.button).setBackgroundDrawable(null);
} }
return bageView; return bageView;
} }
private View createWalkRouteBadge(@NonNull final RouteCalculationResult result) { private View createWalkRouteBadge(@NonNull final RouteCalculationResult result, boolean badgesRowClickable) {
View v = createWalkRouteBadge(result.getRoutingTime(), null, null); View v = createWalkRouteBadge(result.getRoutingTime(), null, null, badgesRowClickable);
if (transportCardListener != null) { if (transportCardListener != null && !badgesRowClickable) {
v.setOnClickListener(new View.OnClickListener() { v.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -357,7 +381,7 @@ public class PublicTransportCard extends BaseCard {
return v; return v;
} }
private View createWalkRouteBadge(double walkTime, @Nullable final LatLon start, @Nullable final LatLon end) { private View createWalkRouteBadge(double walkTime, @Nullable final LatLon start, @Nullable final LatLon end, boolean badgesRowClickable) {
LinearLayout bageView = (LinearLayout) getMapActivity().getLayoutInflater().inflate(R.layout.transport_stop_route_item_with_icon, null, false); LinearLayout bageView = (LinearLayout) getMapActivity().getLayoutInflater().inflate(R.layout.transport_stop_route_item_with_icon, null, false);
int activeColor = getActiveColor(); int activeColor = getActiveColor();
@ -372,13 +396,15 @@ public class PublicTransportCard extends BaseCard {
AndroidUtils.setBackground(app, bageView, nightMode, R.drawable.btn_border_active_light, R.drawable.btn_border_active_dark); AndroidUtils.setBackground(app, bageView, nightMode, R.drawable.btn_border_active_light, R.drawable.btn_border_active_dark);
if (transportCardListener != null && start != null && end != null) { if (transportCardListener != null && start != null && end != null && !badgesRowClickable) {
bageView.findViewById(R.id.button).setOnClickListener(new View.OnClickListener() { bageView.findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
transportCardListener.onPublicTransportCardBadgePressed(PublicTransportCard.this, start, end); transportCardListener.onPublicTransportCardBadgePressed(PublicTransportCard.this, start, end);
} }
}); });
} else {
bageView.findViewById(R.id.button).setBackgroundDrawable(null);
} }
return bageView; return bageView;
} }

View file

@ -180,6 +180,7 @@ public class AnimateDraggingMapThread {
public void startMoving(final double finalLat, final double finalLon, final int endZoom, public void startMoving(final double finalLat, final double finalLon, final int endZoom,
final boolean notifyListener, final Runnable finishAnimationCallback) { final boolean notifyListener, final Runnable finishAnimationCallback) {
boolean wasAnimating = isAnimating();
stopAnimatingSync(); stopAnimatingSync();
final RotatedTileBox rb = tileView.getCurrentRotatedTileBox().copy(); final RotatedTileBox rb = tileView.getCurrentRotatedTileBox().copy();
@ -187,22 +188,12 @@ public class AnimateDraggingMapThread {
double startLon = rb.getLongitude(); double startLon = rb.getLongitude();
final int startZoom = rb.getZoom(); final int startZoom = rb.getZoom();
final double startZoomFP = rb.getZoomFloatPart(); final double startZoomFP = rb.getZoomFloatPart();
float[] mSt = new float[2];
boolean skipAnimation = false; final int moveZoom = calculateMoveZoom(rb, finalLat, finalLon, mSt);
float mStX = rb.getPixXFromLatLon(startLat, startLon) - rb.getPixXFromLatLon(finalLat, finalLon); boolean skipAnimation = moveZoom == 0;
float mStY = rb.getPixYFromLatLon(startLat, startLon) - rb.getPixYFromLatLon(finalLat, finalLon);
while (Math.abs(mStX) + Math.abs(mStY) > 1200) {
rb.setZoom(rb.getZoom() - 1);
if (rb.getZoom() <= 4) {
skipAnimation = true;
}
mStX = rb.getPixXFromLatLon(startLat, startLon) - rb.getPixXFromLatLon(finalLat, finalLon);
mStY = rb.getPixYFromLatLon(startLat, startLon) - rb.getPixYFromLatLon(finalLat, finalLon);
}
final int moveZoom = rb.getZoom();
// check if animation needed // check if animation needed
skipAnimation = skipAnimation || (Math.abs(moveZoom - startZoom) >= 3 || Math.abs(endZoom - moveZoom) > 3); skipAnimation = skipAnimation || (Math.abs(moveZoom - startZoom) >= 3 || Math.abs(endZoom - moveZoom) > 3);
if (skipAnimation) { if (skipAnimation || wasAnimating) {
tileView.setLatLonAnimate(finalLat, finalLon, notifyListener); tileView.setLatLonAnimate(finalLat, finalLon, notifyListener);
tileView.setFractionalZoom(endZoom, 0, notifyListener); tileView.setFractionalZoom(endZoom, 0, notifyListener);
if (finishAnimationCallback != null) { if (finishAnimationCallback != null) {
@ -219,7 +210,7 @@ public class AnimateDraggingMapThread {
final float mMoveY = rb.getPixYFromLatLon(startLat, startLon) - rb.getPixYFromLatLon(finalLat, finalLon); final float mMoveY = rb.getPixYFromLatLon(startLat, startLon) - rb.getPixYFromLatLon(finalLat, finalLon);
final boolean doNotUseAnimations = tileView.getSettings().DO_NOT_USE_ANIMATIONS.get(); final boolean doNotUseAnimations = tileView.getSettings().DO_NOT_USE_ANIMATIONS.get();
final float animationTime = doNotUseAnimations ? 1 : Math.max(450, (Math.abs(mStX) + Math.abs(mStY)) / 1200f * MOVE_MOVE_ANIMATION_TIME); final float animationTime = doNotUseAnimations ? 1 : Math.max(450, (Math.abs(mSt[0]) + Math.abs(mSt[1])) / 1200f * MOVE_MOVE_ANIMATION_TIME);
startThreadAnimating(new Runnable() { startThreadAnimating(new Runnable() {
@ -248,6 +239,30 @@ public class AnimateDraggingMapThread {
}); });
} }
public int calculateMoveZoom(RotatedTileBox rb, final double finalLat, final double finalLon, float[] mSt) {
if (rb == null) {
rb = tileView.getCurrentRotatedTileBox().copy();
}
double startLat = rb.getLatitude();
double startLon = rb.getLongitude();
boolean skipAnimation = false;
if (mSt == null) {
mSt = new float[2];
}
mSt[0] = rb.getPixXFromLatLon(startLat, startLon) - rb.getPixXFromLatLon(finalLat, finalLon);
mSt[1] = rb.getPixYFromLatLon(startLat, startLon) - rb.getPixYFromLatLon(finalLat, finalLon);
while (Math.abs(mSt[0]) + Math.abs(mSt[1]) > 1200) {
rb.setZoom(rb.getZoom() - 1);
if (rb.getZoom() <= 4) {
skipAnimation = true;
}
mSt[0] = rb.getPixXFromLatLon(startLat, startLon) - rb.getPixXFromLatLon(finalLat, finalLon);
mSt[1] = rb.getPixYFromLatLon(startLat, startLon) - rb.getPixYFromLatLon(finalLat, finalLon);
}
return skipAnimation ? 0 : rb.getZoom();
}
private void animatingRotateInThread(float rotate, float animationTime, boolean notify) { private void animatingRotateInThread(float rotate, float animationTime, boolean notify) {
AccelerateDecelerateInterpolator interpolator = new AccelerateDecelerateInterpolator(); AccelerateDecelerateInterpolator interpolator = new AccelerateDecelerateInterpolator();
float startRotate = tileView.getRotate(); float startRotate = tileView.getRotate();

View file

@ -59,12 +59,14 @@ import net.osmand.plus.mapcontextmenu.controllers.TransportStopController;
import net.osmand.plus.mapcontextmenu.other.MapMultiSelectionMenu; import net.osmand.plus.mapcontextmenu.other.MapMultiSelectionMenu;
import net.osmand.plus.render.MapRenderRepositories; import net.osmand.plus.render.MapRenderRepositories;
import net.osmand.plus.render.NativeOsmandLibrary; import net.osmand.plus.render.NativeOsmandLibrary;
import net.osmand.plus.routepreparationmenu.ChooseRouteFragment;
import net.osmand.plus.routepreparationmenu.MapRouteInfoMenu; import net.osmand.plus.routepreparationmenu.MapRouteInfoMenu;
import net.osmand.plus.views.AddGpxPointBottomSheetHelper.NewGpxPoint; import net.osmand.plus.views.AddGpxPointBottomSheetHelper.NewGpxPoint;
import net.osmand.plus.views.corenative.NativeCoreContext; import net.osmand.plus.views.corenative.NativeCoreContext;
import net.osmand.util.Algorithms; import net.osmand.util.Algorithms;
import net.osmand.util.MapUtils; import net.osmand.util.MapUtils;
import java.lang.ref.WeakReference;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -962,6 +964,16 @@ public class ContextMenuLayer extends OsmandMapLayer {
boolean processed = hideVisibleMenues(); boolean processed = hideVisibleMenues();
processed |= menu.onSingleTapOnMap(); processed |= menu.onSingleTapOnMap();
if (!processed && MapRouteInfoMenu.chooseRoutesVisible) {
WeakReference<ChooseRouteFragment> chooseRouteFragmentRef = activity.getMapRouteInfoMenu().findChooseRouteFragment();
if (chooseRouteFragmentRef != null) {
ChooseRouteFragment chooseRouteFragment = chooseRouteFragmentRef.get();
if (chooseRouteFragment != null) {
chooseRouteFragment.dismiss();
processed = true;
}
}
}
if (!processed && activity.getMyApplication().getSettings().MAP_EMPTY_STATE_ALLOWED.get()) { if (!processed && activity.getMyApplication().getSettings().MAP_EMPTY_STATE_ALLOWED.get()) {
activity.getMapLayers().getMapControlsLayer().switchMapControlsVisibility(true); activity.getMapLayers().getMapControlsLayer().switchMapControlsVisibility(true);
} }