You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by mw...@apache.org on 2013/05/15 22:35:58 UTC

[19/37] Add WP7 and WP8 platform files.

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/f59ddbbd/lib/cordova-wp8/templates/standalone/Plugins/Camera.cs
----------------------------------------------------------------------
diff --git a/lib/cordova-wp8/templates/standalone/Plugins/Camera.cs b/lib/cordova-wp8/templates/standalone/Plugins/Camera.cs
new file mode 100644
index 0000000..5ff8045
--- /dev/null
+++ b/lib/cordova-wp8/templates/standalone/Plugins/Camera.cs
@@ -0,0 +1,490 @@
+/*  
+	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.Collections.Generic;
+using Microsoft.Phone.Tasks;
+using System.Runtime.Serialization;
+using System.IO;
+using System.IO.IsolatedStorage;
+using System.Windows.Media.Imaging;
+using Microsoft.Phone;
+using Microsoft.Xna.Framework.Media;
+using System.Diagnostics;
+
+namespace WPCordovaClassLib.Cordova.Commands
+{
+    public class Camera : BaseCommand
+    {
+
+        /// <summary>
+        /// Return base64 encoded string
+        /// </summary>
+        private const int DATA_URL = 0;
+
+        /// <summary>
+        /// Return file uri
+        /// </summary>
+        private const int FILE_URI = 1;
+
+        /// <summary>
+        /// Choose image from picture library
+        /// </summary>
+        private const int PHOTOLIBRARY = 0;
+
+        /// <summary>
+        /// Take picture from camera
+        /// </summary>
+
+        private const int CAMERA = 1;
+
+        /// <summary>
+        /// Choose image from picture library
+        /// </summary>
+        private const int SAVEDPHOTOALBUM = 2;
+
+        /// <summary>
+        /// Take a picture of type JPEG
+        /// </summary>
+        private const int JPEG = 0;
+
+        /// <summary>
+        /// Take a picture of type PNG
+        /// </summary>
+        private const int PNG = 1;
+
+        /// <summary>
+        /// Folder to store captured images
+        /// </summary>
+        private const string isoFolder = "CapturedImagesCache";
+
+        /// <summary>
+        /// Represents captureImage action options.
+        /// </summary>
+        [DataContract]
+        public class CameraOptions
+        {
+            /// <summary>
+            /// Source to getPicture from.
+            /// </summary>
+            [DataMember(IsRequired = false, Name = "sourceType")]
+            public int PictureSourceType { get; set; }
+
+            /// <summary>
+            /// Format of image that returned from getPicture.
+            /// </summary>
+            [DataMember(IsRequired = false, Name = "destinationType")]
+            public int DestinationType { get; set; }
+
+            /// <summary>
+            /// Quality of saved image
+            /// </summary>
+            [DataMember(IsRequired = false, Name = "quality")]
+            public int Quality { get; set; }
+
+            /// <summary>
+            /// Controls whether or not the image is also added to the device photo album.
+            /// </summary>
+            [DataMember(IsRequired = false, Name = "saveToPhotoAlbum")]
+            public bool SaveToPhotoAlbum { get; set; }
+
+            /// <summary>
+            /// Ignored
+            /// </summary>
+            [DataMember(IsRequired = false, Name = "correctOrientation")]
+            public bool CorrectOrientation { get; set; }
+
+            
+
+            /// <summary>
+            /// Ignored
+            /// </summary>
+            [DataMember(IsRequired = false, Name = "allowEdit")]
+            public bool AllowEdit { get; set; }
+
+                        /// <summary>
+            /// Height in pixels to scale image
+            /// </summary>
+            [DataMember(IsRequired = false, Name = "encodingType")]
+            public int EncodingType { get; set; }
+
+                        /// <summary>
+            /// Height in pixels to scale image
+            /// </summary>
+            [DataMember(IsRequired = false, Name = "mediaType")]
+            public int MediaType { get; set; }
+
+
+            /// <summary>
+            /// Height in pixels to scale image
+            /// </summary>
+            [DataMember(IsRequired = false, Name = "targetHeight")]
+            public int TargetHeight { get; set; }
+
+
+            /// <summary>
+            /// Width in pixels to scale image
+            /// </summary>
+            [DataMember(IsRequired = false, Name = "targetWidth")]
+            public int TargetWidth { get; set; }
+
+            /// <summary>
+            /// Creates options object with default parameters
+            /// </summary>
+            public CameraOptions()
+            {
+                this.SetDefaultValues(new StreamingContext());
+            }
+
+            /// <summary>
+            /// Initializes default values for class fields.
+            /// Implemented in separate method because default constructor is not invoked during deserialization.
+            /// </summary>
+            /// <param name="context"></param>
+            [OnDeserializing()]
+            public void SetDefaultValues(StreamingContext context)
+            {
+                PictureSourceType = CAMERA;
+                DestinationType = FILE_URI;
+                Quality = 80;
+                TargetHeight = -1;
+                TargetWidth = -1;
+                SaveToPhotoAlbum = false;
+                CorrectOrientation = true;
+                AllowEdit = false;
+                MediaType = -1;
+                EncodingType = -1;
+            }
+        }
+
+        /// <summary>
+        /// Used to open photo library
+        /// </summary>
+        PhotoChooserTask photoChooserTask;
+
+        /// <summary>
+        /// Used to open camera application
+        /// </summary>
+        CameraCaptureTask cameraTask;
+
+        /// <summary>
+        /// Camera options
+        /// </summary>
+        CameraOptions cameraOptions;
+
+        public void takePicture(string options)
+        {
+            try
+            {
+                string[] args = JSON.JsonHelper.Deserialize<string[]>(options);
+                // ["quality", "destinationType", "sourceType", "targetWidth", "targetHeight", "encodingType",
+                //     "mediaType", "allowEdit", "correctOrientation", "saveToPhotoAlbum" ]
+                this.cameraOptions = new CameraOptions();
+                this.cameraOptions.Quality = int.Parse(args[0]);
+                this.cameraOptions.DestinationType = int.Parse(args[1]);
+                this.cameraOptions.PictureSourceType = int.Parse(args[2]);
+                this.cameraOptions.TargetWidth = int.Parse(args[3]);
+                this.cameraOptions.TargetHeight = int.Parse(args[4]);
+                this.cameraOptions.EncodingType = int.Parse(args[5]);
+                this.cameraOptions.MediaType = int.Parse(args[6]);
+                this.cameraOptions.AllowEdit = bool.Parse(args[7]);
+                this.cameraOptions.CorrectOrientation = bool.Parse(args[8]);
+                this.cameraOptions.SaveToPhotoAlbum = bool.Parse(args[9]);
+                
+                //this.cameraOptions = String.IsNullOrEmpty(options) ?
+                //        new CameraOptions() : JSON.JsonHelper.Deserialize<CameraOptions>(options);
+            }
+            catch (Exception ex)
+            {
+                this.DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION, ex.Message));
+                return;
+            }
+
+            //TODO Check if all the options are acceptable
+
+
+            if (cameraOptions.PictureSourceType == CAMERA)
+            {
+                cameraTask = new CameraCaptureTask();
+                cameraTask.Completed += onCameraTaskCompleted;
+                cameraTask.Show();
+            }
+            else
+            {
+                if ((cameraOptions.PictureSourceType == PHOTOLIBRARY) || (cameraOptions.PictureSourceType == SAVEDPHOTOALBUM))
+                {
+                    photoChooserTask = new PhotoChooserTask();
+                    photoChooserTask.Completed += onPickerTaskCompleted;
+                    photoChooserTask.Show();
+                }
+                else
+                {
+                    DispatchCommandResult(new PluginResult(PluginResult.Status.NO_RESULT));
+                }
+            }
+
+        }
+
+        public void onCameraTaskCompleted(object sender, PhotoResult e)
+        {
+            if (e.Error != null)
+            {
+                DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR));
+                return;
+            }
+
+            switch (e.TaskResult)
+            {
+                case TaskResult.OK:
+                    try
+                    {
+                        string imagePathOrContent = string.Empty;
+
+                        if (cameraOptions.DestinationType == FILE_URI)
+                        {
+                            // Save image in media library
+                            if (cameraOptions.SaveToPhotoAlbum)
+                            {
+                                MediaLibrary library = new MediaLibrary();
+                                Picture pict = library.SavePicture(e.OriginalFileName, e.ChosenPhoto); // to save to photo-roll ...
+                            }
+
+                            int orient = ImageExifHelper.getImageOrientationFromStream(e.ChosenPhoto);
+                            int newAngle = 0;
+                            switch (orient)
+                            {
+                                case ImageExifOrientation.LandscapeLeft:
+                                    newAngle = 90;
+                                    break;
+                                case ImageExifOrientation.PortraitUpsideDown:
+                                    newAngle = 180;
+                                    break;
+                                case ImageExifOrientation.LandscapeRight:
+                                    newAngle = 270;
+                                    break;
+                                case ImageExifOrientation.Portrait:
+                                default: break; // 0 default already set
+                            }
+
+                            Stream rotImageStream = ImageExifHelper.RotateStream(e.ChosenPhoto, newAngle);
+
+                            // we should return stream position back after saving stream to media library
+                            rotImageStream.Seek(0, SeekOrigin.Begin);
+
+                            WriteableBitmap image = PictureDecoder.DecodeJpeg(rotImageStream);
+
+                            imagePathOrContent = this.SaveImageToLocalStorage(image, Path.GetFileName(e.OriginalFileName));
+
+
+                        }
+                        else if (cameraOptions.DestinationType == DATA_URL)
+                        {
+                            imagePathOrContent = this.GetImageContent(e.ChosenPhoto);
+                        }
+                        else
+                        {
+                            // TODO: shouldn't this happen before we launch the camera-picker?
+                            DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Incorrect option: destinationType"));
+                            return;
+                        }
+
+                        DispatchCommandResult(new PluginResult(PluginResult.Status.OK, imagePathOrContent));
+
+                    }
+                    catch (Exception)
+                    {
+                        DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Error retrieving image."));
+                    }
+                    break;
+
+                case TaskResult.Cancel:
+                    DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Selection cancelled."));
+                    break;
+
+                default:
+                    DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Selection did not complete!"));
+                    break;
+            }
+
+        }
+
+        public void onPickerTaskCompleted(object sender, PhotoResult e)
+        {
+            if (e.Error != null)
+            {
+                DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR));
+                return;
+            }
+
+            switch (e.TaskResult)
+            {
+                case TaskResult.OK:
+                    try
+                    {
+                        string imagePathOrContent = string.Empty;
+
+                        if (cameraOptions.DestinationType == FILE_URI)
+                        {
+                            WriteableBitmap image = PictureDecoder.DecodeJpeg(e.ChosenPhoto);
+                            imagePathOrContent = this.SaveImageToLocalStorage(image, Path.GetFileName(e.OriginalFileName));
+                        }
+                        else if (cameraOptions.DestinationType == DATA_URL)
+                        {
+                            imagePathOrContent = this.GetImageContent(e.ChosenPhoto);
+
+                        }
+                        else
+                        {
+                            // TODO: shouldn't this happen before we launch the camera-picker?
+                            DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Incorrect option: destinationType"));
+                            return;
+                        }
+
+                        DispatchCommandResult(new PluginResult(PluginResult.Status.OK, imagePathOrContent));
+
+                    }
+                    catch (Exception)
+                    {
+                        DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Error retrieving image."));
+                    }
+                    break;
+
+                case TaskResult.Cancel:
+                    DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Selection cancelled."));
+                    break;
+
+                default:
+                    DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Selection did not complete!"));
+                    break;
+            }
+        }
+
+        /// <summary>
+        /// Returns image content in a form of base64 string
+        /// </summary>
+        /// <param name="stream">Image stream</param>
+        /// <returns>Base64 representation of the image</returns>
+        private string GetImageContent(Stream stream)
+        {
+            int streamLength = (int)stream.Length;
+            byte[] fileData = new byte[streamLength + 1];
+            stream.Read(fileData, 0, streamLength);
+
+            //use photo's actual width & height if user doesn't provide width & height
+            if (cameraOptions.TargetWidth < 0 && cameraOptions.TargetHeight < 0)
+            {
+                stream.Close();
+                return Convert.ToBase64String(fileData);
+            }
+            else
+            {
+                // resize photo
+                byte[] resizedFile = ResizePhoto(stream, fileData);
+                stream.Close();
+                return Convert.ToBase64String(resizedFile);
+            }
+        }
+
+        /// <summary>
+        /// Resize image
+        /// </summary>
+        /// <param name="stream">Image stream</param>
+        /// <param name="fileData">File data</param>
+        /// <returns>resized image</returns>
+        private byte[] ResizePhoto(Stream stream, byte[] fileData)
+        {
+            int streamLength = (int)stream.Length;
+            int intResult = 0;
+
+            byte[] resizedFile;
+
+            stream.Read(fileData, 0, streamLength);
+
+            BitmapImage objBitmap = new BitmapImage();
+            MemoryStream objBitmapStream = new MemoryStream(fileData);
+            MemoryStream objBitmapStreamResized = new MemoryStream();
+            WriteableBitmap objWB;
+            objBitmap.SetSource(stream);
+            objWB = new WriteableBitmap(objBitmap);
+
+            // resize the photo with user defined TargetWidth & TargetHeight
+            Extensions.SaveJpeg(objWB, objBitmapStreamResized, cameraOptions.TargetWidth, cameraOptions.TargetHeight, 0, cameraOptions.Quality);
+
+            //Convert the resized stream to a byte array. 
+            streamLength = (int)objBitmapStreamResized.Length;
+            resizedFile = new Byte[streamLength]; //-1 
+            objBitmapStreamResized.Position = 0;
+            //for some reason we have to set Position to zero, but we don't have to earlier when we get the bytes from the chosen photo... 
+            intResult = objBitmapStreamResized.Read(resizedFile, 0, streamLength);
+
+            return resizedFile;
+        }
+
+        /// <summary>
+        /// Saves captured image in isolated storage
+        /// </summary>
+        /// <param name="imageFileName">image file name</param>
+        /// <returns>Image path</returns>
+        private string SaveImageToLocalStorage(WriteableBitmap image, string imageFileName)
+        {
+
+            if (image == null)
+            {
+                throw new ArgumentNullException("imageBytes");
+            }
+            try
+            {
+
+
+                var isoFile = IsolatedStorageFile.GetUserStoreForApplication();
+
+                if (!isoFile.DirectoryExists(isoFolder))
+                {
+                    isoFile.CreateDirectory(isoFolder);
+                }
+
+                string filePath = System.IO.Path.Combine("///" + isoFolder + "/", imageFileName);
+
+                using (var stream = isoFile.CreateFile(filePath))
+                {
+                    // resize image if Height and Width defined via options 
+                    if (cameraOptions.TargetHeight > 0 && cameraOptions.TargetWidth > 0)
+                    {
+                        image.SaveJpeg(stream, cameraOptions.TargetWidth, cameraOptions.TargetHeight, 0, cameraOptions.Quality);
+                    }
+                    else
+                    {
+                        image.SaveJpeg(stream, image.PixelWidth, image.PixelHeight, 0, cameraOptions.Quality);
+                    }
+                }
+
+                return new Uri(filePath, UriKind.Relative).ToString();
+            }
+            catch (Exception)
+            {
+                //TODO: log or do something else
+                throw;
+            }
+        }
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/f59ddbbd/lib/cordova-wp8/templates/standalone/Plugins/Capture.cs
----------------------------------------------------------------------
diff --git a/lib/cordova-wp8/templates/standalone/Plugins/Capture.cs b/lib/cordova-wp8/templates/standalone/Plugins/Capture.cs
new file mode 100644
index 0000000..5e14a16
--- /dev/null
+++ b/lib/cordova-wp8/templates/standalone/Plugins/Capture.cs
@@ -0,0 +1,736 @@
+/*  
+	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.Collections.Generic;
+using System.IO;
+using System.IO.IsolatedStorage;
+using System.Runtime.Serialization;
+using System.Windows.Media.Imaging;
+using Microsoft.Phone;
+using Microsoft.Phone.Tasks;
+using Microsoft.Xna.Framework.Media;
+using WPCordovaClassLib.Cordova.UI;
+using AudioResult = WPCordovaClassLib.Cordova.UI.AudioCaptureTask.AudioResult;
+using VideoResult = WPCordovaClassLib.Cordova.UI.VideoCaptureTask.VideoResult;
+using System.Windows;
+using System.Diagnostics;
+using Microsoft.Phone.Controls;
+
+namespace WPCordovaClassLib.Cordova.Commands
+{
+    /// <summary>
+    /// Provides access to the audio, image, and video capture capabilities of the device
+    /// </summary>
+    public class Capture : BaseCommand
+    {
+        #region Internal classes (options and resultant objects)
+
+        /// <summary>
+        /// Represents captureImage action options.
+        /// </summary>
+        [DataContract]
+        public class CaptureImageOptions
+        {
+            /// <summary>
+            /// The maximum number of images the device user can capture in a single capture operation. The value must be greater than or equal to 1 (defaults to 1).
+            /// </summary>
+            [DataMember(IsRequired = false, Name = "limit")]
+            public int Limit { get; set; }
+
+            public static CaptureImageOptions Default
+            {
+                get { return new CaptureImageOptions() { Limit = 1 }; }
+            }
+        }
+
+        /// <summary>
+        /// Represents captureAudio action options.
+        /// </summary>
+        [DataContract]
+        public class CaptureAudioOptions
+        {
+            /// <summary>
+            /// The maximum number of audio files the device user can capture in a single capture operation. The value must be greater than or equal to 1 (defaults to 1).
+            /// </summary>
+            [DataMember(IsRequired = false, Name = "limit")]
+            public int Limit { get; set; }
+
+            public static CaptureAudioOptions Default
+            {
+                get { return new CaptureAudioOptions() { Limit = 1 }; }
+            }
+        }
+
+        /// <summary>
+        /// Represents captureVideo action options.
+        /// </summary>
+        [DataContract]
+        public class CaptureVideoOptions
+        {
+            /// <summary>
+            /// The maximum number of video files the device user can capture in a single capture operation. The value must be greater than or equal to 1 (defaults to 1).
+            /// </summary>
+            [DataMember(IsRequired = false, Name = "limit")]
+            public int Limit { get; set; }
+
+            public static CaptureVideoOptions Default
+            {
+                get { return new CaptureVideoOptions() { Limit = 1 }; }
+            }
+        }
+
+        /// <summary>
+        /// Represents getFormatData action options.
+        /// </summary>
+        [DataContract]
+        public class MediaFormatOptions
+        {
+            /// <summary>
+            /// File path
+            /// </summary>
+            [DataMember(IsRequired = true, Name = "fullPath")]
+            public string FullPath { get; set; }
+
+            /// <summary>
+            /// File mime type
+            /// </summary>
+            [DataMember(Name = "type")]
+            public string Type { get; set; }
+
+        }
+
+        /// <summary>
+        /// Stores image info
+        /// </summary>
+        [DataContract]
+        public class MediaFile
+        {
+
+            [DataMember(Name = "name")]
+            public string FileName { get; set; }
+
+            [DataMember(Name = "fullPath")]
+            public string FilePath { get; set; }
+
+            [DataMember(Name = "type")]
+            public string Type { get; set; }
+
+            [DataMember(Name = "lastModifiedDate")]
+            public string LastModifiedDate { get; set; }
+
+            [DataMember(Name = "size")]
+            public long Size { get; set; }
+
+            public MediaFile(string filePath, Picture image)
+            {
+                this.FilePath = filePath;
+                this.FileName = System.IO.Path.GetFileName(this.FilePath);
+                this.Type = MimeTypeMapper.GetMimeType(FileName);
+                this.Size = image.GetImage().Length;
+
+                using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication())
+                {
+                    this.LastModifiedDate = storage.GetLastWriteTime(filePath).DateTime.ToString();
+                }
+
+            }
+
+            public MediaFile(string filePath, Stream stream)
+            {
+                this.FilePath = filePath;
+                this.FileName = System.IO.Path.GetFileName(this.FilePath);
+                this.Type = MimeTypeMapper.GetMimeType(FileName);
+                this.Size = stream.Length;
+
+                using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication())
+                {
+                    this.LastModifiedDate = storage.GetLastWriteTime(filePath).DateTime.ToString();
+                }
+            }
+        }
+
+        /// <summary>
+        /// Stores additional media file data
+        /// </summary>
+        [DataContract]
+        public class MediaFileData
+        {
+            [DataMember(Name = "height")]
+            public int Height { get; set; }
+
+            [DataMember(Name = "width")]
+            public int Width { get; set; }
+
+            [DataMember(Name = "bitrate")]
+            public int Bitrate { get; set; }
+
+            [DataMember(Name = "duration")]
+            public int Duration { get; set; }
+
+            [DataMember(Name = "codecs")]
+            public string Codecs { get; set; }
+
+            public MediaFileData(WriteableBitmap image)
+            {
+                this.Height = image.PixelHeight;
+                this.Width = image.PixelWidth;
+                this.Bitrate = 0;
+                this.Duration = 0;
+                this.Codecs = "";
+            }
+        }
+
+        #endregion
+
+        /// <summary>
+        /// Folder to store captured images
+        /// </summary>
+        private string isoFolder = "CapturedImagesCache";
+
+        /// <summary>
+        /// Capture Image options
+        /// </summary>
+        protected CaptureImageOptions captureImageOptions;
+
+        /// <summary>
+        /// Capture Audio options
+        /// </summary>
+        protected CaptureAudioOptions captureAudioOptions;
+
+        /// <summary>
+        /// Capture Video options
+        /// </summary>
+        protected CaptureVideoOptions captureVideoOptions;
+
+        /// <summary>
+        /// Used to open camera application
+        /// </summary>
+        private CameraCaptureTask cameraTask;
+
+        /// <summary>
+        /// Used for audio recording
+        /// </summary>
+        private AudioCaptureTask audioCaptureTask;
+
+        /// <summary>
+        /// Used for video recording
+        /// </summary>
+        private VideoCaptureTask videoCaptureTask;
+
+        /// <summary>
+        /// Stores information about captured files
+        /// </summary>
+        List<MediaFile> files = new List<MediaFile>();
+
+        /// <summary>
+        /// Launches default camera application to capture image
+        /// </summary>
+        /// <param name="options">may contains limit or mode parameters</param>
+        public void captureImage(string options)
+        {
+            try
+            {
+                try
+                {
+
+                    string args = JSON.JsonHelper.Deserialize<string[]>(options)[0];
+                    this.captureImageOptions = String.IsNullOrEmpty(args) ? CaptureImageOptions.Default : JSON.JsonHelper.Deserialize<CaptureImageOptions>(args);
+
+                }
+                catch (Exception ex)
+                {
+                    this.DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION, ex.Message));
+                    return;
+                }
+
+
+                cameraTask = new CameraCaptureTask();
+                cameraTask.Completed += this.cameraTask_Completed;
+                cameraTask.Show();
+            }
+            catch (Exception e)
+            {
+                DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message));
+            }
+        }
+
+        /// <summary>
+        /// Launches our own audio recording control to capture audio
+        /// </summary>
+        /// <param name="options">may contains additional parameters</param>
+        public void captureAudio(string options)
+        {
+            try
+            {
+                try
+                {
+                    string args = JSON.JsonHelper.Deserialize<string[]>(options)[0];
+                    this.captureAudioOptions = String.IsNullOrEmpty(args) ? CaptureAudioOptions.Default : JSON.JsonHelper.Deserialize<CaptureAudioOptions>(args);
+
+                }
+                catch (Exception ex)
+                {
+                    this.DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION, ex.Message));
+                    return;
+                }
+
+                audioCaptureTask = new AudioCaptureTask();
+                audioCaptureTask.Completed += audioRecordingTask_Completed;
+                audioCaptureTask.Show();
+
+            }
+            catch (Exception e)
+            {
+                DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message));
+            }
+        }
+
+        /// <summary>
+        /// Launches our own video recording control to capture video
+        /// </summary>
+        /// <param name="options">may contains additional parameters</param>
+        public void captureVideo(string options)
+        {
+            try
+            {
+                try
+                {
+                    string args = JSON.JsonHelper.Deserialize<string[]>(options)[0];
+                    this.captureVideoOptions = String.IsNullOrEmpty(args) ? CaptureVideoOptions.Default : JSON.JsonHelper.Deserialize<CaptureVideoOptions>(args);
+
+                }
+                catch (Exception ex)
+                {
+                    this.DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION, ex.Message));
+                    return;
+                }
+
+                videoCaptureTask = new VideoCaptureTask();
+                videoCaptureTask.Completed += videoRecordingTask_Completed;
+                videoCaptureTask.Show();
+
+            }
+            catch (Exception e)
+            {
+                DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message));
+            }
+        }
+
+        /// <summary>
+        /// Retrieves the format information of the media file.
+        /// </summary>
+        /// <param name="options"></param>
+        public void getFormatData(string options)
+        {
+            try
+            {
+                MediaFormatOptions mediaFormatOptions;
+                try
+                {
+                    mediaFormatOptions = new MediaFormatOptions();
+                    string[] optionStrings = JSON.JsonHelper.Deserialize<string[]>(options);
+                    mediaFormatOptions.FullPath = optionStrings[0];
+                    mediaFormatOptions.Type = optionStrings[1];
+                }
+                catch (Exception ex)
+                {
+                    this.DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION, ex.Message));
+                    return;
+                }
+
+                if (string.IsNullOrEmpty(mediaFormatOptions.FullPath))
+                {
+                    DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
+                }
+
+                string mimeType = mediaFormatOptions.Type;
+
+                if (string.IsNullOrEmpty(mimeType))
+                {
+                    mimeType = MimeTypeMapper.GetMimeType(mediaFormatOptions.FullPath);
+                }
+
+                if (mimeType.Equals("image/jpeg"))
+                {
+                    Deployment.Current.Dispatcher.BeginInvoke(() =>
+                    {
+                        WriteableBitmap image = ExtractImageFromLocalStorage(mediaFormatOptions.FullPath);
+
+                        if (image == null)
+                        {
+                            DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "File not found"));
+                            return;
+                        }
+
+                        MediaFileData mediaData = new MediaFileData(image);
+                        DispatchCommandResult(new PluginResult(PluginResult.Status.OK, mediaData));
+                    });
+                }
+                else
+                {
+                    DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR));
+                }
+            }
+            catch (Exception)
+            {
+                DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR));
+            }
+        }
+
+        /// <summary>
+        /// Opens specified file in media player
+        /// </summary>
+        /// <param name="options">MediaFile to play</param>
+        public void play(string options)
+        {
+            try
+            {
+                MediaFile file;
+
+                try
+                {
+                    file = String.IsNullOrEmpty(options) ? null : JSON.JsonHelper.Deserialize<MediaFile[]>(options)[0];
+
+                }
+                catch (Exception ex)
+                {
+                    this.DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION, ex.Message));
+                    return;
+                }
+
+                if (file == null || String.IsNullOrEmpty(file.FilePath))
+                {
+                    DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "File path is missing"));
+                    return;
+                }
+
+                // if url starts with '/' media player throws FileNotFound exception
+                Uri fileUri = new Uri(file.FilePath.TrimStart(new char[] { '/', '\\' }), UriKind.Relative);
+
+                MediaPlayerLauncher player = new MediaPlayerLauncher();
+                player.Media = fileUri;
+                player.Location = MediaLocationType.Data;
+                player.Show();
+
+                this.DispatchCommandResult(new PluginResult(PluginResult.Status.OK));
+
+            }
+            catch (Exception e)
+            {
+                DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message));
+            }
+        }
+
+
+        /// <summary>
+        /// Handles result of capture to save image information 
+        /// </summary>
+        /// <param name="sender"></param>
+        /// <param name="e">stores information about current captured image</param>
+        private void cameraTask_Completed(object sender, PhotoResult e)
+        {
+
+            if (e.Error != null)
+            {
+                DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR));
+                return;
+            }
+
+            switch (e.TaskResult)
+            {
+                case TaskResult.OK:
+                    try
+                    {
+                        string fileName = System.IO.Path.GetFileName(e.OriginalFileName);
+
+                        // Save image in media library
+                        MediaLibrary library = new MediaLibrary();
+                        Picture image = library.SavePicture(fileName, e.ChosenPhoto);
+
+                        int orient = ImageExifHelper.getImageOrientationFromStream(e.ChosenPhoto);
+                        int newAngle = 0;
+                        switch (orient)
+                        {
+                            case ImageExifOrientation.LandscapeLeft:
+                                newAngle = 90;
+                                break;
+                            case ImageExifOrientation.PortraitUpsideDown:
+                                newAngle = 180;
+                                break;
+                            case ImageExifOrientation.LandscapeRight:
+                                newAngle = 270;
+                                break;
+                            case ImageExifOrientation.Portrait:
+                            default: break; // 0 default already set
+                        }
+
+                        Stream rotImageStream = ImageExifHelper.RotateStream(e.ChosenPhoto, newAngle);
+
+                        // Save image in isolated storage    
+
+                        // we should return stream position back after saving stream to media library
+                        rotImageStream.Seek(0, SeekOrigin.Begin);
+
+                        byte[] imageBytes = new byte[rotImageStream.Length];
+                        rotImageStream.Read(imageBytes, 0, imageBytes.Length);
+                        rotImageStream.Dispose();
+                        string pathLocalStorage = this.SaveImageToLocalStorage(fileName, isoFolder, imageBytes);
+                        imageBytes = null;
+                        // Get image data
+                        MediaFile data = new MediaFile(pathLocalStorage, image);
+
+                        this.files.Add(data);
+
+                        if (files.Count < this.captureImageOptions.Limit)
+                        {
+                            cameraTask.Show();
+                        }
+                        else
+                        {
+                            DispatchCommandResult(new PluginResult(PluginResult.Status.OK, files));
+                            files.Clear();
+                        }
+                    }
+                    catch (Exception)
+                    {
+                        DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Error capturing image."));
+                    }
+                    break;
+
+                case TaskResult.Cancel:
+                    if (files.Count > 0)
+                    {
+                        // User canceled operation, but some images were made
+                        DispatchCommandResult(new PluginResult(PluginResult.Status.OK, files));
+                        files.Clear();
+                    }
+                    else
+                    {
+                        DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Canceled."));
+                    }
+                    break;
+
+                default:
+                    if (files.Count > 0)
+                    {
+                        DispatchCommandResult(new PluginResult(PluginResult.Status.OK, files));
+                        files.Clear();
+                    }
+                    else
+                    {
+                        DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Did not complete!"));
+                    }
+                    break;
+            }
+        }
+
+        /// <summary>
+        /// Handles result of audio recording tasks 
+        /// </summary>
+        /// <param name="sender"></param>
+        /// <param name="e">stores information about current captured audio</param>
+        private void audioRecordingTask_Completed(object sender, AudioResult e)
+        {
+
+            if (e.Error != null)
+            {
+                DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR));
+                return;
+            }
+
+            switch (e.TaskResult)
+            {
+                case TaskResult.OK:
+                    try
+                    {
+                        // Get image data
+                        MediaFile data = new MediaFile(e.AudioFileName, e.AudioFile);
+
+                        this.files.Add(data);
+
+                        if (files.Count < this.captureAudioOptions.Limit)
+                        {
+                            audioCaptureTask.Show();
+                        }
+                        else
+                        {
+                            DispatchCommandResult(new PluginResult(PluginResult.Status.OK, files));
+                            files.Clear();
+                        }
+                    }
+                    catch (Exception)
+                    {
+                        DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Error capturing audio."));
+                    }
+                    break;
+
+                case TaskResult.Cancel:
+                    if (files.Count > 0)
+                    {
+                        // User canceled operation, but some audio clips were made
+                        DispatchCommandResult(new PluginResult(PluginResult.Status.OK, files));
+                        files.Clear();
+                    }
+                    else
+                    {
+                        DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Canceled."));
+                    }
+                    break;
+
+                default:
+                    if (files.Count > 0)
+                    {
+                        DispatchCommandResult(new PluginResult(PluginResult.Status.OK, files));
+                        files.Clear();
+                    }
+                    else
+                    {
+                        DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Did not complete!"));
+                    }
+                    break;
+            }
+        }
+
+        /// <summary>
+        /// Handles result of video recording tasks 
+        /// </summary>
+        /// <param name="sender"></param>
+        /// <param name="e">stores information about current captured video</param>
+        private void videoRecordingTask_Completed(object sender, VideoResult e)
+        {
+
+            if (e.Error != null)
+            {
+                DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR));
+                return;
+            }
+
+            switch (e.TaskResult)
+            {
+                case TaskResult.OK:
+                    try
+                    {
+                        // Get image data
+                        MediaFile data = new MediaFile(e.VideoFileName, e.VideoFile);
+
+                        this.files.Add(data);
+
+                        if (files.Count < this.captureVideoOptions.Limit)
+                        {
+                            videoCaptureTask.Show();
+                        }
+                        else
+                        {
+                            DispatchCommandResult(new PluginResult(PluginResult.Status.OK, files));
+                            files.Clear();
+                        }
+                    }
+                    catch (Exception)
+                    {
+                        DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Error capturing video."));
+                    }
+                    break;
+
+                case TaskResult.Cancel:
+                    if (files.Count > 0)
+                    {
+                        // User canceled operation, but some video clips were made
+                        DispatchCommandResult(new PluginResult(PluginResult.Status.OK, files));
+                        files.Clear();
+                    }
+                    else
+                    {
+                        DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Canceled."));
+                    }
+                    break;
+
+                default:
+                    if (files.Count > 0)
+                    {
+                        DispatchCommandResult(new PluginResult(PluginResult.Status.OK, files));
+                        files.Clear();
+                    }
+                    else
+                    {
+                        DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Did not complete!"));
+                    }
+                    break;
+            }
+        }
+
+        /// <summary>
+        /// Extract file from Isolated Storage as WriteableBitmap object
+        /// </summary>
+        /// <param name="filePath"></param>
+        /// <returns></returns>
+        private WriteableBitmap ExtractImageFromLocalStorage(string filePath)
+        {
+            try
+            {
+
+                var isoFile = IsolatedStorageFile.GetUserStoreForApplication();
+
+                using (var imageStream = isoFile.OpenFile(filePath, FileMode.Open, FileAccess.Read))
+                {
+                    var imageSource = PictureDecoder.DecodeJpeg(imageStream);
+                    return imageSource;
+                }
+            }
+            catch (Exception)
+            {
+                return null;
+            }
+        }
+
+
+        /// <summary>
+        /// Saves captured image in isolated storage
+        /// </summary>
+        /// <param name="imageFileName">image file name</param>
+        /// <param name="imageFolder">folder to store images</param>
+        /// <returns>Image path</returns>
+        private string SaveImageToLocalStorage(string imageFileName, string imageFolder, byte[] imageBytes)
+        {
+            if (imageBytes == null)
+            {
+                throw new ArgumentNullException("imageBytes");
+            }
+            try
+            {
+                var isoFile = IsolatedStorageFile.GetUserStoreForApplication();
+
+                if (!isoFile.DirectoryExists(imageFolder))
+                {
+                    isoFile.CreateDirectory(imageFolder);
+                }
+                string filePath = System.IO.Path.Combine("/" + imageFolder + "/", imageFileName);
+
+                using (IsolatedStorageFileStream stream = isoFile.CreateFile(filePath))
+                {
+                    stream.Write(imageBytes, 0, imageBytes.Length);
+                }
+
+                return filePath;
+            }
+            catch (Exception)
+            {
+                //TODO: log or do something else
+                throw;
+            }
+        }
+
+
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/f59ddbbd/lib/cordova-wp8/templates/standalone/Plugins/Compass.cs
----------------------------------------------------------------------
diff --git a/lib/cordova-wp8/templates/standalone/Plugins/Compass.cs b/lib/cordova-wp8/templates/standalone/Plugins/Compass.cs
new file mode 100644
index 0000000..c9e1c4d
--- /dev/null
+++ b/lib/cordova-wp8/templates/standalone/Plugins/Compass.cs
@@ -0,0 +1,362 @@
+/*  
+	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 DeviceCompass = Microsoft.Devices.Sensors.Compass;
+using System.Windows.Threading;
+using System.Runtime.Serialization;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.Threading;
+using Microsoft.Devices.Sensors;
+
+namespace WPCordovaClassLib.Cordova.Commands
+{
+
+    public class Compass : BaseCommand
+    {
+        #region Static members
+
+        /// <summary>
+        /// Status of listener
+        /// </summary>
+        private static int currentStatus;
+
+        /// <summary>
+        /// Id for get getCompass method
+        /// </summary>
+        private static string getCompassId = "getCompassId";
+
+        /// <summary>
+        /// Compass
+        /// </summary>
+        private static DeviceCompass compass = new DeviceCompass();
+
+        /// <summary>
+        /// Listeners for callbacks
+        /// </summary>
+        private static Dictionary<string, Compass> watchers = new Dictionary<string, Compass>();
+
+        #endregion
+
+        #region Status codes
+
+        public const int Stopped = 0;
+        public const int Starting = 1;
+        public const int Running = 2;
+        public const int ErrorFailedToStart = 4;
+        public const int Not_Supported = 20;
+
+        /*
+         *   // Capture error codes
+            CompassError.COMPASS_INTERNAL_ERR = 0;
+            CompassError.COMPASS_NOT_SUPPORTED = 20;
+         * */
+
+        #endregion
+
+        #region CompassOptions class
+        /// <summary>
+        /// Represents Accelerometer options.
+        /// </summary>
+        [DataContract]
+        public class CompassOptions
+        {
+            /// <summary>
+            /// How often to retrieve the Acceleration in milliseconds
+            /// </summary>
+            [DataMember(IsRequired = false, Name = "frequency")]
+            public int Frequency { get; set; }
+
+            /// <summary>
+            /// The change in degrees required to initiate a watchHeadingFilter success callback.
+            /// </summary>
+            [DataMember(IsRequired = false, Name = "filter")]
+            public int Filter { get; set; }
+
+            /// <summary>
+            /// Watcher id
+            /// </summary>
+            [DataMember(IsRequired = false, Name = "id")]
+            public string Id { get; set; }
+
+        }
+        #endregion
+
+
+        /// <summary>
+        /// Time the value was last changed
+        /// </summary>
+        //private DateTime lastValueChangedTime;
+
+        /// <summary>
+        /// Accelerometer options
+        /// </summary>
+        private CompassOptions compassOptions;
+
+        //bool isDataValid;
+
+        //bool calibrating = false;
+
+        public Compass()
+        {
+
+        }
+
+        /// <summary>
+        /// Formats current coordinates into JSON format
+        /// </summary>
+        /// <returns>Coordinates in JSON format</returns>
+        private string GetHeadingFormatted(CompassReading reading)
+        {   
+            // NOTE: timestamp is generated on the JS side, to avoid issues with format conversions
+            string result = String.Format("\"magneticHeading\":{0},\"headingAccuracy\":{1},\"trueHeading\":{2}",
+                            reading.MagneticHeading.ToString("0.0", CultureInfo.InvariantCulture),
+                            reading.HeadingAccuracy.ToString("0.0", CultureInfo.InvariantCulture),
+                            reading.TrueHeading.ToString("0.0", CultureInfo.InvariantCulture));
+            return "{" + result + "}";
+        }
+
+        public void getHeading(string options)
+        {
+            if (!DeviceCompass.IsSupported)
+            {
+                DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "{code:" + Not_Supported + "}"));
+            }
+            else
+            {
+                //if (compass == null)
+                //{
+                //    // Instantiate the compass.
+                //    compass = new DeviceCompass();
+                //    compass.TimeBetweenUpdates = TimeSpan.FromMilliseconds(40);
+                //    compass.CurrentValueChanged += new EventHandler<Microsoft.Devices.Sensors.SensorReadingEventArgs<Microsoft.Devices.Sensors.CompassReading>>(compass_CurrentValueChanged);
+                //    compass.Calibrate += new EventHandler<Microsoft.Devices.Sensors.CalibrationEventArgs>(compass_Calibrate);
+                //}
+
+
+                //compass.Start();
+
+            }
+
+            try
+            {
+                if (currentStatus != Running)
+                {
+                    lock (compass)
+                    {
+                        compass.CurrentValueChanged += compass_SingleHeadingValueChanged;
+                        compass.Start();
+                        this.SetStatus(Starting);
+                    }
+
+                    long timeout = 2000;
+                    while ((currentStatus == Starting) && (timeout > 0))
+                    {
+                        timeout = timeout - 100;
+                        Thread.Sleep(100);
+                    }
+
+                    if (currentStatus != Running)
+                    {
+                        DispatchCommandResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, ErrorFailedToStart));
+                        return;
+                    }
+                }
+                lock (compass)
+                {
+                    compass.CurrentValueChanged -= compass_SingleHeadingValueChanged;
+                    if (watchers.Count < 1)
+                    {
+                        compass.Stop();
+                        this.SetStatus(Stopped);
+                    }
+                }
+            }
+            catch (UnauthorizedAccessException)
+            {
+                DispatchCommandResult(new PluginResult(PluginResult.Status.ILLEGAL_ACCESS_EXCEPTION, ErrorFailedToStart));
+            }
+            catch (Exception)
+            {
+                DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, ErrorFailedToStart));
+            }
+        }
+
+        void compass_SingleHeadingValueChanged(object sender, Microsoft.Devices.Sensors.SensorReadingEventArgs<CompassReading> e)
+        {
+            this.SetStatus(Running);
+            if (compass.IsDataValid)
+            {
+                // trueHeading :: The heading in degrees from 0 - 359.99 at a single moment in time.
+                //  magneticHeading:: The heading relative to the geographic North Pole in degrees 0 - 359.99 at a single moment in time. 
+                //  A negative value indicates that the true heading could not be determined.
+                // headingAccuracy :: The deviation in degrees between the reported heading and the true heading.
+                //rawMagnetometerReading = e.SensorReading.MagnetometerReading;
+
+                //Debug.WriteLine("Compass Result :: " + GetHeadingFormatted(e.SensorReading));
+
+                PluginResult result = new PluginResult(PluginResult.Status.OK, GetHeadingFormatted(e.SensorReading));
+
+                DispatchCommandResult(result);
+            }
+        }
+
+        /// <summary>
+        /// Starts listening for compass sensor
+        /// </summary>
+        /// <returns>status of listener</returns>
+        private int start()
+        {
+            if ((currentStatus == Running) || (currentStatus == Starting))
+            {
+                return currentStatus;
+            }
+            try
+            {
+                lock (compass)
+                {
+                    watchers.Add(getCompassId, this);
+                    compass.CurrentValueChanged += watchers[getCompassId].compass_CurrentValueChanged;
+                    compass.Start();
+                    this.SetStatus(Starting);
+                }
+            }
+            catch (Exception)
+            {
+                this.SetStatus(ErrorFailedToStart);
+            }
+            return currentStatus;
+        }
+
+        public void startWatch(string options)
+        {
+            if (!DeviceCompass.IsSupported)
+            {
+                DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, Not_Supported));
+            }
+
+            try
+            {
+                compassOptions = JSON.JsonHelper.Deserialize<CompassOptions>(options);
+            }
+            catch (Exception ex)
+            {
+                this.DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION, ex.Message));
+                return;
+            }
+
+            if (string.IsNullOrEmpty(compassOptions.Id))
+            {
+                this.DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
+                return;
+            }
+
+            try
+            {
+                lock (compass)
+                {
+                    watchers.Add(compassOptions.Id, this);
+                    compass.CurrentValueChanged += watchers[compassOptions.Id].compass_CurrentValueChanged;
+                    compass.Start();
+                    this.SetStatus(Starting);
+                }
+            }
+            catch (Exception)
+            {
+                this.DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, ErrorFailedToStart));
+                return;
+            }
+        }
+
+        public void stopWatch(string options)
+        {
+            try
+            {
+                compassOptions = JSON.JsonHelper.Deserialize<CompassOptions>(options);
+            }
+            catch (Exception ex)
+            {
+                this.DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION, ex.Message));
+                return;
+            }
+
+            if (string.IsNullOrEmpty(compassOptions.Id))
+            {
+                this.DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
+                return;
+            }
+
+            if (currentStatus != Stopped)
+            {
+                lock (compass)
+                {
+                    Compass watcher = watchers[compassOptions.Id];
+                    compass.CurrentValueChanged -= watcher.compass_CurrentValueChanged;
+                    watchers.Remove(compassOptions.Id);
+                    watcher.Dispose();
+                }
+            }
+            this.SetStatus(Stopped);
+
+            this.DispatchCommandResult();
+        }
+
+        void compass_Calibrate(object sender, Microsoft.Devices.Sensors.CalibrationEventArgs e)
+        {
+            //throw new NotImplementedException();
+            // TODO: pass calibration error to JS
+        }
+
+        void compass_CurrentValueChanged(object sender, Microsoft.Devices.Sensors.SensorReadingEventArgs<CompassReading> e)
+        {
+            this.SetStatus(Running);
+            if (compass.IsDataValid)
+            {
+                // trueHeading :: The heading in degrees from 0 - 359.99 at a single moment in time.
+                //  magneticHeading:: The heading relative to the geographic North Pole in degrees 0 - 359.99 at a single moment in time. 
+                //  A negative value indicates that the true heading could not be determined.
+                // headingAccuracy :: The deviation in degrees between the reported heading and the true heading.
+                //rawMagnetometerReading = e.SensorReading.MagnetometerReading;
+
+                //Debug.WriteLine("Compass Result :: " + GetHeadingFormatted(e.SensorReading));
+
+                PluginResult result = new PluginResult(PluginResult.Status.OK, GetHeadingFormatted(e.SensorReading));
+                result.KeepCallback = true;
+
+                DispatchCommandResult(result);
+            }
+        }
+
+        /// <summary>
+        /// Sets current status
+        /// </summary>
+        /// <param name="status">current status</param>
+        private void SetStatus(int status)
+        {
+            currentStatus = status;
+        }
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/f59ddbbd/lib/cordova-wp8/templates/standalone/Plugins/Contacts.cs
----------------------------------------------------------------------
diff --git a/lib/cordova-wp8/templates/standalone/Plugins/Contacts.cs b/lib/cordova-wp8/templates/standalone/Plugins/Contacts.cs
new file mode 100644
index 0000000..af78942
--- /dev/null
+++ b/lib/cordova-wp8/templates/standalone/Plugins/Contacts.cs
@@ -0,0 +1,664 @@
+/*  
+	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.Tasks;
+using Microsoft.Phone.UserData;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Runtime.Serialization;
+using System.Windows;
+using DeviceContacts = Microsoft.Phone.UserData.Contacts;
+
+
+namespace WPCordovaClassLib.Cordova.Commands
+{
+    [DataContract]
+    public class SearchOptions
+    {
+        [DataMember]
+        public string filter { get; set; }
+        [DataMember]
+        public bool multiple { get; set; }
+    }
+
+    [DataContract]
+    public class ContactSearchParams
+    {
+        [DataMember]
+        public string[] fields { get; set; }
+        [DataMember]
+        public SearchOptions options { get; set; }
+    }
+
+    [DataContract]
+    public class JSONContactAddress
+    {
+        [DataMember]
+        public string formatted { get; set; }
+        [DataMember]
+        public string type { get; set; }
+        [DataMember]
+        public string streetAddress { get; set; }
+        [DataMember]
+        public string locality { get; set; }
+        [DataMember]
+        public string region { get; set; }
+        [DataMember]
+        public string postalCode { get; set; }
+        [DataMember]
+        public string country { get; set; }
+        [DataMember]
+        public bool pref { get; set; }
+    }
+
+    [DataContract]
+    public class JSONContactName
+    {
+        [DataMember]
+        public string formatted { get; set; }
+        [DataMember]
+        public string familyName { get; set; }
+        [DataMember]
+        public string givenName { get; set; }
+        [DataMember]
+        public string middleName { get; set; }
+        [DataMember]
+        public string honorificPrefix { get; set; }
+        [DataMember]
+        public string honorificSuffix { get; set; }
+    }
+
+    [DataContract]
+    public class JSONContactField
+    {
+        [DataMember]
+        public string type { get; set; }
+        [DataMember]
+        public string value { get; set; }
+        [DataMember]
+        public bool pref { get; set; }
+    }
+
+    [DataContract]
+    public class JSONContactOrganization
+    {
+        [DataMember]
+        public string type { get; set; }
+        [DataMember]
+        public string name { get; set; }
+        [DataMember]
+        public bool pref { get; set; }
+        [DataMember]
+        public string department { get; set; }
+        [DataMember]
+        public string title { get; set; }
+    }
+
+    [DataContract]
+    public class JSONContact
+    {
+        [DataMember]
+        public string id { get; set; }
+        [DataMember]
+        public string rawId { get; set; }
+        [DataMember]
+        public string displayName { get; set; }
+        [DataMember]
+        public string nickname { get; set; }
+        [DataMember]
+        public string note { get; set; }
+
+        [DataMember]
+        public JSONContactName name { get; set; }
+
+        [DataMember]
+        public JSONContactField[] emails { get; set; }
+
+        [DataMember]
+        public JSONContactField[] phoneNumbers { get; set; }
+
+        [DataMember]
+        public JSONContactField[] ims { get; set; }
+
+        [DataMember]
+        public JSONContactField[] photos { get; set; }
+
+        [DataMember]
+        public JSONContactField[] categories { get; set; }
+
+        [DataMember]
+        public JSONContactField[] urls { get; set; }
+
+        [DataMember]
+        public JSONContactOrganization[] organizations { get; set; }
+
+        [DataMember]
+        public JSONContactAddress[] addresses { get; set; }
+    }
+
+
+    public class Contacts : BaseCommand
+    {
+
+        public const int UNKNOWN_ERROR = 0;
+        public const int INVALID_ARGUMENT_ERROR = 1;
+        public const int TIMEOUT_ERROR = 2;
+        public const int PENDING_OPERATION_ERROR = 3;
+        public const int IO_ERROR = 4;
+        public const int NOT_SUPPORTED_ERROR = 5;
+        public const int PERMISSION_DENIED_ERROR = 20;
+        public const int SYNTAX_ERR = 8;
+
+        public Contacts()
+        {
+
+        }
+
+        // refer here for contact properties we can access: http://msdn.microsoft.com/en-us/library/microsoft.phone.tasks.savecontacttask_members%28v=VS.92%29.aspx
+        public void save(string jsonContact)
+        {
+
+            // jsonContact is actually an array of 1 {contact}
+            string[] args = JSON.JsonHelper.Deserialize<string[]>(jsonContact);
+
+
+            JSONContact contact = JSON.JsonHelper.Deserialize<JSONContact>(args[0]);
+
+            SaveContactTask contactTask = new SaveContactTask();
+
+            if (contact.nickname != null)
+            {
+                contactTask.Nickname = contact.nickname;
+            }
+            if (contact.urls != null && contact.urls.Length > 0)
+            {
+                contactTask.Website = contact.urls[0].value;
+            }
+            if (contact.note != null)
+            {
+                contactTask.Notes = contact.note;
+            }
+
+            #region contact.name
+            if (contact.name != null)
+            {
+                if (contact.name.givenName != null)
+                    contactTask.FirstName = contact.name.givenName;
+                if (contact.name.familyName != null)
+                    contactTask.LastName = contact.name.familyName;
+                if (contact.name.middleName != null)
+                    contactTask.MiddleName = contact.name.middleName;
+                if (contact.name.honorificSuffix != null)
+                    contactTask.Suffix = contact.name.honorificSuffix;
+                if (contact.name.honorificPrefix != null)
+                    contactTask.Title = contact.name.honorificPrefix;
+            }
+            #endregion
+
+            #region contact.org
+            if (contact.organizations != null && contact.organizations.Count() > 0)
+            {
+                contactTask.Company = contact.organizations[0].name;
+                contactTask.JobTitle = contact.organizations[0].title;
+            }
+            #endregion
+
+            #region contact.phoneNumbers
+            if (contact.phoneNumbers != null && contact.phoneNumbers.Length > 0)
+            {
+                foreach (JSONContactField field in contact.phoneNumbers)
+                {
+                    string fieldType = field.type.ToLower();
+                    if (fieldType == "work")
+                    {
+                        contactTask.WorkPhone = field.value;
+                    }
+                    else if (fieldType == "home")
+                    {
+                        contactTask.HomePhone = field.value;
+                    }
+                    else if (fieldType == "mobile")
+                    {
+                        contactTask.MobilePhone = field.value;
+                    }
+                }
+            }
+            #endregion
+
+            #region contact.emails
+
+            if (contact.emails != null && contact.emails.Length > 0)
+            {
+
+                // set up different email types if they are not explicitly defined
+                foreach (string type in new string[] { "personal", "work", "other" })
+                {
+                    foreach (JSONContactField field in contact.emails)
+                    {
+                        if (field != null && String.IsNullOrEmpty(field.type))
+                        {
+                            field.type = type;
+                            break;
+                        }
+                    }
+                }
+
+                foreach (JSONContactField field in contact.emails)
+                {
+                    if (field != null)
+                    {
+                        if (field.type != null && field.type != "other")
+                        {
+                            string fieldType = field.type.ToLower();
+                            if (fieldType == "work")
+                            {
+                                contactTask.WorkEmail = field.value;
+                            }
+                            else if (fieldType == "home" || fieldType == "personal")
+                            {
+                                contactTask.PersonalEmail = field.value;
+                            }
+                        }
+                        else
+                        {
+                            contactTask.OtherEmail = field.value;
+                        }
+                    }
+
+                }
+            }
+            #endregion
+
+            if (contact.note != null && contact.note.Length > 0)
+            {
+                contactTask.Notes = contact.note;
+            }
+
+            #region contact.addresses
+            if (contact.addresses != null && contact.addresses.Length > 0)
+            {
+                foreach (JSONContactAddress address in contact.addresses)
+                {
+                    if (address.type == null)
+                    {
+                        address.type = "home"; // set a default
+                    }
+                    string fieldType = address.type.ToLower();
+                    if (fieldType == "work")
+                    {
+                        contactTask.WorkAddressCity = address.locality;
+                        contactTask.WorkAddressCountry = address.country;
+                        contactTask.WorkAddressState = address.region;
+                        contactTask.WorkAddressStreet = address.streetAddress;
+                        contactTask.WorkAddressZipCode = address.postalCode;
+                    }
+                    else if (fieldType == "home" || fieldType == "personal")
+                    {
+                        contactTask.HomeAddressCity = address.locality;
+                        contactTask.HomeAddressCountry = address.country;
+                        contactTask.HomeAddressState = address.region;
+                        contactTask.HomeAddressStreet = address.streetAddress;
+                        contactTask.HomeAddressZipCode = address.postalCode;
+                    }
+                    else
+                    {
+                        // no other address fields available ...
+                        Debug.WriteLine("Creating contact with unsupported address type :: " + address.type);
+                    }
+                }
+            }
+            #endregion
+
+
+            contactTask.Completed += new EventHandler<SaveContactResult>(ContactSaveTaskCompleted);
+            contactTask.Show();
+        }
+
+        void ContactSaveTaskCompleted(object sender, SaveContactResult e)
+        {
+            SaveContactTask task = sender as SaveContactTask;
+
+            if (e.TaskResult == TaskResult.OK)
+            {
+
+                Deployment.Current.Dispatcher.BeginInvoke(() =>
+                {
+                    DeviceContacts deviceContacts = new DeviceContacts();
+                    deviceContacts.SearchCompleted += new EventHandler<ContactsSearchEventArgs>(postAdd_SearchCompleted);
+
+                    string displayName = String.Format("{0}{2}{1}", task.FirstName, task.LastName, String.IsNullOrEmpty(task.FirstName) ? "" : " ");
+
+                    deviceContacts.SearchAsync(displayName, FilterKind.DisplayName, task);
+                });
+
+
+            }
+            else if (e.TaskResult == TaskResult.Cancel)
+            {
+                DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Operation cancelled."));
+            }
+        }
+
+        void postAdd_SearchCompleted(object sender, ContactsSearchEventArgs e)
+        {
+            if (e.Results.Count() > 0)
+            {
+                List<Contact> foundContacts = new List<Contact>();
+
+                int n = (from Contact contact in e.Results select contact.GetHashCode()).Max();
+                Contact newContact = (from Contact contact in e.Results
+                                      where contact.GetHashCode() == n
+                                      select contact).First();
+
+                DispatchCommandResult(new PluginResult(PluginResult.Status.OK, FormatJSONContact(newContact, null)));
+            }
+            else
+            {
+                DispatchCommandResult(new PluginResult(PluginResult.Status.NO_RESULT));
+            }
+        }
+
+
+
+        public void remove(string id)
+        {
+            // note id is wrapped in [] and always has exactly one string ...
+            DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "{\"code\":" + NOT_SUPPORTED_ERROR + "}"));
+        }
+
+        public void search(string searchCriteria)
+        {
+            string[] args = JSON.JsonHelper.Deserialize<string[]>(searchCriteria);
+
+            ContactSearchParams searchParams = new ContactSearchParams();
+            try
+            {
+                searchParams.fields = JSON.JsonHelper.Deserialize<string[]>(args[0]);
+                searchParams.options = JSON.JsonHelper.Deserialize<SearchOptions>(args[1]);
+            }
+            catch (Exception)
+            {
+                DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, INVALID_ARGUMENT_ERROR));
+                return;
+            }
+
+            if (searchParams.options == null)
+            {
+                searchParams.options = new SearchOptions();
+                searchParams.options.filter = "";
+                searchParams.options.multiple = true;
+            }
+
+            DeviceContacts deviceContacts = new DeviceContacts();
+            deviceContacts.SearchCompleted += new EventHandler<ContactsSearchEventArgs>(contacts_SearchCompleted);
+
+            // default is to search all fields
+            FilterKind filterKind = FilterKind.None;
+            // if only one field is specified, we will try the 3 available DeviceContact search filters
+            if (searchParams.fields.Count() == 1)
+            {
+                if (searchParams.fields.Contains("name"))
+                {
+                    filterKind = FilterKind.DisplayName;
+                }
+                else if (searchParams.fields.Contains("emails"))
+                {
+                    filterKind = FilterKind.EmailAddress;
+                }
+                else if (searchParams.fields.Contains("phoneNumbers"))
+                {
+                    filterKind = FilterKind.PhoneNumber;
+                }
+            }
+
+            try
+            {
+
+                deviceContacts.SearchAsync(searchParams.options.filter, filterKind, searchParams);
+            }
+            catch (Exception ex)
+            {
+                Debug.WriteLine("search contacts exception :: " + ex.Message);
+            }
+        }
+
+        private void contacts_SearchCompleted(object sender, ContactsSearchEventArgs e)
+        {
+            ContactSearchParams searchParams = (ContactSearchParams)e.State;
+
+            List<Contact> foundContacts = null;
+
+            // if we have multiple search fields
+            if (searchParams.options.filter.Length > 0 && searchParams.fields.Count() > 1)
+            {
+                foundContacts = new List<Contact>();
+                if (searchParams.fields.Contains("emails"))
+                {
+                    foundContacts.AddRange(from Contact con in e.Results
+                                           from ContactEmailAddress a in con.EmailAddresses
+                                           where a.EmailAddress.Contains(searchParams.options.filter)
+                                           select con);
+                }
+                if (searchParams.fields.Contains("displayName"))
+                {
+                    foundContacts.AddRange(from Contact con in e.Results
+                                           where con.DisplayName.Contains(searchParams.options.filter)
+                                           select con);
+                }
+                if (searchParams.fields.Contains("name"))
+                {
+                    foundContacts.AddRange(from Contact con in e.Results
+                                           where con.CompleteName != null && con.CompleteName.ToString().Contains(searchParams.options.filter)
+                                           select con);
+                }
+                if (searchParams.fields.Contains("phoneNumbers"))
+                {
+                    foundContacts.AddRange(from Contact con in e.Results
+                                           from ContactPhoneNumber a in con.PhoneNumbers
+                                           where a.PhoneNumber.Contains(searchParams.options.filter)
+                                           select con);
+                }
+                if (searchParams.fields.Contains("urls"))
+                {
+                    foundContacts.AddRange(from Contact con in e.Results
+                                           from string a in con.Websites
+                                           where a.Contains(searchParams.options.filter)
+                                           select con);
+                }
+            }
+            else
+            {
+                foundContacts = new List<Contact>(e.Results);
+            }
+
+            //List<string> contactList = new List<string>();
+
+            string strResult = "";
+
+            IEnumerable<Contact> distinctContacts = foundContacts.Distinct();
+
+            foreach (Contact contact in distinctContacts)
+            {
+                strResult += FormatJSONContact(contact, null) + ",";
+                //contactList.Add(FormatJSONContact(contact, null));
+                if (!searchParams.options.multiple)
+                {
+                    break; // just return the first item
+                }
+            }
+            PluginResult result = new PluginResult(PluginResult.Status.OK);
+            result.Message = "[" + strResult.TrimEnd(',') + "]";
+            DispatchCommandResult(result);
+
+        }
+
+        private string FormatJSONPhoneNumbers(Contact con)
+        {
+            string retVal = "";
+            string contactFieldFormat = "\"type\":\"{0}\",\"value\":\"{1}\",\"pref\":\"false\"";
+            foreach (ContactPhoneNumber number in con.PhoneNumbers)
+            {
+
+                string contactField = string.Format(contactFieldFormat,
+                                                    number.Kind.ToString(),
+                                                    number.PhoneNumber);
+
+                retVal += "{" + contactField + "},";
+            }
+            return retVal.TrimEnd(',');
+        }
+
+        private string FormatJSONEmails(Contact con)
+        {
+            string retVal = "";
+            string contactFieldFormat = "\"type\":\"{0}\",\"value\":\"{1}\",\"pref\":\"false\"";
+            foreach (ContactEmailAddress address in con.EmailAddresses)
+            {
+                string contactField = string.Format(contactFieldFormat,
+                                                    address.Kind.ToString(),
+                                                    address.EmailAddress);
+
+                retVal += "{" + contactField + "},";
+            }
+            return retVal.TrimEnd(',');
+        }
+
+        private string getFormattedJSONAddress(ContactAddress address, bool isPrefered)
+        {
+
+            string addressFormatString = "\"pref\":{0}," + // bool
+                          "\"type\":\"{1}\"," +
+                          "\"formatted\":\"{2}\"," +
+                          "\"streetAddress\":\"{3}\"," +
+                          "\"locality\":\"{4}\"," +
+                          "\"region\":\"{5}\"," +
+                          "\"postalCode\":\"{6}\"," +
+                          "\"country\":\"{7}\"";
+
+            string formattedAddress = address.PhysicalAddress.AddressLine1 + " "
+                                    + address.PhysicalAddress.AddressLine2 + " "
+                                    + address.PhysicalAddress.City + " "
+                                    + address.PhysicalAddress.StateProvince + " "
+                                    + address.PhysicalAddress.CountryRegion + " "
+                                    + address.PhysicalAddress.PostalCode;
+
+            string jsonAddress = string.Format(addressFormatString,
+                                               isPrefered ? "\"true\"" : "\"false\"",
+                                               address.Kind.ToString(),
+                                               formattedAddress,
+                                               address.PhysicalAddress.AddressLine1 + " " + address.PhysicalAddress.AddressLine2,
+                                               address.PhysicalAddress.City,
+                                               address.PhysicalAddress.StateProvince,
+                                               address.PhysicalAddress.PostalCode,
+                                               address.PhysicalAddress.CountryRegion);
+
+            //Debug.WriteLine("getFormattedJSONAddress returning :: " + jsonAddress);
+
+            return "{" + jsonAddress + "}";
+        }
+
+        private string FormatJSONAddresses(Contact con)
+        {
+            string retVal = "";
+            foreach (ContactAddress address in con.Addresses)
+            {
+                retVal += this.getFormattedJSONAddress(address, false) + ",";
+            }
+
+            //Debug.WriteLine("FormatJSONAddresses returning :: " + retVal);
+            return retVal.TrimEnd(',');
+        }
+
+        private string FormatJSONWebsites(Contact con)
+        {
+            string retVal = "";
+            foreach (string website in con.Websites)
+            {
+                retVal += "\"" + website + "\",";
+            }
+            return retVal.TrimEnd(',');
+        }
+
+        /*
+         *  formatted: The complete name of the contact. (DOMString)
+            familyName: The contacts family name. (DOMString)
+            givenName: The contacts given name. (DOMString)
+            middleName: The contacts middle name. (DOMString)
+            honorificPrefix: The contacts prefix (example Mr. or Dr.) (DOMString)
+            honorificSuffix: The contacts suffix (example Esq.). (DOMString)
+         */
+        private string FormatJSONName(Contact con)
+        {
+            string retVal = "";
+            string formatStr = "\"formatted\":\"{0}\"," +
+                                "\"familyName\":\"{1}\"," +
+                                "\"givenName\":\"{2}\"," +
+                                "\"middleName\":\"{3}\"," +
+                                "\"honorificPrefix\":\"{4}\"," +
+                                "\"honorificSuffix\":\"{5}\"";
+
+            if (con.CompleteName != null)
+            {
+                retVal = string.Format(formatStr,
+                                   con.CompleteName.FirstName + " " + con.CompleteName.LastName, // TODO: does this need suffix? middlename?
+                                   con.CompleteName.LastName,
+                                   con.CompleteName.FirstName,
+                                   con.CompleteName.MiddleName,
+                                   con.CompleteName.Title,
+                                   con.CompleteName.Suffix);
+            }
+            else
+            {
+                retVal = string.Format(formatStr,"","","","","","");
+            }
+
+            return "{" + retVal + "}";
+        }
+
+        private string FormatJSONContact(Contact con, string[] fields)
+        {
+
+            string contactFormatStr = "\"id\":\"{0}\"," +
+                                      "\"displayName\":\"{1}\"," +
+                                      "\"nickname\":\"{2}\"," +
+                                      "\"phoneNumbers\":[{3}]," +
+                                      "\"emails\":[{4}]," +
+                                      "\"addresses\":[{5}]," +
+                                      "\"urls\":[{6}]," +
+                                      "\"name\":{7}," +
+                                      "\"note\":\"{8}\"," +
+                                      "\"birthday\":\"{9}\"";
+
+
+            string jsonContact = String.Format(contactFormatStr,
+                                               con.GetHashCode(),
+                                               con.DisplayName,
+                                               con.CompleteName != null ? con.CompleteName.Nickname : "",
+                                               FormatJSONPhoneNumbers(con),
+                                               FormatJSONEmails(con),
+                                               FormatJSONAddresses(con),
+                                               FormatJSONWebsites(con),
+                                               FormatJSONName(con),
+                                               con.Notes.FirstOrDefault(),
+                                               con.Birthdays.FirstOrDefault());
+
+            //Debug.WriteLine("jsonContact = " + jsonContact);
+            // JSON requires new line characters be escaped
+            return "{" + jsonContact.Replace("\n", "\\n") + "}";
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/f59ddbbd/lib/cordova-wp8/templates/standalone/Plugins/DebugConsole.cs
----------------------------------------------------------------------
diff --git a/lib/cordova-wp8/templates/standalone/Plugins/DebugConsole.cs b/lib/cordova-wp8/templates/standalone/Plugins/DebugConsole.cs
new file mode 100644
index 0000000..fa9863a
--- /dev/null
+++ b/lib/cordova-wp8/templates/standalone/Plugins/DebugConsole.cs
@@ -0,0 +1,49 @@
+/*  
+	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.Diagnostics;
+
+namespace WPCordovaClassLib.Cordova.Commands
+{
+
+    public class DebugConsole : BaseCommand
+    {
+        // warn, error
+        public void log(string msg)
+        {
+            Debug.WriteLine("Log:" + msg);
+        }
+
+        public void error(string msg)
+        {
+            Debug.WriteLine("Error:" + msg);
+        }
+
+        public void warn(string msg)
+        {
+            Debug.WriteLine("Warn:" + msg);
+        }
+
+    }
+}