diff --git a/android/src/app/organicmaps/Map.java b/android/src/app/organicmaps/Map.java index a707290ee6..bac0c0c03b 100644 --- a/android/src/app/organicmaps/Map.java +++ b/android/src/app/organicmaps/Map.java @@ -348,7 +348,7 @@ public final class Map private static native boolean nativeIsEngineCreated(); private static native void nativeSetRenderingInitializationFinishedListener( @Nullable MapRenderingListener listener); - private static native boolean nativeShowMapForUrl(String url); + public static native boolean nativeShowMapForUrl(String url); // Surface private static native boolean nativeAttachSurface(Surface surface); diff --git a/android/src/app/organicmaps/MwmActivity.java b/android/src/app/organicmaps/MwmActivity.java index 5cbe8ec89b..ff23790129 100644 --- a/android/src/app/organicmaps/MwmActivity.java +++ b/android/src/app/organicmaps/MwmActivity.java @@ -2,17 +2,27 @@ package app.organicmaps; import android.annotation.SuppressLint; import android.app.Activity; +import android.app.ProgressDialog; +import android.content.ClipData; +import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; import android.location.Location; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; import android.net.Uri; +import android.os.AsyncTask; import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; import android.text.TextUtils; +import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; import android.widget.TextView; +import android.widget.Toast; import androidx.annotation.CallSuper; import androidx.annotation.NonNull; @@ -26,7 +36,6 @@ import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentFactory; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; -import androidx.lifecycle.LiveData; import androidx.lifecycle.ViewModelProvider; import app.organicmaps.Framework.PlacePageActivationListener; import app.organicmaps.api.Const; @@ -95,7 +104,15 @@ import app.organicmaps.widget.placepage.PlacePageController; import app.organicmaps.widget.placepage.PlacePageData; import app.organicmaps.widget.placepage.PlacePageViewModel; import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.google.android.material.snackbar.Snackbar; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; import java.util.ArrayList; import java.util.Objects; import java.util.Stack; @@ -956,6 +973,19 @@ public class MwmActivity extends BaseMwmFragmentActivity super.onResume(); refreshSearchToolbar(); setFullscreen(isFullscreen()); + Handler handler = new Handler(Looper.getMainLooper()); + handler.postDelayed(new Runnable() { + @Override + public void run() { + try { + checkClipboardForUrl(); + } + catch (Exception e) { + Log.e("test1","Exception caught"); + } + } + }, 1000); + if (Framework.nativeIsInChoosePositionMode()) { UiUtils.show(mPointChooser); @@ -1610,6 +1640,101 @@ public class MwmActivity extends BaseMwmFragmentActivity builder.show(); } + private class HttpRequestTask extends AsyncTask { + private Context context; + private ProgressDialog progressDialog; + public HttpRequestTask(Context context) { + this.context = context; + } + @Override + protected void onPreExecute() { + super.onPreExecute(); + // Show the progress dialog + progressDialog = ProgressDialog.show(context, "", "Redirecting to URL using the anonymous proxy", true); + } + + @Override + protected String doInBackground(String... params) { + String requestUrl = params[0]; + try { + URL url = new URL(requestUrl); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + connection.connect(); + + InputStream inputStream = connection.getInputStream(); + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + byteArrayOutputStream.write(buffer, 0, bytesRead); + } + byte[] responseBytes = byteArrayOutputStream.toByteArray(); + return new String(responseBytes); + } catch (Exception e) { + progressDialog.dismiss(); + View view = findViewById(android.R.id.content); + Snackbar.make(view, "Redirection failed, please check your internet connection.", Snackbar.LENGTH_SHORT).show(); + Log.e("test1", "Error performing HTTP request: " + e.getMessage()); + return null; + } + } + + @Override + protected void onPostExecute(String response) { + if (response != null) { + try { + JSONObject jsonObject = new JSONObject(response); + JSONObject urlObject = jsonObject.getJSONObject("url"); + String geo = urlObject.getString("geo"); + Log.e("test1", "Geo: " + geo); + Log.e("test1", geo); + progressDialog.dismiss(); + Map.nativeShowMapForUrl(geo); + ClipboardManager clipService = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clipData = ClipData.newPlainText("", ""); + clipService.setPrimaryClip(clipData); + Log.e("test1","finish"); + } catch (JSONException e) { + progressDialog.dismiss(); + View view = findViewById(android.R.id.content); + Snackbar.make(view, "Please check that your URL points to an address.", Snackbar.LENGTH_SHORT).show(); + Log.e("test1", "Error"); + e.printStackTrace(); + } + } else { + Log.e("test1", "HTTP request failed"); + } + } + } + public void checkClipboardForUrl() { + ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); + Log.e("test1","clipboard is "+ String.valueOf(clipboard!=null) + "and primary clip" + String.valueOf(clipboard.hasPrimaryClip())); + if (clipboard != null ) { + Log.e("test1","first"); + ClipData clipData = clipboard.getPrimaryClip(); + Log.e("test1","clipdata is "+ String.valueOf(clipData!=null)); + if (clipData != null && clipData.getItemCount() > 0) { + Log.e("test1","second"); + CharSequence text = clipData.getItemAt(0).getText(); + String clipboardText = text.toString(); + Log.e("test1", clipboardText); + if (clipboardText.contains("goo.gl") || clipboardText.contains("maps.app.goo.gl") || clipboardText.contains("www.google.com")) { + Log.e("test1", "The copied text is from Google"); + try { + String url = clipboardText; + String requestUrl = "https://url-un.kartikay-2101ce32.workers.dev/coordinates?url=" + url; + Log.e("test1", requestUrl); + new HttpRequestTask(this).execute(requestUrl); + } catch (Exception e) { + Log.e("test1", "Error performing HTTP request: " + e.getMessage()); + } + } + } + } +} + + @Override public void onMyPositionModeChanged(int newMode) { @@ -1846,4 +1971,4 @@ public class MwmActivity extends BaseMwmFragmentActivity if (level >= TRIM_MEMORY_RUNNING_LOW) Framework.nativeMemoryWarning(); } -} +} \ No newline at end of file diff --git a/iphone/Maps/Classes/CoordinatesFromClipboard.h b/iphone/Maps/Classes/CoordinatesFromClipboard.h new file mode 100644 index 0000000000..ed516bae1c --- /dev/null +++ b/iphone/Maps/Classes/CoordinatesFromClipboard.h @@ -0,0 +1,8 @@ +#import +#import + +@interface ClipboardCheck : NSObject + +- (void)Check:(UIWindow *)window; + +@end diff --git a/iphone/Maps/Classes/CoordinatesFromClipboard.mm b/iphone/Maps/Classes/CoordinatesFromClipboard.mm new file mode 100644 index 0000000000..a3e675bd96 --- /dev/null +++ b/iphone/Maps/Classes/CoordinatesFromClipboard.mm @@ -0,0 +1,109 @@ +#import "MapsAppDelegate.h" +#import "CoordinatesFromClipboard.h" +#import "UIKitCategories.h" +#include + +@interface ClipboardCheck () + +@property (nonatomic, strong) NSTimer *timer; +@property (nonatomic, copy) NSString *previousClipboardText; +@property (nonatomic, assign) BOOL isErrorPromptShown; + +@end + +@implementation ClipboardCheck + +NSString * privacyProxy = @"https://url-un.kartikay-2101ce32.workers.dev/coordinates?url=%@"; +NSString *localizedStringOk = NSLocalizedString(@"ok", @""); +- (void)Check:(UIWindow *)window { + UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; + NSString *currentClipboardText = pasteboard.string; + NSLog(@"Local String%@", localizedStringOk); + [self checkClipboardWithText:currentClipboardText window:window]; +} +- (void)checkClipboardWithText:(NSString *)copiedText window:(UIWindow *)window { + NSLog(@"PROXY REQUEST PROCEEDING %@", copiedText); + + // Check if the copied text is a URL + if ([self isURL:copiedText]) { + UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; + [pasteboard setString:@""]; + NSLog(privacyProxy,copiedText); + dispatch_async(dispatch_get_main_queue(), ^{ + NSString *message = [NSString stringWithFormat:@"Extracting coordinates from %@ using the anonymous proxy...", copiedText]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Please Wait" message:message preferredStyle:UIAlertControllerStyleAlert]; + [window.rootViewController presentViewController:alertController animated:YES completion:nil]; + }); + + // Make an API request + NSURL *apiURL = [NSURL URLWithString:[NSString stringWithFormat:privacyProxy, copiedText]]; + NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:apiURL completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { + dispatch_async(dispatch_get_main_queue(), ^{ + [window.rootViewController dismissViewControllerAnimated:YES completion:nil]; + }); + + if (error) { + NSLog(@"Proxy request error: %@", error); + NSString *errorMessage = @"Failed to extract coordinates from %url using an anonymous proxy. Please check your Internet connection"; + errorMessage = [errorMessage stringByReplacingOccurrencesOfString:@"%url" withString:copiedText]; + [self displayErrorMessage:errorMessage inWindow:window]; + } else { + NSError *jsonError; + NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + NSLog(@"Proxy response:%@", responseString); + NSDictionary *responseDict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&jsonError]; + if (jsonError) { + NSLog(@"JSON Parsing Error: %@", jsonError); + NSString *errorMessage = @"Can not extract coordinates from %url"; + errorMessage = [errorMessage stringByReplacingOccurrencesOfString:@"%url" withString:copiedText]; + [self displayErrorMessage:errorMessage inWindow:window]; + } else { + NSDictionary *urlDict = responseDict[@"url"]; + NSString *geoURL = urlDict[@"geo"]; + if (geoURL) { + NSLog(@"Geo URL: %@", geoURL); + dispatch_async(dispatch_get_main_queue(), ^{ + GetFramework().ShowMapForURL(geoURL.UTF8String); + }); + } + } + } + }]; + [task resume]; + } +} + +- (BOOL)isURL:(NSString *)text { + NSDataDetector *detector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:nil]; + NSArray *matches = [detector matchesInString:text options:0 range:NSMakeRange(0, text.length)]; + + NSArray *googleHosts = @[@"google", @"google.com", @"goo.gl", @"maps.app.goo.gl"]; + + for (NSTextCheckingResult *match in matches) { + NSURL *url = match.URL; + NSString *hostname = url.host; + + for (NSString *googleHost in googleHosts) { + if ([hostname containsString:googleHost]) return YES; + } + } + return NO; +} + +- (void)displayErrorMessage:(NSString *)errorMessage inWindow:(UIWindow *)window { + if (!self.isErrorPromptShown) { + self.isErrorPromptShown = YES; + + dispatch_async(dispatch_get_main_queue(), ^{ + NSString *title = @"Redirection to the coordinates failed"; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:errorMessage preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction *okAction = [UIAlertAction actionWithTitle:localizedStringOk style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + self.isErrorPromptShown = NO; + }]; + [alertController addAction:okAction]; + [window.rootViewController presentViewController:alertController animated:YES completion:nil]; + }); + } +} + +@end diff --git a/iphone/Maps/Classes/MapsAppDelegate.mm b/iphone/Maps/Classes/MapsAppDelegate.mm index e19490f3dd..75c3c5f9a1 100644 --- a/iphone/Maps/Classes/MapsAppDelegate.mm +++ b/iphone/Maps/Classes/MapsAppDelegate.mm @@ -1,4 +1,7 @@ #import "MapsAppDelegate.h" +#import "CoordinatesFromClipboard.h" + +#import #import "EAGLView.h" #import "MWMAuthorizationCommon.h" @@ -66,6 +69,7 @@ using namespace osm_auth_ios; @property(nonatomic) NSInteger standbyCounter; @property(nonatomic) MWMBackgroundFetchScheduler *backgroundFetchScheduler; +@property (nonatomic, strong) ClipboardCheck *clipboardCheck; @end @@ -222,12 +226,19 @@ using namespace osm_auth_ios; f.OnRecoverSurface(static_cast(objcSize.width), static_cast(objcSize.height), true /* recreateContextDependentResources */); } + [self checkClipboard]; [MWMLocationManager applicationDidBecomeActive]; [MWMSearch addCategoriesToSpotlight]; [MWMKeyboard applicationDidBecomeActive]; [MWMTextToSpeech applicationDidBecomeActive]; LOG(LINFO, ("applicationDidBecomeActive - end")); } +- (void)checkClipboard { + NSLog(@"Watching started"); + ClipboardCheck *clipboardCheck = [[ClipboardCheck alloc] init]; + UIWindow *targetWindow = [UIApplication sharedApplication].keyWindow; + [clipboardCheck Check:targetWindow]; +} - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity diff --git a/iphone/Maps/Maps.xcodeproj/project.pbxproj b/iphone/Maps/Maps.xcodeproj/project.pbxproj index 7b93dd8d5b..ceba934271 100644 --- a/iphone/Maps/Maps.xcodeproj/project.pbxproj +++ b/iphone/Maps/Maps.xcodeproj/project.pbxproj @@ -324,6 +324,7 @@ 6B15907226623AE500944BBA /* 00_NotoSansThai-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6B15907026623AE500944BBA /* 00_NotoSansThai-Regular.ttf */; }; 6B679E89266BFD0A0074AE2A /* 00_NotoNaskhArabic-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6B679E88266BFD090074AE2A /* 00_NotoNaskhArabic-Regular.ttf */; }; 6B9978361C89A316003B8AA0 /* editor.config in Resources */ = {isa = PBXBuildFile; fileRef = 6B9978341C89A316003B8AA0 /* editor.config */; }; + 8FE7398F2A4208CC00DC5837 /* CoordinatesFromClipboard.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8FE7398E2A4208CC00DC5837 /* CoordinatesFromClipboard.mm */; }; 99012847243F0D6900C72B10 /* UIViewController+alternative.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99012846243F0D6900C72B10 /* UIViewController+alternative.swift */; }; 9901284F244732DB00C72B10 /* BottomTabBarPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99012849244732DB00C72B10 /* BottomTabBarPresenter.swift */; }; 99012851244732DB00C72B10 /* BottomTabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9901284B244732DB00C72B10 /* BottomTabBarViewController.swift */; }; @@ -764,7 +765,6 @@ 3444DFCF1F17620C00E73099 /* MWMMapWidgetsHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MWMMapWidgetsHelper.h; sourceTree = ""; }; 3444DFD01F17620C00E73099 /* MWMMapWidgetsHelper.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MWMMapWidgetsHelper.mm; sourceTree = ""; }; 3444DFDC1F18A5AF00E73099 /* SideButtonsArea.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SideButtonsArea.swift; sourceTree = ""; }; - 345050211E028B8000A8DC59 /* Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Bridging-Header.h"; sourceTree = ""; }; 3454D79B1E07F045004AF2AD /* CLLocation+Mercator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CLLocation+Mercator.h"; sourceTree = ""; }; 3454D79C1E07F045004AF2AD /* CLLocation+Mercator.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "CLLocation+Mercator.mm"; sourceTree = ""; }; 3454D79D1E07F045004AF2AD /* DateComponentsFormatter+ETA.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DateComponentsFormatter+ETA.swift"; sourceTree = ""; }; @@ -1135,6 +1135,9 @@ 6B679E88266BFD090074AE2A /* 00_NotoNaskhArabic-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "00_NotoNaskhArabic-Regular.ttf"; path = "../../data/00_NotoNaskhArabic-Regular.ttf"; sourceTree = ""; }; 6B9978341C89A316003B8AA0 /* editor.config */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = editor.config; path = ../../data/editor.config; sourceTree = ""; }; 8D1107310486CEB800E47090 /* OMaps.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = OMaps.plist; plistStructureDefinitionIdentifier = "com.apple.xcode.plist.structure-definition.iphone.info-plist"; sourceTree = ""; }; + 8F71F2442A46035800089B33 /* Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Bridging-Header.h"; sourceTree = ""; }; + 8FE7398D2A4208A100DC5837 /* CoordinatesFromClipboard.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CoordinatesFromClipboard.h; sourceTree = ""; }; + 8FE7398E2A4208CC00DC5837 /* CoordinatesFromClipboard.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = CoordinatesFromClipboard.mm; sourceTree = ""; }; 978D4A30199A11E600D72CA7 /* faq.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = faq.html; path = ../../data/faq.html; sourceTree = ""; }; 97A5967E19B9CD47007A963F /* copyright.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = copyright.html; path = ../../data/copyright.html; sourceTree = ""; }; 99012846243F0D6900C72B10 /* UIViewController+alternative.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+alternative.swift"; sourceTree = ""; }; @@ -1703,6 +1706,8 @@ 34FE4C421BCC013500066718 /* Widgets */, FA8E808825F412E2002A1434 /* FirstSession.mm */, FA8E808A25F41337002A1434 /* FirstSession.h */, + 8FE7398D2A4208A100DC5837 /* CoordinatesFromClipboard.h */, + 8FE7398E2A4208CC00DC5837 /* CoordinatesFromClipboard.mm */, ); path = Classes; sourceTree = ""; @@ -1724,6 +1729,7 @@ 080E96DDFE201D6D7F000001 /* Classes */, 340475141E081A4600C92850 /* Common */, 347526FA1DC0B00F00918CF5 /* common-debug.xcconfig */, + 8F71F2442A46035800089B33 /* Bridging-Header.h */, 347526FB1DC0B00F00918CF5 /* common-release.xcconfig */, 340475281E081A4600C92850 /* Core */, FA065FC61286143F00FEA989 /* External Resources */, @@ -1744,7 +1750,6 @@ 29B97315FDCFA39411CA2CEA /* Other Sources */ = { isa = PBXGroup; children = ( - 345050211E028B8000A8DC59 /* Bridging-Header.h */, 29B97316FDCFA39411CA2CEA /* main.mm */, 28A0AB4B0D9B1048005BE974 /* Maps_Prefix.pch */, ); @@ -3876,6 +3881,7 @@ 34AB666E1FC5AA330078E451 /* TransportTransitStepsCollectionView.swift in Sources */, 993DF11E23F6BDB100AC231A /* UITextViewRenderer.swift in Sources */, F6E2FF5A1E097BA00083EBEC /* MWMNightModeController.m in Sources */, + 8FE7398F2A4208CC00DC5837 /* CoordinatesFromClipboard.mm in Sources */, 471A7BB8247FE3C300A0D4C1 /* URL+Query.swift in Sources */, 47F86D0120C93D8D00FEE291 /* TabViewController.swift in Sources */, 99536113235DB86C008B218F /* InsetsLabel.swift in Sources */,