You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by pu...@apache.org on 2014/09/19 00:49:51 UTC
[16/27] moving stuff around
http://git-wip-us.apache.org/repos/asf/cordova-wp8/blob/c74fdafa/template/cordovalib/CordovaView.xaml.cs
----------------------------------------------------------------------
diff --git a/template/cordovalib/CordovaView.xaml.cs b/template/cordovalib/CordovaView.xaml.cs
new file mode 100644
index 0000000..0514aa6
--- /dev/null
+++ b/template/cordovalib/CordovaView.xaml.cs
@@ -0,0 +1,567 @@
+/*
+ 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.
+*/
+
+using System.Globalization;
+using Microsoft.Phone.Controls;
+using Microsoft.Phone.Shell;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.IO;
+using System.IO.IsolatedStorage;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+using WPCordovaClassLib.Cordova;
+using WPCordovaClassLib.Cordova.JSON;
+using WPCordovaClassLib.CordovaLib;
+
+
+
+namespace WPCordovaClassLib
+{
+ public partial class CordovaView : UserControl
+ {
+
+ /// <summary>
+ /// Indicates whether web control has been loaded and no additional initialization is needed.
+ /// Prevents data clearing during page transitions.
+ /// </summary>
+ private bool IsBrowserInitialized = false;
+
+ /// <summary>
+ /// Set when the user attaches a back button handler inside the WebBrowser
+ /// </summary>
+ private bool OverrideBackButton = false;
+
+ /// <summary>
+ /// Sentinal to keep track of page changes as a result of the hardware back button
+ /// Set to false when the back-button is pressed, which calls js window.history.back()
+ /// If the page changes as a result of the back button the event is cancelled.
+ /// </summary>
+ private bool PageDidChange = false;
+
+ private static string AppRoot = "";
+
+
+ /// <summary>
+ /// Handles native api calls
+ /// </summary>
+ private NativeExecution nativeExecution;
+
+ protected BrowserMouseHelper bmHelper;
+
+ private ConfigHandler configHandler;
+
+ protected bool IsExiting = false;
+
+ private Dictionary<string, IBrowserDecorator> browserDecorators;
+
+ public System.Windows.Controls.Grid _LayoutRoot
+ {
+ get
+ {
+ return ((System.Windows.Controls.Grid)(this.FindName("LayoutRoot")));
+ }
+ }
+
+ public WebBrowser Browser
+ {
+ get
+ {
+ return CordovaBrowser;
+ }
+ }
+
+
+
+ /*
+ * Setting StartPageUri only has an effect if called before the view is loaded.
+ **/
+ protected Uri _startPageUri = null;
+ public Uri StartPageUri
+ {
+ get
+ {
+ if (_startPageUri == null)
+ {
+ // default
+
+ return new Uri(AppRoot + "www/index.html", UriKind.Relative);
+ }
+ else
+ {
+ return _startPageUri;
+ }
+ }
+ set
+ {
+ if (!this.IsBrowserInitialized)
+ {
+ _startPageUri = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets whether to suppress bouncy scrolling of
+ /// the WebBrowser control;
+ /// </summary>
+ public bool DisableBouncyScrolling
+ {
+ get;
+ set;
+ }
+
+ public CordovaView()
+ {
+
+ InitializeComponent();
+
+ if (DesignerProperties.IsInDesignTool)
+ {
+ return;
+ }
+
+
+ StartupMode mode = PhoneApplicationService.Current.StartupMode;
+
+ if (mode == StartupMode.Launch)
+ {
+ PhoneApplicationService service = PhoneApplicationService.Current;
+ service.Activated += new EventHandler<Microsoft.Phone.Shell.ActivatedEventArgs>(AppActivated);
+ service.Launching += new EventHandler<LaunchingEventArgs>(AppLaunching);
+ service.Deactivated += new EventHandler<DeactivatedEventArgs>(AppDeactivated);
+ service.Closing += new EventHandler<ClosingEventArgs>(AppClosing);
+ }
+ else
+ {
+
+ }
+
+ // initializes native execution logic
+ configHandler = new ConfigHandler();
+ configHandler.LoadAppPackageConfig();
+
+ if (configHandler.ContentSrc != null)
+ {
+ if (Uri.IsWellFormedUriString(configHandler.ContentSrc, UriKind.Absolute))
+ {
+ this.StartPageUri = new Uri(configHandler.ContentSrc, UriKind.Absolute);
+ }
+ else
+ {
+ this.StartPageUri = new Uri(AppRoot + "www/" + configHandler.ContentSrc, UriKind.Relative);
+ }
+ }
+
+ browserDecorators = new Dictionary<string, IBrowserDecorator>();
+
+ nativeExecution = new NativeExecution(ref this.CordovaBrowser);
+ bmHelper = new BrowserMouseHelper(ref this.CordovaBrowser);
+
+ ApplyConfigurationPreferences();
+
+ CreateDecorators();
+ }
+
+ /// <summary>
+ /// Applies configuration preferences. Only BackgroundColor+fullscreen is currently supported.
+ /// </summary>
+ private void ApplyConfigurationPreferences()
+ {
+ string bgColor = configHandler.GetPreference("backgroundcolor");
+
+ if (!String.IsNullOrEmpty(bgColor))
+ {
+ try
+ {
+ Browser.Background = new SolidColorBrush(ColorFromHex(bgColor));
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine("Unable to parse BackgroundColor value '{0}'. Error: {1}", bgColor, ex.Message);
+ }
+ }
+ }
+
+ /*
+ * browserDecorators are a collection of plugin-like classes (IBrowserDecorator) that add some bit of functionality to the browser.
+ * These are somewhat different than plugins in that they are usually not async and patch a browser feature that we would
+ * already expect to have. Essentially these are browser polyfills that are patched from the outside in.
+ * */
+ void CreateDecorators()
+ {
+ XHRHelper xhrProxy = new XHRHelper();
+ xhrProxy.Browser = CordovaBrowser;
+ browserDecorators.Add("XHRLOCAL", xhrProxy);
+
+ OrientationHelper orientHelper = new OrientationHelper();
+ orientHelper.Browser = CordovaBrowser;
+ browserDecorators.Add("Orientation", orientHelper);
+
+ ConsoleHelper console = new ConsoleHelper();
+ console.Browser = CordovaBrowser;
+ browserDecorators.Add("ConsoleLog", console);
+
+ }
+
+ void AppClosing(object sender, ClosingEventArgs e)
+ {
+ Debug.WriteLine("AppClosing");
+ }
+
+ void AppDeactivated(object sender, DeactivatedEventArgs e)
+ {
+ Debug.WriteLine("INFO: AppDeactivated because " + e.Reason);
+ try
+ {
+ CordovaBrowser.InvokeScript("eval", new string[] { "cordova.fireDocumentEvent('pause');" });
+ }
+ catch (Exception)
+ {
+ Debug.WriteLine("ERROR: Pause event error");
+ }
+ }
+
+ void AppLaunching(object sender, LaunchingEventArgs e)
+ {
+ Debug.WriteLine("INFO: AppLaunching");
+ }
+
+ void AppActivated(object sender, Microsoft.Phone.Shell.ActivatedEventArgs e)
+ {
+ Debug.WriteLine("INFO: AppActivated");
+ try
+ {
+ CordovaBrowser.InvokeScript("eval", new string[] { "cordova.fireDocumentEvent('resume');" });
+ }
+ catch (Exception)
+ {
+ Debug.WriteLine("ERROR: Resume event error");
+ }
+ }
+
+ void CordovaBrowser_Loaded(object sender, RoutedEventArgs e)
+ {
+
+ this.bmHelper.ScrollDisabled = this.DisableBouncyScrolling;
+
+ if (DesignerProperties.IsInDesignTool)
+ {
+ return;
+ }
+
+ // prevents refreshing web control to initial state during pages transitions
+ if (this.IsBrowserInitialized) return;
+
+
+ try
+ {
+
+ // Before we possibly clean the ISO-Store, we need to grab our generated UUID, so we can rewrite it after.
+ string deviceUUID = "";
+
+ using (IsolatedStorageFile appStorage = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ try
+ {
+ IsolatedStorageFileStream fileStream = new IsolatedStorageFileStream("DeviceID.txt", FileMode.Open, FileAccess.Read, appStorage);
+
+ using (StreamReader reader = new StreamReader(fileStream))
+ {
+ deviceUUID = reader.ReadLine();
+ }
+ }
+ catch (Exception /*ex*/)
+ {
+ deviceUUID = Guid.NewGuid().ToString();
+ Debug.WriteLine("Updating IsolatedStorage for APP:DeviceID :: " + deviceUUID);
+ IsolatedStorageFileStream file = new IsolatedStorageFileStream("DeviceID.txt", FileMode.Create, FileAccess.Write, appStorage);
+ using (StreamWriter writeFile = new StreamWriter(file))
+ {
+ writeFile.WriteLine(deviceUUID);
+ writeFile.Close();
+ }
+ }
+ }
+
+ CordovaBrowser.Navigate(StartPageUri);
+ IsBrowserInitialized = true;
+ AttachHardwareButtonHandlers();
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine("ERROR: Exception in CordovaBrowser_Loaded :: {0}", ex.Message);
+ }
+ }
+
+ void AttachHardwareButtonHandlers()
+ {
+ PhoneApplicationFrame frame = Application.Current.RootVisual as PhoneApplicationFrame;
+ if (frame != null)
+ {
+ PhoneApplicationPage page = frame.Content as PhoneApplicationPage;
+
+ if (page != null)
+ {
+ page.BackKeyPress += new EventHandler<CancelEventArgs>(page_BackKeyPress);
+ // CB-2347 -jm
+ string fullscreen = configHandler.GetPreference("fullscreen");
+ bool bFullScreen = false;
+ if (bool.TryParse(fullscreen, out bFullScreen) && bFullScreen)
+ {
+ SystemTray.SetIsVisible(page, false);
+ }
+ }
+ }
+ }
+
+ void page_BackKeyPress(object sender, CancelEventArgs e)
+ {
+
+ if (OverrideBackButton)
+ {
+ try
+ {
+ CordovaBrowser.InvokeScript("eval", new string[] { "cordova.fireDocumentEvent('backbutton', {}, true);" });
+ e.Cancel = true;
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine("Exception while invoking backbutton into cordova view: " + ex.Message);
+ }
+ }
+ else
+ {
+ try
+ {
+ PageDidChange = false;
+
+ Uri uriBefore = this.Browser.Source;
+ // calling js history.back with result in a page change if history was valid.
+ CordovaBrowser.InvokeScript("eval", new string[] { "(function(){window.history.back();})()" });
+
+ Uri uriAfter = this.Browser.Source;
+
+ e.Cancel = PageDidChange || (uriBefore != uriAfter);
+ }
+ catch (Exception)
+ {
+ e.Cancel = false; // exit the app ... ?
+ }
+ }
+ }
+
+ void CordovaBrowser_LoadCompleted(object sender, System.Windows.Navigation.NavigationEventArgs e)
+ {
+ if (IsExiting)
+ {
+ // Special case, we navigate to about:blank when we are about to exit.
+ IsolatedStorageSettings.ApplicationSettings.Save();
+ Application.Current.Terminate();
+ return;
+ }
+
+ Debug.WriteLine("CordovaBrowser_LoadCompleted");
+
+ string version = "?";
+ System.Windows.Resources.StreamResourceInfo streamInfo = Application.GetResourceStream(new Uri("VERSION", UriKind.Relative));
+ if (streamInfo != null)
+ {
+ using(StreamReader sr = new StreamReader(streamInfo.Stream))
+ {
+ version = sr.ReadLine();
+ }
+ }
+ Debug.WriteLine("Apache Cordova native platform version " + version + " is starting");
+
+ string[] autoloadPlugs = this.configHandler.AutoloadPlugins;
+ foreach (string plugName in autoloadPlugs)
+ {
+ nativeExecution.AutoLoadCommand(plugName);
+ }
+
+ // send js code to fire ready event
+ string nativeReady = "(function(){ cordova.require('cordova/channel').onNativeReady.fire()})();";
+ try
+ {
+ CordovaBrowser.InvokeScript("eval", new string[] { nativeReady });
+ }
+ catch (Exception /*ex*/)
+ {
+ Debug.WriteLine("Error calling js to fire nativeReady event. Did you include cordova.js in your html script tag?");
+ }
+ // attach js code to dispatch exitApp
+ string appExitHandler = "(function(){navigator.app = navigator.app || {}; navigator.app.exitApp= function(){cordova.exec(null,null,'CoreEvents','__exitApp',[]); }})();";
+ try
+ {
+ CordovaBrowser.InvokeScript("eval", new string[] { appExitHandler });
+ }
+ catch (Exception /*ex*/)
+ {
+ Debug.WriteLine("Error calling js to add appExit funtion.");
+ }
+
+ if (this.CordovaBrowser.Opacity < 1)
+ {
+ FadeIn.Begin();
+ }
+ }
+
+
+ void CordovaBrowser_Navigating(object sender, NavigatingEventArgs e)
+ {
+ if (!configHandler.URLIsAllowed(e.Uri.ToString()))
+ {
+ Debug.WriteLine("Whitelist exception: Stopping browser from navigating to :: " + e.Uri.ToString());
+ e.Cancel = true;
+ return;
+ }
+
+ this.PageDidChange = true;
+ this.nativeExecution.ResetAllCommands();
+ }
+
+ /*
+ * This method does the work of routing commands
+ * NotifyEventArgs.Value contains a string passed from JS
+ * If the command already exists in our map, we will just attempt to call the method(action) specified, and pass the args along
+ * Otherwise, we create a new instance of the command, add it to the map, and call it ...
+ * This method may also receive JS error messages caught by window.onerror, in any case where the commandStr does not appear to be a valid command
+ * it is simply output to the debugger output, and the method returns.
+ *
+ **/
+ void CordovaBrowser_ScriptNotify(object sender, NotifyEventArgs e)
+ {
+ string commandStr = e.Value;
+
+ string commandName = commandStr.Split('/').FirstOrDefault();
+
+ if (browserDecorators.ContainsKey(commandName))
+ {
+ browserDecorators[commandName].HandleCommand(commandStr);
+ return;
+ }
+
+ CordovaCommandCall commandCallParams = CordovaCommandCall.Parse(commandStr);
+
+ if (commandCallParams == null)
+ {
+ // ERROR
+ Debug.WriteLine("ScriptNotify :: " + commandStr);
+ }
+ else if (commandCallParams.Service == "CoreEvents")
+ {
+ switch (commandCallParams.Action.ToLower())
+ {
+ case "overridebackbutton":
+ string arg0 = JsonHelper.Deserialize<string[]>(commandCallParams.Args)[0];
+ this.OverrideBackButton = (arg0 != null && arg0.Length > 0 && arg0.ToLower() == "true");
+ break;
+ case "__exitapp":
+ Debug.WriteLine("Received exitApp command from javascript, app will now exit.");
+ CordovaBrowser.InvokeScript("eval", new string[] { "cordova.fireDocumentEvent('pause');" });
+ CordovaBrowser.InvokeScript("eval", new string[] { "setTimeout(function(){ cordova.fireDocumentEvent('exit'); cordova.exec(null,null,'CoreEvents','__finalexit',[]); },0);" });
+ break;
+ case "__finalexit":
+ IsExiting = true;
+ // hide the browser to prevent white flashes, since about:blank seems to always be white
+ CordovaBrowser.Opacity = 0d;
+ CordovaBrowser.Navigate(new Uri("about:blank", UriKind.Absolute));
+ break;
+ }
+ }
+ else
+ {
+ if (configHandler.IsPluginAllowed(commandCallParams.Service))
+ {
+ commandCallParams.Namespace = configHandler.GetNamespaceForCommand(commandCallParams.Service);
+ nativeExecution.ProcessCommand(commandCallParams);
+ }
+ else
+ {
+ Debug.WriteLine("Error::Plugin not allowed in config.xml. " + commandCallParams.Service);
+ }
+ }
+ }
+
+ public void LoadPage(string url)
+ {
+ if (this.configHandler.URLIsAllowed(url))
+ {
+ this.CordovaBrowser.Navigate(new Uri(url, UriKind.RelativeOrAbsolute));
+ }
+ else
+ {
+ Debug.WriteLine("Oops, Can't load url based on config.xml :: " + url);
+ }
+ }
+
+ private void CordovaBrowser_Unloaded(object sender, RoutedEventArgs e)
+ {
+ IBrowserDecorator console;
+ if (browserDecorators.TryGetValue("ConsoleLog", out console))
+ {
+ ((ConsoleHelper)console).DetachHandler();
+ }
+
+ PhoneApplicationService service = PhoneApplicationService.Current;
+ service.Activated -= new EventHandler<Microsoft.Phone.Shell.ActivatedEventArgs>(AppActivated);
+ service.Launching -= new EventHandler<LaunchingEventArgs>(AppLaunching);
+ service.Deactivated -= new EventHandler<DeactivatedEventArgs>(AppDeactivated);
+ service.Closing -= new EventHandler<ClosingEventArgs>(AppClosing);
+ }
+
+ private void CordovaBrowser_NavigationFailed(object sender, System.Windows.Navigation.NavigationFailedEventArgs e)
+ {
+ Debug.WriteLine("CordovaBrowser_NavigationFailed :: " + e.Uri.ToString());
+ }
+
+ private void CordovaBrowser_Navigated(object sender, System.Windows.Navigation.NavigationEventArgs e)
+ {
+ foreach(IBrowserDecorator iBD in browserDecorators.Values)
+ {
+ iBD.InjectScript();
+ }
+ }
+
+ /// <summary>
+ /// Converts hex color string to a new System.Windows.Media.Color structure.
+ /// If the hex is only rgb, it will be full opacity.
+ /// </summary>
+ protected Color ColorFromHex(string hexString)
+ {
+ string cleanHex = hexString.Replace("#", "").Replace("0x", "");
+ // turn #FFF into #FFFFFF
+ if (cleanHex.Length == 3)
+ {
+ cleanHex = "" + cleanHex[0] + cleanHex[0] + cleanHex[1] + cleanHex[1] + cleanHex[2] + cleanHex[2];
+ }
+ // add an alpha 100% if it is missing
+ if (cleanHex.Length == 6)
+ {
+ cleanHex = "FF" + cleanHex;
+ }
+ int argb = Int32.Parse(cleanHex, NumberStyles.HexNumber);
+ Color clr = Color.FromArgb((byte)((argb & 0xff000000) >> 0x18),
+ (byte)((argb & 0xff0000) >> 0x10),
+ (byte)((argb & 0xff00) >> 8),
+ (byte)(argb & 0xff));
+ return clr;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/cordova-wp8/blob/c74fdafa/template/cordovalib/IBrowserDecorator.cs
----------------------------------------------------------------------
diff --git a/template/cordovalib/IBrowserDecorator.cs b/template/cordovalib/IBrowserDecorator.cs
new file mode 100644
index 0000000..bc1dbee
--- /dev/null
+++ b/template/cordovalib/IBrowserDecorator.cs
@@ -0,0 +1,30 @@
+/*
+ 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.
+*/
+
+using Microsoft.Phone.Controls;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace WPCordovaClassLib.CordovaLib
+{
+ interface IBrowserDecorator
+ {
+ WebBrowser Browser { get; set; }
+ void InjectScript();
+ bool HandleCommand(string cmd);
+ }
+}
http://git-wip-us.apache.org/repos/asf/cordova-wp8/blob/c74fdafa/template/cordovalib/ImageExifHelper.cs
----------------------------------------------------------------------
diff --git a/template/cordovalib/ImageExifHelper.cs b/template/cordovalib/ImageExifHelper.cs
new file mode 100644
index 0000000..62b6462
--- /dev/null
+++ b/template/cordovalib/ImageExifHelper.cs
@@ -0,0 +1,209 @@
+/*
+ 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.
+
+*/
+
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Windows.Media.Imaging;
+
+namespace WPCordovaClassLib.Cordova.Commands
+{
+ public class ImageExifOrientation
+ {
+ public const int Portrait = 1;
+ public const int PortraitUpsideDown = 3;
+ public const int LandscapeLeft = 6;
+ public const int LandscapeRight = 8;
+ }
+
+ public class ImageExifHelper
+ {
+
+ public static Stream RotateStream(Stream stream, int angle)
+ {
+ stream.Position = 0;
+ if (angle % 90 != 0 || angle < 0)
+ {
+ throw new ArgumentException();
+ }
+ if (angle % 360 == 0)
+ {
+ return stream;
+ }
+
+ angle = angle % 360;
+
+ BitmapImage bitmap = new BitmapImage();
+ bitmap.SetSource(stream);
+ WriteableBitmap wbSource = new WriteableBitmap(bitmap);
+
+ WriteableBitmap wbTarget = null;
+
+ int srcPixelWidth = wbSource.PixelWidth;
+ int srcPixelHeight = wbSource.PixelHeight;
+
+ if (angle % 180 == 0)
+ {
+ wbTarget = new WriteableBitmap(srcPixelWidth, srcPixelHeight);
+ }
+ else
+ {
+ wbTarget = new WriteableBitmap(srcPixelHeight, srcPixelWidth);
+ }
+
+ int destPixelWidth = wbTarget.PixelWidth;
+ int[] srcPxls = wbSource.Pixels;
+ int[] destPxls = wbTarget.Pixels;
+
+ // this ugly if/else is to avoid a conditional check for every pixel
+ if (angle == 90)
+ {
+ for (int x = 0; x < srcPixelWidth; x++)
+ {
+ for (int y = 0; y < srcPixelHeight; y++)
+ {
+ destPxls[(srcPixelHeight - y - 1) + (x * destPixelWidth)] = srcPxls[x + y * srcPixelWidth];
+ }
+ }
+ }
+ else if (angle == 180)
+ {
+ for (int x = 0; x < srcPixelWidth; x++)
+ {
+ for (int y = 0; y < srcPixelHeight; y++)
+ {
+ destPxls[(srcPixelWidth - x - 1) + (srcPixelHeight - y - 1) * srcPixelWidth] = srcPxls[x + y * srcPixelWidth];
+ }
+ }
+ }
+ else if (angle == 270)
+ {
+ for (int x = 0; x < srcPixelWidth; x++)
+ {
+ for (int y = 0; y < srcPixelHeight; y++)
+ {
+ destPxls[y + (srcPixelWidth - x - 1) * destPixelWidth] = srcPxls[x + y * srcPixelWidth];
+ }
+ }
+ }
+
+ MemoryStream targetStream = new MemoryStream();
+ wbTarget.SaveJpeg(targetStream, destPixelWidth, wbTarget.PixelHeight, 0, 100);
+ return targetStream;
+ }
+
+ public static int getImageOrientationFromStream(Stream imgStream)
+ {
+
+ // 0xFFD8 : jpgHeader
+ // 0xFFE1 :
+ // 0x???? : length of exif data
+ // 0x????, 0x???? : Chars 'E','x','i','f'
+ // 0x0000 : 2 empty bytes
+ // <== mark beginning of tags SIZE:ID:VALUE
+ // 0x???? : 'II' or 'MM' for Intel or Motorola ( always getting II on my WP7 devices ), determines littleEndian-ness
+ // 0x002A : marker value
+ // 0x???? : offset to the Image File Data
+
+ // XXXX possible space before actual tag data ... we skip to mark + offset
+
+ // 0x???? number of exif tags present
+
+ // make sure we are at the beginning
+ imgStream.Seek(0, SeekOrigin.Begin);
+ BinaryReader reader = new BinaryReader(imgStream);
+
+ byte[] jpgHdr = reader.ReadBytes(2); // always (0xFFD8)
+
+ byte start = reader.ReadByte(); // 0xFF
+ byte index = reader.ReadByte(); // 0xE1
+
+ while (start == 0xFF && index != 0xE1) // This never seems to happen, todo: optimize
+ {
+ // Get the data length
+ ushort dLen = BitConverter.ToUInt16(reader.ReadBytes(2), 0);
+ // skip along
+ reader.ReadBytes(dLen - 2);
+ start = reader.ReadByte();
+ index = reader.ReadByte();
+ }
+
+ // It's only success if we found the 0xFFE1 marker
+ if (start != 0xFF || index != 0xE1)
+ {
+ // throw new Exception("Could not find Exif data block");
+ Debug.WriteLine("Did not find EXIF data");
+ return 0;
+ }
+
+ // read 2 byte length of EXIF data
+ ushort exifLen = BitConverter.ToUInt16(reader.ReadBytes(2), 0);
+ String exif = ""; // build the string
+ for (var n = 0; n < 4; n++)
+ {
+ exif += reader.ReadChar();
+ }
+ if (exif != "Exif")
+ {
+ // did not find exif data ...
+ Debug.WriteLine("Did not find EXIF data");
+ return 0;
+ }
+
+ // read 2 empty bytes
+ //ushort emptyBytes = BitConverter.ToUInt16(reader.ReadBytes(2), 0);
+ reader.ReadBytes(2);
+
+ long headerMark = reader.BaseStream.Position; // where are we now <==
+
+ //bool isLEndian = (reader.ReadChar() + "" + reader.ReadChar()) == "II";
+ reader.ReadBytes(2); // 'II' or 'MM', but we don't care
+
+ if (0x002A != BitConverter.ToUInt16(reader.ReadBytes(2), 0))
+ {
+ Debug.WriteLine("Error in data != 0x002A");
+ return 0;
+ }
+
+ // Get the offset to the IFD (image file directory)
+ ushort imgOffset = BitConverter.ToUInt16(reader.ReadBytes(2), 0);
+
+ imgStream.Position = headerMark + imgOffset;
+ ushort tagCount = BitConverter.ToUInt16(reader.ReadBytes(2), 0);
+ for (ushort x = 0; x < tagCount; x++)
+ {
+ // Orientation = 0x112, aka 274
+ if (0x112 == BitConverter.ToUInt16(reader.ReadBytes(2), 0))
+ {
+ ushort dType = BitConverter.ToUInt16(reader.ReadBytes(2), 0);
+ // don't care ..
+ uint comps = reader.ReadUInt32();
+ byte[] tagData = reader.ReadBytes(4);
+ int orientation = (int)tagData[0];
+ Debug.WriteLine("orientation = " + orientation.ToString());
+ return orientation;
+ // 6 means rotate clockwise 90 deg
+ // 8 means rotate counter-clockwise 90 deg
+ // 1 means all is good
+ // 3 means flip vertical
+ }
+ // skip to the next item, 12 bytes each
+ reader.BaseStream.Seek(10, SeekOrigin.Current);
+ }
+ return 0;
+ }
+
+ }
+}
http://git-wip-us.apache.org/repos/asf/cordova-wp8/blob/c74fdafa/template/cordovalib/JSON/JsonHelper.cs
----------------------------------------------------------------------
diff --git a/template/cordovalib/JSON/JsonHelper.cs b/template/cordovalib/JSON/JsonHelper.cs
new file mode 100644
index 0000000..44511f6
--- /dev/null
+++ b/template/cordovalib/JSON/JsonHelper.cs
@@ -0,0 +1,97 @@
+/*
+ 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.
+*/
+
+using System;
+using System.Net;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Documents;
+using System.Windows.Ink;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Animation;
+using System.Windows.Shapes;
+using System.Runtime.Serialization.Json;
+using System.IO;
+using System.Collections.Generic;
+using System.Text;
+using System.Diagnostics;
+
+namespace WPCordovaClassLib.Cordova.JSON
+{
+ /// <summary>
+ /// Provides JSON serialization/deserialization functionality.
+ /// </summary>
+ public static class JsonHelper
+ {
+ /// <summary>
+ /// Serializes object to JSON string representation
+ /// </summary>
+ /// <param name="obj">object to serialize</param>
+ /// <returns>JSON representation of the object. Returns 'null' string for null passed as argument</returns>
+ public static string Serialize(object obj)
+ {
+ if (obj == null)
+ {
+ return "null";
+ }
+
+ DataContractJsonSerializer ser = new DataContractJsonSerializer(obj.GetType());
+
+ MemoryStream ms = new MemoryStream();
+ ser.WriteObject(ms, obj);
+
+ ms.Position = 0;
+
+ string json = String.Empty;
+
+ using (StreamReader sr = new StreamReader(ms))
+ {
+ json = sr.ReadToEnd();
+ }
+
+ ms.Close();
+
+ return json;
+
+ }
+
+ /// <summary>
+ /// Parses json string to object instance
+ /// </summary>
+ /// <typeparam name="T">type of the object</typeparam>
+ /// <param name="json">json string representation of the object</param>
+ /// <returns>Deserialized object instance</returns>
+ public static T Deserialize<T>(string json)
+ {
+ DataContractJsonSerializer deserializer = new DataContractJsonSerializer(typeof(T));
+ object result = null;
+ try
+ {
+ using (MemoryStream mem = new MemoryStream(Encoding.UTF8.GetBytes(json)))
+ {
+ result = deserializer.ReadObject(mem);
+ }
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine(ex.Message);
+ Debug.WriteLine("Failed to deserialize " + typeof(T) + " with JSON value :: " + json);
+ }
+
+ return (T)result;
+
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/cordova-wp8/blob/c74fdafa/template/cordovalib/MimeTypeMapper.cs
----------------------------------------------------------------------
diff --git a/template/cordovalib/MimeTypeMapper.cs b/template/cordovalib/MimeTypeMapper.cs
new file mode 100644
index 0000000..a2794f5
--- /dev/null
+++ b/template/cordovalib/MimeTypeMapper.cs
@@ -0,0 +1,101 @@
+/*
+ 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.
+*/
+
+using System.Collections.Generic;
+using System.IO;
+
+namespace WPCordovaClassLib.Cordova.Commands
+{
+ /// <summary>
+ /// Represents file extension to mime type mapper.
+ /// </summary>
+ public static class MimeTypeMapper
+ {
+ /// <summary>
+ /// For unknown type it is recommended to use 'application/octet-stream'
+ /// http://stackoverflow.com/questions/1176022/unknown-file-type-mime
+ /// </summary>
+ private static string DefaultMimeType = "application/octet-stream";
+
+ /// <summary>
+ /// Stores mime type for all necessary extension
+ /// </summary>
+ private static readonly Dictionary<string, string> MIMETypesDictionary = new Dictionary<string, string>
+ {
+ {"avi", "video/x-msvideo"},
+ {"bmp", "image/bmp"},
+ {"gif", "image/gif"},
+ {"html","text/html"},
+ {"jpe", "image/jpeg"},
+ {"jpeg", "image/jpeg"},
+ {"jpg", "image/jpeg"},
+ {"js","text/javascript"},
+ {"mov", "video/quicktime"},
+ {"mp2", "audio/mpeg"},
+ {"mp3", "audio/mpeg"},
+ {"mp4", "video/mp4"},
+ {"mpe", "video/mpeg"},
+ {"mpeg", "video/mpeg"},
+ {"mpg", "video/mpeg"},
+ {"mpga", "audio/mpeg"},
+ {"pbm", "image/x-portable-bitmap"},
+ {"pcm", "audio/x-pcm"},
+ {"pct", "image/pict"},
+ {"pgm", "image/x-portable-graymap"},
+ {"pic", "image/pict"},
+ {"pict", "image/pict"},
+ {"png", "image/png"},
+ {"pnm", "image/x-portable-anymap"},
+ {"pnt", "image/x-macpaint"},
+ {"pntg", "image/x-macpaint"},
+ {"ppm", "image/x-portable-pixmap"},
+ {"qt", "video/quicktime"},
+ {"ra", "audio/x-pn-realaudio"},
+ {"ram", "audio/x-pn-realaudio"},
+ {"ras", "image/x-cmu-raster"},
+ {"rgb", "image/x-rgb"},
+ {"snd", "audio/basic"},
+ {"txt", "text/plain"},
+ {"tif", "image/tiff"},
+ {"tiff", "image/tiff"},
+ {"wav", "audio/x-wav"},
+ {"wbmp", "image/vnd.wap.wbmp"},
+
+ };
+ /// <summary>
+ /// Gets mime type by file extension
+ /// </summary>
+ /// <param name="fileName">file name to extract extension</param>
+ /// <returns>mime type</returns>
+ public static string GetMimeType(string fileName)
+ {
+ string ext = Path.GetExtension(fileName);
+
+ // invalid extension
+ if (string.IsNullOrEmpty(ext) || !ext.StartsWith("."))
+ {
+ return DefaultMimeType;
+ }
+
+ ext = ext.Remove(0, 1);
+
+ if (MIMETypesDictionary.ContainsKey(ext))
+ {
+ return MIMETypesDictionary[ext];
+ }
+
+ return DefaultMimeType;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/cordova-wp8/blob/c74fdafa/template/cordovalib/NativeExecution.cs
----------------------------------------------------------------------
diff --git a/template/cordovalib/NativeExecution.cs b/template/cordovalib/NativeExecution.cs
new file mode 100644
index 0000000..18ca910
--- /dev/null
+++ b/template/cordovalib/NativeExecution.cs
@@ -0,0 +1,259 @@
+/*
+ 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.
+*/
+
+using System;
+using System.Diagnostics;
+using System.Threading;
+using Microsoft.Devices;
+using Microsoft.Phone.Controls;
+using WPCordovaClassLib.Cordova.Commands;
+using System.Collections.Generic;
+using System.Windows;
+
+namespace WPCordovaClassLib.Cordova
+{
+ /// <summary>
+ /// Implements logic to execute native command and return result back.
+ /// All commands are executed asynchronous.
+ /// </summary>
+ public class NativeExecution
+ {
+ /// <summary>
+ /// Reference to web part where application is hosted
+ /// </summary>
+ private readonly WebBrowser webBrowser;
+
+ /// <summary>
+ /// List of commands with attached handlers
+ /// </summary>
+ private List<BaseCommand> commands;
+
+ /// <summary>
+ /// Creates new instance of a NativeExecution class.
+ /// </summary>
+ /// <param name="browser">Reference to web part where application is hosted</param>
+ public NativeExecution(ref WebBrowser browser)
+ {
+ if (browser == null)
+ {
+ throw new ArgumentNullException("browser");
+ }
+
+ this.webBrowser = browser;
+ this.commands = new List<BaseCommand>();
+ webBrowser.Unloaded += webBrowser_Unloaded;
+ }
+
+ /// <summary>
+ /// Detaches event handlers to prevent memory leak on page navigation
+ /// </summary>
+ void webBrowser_Unloaded(object sender, RoutedEventArgs e)
+ {
+ for (int i = commands.Count - 1; i >= 0; i--)
+ {
+ if (commands[i] != null)
+ {
+ commands[i].DetachHandlers();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Returns where application is running on emulator
+ /// </summary>
+ /// <returns>True if running on emulator, otherwise False</returns>
+ public static bool IsRunningOnEmulator()
+ {
+ return Microsoft.Devices.Environment.DeviceType == DeviceType.Emulator;
+ }
+
+ public void ResetAllCommands()
+ {
+ CommandFactory.ResetAllCommands();
+ }
+
+ public void AutoLoadCommand(string commandService)
+ {
+ BaseCommand bc = CommandFactory.CreateByServiceName(commandService);
+ if (bc != null)
+ {
+ bc.OnInit();
+ }
+
+ }
+
+ /// <summary>
+ /// Executes command and returns result back.
+ /// </summary>
+ /// <param name="commandCallParams">Command to execute</param>
+ public void ProcessCommand(CordovaCommandCall commandCallParams)
+ {
+
+ if (commandCallParams == null)
+ {
+ throw new ArgumentNullException("commandCallParams");
+ }
+
+ try
+ {
+ BaseCommand bc = CommandFactory.CreateByServiceName(commandCallParams.Service, commandCallParams.Namespace);
+
+ if (bc == null)
+ {
+ this.OnCommandResult(commandCallParams.CallbackId, new PluginResult(PluginResult.Status.CLASS_NOT_FOUND_EXCEPTION));
+ return;
+ }
+
+ EventHandler<PluginResult> OnCommandResultHandler = delegate(object o, PluginResult res)
+ {
+ if (res.CallbackId == null || res.CallbackId == commandCallParams.CallbackId)
+ {
+ this.OnCommandResult(commandCallParams.CallbackId, res);
+ if (!res.KeepCallback)
+ {
+ bc.RemoveResultHandler(commandCallParams.CallbackId);
+ }
+ }
+ };
+
+ //bc.OnCommandResult += OnCommandResultHandler;
+ bc.AddResultHandler(commandCallParams.CallbackId, OnCommandResultHandler);
+
+ EventHandler<ScriptCallback> OnCustomScriptHandler = delegate(object o, ScriptCallback script)
+ {
+ this.InvokeScriptCallback(script);
+ };
+
+ bc.OnCustomScript += OnCustomScriptHandler;
+
+ ThreadStart methodInvokation = () =>
+ {
+ try
+ {
+ bc.InvokeMethodNamed(commandCallParams.CallbackId, commandCallParams.Action, commandCallParams.Args);
+ commands.Add(bc);
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine("ERROR: Exception in ProcessCommand :: " + ex.Message);
+ bc.RemoveResultHandler(commandCallParams.CallbackId);
+ bc.OnCustomScript -= OnCustomScriptHandler;
+
+ Debug.WriteLine("ERROR: failed to InvokeMethodNamed :: " + commandCallParams.Action + " on Object :: " + commandCallParams.Service);
+ this.OnCommandResult(commandCallParams.CallbackId, new PluginResult(PluginResult.Status.INVALID_ACTION));
+ return;
+ }
+ };
+
+ new Thread(methodInvokation).Start();
+
+ }
+ catch (Exception ex)
+ {
+ // ERROR
+ Debug.WriteLine(String.Format("ERROR: Unable to execute command :: {0}:{1}:{2} ",
+ commandCallParams.Service, commandCallParams.Action, ex.Message));
+
+ this.OnCommandResult(commandCallParams.CallbackId, new PluginResult(PluginResult.Status.ERROR));
+ return;
+ }
+ }
+
+ /// <summary>
+ /// Handles command execution result.
+ /// </summary>
+ /// <param name="callbackId">Command callback identifier on client side</param>
+ /// <param name="result">Execution result</param>
+ private void OnCommandResult(string callbackId, PluginResult result)
+ {
+ #region args checking
+
+ if (result == null)
+ {
+ Debug.WriteLine("ERROR: OnCommandResult missing result argument");
+ return;
+ }
+
+ if (String.IsNullOrEmpty(callbackId))
+ {
+ Debug.WriteLine("ERROR: OnCommandResult missing callbackId argument");
+ return;
+ }
+
+ if (!String.IsNullOrEmpty(result.CallbackId) && callbackId != result.CallbackId)
+ {
+ Debug.WriteLine("Multiple Overlapping Results :: " + result.CallbackId + " :: " + callbackId);
+ return;
+ }
+
+ #endregion
+
+ string jsonResult = result.ToJSONString();
+
+ string callback;
+ string args = string.Format("('{0}',{1});", callbackId, jsonResult);
+
+ if (result.Result == PluginResult.Status.NO_RESULT ||
+ result.Result == PluginResult.Status.OK)
+ {
+ callback = @"(function(callbackId,args) {
+ try { args.message = JSON.parse(args.message); } catch (ex) { }
+ cordova.callbackSuccess(callbackId,args);
+ })" + args;
+ }
+ else
+ {
+ callback = @"(function(callbackId,args) {
+ try { args.message = JSON.parse(args.message); } catch (ex) { }
+ cordova.callbackError(callbackId,args);
+ })" + args;
+ }
+ this.InvokeScriptCallback(new ScriptCallback("eval", new string[] { callback }));
+
+ }
+
+ /// <summary>
+ /// Executes client java script
+ /// </summary>
+ /// <param name="script">Script to execute on client side</param>
+ private void InvokeScriptCallback(ScriptCallback script)
+ {
+ if (script == null)
+ {
+ throw new ArgumentNullException("script");
+ }
+
+ if (String.IsNullOrEmpty(script.ScriptName))
+ {
+ throw new ArgumentNullException("ScriptName");
+ }
+
+ //Debug.WriteLine("INFO:: About to invoke ::" + script.ScriptName + " with args ::" + script.Args[0]);
+ this.webBrowser.Dispatcher.BeginInvoke((ThreadStart)delegate()
+ {
+ try
+ {
+ //Debug.WriteLine("INFO:: InvokingScript::" + script.ScriptName + " with args ::" + script.Args[0]);
+ this.webBrowser.InvokeScript(script.ScriptName, script.Args);
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine("ERROR: Exception in InvokeScriptCallback :: " + ex.Message);
+ }
+
+ });
+ }
+
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/cordova-wp8/blob/c74fdafa/template/cordovalib/OrientationHelper.cs
----------------------------------------------------------------------
diff --git a/template/cordovalib/OrientationHelper.cs b/template/cordovalib/OrientationHelper.cs
new file mode 100644
index 0000000..299f9dd
--- /dev/null
+++ b/template/cordovalib/OrientationHelper.cs
@@ -0,0 +1,131 @@
+/*
+ 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.
+*/
+
+using System;
+using System.Net;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Documents;
+using System.Windows.Ink;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Animation;
+using System.Windows.Shapes;
+using Microsoft.Phone.Controls;
+using WPCordovaClassLib.CordovaLib;
+
+namespace WPCordovaClassLib.Cordova
+{
+ public class OrientationHelper : IBrowserDecorator
+ {
+ public WebBrowser Browser { get; set; }
+
+ public PhoneApplicationPage Page
+ {
+ get
+ {
+ PhoneApplicationFrame frame = Application.Current.RootVisual as PhoneApplicationFrame;
+ if (frame != null)
+ {
+ return frame.Content as PhoneApplicationPage;
+ }
+ return null;
+ }
+ }
+
+ // private PageOrientation CurrentOrientation = PageOrientation.PortraitUp;
+ //private PageOrientation[] SupportedOrientations; // TODO:
+
+ public void InjectScript()
+ {
+ int i = 0;
+
+ switch (Page.Orientation)
+ {
+ case PageOrientation.Portrait: // intentional fall through
+ case PageOrientation.PortraitUp:
+ i = 0;
+ break;
+ case PageOrientation.PortraitDown:
+ i = 180;
+ break;
+ case PageOrientation.Landscape: // intentional fall through
+ case PageOrientation.LandscapeLeft:
+ i = -90;
+ break;
+ case PageOrientation.LandscapeRight:
+ i = 90;
+ break;
+ }
+ string jsCallback = String.Format("window.orientation = {0};", i);
+
+ try
+ {
+ Browser.InvokeScript("eval", new string[] { jsCallback });
+ }
+ catch (Exception)
+ {
+ }
+ }
+
+ void page_OrientationChanged(object sender, OrientationChangedEventArgs e)
+ {
+ int i = 0;
+
+ switch (e.Orientation)
+ {
+ case PageOrientation.Portrait: // intentional fall through
+ case PageOrientation.PortraitUp:
+ i = 0;
+ break;
+ case PageOrientation.PortraitDown:
+ i = 180;
+ break;
+ case PageOrientation.Landscape: // intentional fall through
+ case PageOrientation.LandscapeLeft:
+ i = -90;
+ break;
+ case PageOrientation.LandscapeRight:
+ i = 90;
+ break;
+ }
+ // Cordova.fireEvent('orientationchange', window);
+ string jsCallback = String.Format("window.orientation = {0};", i);
+
+ try
+ {
+
+ Browser.InvokeScript("eval", new string[] { jsCallback });
+
+ jsCallback = "var evt = document.createEvent('HTMLEvents');";
+ jsCallback += "evt.initEvent( 'orientationchange', true, false );";
+ jsCallback += "window.dispatchEvent(evt);";
+ jsCallback += "if(window.onorientationchange){window.onorientationchange(evt);}";
+
+ Browser.InvokeScript("eval", new string[] {jsCallback});
+ }
+ catch (Exception)
+ {
+ }
+ }
+
+ public bool HandleCommand(string commandStr)
+ {
+ // No commands are currently accepted.
+ return true;
+ }
+ }
+
+
+}
http://git-wip-us.apache.org/repos/asf/cordova-wp8/blob/c74fdafa/template/cordovalib/PluginResult.cs
----------------------------------------------------------------------
diff --git a/template/cordovalib/PluginResult.cs b/template/cordovalib/PluginResult.cs
new file mode 100644
index 0000000..00017d2
--- /dev/null
+++ b/template/cordovalib/PluginResult.cs
@@ -0,0 +1,139 @@
+/*
+ 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.
+*/
+
+using System;
+using System.Net;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Documents;
+using System.Windows.Ink;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Animation;
+using System.Windows.Shapes;
+using System.Text;
+using System.Diagnostics;
+
+namespace WPCordovaClassLib.Cordova
+{
+ /// <summary>
+ /// Represents command execution result
+ /// </summary>
+ public class PluginResult : EventArgs
+ {
+ /// <summary>
+ /// Predefined resultant messages
+ /// </summary>
+ public static string[] StatusMessages = new string[]
+ {
+ "No result",
+ "OK",
+ "Class not found",
+ "Illegal access",
+ "Instantiation error",
+ "Malformed url",
+ "IO error",
+ "Invalid action",
+ "JSON error",
+ "Error"
+ };
+
+ /// <summary>
+ /// Possible command results status codes
+ /// </summary>
+ public enum Status : int
+ {
+ NO_RESULT = 0,
+ OK,
+ CLASS_NOT_FOUND_EXCEPTION,
+ ILLEGAL_ACCESS_EXCEPTION,
+ INSTANTIATION_EXCEPTION,
+ MALFORMED_URL_EXCEPTION,
+ IO_EXCEPTION,
+ INVALID_ACTION,
+ JSON_EXCEPTION,
+ ERROR
+ };
+
+ public Status Result { get; private set; }
+ public string Message { get; set; }
+ public bool KeepCallback { get; set; }
+ public string CallbackId { get; set; }
+
+ /// <summary>
+ /// Whether command succeded or not
+ /// </summary>
+ public bool IsSuccess
+ {
+ get
+ {
+ return this.Result == Status.OK || this.Result == Status.NO_RESULT;
+ }
+ }
+
+ /// <summary>
+ /// Creates new instance of the PluginResult class.
+ /// </summary>
+ /// <param name="status">Execution result</param>
+ public PluginResult(Status status)
+ : this(status, PluginResult.StatusMessages[(int)status])
+ {
+ }
+
+ /// <summary>
+ /// Creates new instance of the PluginResult class.
+ /// </summary>
+ /// <param name="status">Execution result</param>
+ /// <param name="message">The message</param>
+ public PluginResult(Status status, object message)
+ {
+ this.Result = status;
+ this.Message = JSON.JsonHelper.Serialize(message);
+ }
+
+ public string ToJSONString()
+ {
+ string res = String.Format("\"status\":{0},\"message\":{1},\"keepCallback\":{2}",
+ (int)this.Result,
+ this.Message,
+ this.KeepCallback.ToString().ToLower());
+
+ res = "{" + res + "}";
+ return res;
+
+ }
+
+ [Obsolete]
+ public string ToCallbackString(string callbackId, string successCallback, string errorCallback)
+ {
+ if (this.IsSuccess)
+ {
+ StringBuilder buf = new StringBuilder("");
+ buf.Append(String.Format("{0}('{1}',{2});", successCallback, callbackId, this.ToJSONString()));
+ return buf.ToString();
+ }
+ else
+ {
+ return String.Format("{0}('{1}',{2});", errorCallback, callbackId, this.ToJSONString());
+ }
+ }
+
+ public override String ToString()
+ {
+ return this.ToJSONString();
+ }
+
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/cordova-wp8/blob/c74fdafa/template/cordovalib/ScriptCallback.cs
----------------------------------------------------------------------
diff --git a/template/cordovalib/ScriptCallback.cs b/template/cordovalib/ScriptCallback.cs
new file mode 100644
index 0000000..05bba06
--- /dev/null
+++ b/template/cordovalib/ScriptCallback.cs
@@ -0,0 +1,75 @@
+/*
+ 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.
+*/
+
+using System;
+using System.Net;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Documents;
+using System.Windows.Ink;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Animation;
+using System.Windows.Shapes;
+using WPCordovaClassLib.Cordova.JSON;
+using System.Diagnostics;
+
+namespace WPCordovaClassLib.Cordova
+{
+ /// <summary>
+ /// Represents client script function to execute
+ /// </summary>
+ public class ScriptCallback : EventArgs
+ {
+ /// <summary>
+ /// The scripting function to execute.
+ /// </summary>
+ public string ScriptName { get; private set; }
+
+ /// <summary>
+ /// A variable number of strings to pass to the function as parameters.
+ /// </summary>
+ public string[] Args { get; private set; }
+
+ /// <summary>
+ /// Creates new instance of a ScriptCallback class.
+ /// </summary>
+ /// <param name="function">The scripting function to execute</param>
+ /// <param name="args">A variable number of strings to pass to the function as parameters</param>
+ public ScriptCallback(string function, string[] args)
+ {
+ this.ScriptName = function;
+ this.Args = args;
+ }
+
+ /// <summary>
+ /// Creates new instance of a ScriptCallback class.
+ /// </summary>
+ /// <param name="function">The scripting function to execute</param>
+ /// <param name="id">The id argument</param>
+ /// <param name="msg">The message argument</param>
+ /// <param name="value">The value argument</param>
+ public ScriptCallback(string function, string id, object msg, object value)
+ {
+ this.ScriptName = function;
+
+ String arg = String.Format("{{\"id\": {0}, \"msg\": {1}, \"value\": {2}}}",
+ JsonHelper.Serialize(id), JsonHelper.Serialize(msg), JsonHelper.Serialize(value));
+
+ this.Args = new string[] { arg };
+ }
+
+
+ }
+}
http://git-wip-us.apache.org/repos/asf/cordova-wp8/blob/c74fdafa/template/cordovalib/XHRHelper.cs
----------------------------------------------------------------------
diff --git a/template/cordovalib/XHRHelper.cs b/template/cordovalib/XHRHelper.cs
new file mode 100644
index 0000000..81256fe
--- /dev/null
+++ b/template/cordovalib/XHRHelper.cs
@@ -0,0 +1,341 @@
+/*
+ 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.
+*/
+
+using Microsoft.Phone.Controls;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.IO.IsolatedStorage;
+using System.Linq;
+using System.Text;
+using System.Windows;
+
+namespace WPCordovaClassLib.CordovaLib
+{
+ public class XHRHelper : IBrowserDecorator
+ {
+
+ public WebBrowser Browser { get; set; }
+ public PhoneApplicationPage Page { get; set; }
+
+ public void InjectScript()
+ {
+ string script = @"(function(win, doc) {
+
+ var __XHRShimAliases = {};
+
+ window.__onXHRLocalCallback = function (responseCode, responseText, reqId) {
+ if (__XHRShimAliases[reqId]){
+ var alias = __XHRShimAliases[reqId];
+ if (alias){
+ delete __XHRShimAliases[reqId];
+ if (responseCode == '200'){
+ alias.onResult && alias.onResult(responseText);
+ Object.defineProperty(alias, 'responseXML', {
+ get: function () {
+ return new DOMParser().parseFromString(this.responseText, 'text/xml');
+ }
+ });
+ } else {
+ alias.onError && alias.onError(responseText);
+ }
+ }
+ }
+ };
+
+ var docDomain = null;
+ try {
+ docDomain = doc.domain;
+ } catch (err) {}
+
+ if (!docDomain || docDomain.length === 0) {
+
+ var aliasXHR = win.XMLHttpRequest;
+
+ var XHRShim = function() {};
+ win.XMLHttpRequest = XHRShim;
+ XHRShim.noConflict = aliasXHR;
+ XHRShim.UNSENT = 0;
+ XHRShim.OPENED = 1;
+ XHRShim.HEADERS_RECEIVED = 2;
+ XHRShim.LOADING = 3;
+ XHRShim.DONE = 4;
+ XHRShim.prototype = {
+ isAsync: false,
+ onreadystatechange: null,
+ readyState: 0,
+ _url: '',
+ timeout: 0,
+ withCredentials: false,
+ _requestHeaders: null,
+ open: function (reqType, uri, isAsync, user, password) {
+
+ if (uri && uri.indexOf('http') === 0) {
+ if (!this.wrappedXHR) {
+ this.wrappedXHR = new aliasXHR();
+ var self = this;
+ if (this.timeout > 0) {
+ this.wrappedXHR.timeout = this.timeout;
+ }
+ Object.defineProperty(this, 'timeout', {
+ set: function(val) {
+ this.wrappedXHR.timeout = val;
+ },
+ get: function() {
+ return this.wrappedXHR.timeout;
+ }
+ });
+ if (this.withCredentials) {
+ this.wrappedXHR.withCredentials = this.withCredentials;
+ }
+ Object.defineProperty(this, 'withCredentials', {
+ set: function(val) {
+ this.wrappedXHR.withCredentials = val;
+ },
+ get: function() {
+ return this.wrappedXHR.withCredentials;
+ }
+ });
+ Object.defineProperty(this, 'status', {
+ get: function() {
+ return this.wrappedXHR.status;
+ }
+ });
+ Object.defineProperty(this, 'responseText', {
+ get: function() {
+ return this.wrappedXHR.responseText;
+ }
+ });
+ Object.defineProperty(this, 'statusText', {
+ get: function() {
+ return this.wrappedXHR.statusText;
+ }
+ });
+ Object.defineProperty(this, 'responseXML', {
+ get: function() {
+ return this.wrappedXHR.responseXML;
+ }
+ });
+ Object.defineProperty(this, 'response', {
+ get: function() {
+ return this.wrappedXHR.response;
+ }
+ });
+ Object.defineProperty(this, 'responseType', {
+ set: function(val) {
+ return this.wrappedXHR.responseType = val;
+ }
+ });
+ this.getResponseHeader = function(header) {
+ return this.wrappedXHR.getResponseHeader(header);
+ };
+ this.getAllResponseHeaders = function() {
+ return this.wrappedXHR.getAllResponseHeaders();
+ };
+ this.wrappedXHR.onreadystatechange = function() {
+ self.changeReadyState(self.wrappedXHR.readyState);
+ };
+ }
+ return this.wrappedXHR.open(reqType, uri, isAsync, user, password);
+ }
+ else
+ {
+ this.isAsync = isAsync;
+ this.reqType = reqType;
+ this._url = uri;
+ }
+ },
+ statusText: '',
+ changeReadyState: function(newState) {
+ this.readyState = newState;
+ if (this.onreadystatechange) {
+ // mimic simple 'readystatechange' event which should be passed as per spec
+ var evt = {type: 'readystatechange', target: this, timeStamp: new Date().getTime()};
+ this.onreadystatechange(evt);
+ }
+ if (this.readyState == XHRShim.DONE){
+ this.onload && this.onload();
+ }
+ },
+ addEventListener: function (type, listener, useCapture){
+ if (this.wrappedXHR) {
+ this.wrappedXHR.addEventListener(type, listener, useCapture);
+ } else {
+ this['on' + type] = listener;
+ }
+ },
+ removeEventListener: function (type, listener, useCapture){
+ if (this.wrappedXHR) {
+ this.wrappedXHR.removeEventListener(type, listener, useCapture);
+ } else {
+ if (this['on' + type] == listener) { // if listener is currently used
+ delete this['on' + type];
+ }
+ }
+ },
+ setRequestHeader: function(header, value) {
+ if (this.wrappedXHR) {
+ this.wrappedXHR.setRequestHeader(header, value);
+ }
+ },
+ getResponseHeader: function(header) {
+ return this.wrappedXHR ? this.wrappedXHR.getResponseHeader(header) : '';
+ },
+ getAllResponseHeaders: function() {
+ return this.wrappedXHR ? this.wrappedXHR.getAllResponseHeaders() : '';
+ },
+ overrideMimeType: function(mimetype) {
+ return this.wrappedXHR ? this.wrappedXHR.overrideMimeType(mimetype) : '';
+ },
+ responseText: '',
+ responseXML: '',
+ onResult: function(res) {
+ this.status = 200;
+ if (typeof res == 'object') {
+ res = JSON.stringify(res);
+ }
+ this.responseText = res;
+ this.responseXML = res;
+ this.changeReadyState(XHRShim.DONE);
+ },
+ onError: function(err) {
+ this.status = 404;
+ this.changeReadyState(XHRShim.DONE);
+ },
+ abort: function() {
+ if (this.wrappedXHR) {
+ return this.wrappedXHR.abort();
+ }
+ },
+ send: function(data) {
+ if (this.wrappedXHR) {
+ return this.wrappedXHR.send(data);
+ }
+ else {
+ this.changeReadyState(XHRShim.OPENED);
+ var alias = this;
+
+ var root = window.location.href.split('#')[0]; // remove hash
+
+ var rootPath = root.substr(0,root.lastIndexOf('/')) + '/';
+ // Removing unwanted slashes after x-wmapp0 from the basePath, URI cannot process x-wmapp0: /www or //www
+ var basePath = rootPath.replace(/:\/+/gi, ':');
+
+ //console.log( 'Stripping protocol if present and removing leading / characters' );
+ var resolvedUrl =
+ // remove protocol from the beginning of the url if present
+ ( this._url.indexOf( window.location.protocol ) === 0 ?
+ this._url.substring( window.location.protocol.length ) :
+ this._url )
+ // get rid of all the starting slashes
+ .replace(/^[/]*/, '')
+ .split('#')[0]; // remove hash
+
+ var wwwFolderPath = navigator.userAgent.indexOf('MSIE 9.0') > -1 ? 'app/www/' : 'www/';
+
+ // handle special case where url is of form app/www but we are loaded just from /www
+ if( resolvedUrl.indexOf('app/www') == 0 ) {
+ resolvedUrl = window.location.protocol + wwwFolderPath + resolvedUrl.substr(7);
+ }
+ else if( resolvedUrl.indexOf('www') == 0) {
+ resolvedUrl = window.location.protocol + wwwFolderPath + resolvedUrl.substr(4);
+ }
+
+ if(resolvedUrl.indexOf(':') < 0) {
+ resolvedUrl = basePath + resolvedUrl; // consider it relative
+ }
+
+ // Generate unique request ID
+ var reqId = new Date().getTime().toString() + Math.random();
+
+ var funk = function () {
+ __XHRShimAliases[reqId] = alias;
+
+ alias.changeReadyState(XHRShim.LOADING);
+ window.external.Notify('XHRLOCAL/' + reqId + '/' + resolvedUrl);
+ };
+
+ this.isAsync ? setTimeout(funk, 0) : funk();
+ }
+ },
+ status: 404
+ };
+ }
+})(window, document); ";
+
+
+ Browser.InvokeScript("eval", new string[] { script });
+ }
+
+ public bool HandleCommand(string commandStr)
+ {
+ if (commandStr.IndexOf("XHRLOCAL") == 0)
+ {
+ var reqStr = commandStr.Replace("XHRLOCAL/", "").Split(new char[] {'/'}, 2);
+ string reqId = reqStr[0];
+ string url = reqStr[1];
+
+ Uri uri = new Uri(url, UriKind.RelativeOrAbsolute);
+ try
+ {
+ using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ if (isoFile.FileExists(uri.AbsolutePath))
+ {
+ using (TextReader reader = new StreamReader(isoFile.OpenFile(uri.AbsolutePath, FileMode.Open, FileAccess.Read)))
+ {
+ string text = reader.ReadToEnd();
+ Browser.InvokeScript("__onXHRLocalCallback", new string[] { "200", text, reqId });
+ return true;
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine("ERROR: Exception in HandleCommand: " + ex);
+ }
+
+ Uri relUri = new Uri(uri.AbsolutePath, UriKind.Relative);
+
+ var resource = Application.GetResourceStream(relUri);
+ try
+ {
+ if (resource == null)
+ {
+ // 404 ?
+ Browser.InvokeScript("__onXHRLocalCallback", new string[] { "404", string.Empty, reqId });
+ return true;
+ }
+ else
+ {
+ using (StreamReader streamReader = new StreamReader(resource.Stream))
+ {
+ string text = streamReader.ReadToEnd();
+ Browser.InvokeScript("__onXHRLocalCallback", new string[] { "200", text, reqId });
+ return true;
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine("ERROR: Exception in HandleCommand: " + ex);
+ }
+ }
+
+ return false;
+ }
+ }
+}