incubator-callback-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From "Alan Neveu (Commented) (JIRA)" <j...@apache.org>
Subject [jira] [Commented] (CB-520) WP7 Certification and the Back Button
Date Wed, 18 Apr 2012 20:38:39 GMT

    [ https://issues.apache.org/jira/browse/CB-520?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=13256926#comment-13256926
] 

Alan Neveu commented on CB-520:
-------------------------------

I wound up modifying CordovaView.xaml.cs in WP7CordovaClassLib.  It turns out WP7 apps really
need to be able to have more control over the history stack in order to comply with Marketplace
tech cert requirements.  Here is the source I wound up settling on for CordovaView.xaml.cs...
 Note the enhancements to void GapBrowser_ScriptNotify, and also to void page_BackKeyPress.

/*  
	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.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;
using System.IO.IsolatedStorage;
using System.Windows.Resources;
using System.Windows.Interop;
using System.Runtime.Serialization.Json;
using System.IO;
using System.ComponentModel;
using System.Xml.Linq;
using WP7CordovaClassLib.Cordova.Commands;
using System.Diagnostics;
using System.Text;
using WP7CordovaClassLib.Cordova;
using System.Threading;
using Microsoft.Phone.Shell;



namespace WP7CordovaClassLib
{
    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>
        /// Used for keeping track of our history
        /// </summary>
        private Stack<Uri> history = new Stack<Uri>();
        private bool IsBackButtonPressed = false;


        private static string AppRoot = "/app/";


        /// <summary>
        /// Handles native api calls
        /// </summary>
        private NativeExecution nativeExecution;

        protected DOMStorageHelper domStorageHelper;
        protected OrientationHelper orientationHelper;

        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;
                }
            }
        }

        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
            this.nativeExecution = new NativeExecution(ref this.CordovaBrowser);
        }

        

        void AppClosing(object sender, ClosingEventArgs e)
        {
            Debug.WriteLine("AppClosing");
        }

        void AppDeactivated(object sender, DeactivatedEventArgs e)
        {
            Debug.WriteLine("AppDeactivated");

            try
            {
                CordovaBrowser.InvokeScript("CordovaCommandResult", new string[] { "pause"
});
            }
            catch (Exception)
            {
                Debug.WriteLine("Pause event error");
            } 
        }

        void AppLaunching(object sender, LaunchingEventArgs e)
        {
            Debug.WriteLine("AppLaunching");
        }

        void AppActivated(object sender, Microsoft.Phone.Shell.ActivatedEventArgs e)
        {
            Debug.WriteLine("AppActivated");
            try
            {
                CordovaBrowser.InvokeScript("CordovaCommandResult", new string[] { "resume"
});
            }
            catch (Exception)
            {
                Debug.WriteLine("Resume event error");
            }  
        }

        void GapBrowser_Loaded(object sender, RoutedEventArgs e)
        {
            if (DesignerProperties.IsInDesignTool)
            {
                return;
            }

            // prevents refreshing web control to initial state during pages transitions
            if (this.IsBrowserInitialized) return;

            this.domStorageHelper = new DOMStorageHelper(this.CordovaBrowser);

            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();
                    }
   
                }

                StreamResourceInfo streamInfo = Application.GetResourceStream(new Uri("CordovaSourceDictionary.xml",
UriKind.Relative));

                if (streamInfo != null)
                {
                    StreamReader sr = new StreamReader(streamInfo.Stream);
                    //This will Read Keys Collection for the xml file

                    XDocument document = XDocument.Parse(sr.ReadToEnd());

                    var files = from results in document.Descendants("FilePath")
                                 select new
                                 {
                                     path =  (string)results.Attribute("Value")
                                 };
                    StreamResourceInfo fileResourceStreamInfo;

                    using (IsolatedStorageFile appStorage = IsolatedStorageFile.GetUserStoreForApplication())
                    {

                        foreach (var file in files)
                        {
                            fileResourceStreamInfo = Application.GetResourceStream(new Uri(file.path,
UriKind.Relative));

                            if (fileResourceStreamInfo != null)
                            {
                                using (BinaryReader br = new BinaryReader(fileResourceStreamInfo.Stream))
                                {
                                    byte[] data = br.ReadBytes((int)fileResourceStreamInfo.Stream.Length);

                                    string strBaseDir = AppRoot + file.path.Substring(0, file.path.LastIndexOf(System.IO.Path.DirectorySeparatorChar));

                                    if(!appStorage.DirectoryExists(strBaseDir))
                                    {
                                        //Debug.WriteLine("Creating Directory :: " + strBaseDir);
                                        appStorage.CreateDirectory(strBaseDir);
                                    }

                                    // This will truncate/overwrite an existing file, or 
                                    using (IsolatedStorageFileStream outFile = appStorage.OpenFile(AppRoot
+ file.path, FileMode.Create))
                                    {
                                        Debug.WriteLine("Writing data for " + AppRoot + file.path
+ " and length = " + data.Length);
                                        using (var writer = new BinaryWriter(outFile))
                                        {
                                            writer.Write(data);
                                        }
                                    }
                                }
                            }
                            else
                            {
                                Debug.WriteLine("Failed to write file :: " + file.path + "
did you forget to add it to the project?");
                            }
                        }
                    }
                }

                CordovaBrowser.Navigate(StartPageUri);
                IsBrowserInitialized = true;
                AttachHardwareButtonHandlers();
            }
            catch (Exception ex)
            {
                Debug.WriteLine("Exception in GapBrowser_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);

                    this.orientationHelper = new OrientationHelper(this.CordovaBrowser, page);


                }
            }
        }

        void page_BackKeyPress(object sender, CancelEventArgs e)
        {
            if (OverrideBackButton)
            {
                try
                {
                    CordovaBrowser.InvokeScript("CordovaCommandResult", new string[] { "backbutton"
});
                    e.Cancel = true;
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Exception while invoking backbutton into cordova view:
" + ex.Message);
                }
            }
            else
            {
                if (history.Count > 1)
                {
                    Uri current = history.Peek();
                    history.Pop();
                    Uri next = history.Peek();
                    IsBackButtonPressed = true;
                    bool doHistory = false;
                    if (current.ToString().IndexOf("#") > 0 && next.ToString().IndexOf("#")
> 0)
                    {
                        if (current.ToString().Substring(0, current.ToString().IndexOf("#"))
== next.ToString().Substring(0, next.ToString().IndexOf("#")))
                        {
                            doHistory = true;
                        }
                    }
                    if (doHistory)
                    {
                        CordovaBrowser.InvokeScript("eval", "window.history.back()");
                        CordovaBrowser.InvokeScript("eval", "window.location.reload()");
                    }
                    else
                    {
                        CordovaBrowser.Navigate(next);
                    }
                    e.Cancel = true;
                }
            }
        }

        void GapBrowser_LoadCompleted(object sender, System.Windows.Navigation.NavigationEventArgs
e)
        {
            this.CordovaBrowser.Opacity = 1;

            string nativeReady = "(function(){ cordova.require('cordova/channel').onNativeReady.fire()})();";

            try
            {
                CordovaBrowser.InvokeScript("execScript", new string[] { nativeReady });
            }
            catch (Exception ex)
            {
                Debug.WriteLine("Error calling js to fire nativeReady event. Did you include
cordova-x.x.x.js in your html script tag?");
            }
        }


        void GapBrowser_Navigating(object sender, NavigatingEventArgs e)
        {
            if (!IsBackButtonPressed)
            {
                history.Push(e.Uri);
            }
            else
            {
                IsBackButtonPressed = false;
            }

            Debug.WriteLine("GapBrowser_Navigating to :: " + e.Uri.ToString());
            // TODO: tell any running plugins to stop doing what they are doing.
            // TODO: check whitelist / blacklist
            // NOTE: Navigation can be cancelled by setting :        e.Cancel = true;


        }

        /*
         *  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 GapBrowser_ScriptNotify(object sender, NotifyEventArgs e)
        {
            string commandStr = e.Value;

            if (commandStr.IndexOf("DOMStorage") == 0)
            {
                this.domStorageHelper.HandleStorageCommand(commandStr);
                return;
            }
            else if (commandStr.IndexOf("Orientation") == 0)
            {
                this.orientationHelper.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 args = commandCallParams.Args;
                        this.OverrideBackButton = (args != null && args.Length >
0 && args.ToLower() == "true");
                        break;
                }
                if (commandCallParams.Action.ToLower() == "softbackbutton")
                {
                    try
                    {
                        CordovaBrowser.InvokeScript("eval", "window.history.back()");
                        history.Pop();
                    }
                    catch (Exception ex)
                    {
                        //do nothing, must have been called from first page, not our fault
here.
                    }
                }
                if (commandCallParams.Action.ToLower() == "historystackpop")
                {
                    try
                    {
                        history.Pop();
                    }
                    catch (Exception ex)
                    {
                        //do nothing, must have been called from first page, not our fault
here.
                    }
                }
                if (commandCallParams.Action.ToLower() == "historystackclear")
                {
                    try
                    {
                        history = new Stack<Uri>();
                    }
                    catch (Exception ex)
                    {
                        //do nothing, must have been called from first page, not our fault
here.
                    }
                }
            }
            else
            {
                //Debug.WriteLine("ProcessCommand :: " + commandStr);
                this.nativeExecution.ProcessCommand(commandCallParams);
            }
        }

        private void GapBrowser_Unloaded(object sender, RoutedEventArgs e)
        {

        }

        private void GapBrowser_NavigationFailed(object sender, System.Windows.Navigation.NavigationFailedEventArgs
e)
        {
            Debug.WriteLine("GapBrowser_NavigationFailed :: " + e.Uri.ToString());
        }

        private void GapBrowser_Navigated(object sender, System.Windows.Navigation.NavigationEventArgs
e)
        {
            Debug.WriteLine("GapBrowser_Navigated :: " + e.Uri.ToString());
        }

       
    }
}

                
> WP7 Certification and the Back Button
> -------------------------------------
>
>                 Key: CB-520
>                 URL: https://issues.apache.org/jira/browse/CB-520
>             Project: Apache Callback
>          Issue Type: Bug
>          Components: WP7
>    Affects Versions: 1.6.1
>         Environment: VS.NET 2010 and WP7.1 emulator
>            Reporter: Alan Neveu
>            Assignee: Jesse MacFadyen
>            Priority: Minor
>
> I tried submitting my PG 1.5 app to the Windows Marketplace and it was rejected due to
WP7's requirements for the Back Button. I upgraded to PG 1.6.1 and I am inspecting how it
works with the hardware back button.  It seems to work much better, but my app is designed
so that it has a soft back button in the app in various places, and on WP7 the user can always
tap the hardware back button.  I am trying to use navigator.app.historyBack and it appears
to work, but it does something slightly different than actually tapping the hardware back
button does.  I have also tried using window.history.back and that works different yet.  I
am using JQueryMobile 1.1.0 and so I wind up doing quite a lot of $.mobile.changePage calls
to #Page id's, and because I use multiple .html files I also need to do some rel="external"
links or window.location.href= calls.  I think my needs are similar or the same as those of
other WP7 developers.  Here is what currently happens with PG 1.6.1 in a simple Page1/Page2
JQueryMobile app when using the hardware back button versus using navigator.app.historyBack,
vs. window.history.back:
> SCENARIO #1 - using hardware BackButton only
> Page 1 links to Page 2 using $.mobile.changePage("#Page2");
> BackButton tap - goes back to Page1 but page is requested again and reloaded from scratch
which is slow and the user loses any form data they had entered.
> BackButton tap - exits app (great!)
> SCENARIO #2 - hardware BackButton and navigator.app.backHistory
> Page 1 links to Page 2 using $.mobile.changePage("#Page2");
> navigator.app.backHistory(); goes back to Page1 served from cache, which is fast and
form data is preserved.
> BackButton tap: nothing happens
> BackButton tap: Page1 is reloaded from scratch
> BackButton tap: exits app
> SCENARIO #3 - hardware backButton and window.history.back
> Page 1 links to Page 2 using $.mobile.changePage("#Page2");
> window.history.back(); goes back to Page1 served from cache.
> BackButton tap: Page1 is reloaded from scratch (DOH!)
> BackButton tap: exits app
> My Observations:
> 1) The hardware back button does not use the cached page - it reloads/re-requests the
page. This is kind of a drag but I think we have to just go with this because it is the behavior
that the Marketplace testers will be expecting and validating.
> 2) window.history.back() is giving better results than navigator.app.backHistory, but
still not the same as the hardware back button. I think apps will fail Marketplace certification
if they use either of these approaches for soft back buttons.
> 3) When going back to an external page (as opposed to a JQueryMobile #pageID), window.history.back
works but navigator.app.backHistory does not seem to do anything at all.  I say window.history.back
"works" but it is still the same result as in Scenario #3 above, which is not good.

--
This message is automatically generated by JIRA.
If you think it was sent incorrectly, please contact your JIRA administrators: https://issues.apache.org/jira/secure/ContactAdministrators!default.jspa
For more information on JIRA, see: http://www.atlassian.com/software/jira

       

Mime
View raw message