├── .gitignore ├── .gitmodules ├── .jshintrc ├── .reviewboardrc ├── AppHarnessUI ├── AppHarnessUI.m ├── README.md ├── android │ ├── AppHarnessUI.java │ ├── CordovaAppHarness.java │ ├── CustomAndroidWebView.java │ ├── CustomCordovaWebView.java │ ├── CustomCrosswalkWebView.java │ └── TwoFingerDoubleTapGestureDetector.java ├── appharnessui.js └── plugin.xml ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE ├── README.md ├── RELEASE.md ├── UrlRemap ├── README.md ├── plugin.xml ├── src │ ├── android │ │ └── UrlRemap.java │ └── ios │ │ └── UrlRemap.m └── urlremap.js ├── cde-cadt-diagram.png ├── createproject.sh ├── gulpfile.js ├── harness-push ├── README.md ├── harness-push.js ├── node_modules │ └── chrome-app-developer-tool-client │ │ ├── README.md │ │ ├── chromeharnessclient.js │ │ ├── chromepushsession.js │ │ ├── harnessclient.js │ │ ├── package.json │ │ ├── pushsession.js │ │ └── release_hashes.txt └── package.json ├── package.json ├── src ├── cca.js └── style.css ├── template-overrides ├── Activity.java ├── CCAHarness-debug.keystore ├── Info.plist ├── after-hook.js ├── config.xml ├── debug-signing.properties ├── icons │ ├── android │ │ ├── icon-hdpi.png │ │ ├── icon-mdpi.png │ │ ├── icon-xdpi.png │ │ └── icon.png │ └── ios │ │ ├── iTunesArtwork.png │ │ ├── iTunesArtwork@2x.png │ │ ├── icon-40.png │ │ ├── icon-40@2x.png │ │ ├── icon-50.png │ │ ├── icon-50@2x.png │ │ ├── icon-57.png │ │ ├── icon-57@2x.png │ │ ├── icon-60.png │ │ ├── icon-60@2x.png │ │ ├── icon-72.png │ │ ├── icon-72@2x.png │ │ ├── icon-76.png │ │ ├── icon-76@2x.png │ │ ├── icon-small.png │ │ ├── icon-small@2x.png │ │ ├── icon.png │ │ └── icon@2x.png └── strings.xml ├── webpack.config.js └── www └── cdvah ├── css └── Zd2E9abXLFGSr9G3YK2MsNxB8OB85xaNTJvVSB9YUjQ.woff ├── harnessmenu.html ├── js ├── AboutCtrl.js ├── ApkPackager.js ├── AppHarnessUI.js ├── AppsService.js ├── CordovaInstaller.js ├── CrxInstaller.js ├── DetailsCtrl.js ├── DirectoryManager.js ├── HarnessServer.js ├── HttpServer.js ├── InAppMenuCtrl.js ├── Installer.js ├── ListCtrl.js ├── PermissionCtrl.js ├── PluginMetadata.js ├── Reporter.js ├── ResourcesLoader.js ├── UrlRemap.js ├── app.js └── libs │ ├── angular-moment.js │ ├── angular-route.js │ ├── angular.js │ ├── google-analytics-bundle.js │ ├── moment.min.js │ └── slice.js └── views ├── about.html ├── details.html ├── list.html └── permission.html /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /harness-push/node_modules/* 3 | !/harness-push/node_modules/chrome-app-developer-tool-client 4 | /harness-push/node_modules/chrome-app-developer-tool-client/node_modules 5 | /www/cdvah/generated 6 | /node_modules 7 | /ChromeAppDevTool 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "apkpackager"] 2 | path = apkpackager 3 | url = https://github.com/MobileChromeApps/apkpackager.git 4 | [submodule "AndroidApkTemplate"] 5 | path = AndroidApkTemplate 6 | url = https://github.com/MobileChromeApps/AndroidApkTemplate.git 7 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | //enforcing 3 | "bitwise": true, 4 | "camelcase": true, 5 | "curly": true, 6 | "eqeqeq": false, 7 | "forin": false, 8 | "immed": true, 9 | "indent": 4, 10 | "latedef": false, 11 | "newcap": true, 12 | "noarg": true, 13 | "noempty": true, 14 | "nonew": true, 15 | "plusplus": false, 16 | "quotmark": "single", 17 | "strict": false, 18 | "trailing": true, 19 | "undef": true, 20 | "unused": true, 21 | 22 | //relaxing 23 | "eqnull":true, 24 | "sub":true, 25 | 26 | //environment 27 | "node": true, 28 | "browser": true, 29 | "devel":true, 30 | 31 | //other 32 | "globals": {"angular": false, "cordova" : false } 33 | } 34 | -------------------------------------------------------------------------------- /.reviewboardrc: -------------------------------------------------------------------------------- 1 | # 2 | # Settings for post-review (used for uploading diffs to reviews.apache.org). 3 | # 4 | GUESS_FIELDS = True 5 | OPEN_BROWSER = True 6 | TARGET_GROUPS = 'cordova' 7 | REVIEWBOARD_URL = 'http://reviews.apache.org' 8 | 9 | -------------------------------------------------------------------------------- /AppHarnessUI/AppHarnessUI.m: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | #import 20 | #import 21 | #import 22 | #import 23 | 24 | @class AppHarnessUI; 25 | 26 | @interface AHUIViewController : CDVViewController { 27 | @public 28 | __weak AppHarnessUI* _parentPlugin; 29 | UITapGestureRecognizer* _singleTapRecognizer; 30 | } 31 | @end 32 | 33 | @interface AppHarnessUI : CDVPlugin { 34 | AHUIViewController* _slaveCordovaViewController; 35 | NSString* _eventsCallbackId; 36 | BOOL _slaveVisible; 37 | } 38 | - (void)sendEvent:(NSString*)event; 39 | @end 40 | 41 | #pragma mark AHUIViewController 42 | 43 | @implementation AHUIViewController 44 | 45 | - (void)viewDidLoad { 46 | [super viewDidLoad]; 47 | UITapGestureRecognizer *tapRecognizer = 48 | [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)]; 49 | 50 | // Two-finger double-tap. 51 | tapRecognizer.numberOfTapsRequired = 2; 52 | tapRecognizer.numberOfTouchesRequired = 2; 53 | tapRecognizer.delegate = self; 54 | [self.view addGestureRecognizer:tapRecognizer]; 55 | 56 | // Single-tap 57 | _singleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTap:)]; 58 | 59 | } 60 | 61 | - (void)setCaptureSingleTap:(BOOL)value { 62 | [self.webView setUserInteractionEnabled:!value]; 63 | if (value) { 64 | [self.view addGestureRecognizer:_singleTapRecognizer]; 65 | } else { 66 | [self.view removeGestureRecognizer:_singleTapRecognizer]; 67 | } 68 | } 69 | 70 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { 71 | // Required for tap gesture recognizer to work with UIWebView. 72 | return YES; 73 | } 74 | 75 | - (void)handleSingleTap:(UITapGestureRecognizer *)recognizer { 76 | [_parentPlugin sendEvent:@"hideMenu"]; 77 | } 78 | 79 | - (void)handleTap:(UITapGestureRecognizer *)recognizer { 80 | [_parentPlugin sendEvent:@"showMenu"]; 81 | } 82 | 83 | @end 84 | 85 | #pragma mark AppHarnessUI 86 | 87 | @implementation AppHarnessUI 88 | 89 | - (void)events:(CDVInvokedUrlCommand*)command { 90 | _eventsCallbackId = command.callbackId; 91 | } 92 | 93 | - (void)sendEvent:(NSString*)event { 94 | CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:event]; 95 | [pluginResult setKeepCallbackAsBool:YES]; 96 | [self.commandDelegate sendPluginResult:pluginResult callbackId:_eventsCallbackId]; 97 | } 98 | 99 | - (void)evalJs:(CDVInvokedUrlCommand*)command { 100 | NSString* code = [command argumentAtIndex:0]; 101 | if (_slaveCordovaViewController == nil) { 102 | NSLog(@"AppHarnessUI.evalJs: Not evaluating JS since no app is active."); 103 | } else { 104 | [_slaveCordovaViewController.webView stringByEvaluatingJavaScriptFromString:code]; 105 | } 106 | CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; 107 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 108 | } 109 | 110 | - (void)create:(CDVInvokedUrlCommand*)command { 111 | NSString* url = [command argumentAtIndex:0]; 112 | if (_slaveCordovaViewController == nil) { 113 | _slaveCordovaViewController = [[AHUIViewController alloc] init]; 114 | _slaveCordovaViewController.startPage = url; 115 | _slaveCordovaViewController->_parentPlugin = self; 116 | UIView* newView = _slaveCordovaViewController.view; 117 | UIView* superView = self.viewController.view; 118 | [self.viewController addChildViewController:_slaveCordovaViewController]; 119 | [newView layer].anchorPoint = CGPointMake(0.0f, 1.0f); 120 | [newView setFrame:superView.bounds]; 121 | [superView addSubview:newView]; 122 | [_slaveCordovaViewController didMoveToParentViewController:self.viewController]; 123 | _slaveVisible = YES; 124 | } else { 125 | NSLog(@"AppHarnessUI.create: already exists"); 126 | } 127 | CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; 128 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 129 | } 130 | 131 | - (void)reload:(CDVInvokedUrlCommand*)command { 132 | NSString* url = [command argumentAtIndex:0]; 133 | if (_slaveCordovaViewController == nil) { 134 | NSLog(@"AppHarnessUI.reload: no webview exists"); 135 | } else { 136 | [_slaveCordovaViewController setStartPage:url]; 137 | NSURL *urlObj = [NSURL URLWithString:url]; 138 | NSURLRequest *requestObj = [NSURLRequest requestWithURL:urlObj]; 139 | [[_slaveCordovaViewController webView] loadRequest:requestObj]; 140 | } 141 | CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; 142 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 143 | } 144 | 145 | - (void)destroy:(CDVInvokedUrlCommand*)command { 146 | if (_slaveCordovaViewController == nil) { 147 | NSLog(@"AppHarnessUI.destroy: already destroyed."); 148 | } else { 149 | [_slaveCordovaViewController removeFromParentViewController]; 150 | [_slaveCordovaViewController.view removeFromSuperview]; 151 | _slaveCordovaViewController = nil; 152 | _slaveVisible = NO; 153 | [self sendEvent:@"destroyed"]; 154 | } 155 | CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; 156 | [self.commandDelegate sendPluginResult:pluginResult callbackId:_eventsCallbackId]; // Close events channel. 157 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 158 | } 159 | 160 | - (void)setVisibleHelper:(BOOL)value { 161 | if (value == _slaveVisible) { 162 | return; 163 | } 164 | _slaveVisible = value; 165 | if (_slaveCordovaViewController == nil) { 166 | NSLog(@"AppHarnessUI.setVisible: slave doesn't exist"); 167 | } else { 168 | [UIView animateWithDuration:.3 169 | delay:0 170 | options:UIViewAnimationOptionCurveEaseOut 171 | animations:^{ 172 | [_slaveCordovaViewController setCaptureSingleTap:!value]; 173 | UIView* view = _slaveCordovaViewController.view; 174 | if (value) { 175 | [view setTransform:CGAffineTransformIdentity]; 176 | } else { 177 | [view setTransform:CGAffineTransformMakeScale(.25, .25)]; 178 | } 179 | } completion:nil]; 180 | } 181 | } 182 | 183 | - (void)setVisible:(CDVInvokedUrlCommand*)command { 184 | BOOL value = [[command argumentAtIndex:0] boolValue]; 185 | [self setVisibleHelper:value]; 186 | CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; 187 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 188 | } 189 | 190 | @end 191 | 192 | -------------------------------------------------------------------------------- /AppHarnessUI/README.md: -------------------------------------------------------------------------------- 1 | # AppHarnessUI 2 | 3 | Plugin that deals with native windowing. 4 | 5 | -------------------------------------------------------------------------------- /AppHarnessUI/android/CordovaAppHarness.java: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | package org.apache.appharness; 21 | 22 | import org.apache.appharness.AppHarnessUI; 23 | import android.os.Bundle; 24 | import org.apache.cordova.*; 25 | 26 | public class CordovaAppHarness extends CordovaActivity 27 | { 28 | @Override 29 | public void onCreate(Bundle savedInstanceState) 30 | { 31 | super.onCreate(savedInstanceState); 32 | // Set by in config.xml 33 | loadUrl(launchUrl); 34 | } 35 | 36 | @Override 37 | public void onBackPressed() { 38 | // If app is running, quit it. 39 | AppHarnessUI ahui = (AppHarnessUI)appView.getPluginManager().getPlugin("AppHarnessUI"); 40 | if (ahui != null) { 41 | if (ahui.isSlaveCreated()) { 42 | ahui.sendEvent("quitApp"); 43 | return; 44 | } 45 | } 46 | // Otherwise, hide instead of calling .finish(). 47 | moveTaskToBack(true); 48 | } 49 | 50 | @Override 51 | public Object onMessage(String id, Object data) { 52 | // Capture the app calling navigator.app.exitApp(). 53 | if ("exit".equals(id)) { 54 | AppHarnessUI ahui = (AppHarnessUI)appView.getPluginManager().getPlugin("AppHarnessUI"); 55 | if (ahui != null) { 56 | if (ahui.isSlaveCreated()) { 57 | ahui.sendEvent("quitApp"); 58 | return new Object(); 59 | } 60 | } 61 | } 62 | return super.onMessage(id, data); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /AppHarnessUI/android/CustomAndroidWebView.java: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | package org.apache.appharness; 20 | 21 | import org.apache.cordova.CordovaWebViewEngine; 22 | import org.apache.cordova.engine.SystemWebView; 23 | import org.apache.cordova.engine.SystemWebViewEngine; 24 | 25 | import android.annotation.SuppressLint; 26 | import android.content.Context; 27 | import android.os.Build; 28 | import android.util.Log; 29 | import android.view.MotionEvent; 30 | 31 | class CustomAndroidWebView extends SystemWebViewEngine implements CustomCordovaWebView { 32 | private static final String LOG_TAG = "CustomAndroidWebView"; 33 | 34 | private AppHarnessUI parent; 35 | TwoFingerDoubleTapGestureDetector twoFingerTapDetector; 36 | boolean stealTapEvents; 37 | 38 | public CustomAndroidWebView(AppHarnessUI parent, Context context) { 39 | super(new CustomView(context)); 40 | this.parent = parent; 41 | ((CustomView)webView).parent = this; 42 | twoFingerTapDetector = new TwoFingerDoubleTapGestureDetector(parent); 43 | } 44 | 45 | @Override 46 | public void setStealTapEvents(boolean value){ 47 | stealTapEvents=value; 48 | } 49 | 50 | @SuppressLint("NewApi") 51 | @Override 52 | public void evaluateJavascript(String script) { 53 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { 54 | webView.loadUrl("javascript:" + script); 55 | } else { 56 | webView.evaluateJavascript(script, null); 57 | } 58 | } 59 | 60 | @Override 61 | public CordovaWebViewEngine asEngine() { 62 | return this; 63 | } 64 | 65 | @Override 66 | public boolean goBack() { 67 | if (canGoBack()) { 68 | return super.goBack(); 69 | } 70 | if (parent.slaveVisible) { 71 | parent.sendEvent("showMenu"); 72 | return true; 73 | } 74 | // Should never get here since the webview does not have focus. 75 | Log.w(LOG_TAG, "Somehow back button was pressed when app not visible"); 76 | return false; 77 | } 78 | 79 | private static class CustomView extends SystemWebView { 80 | CustomAndroidWebView parent; 81 | public CustomView(Context context) { 82 | super(context); 83 | } 84 | 85 | @Override 86 | public boolean onTouchEvent(MotionEvent e) { 87 | if (parent.stealTapEvents) { 88 | if (e.getAction() == MotionEvent.ACTION_UP) { 89 | parent.parent.sendEvent("hideMenu"); 90 | } 91 | return true; 92 | } 93 | parent.twoFingerTapDetector.onTouchEvent(e); 94 | return super.onTouchEvent(e); 95 | } 96 | 97 | @SuppressLint("NewApi") 98 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 99 | super.onSizeChanged(w, h, oldw, oldh); 100 | // Needed for the view to stay in the bottom when rotating. 101 | setPivotY(h); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /AppHarnessUI/android/CustomCordovaWebView.java: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | package org.apache.appharness; 20 | 21 | import org.apache.cordova.CordovaWebViewEngine; 22 | 23 | interface CustomCordovaWebView { 24 | void setStealTapEvents(boolean value); 25 | void evaluateJavascript(String script); 26 | CordovaWebViewEngine asEngine(); 27 | } 28 | -------------------------------------------------------------------------------- /AppHarnessUI/android/CustomCrosswalkWebView.java: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | package org.apache.appharness; 20 | 21 | import org.apache.cordova.CordovaPreferences; 22 | import org.apache.cordova.CordovaWebViewEngine; 23 | import org.crosswalk.engine.XWalkCordovaView; 24 | import org.crosswalk.engine.XWalkWebViewEngine; 25 | 26 | import android.annotation.SuppressLint; 27 | import android.content.Context; 28 | import android.util.Log; 29 | import android.view.MotionEvent; 30 | 31 | class CustomCrosswalkWebView extends XWalkWebViewEngine implements CustomCordovaWebView { 32 | private static final String LOG_TAG = "AppHarnessUI"; 33 | 34 | private AppHarnessUI parent; 35 | private boolean stealTapEvents; 36 | private TwoFingerDoubleTapGestureDetector twoFingerTapDetector; 37 | 38 | CustomCrosswalkWebView(AppHarnessUI parent, Context context, CordovaPreferences preferences) { 39 | super(new CustomXwalkView(context, preferences)); 40 | this.parent = parent; 41 | ((CustomXwalkView)webView).parent = this; 42 | twoFingerTapDetector = new TwoFingerDoubleTapGestureDetector(parent); 43 | } 44 | 45 | @Override 46 | public void setStealTapEvents(boolean value){ 47 | stealTapEvents=value; 48 | } 49 | 50 | @Override 51 | public void evaluateJavascript(String script) { 52 | webView.evaluateJavascript(script, null); 53 | } 54 | 55 | @Override 56 | public CordovaWebViewEngine asEngine() { 57 | return this; 58 | } 59 | 60 | @Override 61 | public boolean goBack() { 62 | if (webView.getNavigationHistory().canGoBack()) { 63 | return super.goBack(); 64 | } 65 | if (parent.slaveVisible) { 66 | parent.sendEvent("showMenu"); 67 | return true; 68 | } 69 | // Should never get here since the webview does not have focus. 70 | Log.w(LOG_TAG, "Somehow back button was pressed when app not visible"); 71 | return false; 72 | } 73 | 74 | private static class CustomXwalkView extends XWalkCordovaView { 75 | CustomCrosswalkWebView parent; 76 | public CustomXwalkView(Context context, CordovaPreferences preferences) { 77 | super(context, preferences); 78 | } 79 | 80 | @Override 81 | public boolean onTouchEvent(MotionEvent e) { 82 | if (parent.stealTapEvents) { 83 | if (e.getAction() == MotionEvent.ACTION_UP) { 84 | parent.parent.sendEvent("hideMenu"); 85 | } 86 | return true; 87 | } 88 | return super.onTouchEvent(e); 89 | } 90 | @Override 91 | public boolean onInterceptTouchEvent(MotionEvent e) { 92 | if (parent.stealTapEvents) { 93 | if (e.getAction() == MotionEvent.ACTION_UP) { 94 | parent.parent.sendEvent("hideMenu"); 95 | } 96 | return true; 97 | } 98 | parent.twoFingerTapDetector.onTouchEvent(e); 99 | return super.onInterceptTouchEvent(e); 100 | } 101 | 102 | @SuppressLint("NewApi") 103 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 104 | super.onSizeChanged(w, h, oldw, oldh); 105 | // Needed for the view to stay in the bottom when rotating. 106 | setPivotY(h); 107 | } 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /AppHarnessUI/android/TwoFingerDoubleTapGestureDetector.java: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | package org.apache.appharness; 20 | 21 | import android.view.MotionEvent; 22 | import android.view.ViewConfiguration; 23 | 24 | // Based on: http://stackoverflow.com/questions/12414680/how-to-implement-a-two-finger-double-click-in-android 25 | class TwoFingerDoubleTapGestureDetector { 26 | private AppHarnessUI parent; 27 | 28 | private final int TIMEOUT = ViewConfiguration.getDoubleTapTimeout() + 100; 29 | private long mFirstDownTime = 0; 30 | private boolean mSeparateTouches = false; 31 | private byte mTwoFingerTapCount = 0; 32 | 33 | public TwoFingerDoubleTapGestureDetector(AppHarnessUI parent) { 34 | this.parent = parent; 35 | } 36 | 37 | private void reset(long time) { 38 | mFirstDownTime = time; 39 | mSeparateTouches = false; 40 | mTwoFingerTapCount = 0; 41 | } 42 | 43 | public boolean onTouchEvent(MotionEvent event) { 44 | switch(event.getActionMasked()) { 45 | case MotionEvent.ACTION_DOWN: 46 | if(mFirstDownTime == 0 || event.getEventTime() - mFirstDownTime > TIMEOUT) 47 | reset(event.getDownTime()); 48 | break; 49 | case MotionEvent.ACTION_POINTER_UP: 50 | if(event.getPointerCount() == 2) 51 | mTwoFingerTapCount++; 52 | else 53 | mFirstDownTime = 0; 54 | break; 55 | case MotionEvent.ACTION_UP: 56 | if(!mSeparateTouches) 57 | mSeparateTouches = true; 58 | else if(mTwoFingerTapCount == 2 && event.getEventTime() - mFirstDownTime < TIMEOUT) { 59 | parent.sendEvent("showMenu"); 60 | mFirstDownTime = 0; 61 | return true; 62 | } 63 | } 64 | 65 | return false; 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /AppHarnessUI/appharnessui.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | * 19 | */ 20 | var exec = cordova.require('cordova/exec'); 21 | 22 | function eventHandler(type) { 23 | type && exports.onEvent && exports.onEvent(type); 24 | } 25 | 26 | exports.onEvent = null; 27 | 28 | exports.create = function(startUrl, configXmlUrl, serviceNameWhitelist, webViewType, win) { 29 | exec(eventHandler, null, 'AppHarnessUI', 'events', []); 30 | exec(win, null, 'AppHarnessUI', 'create', [startUrl, configXmlUrl, serviceNameWhitelist, webViewType]); 31 | }; 32 | 33 | exports.reload = function(startUrl, configXmlUrl, serviceNameWhitelist, win) { 34 | exec(win, null, 'AppHarnessUI', 'reload', [startUrl, configXmlUrl, serviceNameWhitelist]); 35 | }; 36 | 37 | exports.destroy = function(win) { 38 | exec(win, null, 'AppHarnessUI', 'destroy', []); 39 | }; 40 | 41 | exports.setVisible = function(value, win) { 42 | exec(win, null, 'AppHarnessUI', 'setVisible', [value]); 43 | }; 44 | 45 | exports.evalJs = function(code, win) { 46 | exec(win, null, 'AppHarnessUI', 'evalJs', [code]); 47 | }; 48 | 49 | -------------------------------------------------------------------------------- /AppHarnessUI/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 23 | 24 | 25 | 26 | 27 | AppHarnessUI 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 21 | 22 | # Contributing to Apache Cordova 23 | 24 | Anyone can contribute to Cordova. And we need your contributions. 25 | 26 | There are multiple ways to contribute: report bugs, improve the docs, and 27 | contribute code. 28 | 29 | For instructions on this, start with the 30 | [contribution overview](http://cordova.apache.org/#contribute). 31 | 32 | The details are explained there, but the important items are: 33 | - Sign and submit an Apache ICLA (Contributor License Agreement). 34 | - Have a Jira issue open that corresponds to your contribution. 35 | - Run the tests so your patch doesn't break existing functionality. 36 | 37 | We look forward to your contributions! 38 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Apache Cordova 2 | Copyright 2014 The Apache Software Foundation 3 | 4 | This product includes software developed at 5 | The Apache Software Foundation (http://www.apache.org) 6 | 7 | Topcoat is licensed under the Apache license version 2.0, January 2004 (see LICENSE file). 8 | 9 | Topcoat uses the following third party libraries that may have licenses 10 | differing from that of Topcoat itself. You can find the libraries and their 11 | respective licenses below. 12 | 13 | - Source Code Pro ./src/font/SourceCodePro 14 | 15 | https://github.com/adobe/source-code-pro 16 | 17 | Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. 18 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 19 | This license is available with a FAQ at: http://scripts.sil.org/OFL 20 | 21 | - Source Sans Pro ./src/font/SourceSansPro 22 | 23 | https://github.com/adobe/source-sans-pro 24 | 25 | Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. 26 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 27 | This license is available with a FAQ at: http://scripts.sil.org/OFL 28 | 29 | The following frameworks and libraries are provided just for testing and benchmarking and, as provided, should not be included as part of Topcoat output. 30 | 31 | - Bootstrap - ./test/third-party/bootstrap 32 | 33 | https://github.com/twitter/bootstrap 34 | 35 | Copyright 2012 Twitter, Inc 36 | Licensed under the Apache License v2.0 37 | http://www.apache.org/licenses/LICENSE-2.0 38 | 39 | - jQuery - ./test/third-party/jquery-1.8.2.js 40 | 41 | https://github.com/jquery/jquery 42 | 43 | Copyright 2011, John Resig 44 | Dual licensed under the MIT or GPL Version 2 licenses. 45 | http://jquery.org/license 46 | 47 | Includes Sizzle.js 48 | http://sizzlejs.com/ 49 | Copyright 2011, The Dojo Foundation 50 | Released under the MIT, BSD, and GPL Licenses. 51 | 52 | Permission is hereby granted, free of charge, to any person obtaining 53 | a copy of this software and associated documentation files (the 54 | "Software"), to deal in the Software without restriction, including 55 | without limitation the rights to use, copy, modify, merge, publish, 56 | distribute, sublicense, and/or sell copies of the Software, and to 57 | permit persons to whom the Software is furnished to do so, subject to 58 | the following conditions: 59 | 60 | The above copyright notice and this permission notice shall be 61 | included in all copies or substantial portions of the Software. 62 | 63 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 64 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 65 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 66 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 67 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 68 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 69 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 70 | 71 | 72 | - Modernizr - ./test/third-party/modernizr 73 | 74 | https://github.com/Modernizr/Modernizr 75 | 76 | Modernizr is available under the MIT license 77 | 78 | Copyright (c) 2009–2011 79 | Permission is hereby granted, free of charge, to any person obtaining a copy 80 | of this software and associated documentation files (the "Software"), to deal 81 | in the Software without restriction, including without limitation the rights 82 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 83 | copies of the Software, and to permit persons to whom the Software is 84 | furnished to do so, subject to the following conditions: 85 | The above copyright notice and this permission notice shall be included in 86 | all copies or substantial portions of the Software. 87 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 88 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 89 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 90 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 91 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 92 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 93 | THE SOFTWARE. 94 | 95 | - Benchmark.js - ./test/third-party/benchmarkjs 96 | 97 | https://github.com/alexanderbeletsky/benchmark-js 98 | 99 | Copyright 2010-2012 Mathias Bynens 100 | Based on JSLitmus.js, copyright Robert Kieffer 101 | Modified by John-David Dalton 102 | Available under MIT license 103 | 104 | Permission is hereby granted, free of charge, to any person obtaining 105 | a copy of this software and associated documentation files (the 106 | "Software"), to deal in the Software without restriction, including 107 | without limitation the rights to use, copy, modify, merge, publish, 108 | distribute, sublicense, and/or sell copies of the Software, and to 109 | permit persons to whom the Software is furnished to do so, subject to 110 | the following conditions: 111 | 112 | The above copyright notice and this permission notice shall be 113 | included in all copies or substantial portions of the Software. 114 | 115 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 116 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 117 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 118 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 119 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 120 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 121 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 122 | 123 | - normalize.css - ./test/third-party/normalize 124 | 125 | Copyright (c) Nicolas Gallagher and Jonathan Neal 126 | 127 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 128 | 129 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 130 | 131 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 132 | 133 | - Foundation - ./test/third-party/foundation 134 | 135 | Copyright (c) 2012 Mark Hayes 136 | 137 | MIT License 138 | 139 | Permission is hereby granted, free of charge, to any person obtaining 140 | a copy of this software and associated documentation files (the 141 | "Software"), to deal in the Software without restriction, including 142 | without limitation the rights to use, copy, modify, merge, publish, 143 | distribute, sublicense, and/or sell copies of the Software, and to 144 | permit persons to whom the Software is furnished to do so, subject to 145 | the following conditions: 146 | 147 | The above copyright notice and this permission notice shall be 148 | included in all copies or substantial portions of the Software. 149 | 150 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 151 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 152 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 153 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 154 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 155 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 156 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 157 | 158 | - ua-parser ./test/third-party/ua-parser 159 | 160 | https://github.com/faisalman/ua-parser-js 161 | 162 | Copyright © 2012 Faisalman 163 | Dual licensed under GPLv2 & MIT 164 | Copyright © 2012 Faisalman 165 | 166 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 167 | 168 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 169 | 170 | - HTML5 boilerplate 171 | 172 | https://github.com/h5bp/html5-boilerplate 173 | 174 | Copyright (c) HTML5 Boilerplate 175 | 176 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 177 | 178 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 179 | 180 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 181 | 182 | - classList - ./test/third-party/classlist 183 | 184 | http://purl.eligrey.com/github/classList.js/blob/master/classList.js 185 | 186 | This software is dedicated to the public domain. No warranty is expressed or implied. 187 | Use this software at your own risk. 188 | 189 | - fastclick - ./test/third-party/fastclick 190 | 191 | https://github.com/ftlabs/fastclick 192 | 193 | Copyright (C) 2012 The Financial Times Ltd. 194 | 195 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 196 | 197 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 198 | 199 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 200 | 201 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chrome App Developer Tool for Mobile (CADT) 2 | 3 | ![Using CDE, cca, and CADT to develop Chrome Apps for Mobile](cde-cadt-diagram.png) 4 | 5 | The Chrome App Developer Tool for Mobile (CADT) is a distribution of [Apache Cordova App Harness](https://git-wip-us.apache.org/repos/asf/cordova-app-harness.git) that can run Chrome Apps. It is based on the plugins from the [Chrome Apps for Mobile](https://github.com/MobileChromeApps/mobile-chrome-apps) project. 6 | 7 | CADT is an app for your mobile development device that makes it quick and easy to see your code in action. It provides the Cordova framework of Chrome Apps for Mobile so you can test your code by simply pushing your Chrome App assets to your mobile device (made easy with our tools), which is must faster than packaging up the entire mobile app. This is called **live deploy**. 8 | 9 | With CADT running on your mobile device, live deploy can be initiated from your development computer with either [Chrome Dev Editor (CDE)](https://github.com/dart-lang/chromedeveditor) or the [Chrome Apps for Mobile command line tool](https://github.com/MobileChromeApps/mobile-chrome-apps/blob/master/docs/Installation.md#install-the-cca-command-line-tool), allowing you to instantly preview the Chrome App you're editing, running right on Android or iOS. When you make a change to the code in your editor, you're a quick push away from seeing it straight on your device. 10 | 11 | ## Installation Using a Pre-Built Binary (Android only) 12 | 13 | 1. Enable USB debugging on your device (follow step 2 [here](http://developer.android.com/tools/device.html#setting-up)). 14 | 2. Download an APK from [here](https://github.com/MobileChromeApps/chrome-app-developer-tool/releases). 15 | 3. Run `adb install android-armv7-debug.apk` (of course, navigating to the appropriate directory first). 16 | * **Note:** On Windows, you need [vendor-specific device drivers](http://developer.android.com/tools/extras/oem-usb.html) to connect to certain devices. 17 | * Alternatively, download the `.apk` using your device's browser. 18 | 19 | To start using CADT, follow these instructions for running your Chrome App for Mobile: 20 | 21 | [Option A: Live deploy with CADT](https://github.com/MobileChromeApps/mobile-chrome-apps/blob/master/docs/Develop.md#option-a-live-deploy-with-cadt-quick-and-easy) 22 | 23 | 24 | ## Things that don't work with CADT 25 | CADT is amazing for rapid iteration cycles, but not everyting works the same as when deploying directly. 26 | 27 | * Support for third party plugins ([Issue #31](https://github.com/MobileChromeApps/chrome-app-developer-tool/issues/31)) 28 | * Core cordova plugins work fine for non-`cca`-based projects, but for `cca` projects plugins added via `cca plugin add` are not available in CADT. 29 | * APIs that depend on a Cloud Console project do not work (e.g. `chrome.identity`, `chrome.gcm`, `google.payments`) 30 | * `chrome.identity` works for some simple things, but uses a custom project 31 | * Lifecycle-based APIs (such as `chrome.alarms`) don't work when app is closed 32 | * `chrome.i18n` works only as of 0.12.0, and only when all locale directories use lowercase and underscores. 33 | 34 | ## Building from Source (iOS or Android - instructions for iOS / Linux) 35 | 36 | 1. Clone this repository: 37 | 38 | git clone https://github.com/MobileChromeApps/chrome-app-developer-tool.git 39 | 40 | 2. Create a CADT project using `createproject.sh`. For instance: 41 | 42 | ./createproject.sh ChromeAppDevTool 43 | cd ChromeAppDevTool 44 | cordova build android 45 | cordova build ios 46 | 47 | You can get more info using `./createproject.sh --help`. 48 | 49 | # Release Notes 50 | 51 | ## v0.13.0 (April 5, 2015) 52 | * Use plugins from npm rather than plugins.cordova.io 53 | * Updated crosswalk to v13 54 | * Updated CCA plugins 55 | 56 | ## v0.12.0 (March 24, 2015) 57 | 58 | * Enabled Content-Security-Policy for Chrome Apps 59 | * Updated to Crosswalk 11 60 | * Added chrome.bluetooth plugins 61 | * Made harnessclient compatible with node 0.12 as well as 0.10 62 | * Updated versions of all bundled plugins 63 | 64 | ## v0.11.1 (December 12, 2014) 65 | 66 | * Pushing large files is even faster 67 | * Updated CCA plugins now work better with Polymer 68 | 69 | ## v0.11.0 (November 18, 2014) 70 | 71 | * Fix "Unable to bind to port" message when wifi is disabled. 72 | * Pushing large files is now dramatically faster 73 | * Fix iOS pushes hanging 74 | * Add support for toggling between system / crosswalk webviews via manifest.mobile.json's "webview": "system" 75 | * Fix cordova-android's App plugin not being enabled (e.g. navigator.app.exitApp()) 76 | * Fix /deletefiles not working when deleting more than ~10 files. 77 | * iOS: Stop destroying webview on every launch so that remote inspector remains open 78 | * Add a /getfile endpoing to HarnessServer 79 | 80 | ## v0.10.0 (October 22, 2014) 81 | * New plugins: 82 | * `com.google.payments` 83 | * `org.chromium.sockets.*` 84 | * `org.chromium.system.storage` 85 | * Fix /deletefiles not working when deleting more than ~10 files. 86 | * UI tweaks: 87 | * Nicer permissions page 88 | * Show list of installed plugins in details page 89 | * iOS: Stopped destroying the webview on every launch. 90 | * Added a `/getfile` endpoint to HarnessServer 91 | * Delete manifest.json if it causes an exception 92 | * Share `manifest.json->config.xml` logic with cca via webpack 93 | * Add filesystem location `` to config.xml 94 | * Switch to building with gradle & enable multi-apks 95 | 96 | ## v0.9.0 (September 12, 2014) 97 | * Added basic analytics. 98 | * Updated app icons. 99 | 100 | ## v0.8.2 (August 28, 2014) 101 | * Make createproject.sh delete output directory and rm andrew-create-project.sh 102 | * Rename buildharness.sh -> build-android-release.sh 103 | * Make createproject.sh work even when no dependencies are set up ahead of time 104 | * createproject.sh: include ios by default when host=Darwin 105 | * Remove xwalk as a package.json dep. Use the version within cca instead 106 | * Update list of preloaded plugins 107 | * Install plugins from registry when needed 108 | 109 | ## v0.8.1 (July 11, 2014) 110 | * Fix exception on second launch and stop using plugin whitelist 111 | * Fix wrong path in createproject.sh (failed every time) 112 | * Add ArrayBuffer.slice polyfill for Android pre-KK (#22) 113 | * Update link to Chrome Dev Editor 114 | * Fix line-breaking CSS in About page 115 | 116 | ## v0.8.0 (June 25, 2014) 117 | * Add chrome.alarms and org.apache.cordova.media plugins (mistakenly left out) 118 | * Fix Android back button always quitting the app (now does what you'd expect) 119 | * App now loads only the plugins that an app uses (instead of all installed plugins) 120 | * Initial app push is now much faster for apps with lots of files 121 | * First cut at "Details" and "About" pages 122 | * Deleted notification bubble 123 | * Fix apps not launching when you: push App A, then push App B, then push App A again 124 | 125 | ## v0.7.1 (June 17, 2014) 126 | * Fix crash on launch-after-backbutton (#13) 127 | 128 | ## v0.7.0 (June 17, 2014) 129 | * Speed improvements to initial app push (zippush endpoint) 130 | * Implemented details & about page 131 | * Minor UI tweaks 132 | 133 | ## v0.6.0 (June 17, 2014) 134 | * New name: Chrome App Developer Tool for Mobile 135 | * Default WebView for Chrome Apps is now using Crosswalk, currently based on Chrome/36.0.1985.18 136 | * Changed the Android Launch Mode to singleTop so that clicking icon from launcher doesn't restart the app unnecessarily 137 | * Few updates to the set of plugins we bundle by default 138 | * Fix "failed to start server" when you close the app and start it again 139 | * Displayed IP address now updates when your device's IP changes 140 | * Some minor UI updates 141 | 142 | ## v0.5.1 (June 3, 2014) 143 | * Now bundling all core Cordova plugins 144 | * Use relative times for "last updated" 145 | * Deleted in-app menu in favour of showing the main screen on double two-finger tap 146 | * UI Overhaul 147 | 148 | ## v0.5.0 (May 27, 2014) 149 | * Harness server now written in JS using chrome.socket 150 | * Supports incremental updates to apps for faster pushes 151 | * Server now works on iOS 152 | * Use two-finger double-tap to summon menu instead of three-finger swipe 153 | 154 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Merge and Release Instructions 2 | 3 | ## Updating from cordova-app-harness 4 | 5 | git checkout upstream 6 | git pull /path/to/cordova-app-harness master 7 | git checkout master 8 | git merge upstream 9 | git push origin master upstream 10 | 11 | ## Cutting a Release 12 | 13 | - Double check the status of upstream `cordova-app-harness` to see if we should update (instructions above). 14 | - Update version of cca-manifest-logic to be the latest (if applicable) 15 | - `npm install --save cca-manifest-logic` 16 | - Update release notes (bottom of README.md) 17 | - `git log --pretty=format:'* %s' --no-merges $(git describe --tags --abbrev=0)..HEAD` 18 | - Trim them down liberally & reword them. 19 | - Should also look at MCA logs: `git log --pretty=format:'* %s' --no-merges --since "FOO days ago" 20 | - Where FOO is found here: https://github.com/MobileChromeApps/chrome-app-developer-tool/releases 21 | - Update the version in `package.json` and `app.js` 22 | - `vim package.json www/cdvah/js/app.js` 23 | - Build apks with release plugins: 24 | - `DISABLE_LOCAL_SEARCH_PATH=1 ./createproject.sh ChromeAppDevTool` 25 | - `(cd ChromeAppDevTool && cordova build android)` 26 | - Double check: 27 | - Signed correctly: `jarsigner -verify -keystore template-overrides/CCAHarness-debug.keystore PATH/android-armv7-debug.apk` 28 | - Can push from CDE with "Live deploy" 29 | - `adb install -r PATH/android-armv7-debug.apk` 30 | - Can push via `cca push --watch` 31 | - Commit Changes 32 | - `git commit -am "Releasing chrome-app-developer-tool v$(npm ls --depth=0 | head -n1 | sed -E 's:.*@| .*::g')"` 33 | - Tag release 34 | - `git tag -m "Tagged v$(npm ls --depth=0 | head -n1 | sed -E 's:.*@| .*::g')" chrome-app-developer-tool-$(npm ls --depth=0 | head -n1 | sed -E 's:.*@| .*::g')` 35 | - `git push origin master refs/tags/chrome-app-developer-tool-$(npm ls --depth=0 | head -n1 | sed -E 's:.*@| .*::g')` 36 | - Upload apk to GitHub's releases page 37 | - Attach the apks 38 | - Copy in release notes (follow the format of previous releases) 39 | - Update the version with `-dev` 40 | - `vim package.json www/cdvah/js/app.js` 41 | - `git commit -am "Setting chrome-app-developer-tool version to $(npm ls --depth=0 | head -n1 | sed -E 's:.*@| .*::g')"` 42 | - `git push origin master` 43 | -------------------------------------------------------------------------------- /UrlRemap/README.md: -------------------------------------------------------------------------------- 1 | UrlRemap 2 | ========= 3 | 4 | Enables dynamic rerouting of URLs. 5 | 6 | # iOS Quirks 7 | 8 | - Top-level navigations to file:/// URLs cannot be remapped while preserving history.back() functionality (OS bug). 9 | 10 | # Android Quirks 11 | 12 | - Requires Android 3.0 or greater. 13 | - Cannot remap file:/// URLs when the target file exists on the filesystem (OS bug). 14 | 15 | ## Api 16 | 17 | appBundle.addAlias(string matchRegex, string replaceRegex, string replaceString, boolean redirect, function callback(succeeded){}); 18 | appBundle.setResetUrl(string matchRegex, callback(){}); 19 | appBundle.injectJsForUrl(string matchRegex, string jsToInject, callback(){}); 20 | appBundle.clearAllAliases(function callback(){}); 21 | 22 | * matchRegex -> allows you to specify a regex that determines which url's are replaced. 23 | * replaceRegex -> allows you to specify what part of the url's are replaced. 24 | * replacerString -> what to replace the above match with 25 | * redirect -> this affects top level browser navigation only (changing your browser's location) 26 | Assume you redirect requests from http://mysite.com/ to file:///storage/www/ 27 | If you set this to true, the document.location after redirection would be "file:///storage/www/", if you set it to false it will be "http://mysite.com/" 28 | 29 | The algorithm operates as follows 30 | 31 | currently loading 'url' 32 | if(url matches matchRegex){ 33 | newUrl = url.replace(replaceRegex, replacerString) 34 | if(this is topLevelRequest){ 35 | stopLoadingUrl() 36 | loadUrl(newUrl) 37 | return 38 | } else { 39 | url = newUrl 40 | } 41 | } 42 | continue loading 'url' 43 | 44 | -------------------------------------------------------------------------------- /UrlRemap/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 23 | 24 | 25 | 26 | 27 | URL Remapper 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /UrlRemap/src/android/UrlRemap.java: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | package org.apache.cordova.urlremap; 20 | 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | import java.util.regex.Pattern; 24 | 25 | import org.apache.cordova.CallbackContext; 26 | import org.apache.cordova.CordovaArgs; 27 | import org.apache.cordova.CordovaPlugin; 28 | import org.json.JSONException; 29 | 30 | import android.net.Uri; 31 | 32 | public class UrlRemap extends CordovaPlugin { 33 | private static class RouteParams { 34 | Pattern matchRegex; 35 | Pattern replaceRegex; 36 | String replacer; 37 | boolean redirectToReplacedUrl; 38 | boolean allowFurtherRemapping; 39 | String jsToInject; 40 | } 41 | 42 | // Shared routing for all webviews. 43 | private static RouteParams resetUrlParams; 44 | private static List rerouteParams = new ArrayList(); 45 | private static boolean hasMaster; 46 | private boolean isMaster; 47 | 48 | @Override 49 | protected void pluginInitialize() { 50 | if (!hasMaster) { 51 | hasMaster = true; 52 | isMaster = true; 53 | } 54 | } 55 | 56 | @Override 57 | public boolean execute(String action, CordovaArgs args, final CallbackContext callbackContext) throws JSONException { 58 | synchronized (rerouteParams) { 59 | if ("addAlias".equals(action)) { 60 | RouteParams params = new RouteParams(); 61 | params.matchRegex = Pattern.compile(args.getString(0)); 62 | params.replaceRegex = Pattern.compile(args.getString(1)); 63 | params.replacer = args.getString(2); 64 | params.redirectToReplacedUrl = args.getBoolean(3); 65 | params.allowFurtherRemapping = args.getBoolean(4); 66 | rerouteParams.add(params); 67 | } else if ("clearAllAliases".equals(action)) { 68 | resetMappings(); 69 | } else if ("injectJs".equals(action)) { 70 | RouteParams params = new RouteParams(); 71 | params.matchRegex = Pattern.compile(args.getString(0)); 72 | params.jsToInject = args.getString(1); 73 | rerouteParams.add(params); 74 | } else if ("setResetUrl".equals(action)) { 75 | resetUrlParams = new RouteParams(); 76 | resetUrlParams.matchRegex = Pattern.compile(args.getString(0)); 77 | } else { 78 | return false; 79 | } 80 | callbackContext.success(); 81 | return true; 82 | } 83 | } 84 | 85 | public void resetMappings() { 86 | synchronized (rerouteParams) { 87 | resetUrlParams = null; 88 | rerouteParams.clear(); 89 | } 90 | } 91 | 92 | private RouteParams getChosenParams(String url, boolean forInjection) { 93 | for (RouteParams params : rerouteParams) { 94 | if ((params.jsToInject != null) == forInjection && params.matchRegex.matcher(url).find()) { 95 | return params; 96 | } 97 | } 98 | return null; 99 | } 100 | 101 | @Override 102 | public boolean onOverrideUrlLoading(String url) { 103 | // Don't remap for the main webview. 104 | if (isMaster) { 105 | return false; 106 | } 107 | synchronized (rerouteParams) { 108 | if (resetUrlParams != null && resetUrlParams.matchRegex.matcher(url).find()) { 109 | resetMappings(); 110 | } 111 | 112 | RouteParams params = getChosenParams(url, false); 113 | // Check if we need to replace the url 114 | if (params != null && params.redirectToReplacedUrl) { 115 | String newUrl = params.replaceRegex.matcher(url).replaceFirst(params.replacer); 116 | 117 | if (resetUrlParams != null && resetUrlParams.matchRegex.matcher(newUrl).find()) { 118 | resetMappings(); 119 | } 120 | 121 | webView.loadUrlIntoView(newUrl, false); 122 | return true; 123 | } 124 | return false; 125 | } 126 | } 127 | 128 | @Override 129 | public Object onMessage(String id, Object data) { 130 | // Look for top level navigation changes 131 | if ("onPageFinished".equals(id) && data != null) { 132 | String url = data.toString(); 133 | synchronized (rerouteParams) { 134 | RouteParams params = getChosenParams(url, true); 135 | if (params != null) { 136 | webView.sendJavascript(params.jsToInject); 137 | } 138 | } 139 | } 140 | return null; 141 | } 142 | 143 | @Override 144 | public Uri remapUri(Uri uri) { 145 | // Don't remap for the main webview. 146 | if (isMaster) { 147 | return null; 148 | } 149 | synchronized (rerouteParams) { 150 | String uriAsString = uri.toString(); 151 | RouteParams params = getChosenParams(uriAsString, false); 152 | if (params != null && !params.redirectToReplacedUrl) { 153 | String newUrl = params.replaceRegex.matcher(uriAsString).replaceFirst(params.replacer); 154 | Uri ret = Uri.parse(newUrl); 155 | if (params.allowFurtherRemapping) { 156 | ret = webView.getResourceApi().remapUri(ret); 157 | } 158 | return ret; 159 | } 160 | return null; 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /UrlRemap/urlremap.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | * 19 | */ 20 | var exec = cordova.require('cordova/exec'); 21 | 22 | exports.addAlias = function(sourceUriMatchRegex, sourceUriReplaceRegex, replaceString, redirectToReplacedUrl, allowFurtherRemapping, callback) { 23 | var win = callback && function() { 24 | callback(true); 25 | }; 26 | var fail = callback && function(error) { 27 | console.error('UrlRemap error: ' + error); 28 | callback(false); 29 | }; 30 | exec(win, fail, 'UrlRemap', 'addAlias', [sourceUriMatchRegex, sourceUriReplaceRegex, replaceString, redirectToReplacedUrl, allowFurtherRemapping]); 31 | }; 32 | 33 | exports.setResetUrl = function(urlRegex, callback) { 34 | exec(callback, null, 'UrlRemap', 'setResetUrl', [urlRegex]); 35 | }; 36 | 37 | exports.injectJsForUrl = function(urlRegex, jsSnippet, callback) { 38 | exec(callback, null, 'UrlRemap', 'injectJs', [urlRegex, jsSnippet]); 39 | }; 40 | 41 | exports.clearAllAliases = function(callback){ 42 | exec(callback, null, 'UrlRemap', 'clearAllAliases', []); 43 | }; 44 | -------------------------------------------------------------------------------- /cde-cadt-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MobileChromeApps/chrome-app-developer-tool/765ae4e7e31c4c1266e64278a13536bc84f77cfd/cde-cadt-diagram.png -------------------------------------------------------------------------------- /createproject.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Licensed to the Apache Software Foundation (ASF) under one 3 | # or more contributor license agreements. See the NOTICE file 4 | # distributed with this work for additional information 5 | # regarding copyright ownership. The ASF licenses this file 6 | # to you under the Apache License, Version 2.0 (the 7 | # "License"); you may not use this file except in compliance 8 | # with the License. You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, 13 | # software distributed under the License is distributed on an 14 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | # KIND, either express or implied. See the License for the 16 | # specific language governing permissions and limitations 17 | # under the License. 18 | 19 | if [[ $# -eq 0 || "$1" = "--help" ]]; then 20 | echo "Use this script to create a Chrome ADT project" 21 | echo "Usage: $0 NewDirName" 22 | echo 'Options via variables:' 23 | echo ' PLATFORMS="android ios"' 24 | echo ' PLUGIN_SEARCH_PATH="path1:path2:path3"' 25 | echo ' DISABLE_PLUGIN_REGISTRY=1' 26 | echo ' DISABLE_LOCAL_SEARCH_PATH=1 # Use this for releases' 27 | echo ' ENABLE_APK_PACKAGER=1 # Currently experimental' 28 | exit 1 29 | fi 30 | 31 | DIR_NAME="$1" 32 | if [[ "Darwin" = $(uname -s) ]]; then 33 | PLATFORMS="${PLATFORMS-android ios}" 34 | else 35 | PLATFORMS="${PLATFORMS-android}" 36 | fi 37 | AH_PATH="$(cd $(dirname $0) && pwd)" 38 | APP_ID="org.chromium.appdevtool" 39 | APP_NAME="Chrome App Developer Tool" 40 | APP_VERSION=$(cd "$AH_PATH" && node -e "console.log(require('./package').version)") 41 | extra_search_path="$PLUGIN_SEARCH_PATH" 42 | PLUGIN_SEARCH_PATH="" 43 | 44 | PLUGIN_REGISTRY_FLAG="" 45 | if [[ -n "$DISABLE_PLUGIN_REGISTRY" ]]; then 46 | PLUGIN_REGISTRY_FLAG=--noregistry 47 | fi 48 | 49 | CORDOVA="$AH_PATH/node_modules/cordova/bin/cordova" 50 | ALL_DEPS=$(cd "$AH_PATH" && node -e "console.log(Object.keys(require('./package').devDependencies).join(' '))") 51 | 52 | for dep in $ALL_DEPS; do 53 | if [[ ! -e "$AH_PATH/node_modules/$dep" ]]; then 54 | echo "Missing dependency: $dep" 55 | echo 'Running: npm install' 56 | (cd "$AH_PATH" && npm install) 57 | break 58 | fi 59 | done 60 | 61 | echo "Running: gulp build-dev" 62 | (cd "$AH_PATH" && ./node_modules/gulp/bin/gulp.js build-dev) || exit 1 63 | 64 | function ResolveSymlinks() { 65 | local found_path="$1" 66 | if [[ -n "$found_path" ]]; then 67 | node -e "console.log(require('fs').realpathSync('$found_path'))" 68 | fi 69 | } 70 | function AddSearchPathIfExists() { 71 | if [[ -d "$1" ]]; then 72 | if [[ -n "$PLUGIN_SEARCH_PATH" ]]; then 73 | PLUGIN_SEARCH_PATH="$PLUGIN_SEARCH_PATH:$1" 74 | else 75 | PLUGIN_SEARCH_PATH="$1" 76 | fi 77 | fi 78 | } 79 | 80 | if [[ "1" != "$DISABLE_LOCAL_SEARCH_PATH" ]]; then 81 | # Use coho to find Cordova plugins 82 | COHO_PATH=$(ResolveSymlinks $(which coho)) 83 | if [[ -n "$COHO_PATH" ]]; then 84 | echo "Using locally installed cordova plugins." 85 | CDV_PATH="$(dirname $(dirname "$COHO_PATH"))" 86 | AddSearchPathIfExists "$CDV_PATH" 87 | AddSearchPathIfExists "$CDV_PATH/cordova-plugins" 88 | fi 89 | 90 | # Use cca to find Chrome ones. 91 | AddSearchPathIfExists "$(ResolveSymlinks "$AH_PATH/node_modules/cca")/../mobile-chrome-apps-plugins" 92 | AddSearchPathIfExists "$AH_PATH/node_modules/cca/chrome-cordova/plugins" 93 | # And also cca-bundled versions of Cordova ones if they are not checked out. 94 | AddSearchPathIfExists "$AH_PATH/node_modules/cca/cordova" 95 | AddSearchPathIfExists "$AH_PATH/node_modules/cca/cordova/cordova-plugins" 96 | fi 97 | 98 | if [[ -n "$extra_search_path" ]]; then 99 | PLUGIN_SEARCH_PATH="${extra_search_path}:$PLUGIN_SEARCH_PATH" 100 | fi 101 | 102 | rm -rf "$DIR_NAME" 103 | set -x 104 | "$CORDOVA" create "$DIR_NAME" "$APP_ID" "$APP_NAME" --link-to "$AH_PATH/www" || exit 1 105 | set +x 106 | cd "$DIR_NAME" 107 | cp "$AH_PATH/template-overrides/config.xml" . || exit 1 108 | perl -i -pe "s/{ID}/$APP_ID/g" config.xml || exit 1 109 | perl -i -pe "s/{NAME}/$APP_NAME/g" config.xml || exit 1 110 | perl -i -pe "s/{VERSION}/$APP_VERSION/g" config.xml || exit 1 111 | 112 | PLATFORM_ARGS="${PLATFORMS/android/$AH_PATH/node_modules/cca/cordova/cordova-android}" 113 | PLATFORM_ARGS="${PLATFORM_ARGS/ios/$AH_PATH/node_modules/cca/cordova/cordova-ios}" 114 | 115 | set -x 116 | $CORDOVA platform add $PLATFORM_ARGS --link || exit 1 117 | set +x 118 | 119 | if [[ "$PLATFORMS" = *android* ]]; then 120 | cp "$AH_PATH"/template-overrides/icons/android/icon.png platforms/android/res/drawable/icon.png 121 | rm platforms/android/res/drawable-ldpi/icon.png 122 | cp "$AH_PATH"/template-overrides/icons/android/icon-mdpi.png platforms/android/res/drawable-mdpi/icon.png 123 | cp "$AH_PATH"/template-overrides/icons/android/icon-hdpi.png platforms/android/res/drawable-hdpi/icon.png 124 | cp "$AH_PATH"/template-overrides/icons/android/icon-xdpi.png platforms/android/res/drawable-xhdpi/icon.png 125 | 126 | cp "$AH_PATH"/template-overrides/strings.xml platforms/android/res/values/strings.xml 127 | cp "$AH_PATH"/template-overrides/debug-signing.properties platforms/android 128 | cp "$AH_PATH"/template-overrides/CCAHarness-debug.keystore platforms/android 129 | 130 | echo 'var fs = require("fs"); 131 | var fname = "platforms/android/src/org/chromium/appdevtool/ChromeAppDeveloperTool.java"; 132 | if (!fs.existsSync(fname)) { 133 | fname = "platforms/android/src/org/chromium/appdevtool/MainActivity.java"; 134 | } 135 | var tname = "'$AH_PATH'/template-overrides/Activity.java"; 136 | var orig = fs.readFileSync(fname, "utf8"); 137 | var templ = fs.readFileSync(tname, "utf8"); 138 | var newData = orig.replace(/}\s*$/, templ + "\n}\n").replace(/import.*?$/m, "import org.apache.appharness.AppHarnessUI;\n$&"); 139 | fs.writeFileSync(fname, newData); 140 | ' | node || exit $? 141 | fi 142 | if [[ "$PLATFORMS" = *ios* ]]; then 143 | cp -r "$AH_PATH"/template-overrides/icons/ios/* platforms/ios/*/Resources/icons 144 | # Set CFBundleName to "App Harness" instead of "Chrome App Harness". 145 | cp "$AH_PATH"/template-overrides/Info.plist platforms/ios/*/*-Info.plist 146 | fi 147 | 148 | mkdir -p hooks/after_prepare 149 | cp "$AH_PATH"/template-overrides/after-hook.js hooks/after_prepare 150 | 151 | # if [[ $PLATFORMS = *ios* ]]; then 152 | # ../../cordova-ios/bin/update_cordova_subproject platforms/ios/CordovaAppHarness.xcodeproj 153 | # fi 154 | 155 | echo Installing plugins. 156 | # org.apache.cordova.device isn't used directly, but is convenient to test mobilespec. 157 | set -e 158 | set -x 159 | "$CORDOVA" plugin add\ 160 | "$AH_PATH/UrlRemap" \ 161 | "$AH_PATH/AppHarnessUI" \ 162 | cordova-plugin-file \ 163 | cordova-plugin-file-transfer \ 164 | cordova-plugin-device \ 165 | cordova-plugin-network-information \ 166 | cordova-plugin-chrome-apps-sockets-tcp \ 167 | cordova-plugin-chrome-apps-sockets-tcpserver \ 168 | cordova-plugin-chrome-apps-system-network \ 169 | cordova-plugin-zip \ 170 | --link \ 171 | --searchpath="$PLUGIN_SEARCH_PATH" \ 172 | $PLUGIN_REGISTRY_FLAG 173 | 174 | if [[ "$PLATFORMS" = *android* ]]; then 175 | if [[ -e plugins/cordova-plugin-file/src/android/build-extras.gradle ]]; then 176 | cp plugins/cordova-plugin-file/src/android/build-extras.gradle platforms/android/build-extras.gradle 177 | fi 178 | fi 179 | 180 | # Extra plugins 181 | "$CORDOVA" plugin add \ 182 | cordova-plugin-battery-status \ 183 | cordova-plugin-camera \ 184 | cordova-plugin-contacts \ 185 | cordova-plugin-device-motion \ 186 | cordova-plugin-device-orientation \ 187 | cordova-plugin-device \ 188 | cordova-plugin-dialogs \ 189 | cordova-plugin-file-transfer \ 190 | cordova-plugin-file \ 191 | cordova-plugin-geolocation \ 192 | cordova-plugin-globalization \ 193 | cordova-plugin-inappbrowser \ 194 | cordova-plugin-media \ 195 | cordova-plugin-media-capture \ 196 | cordova-plugin-splashscreen \ 197 | cordova-plugin-statusbar \ 198 | cordova-plugin-vibration \ 199 | cordova-plugin-whitelist \ 200 | --link \ 201 | --searchpath="$PLUGIN_SEARCH_PATH" \ 202 | $PLUGIN_REGISTRY_FLAG 203 | 204 | # Using CCA here to get the right search path. 205 | "$CORDOVA" plugin add \ 206 | cordova-plugin-chrome-apps-alarms \ 207 | cordova-plugin-chrome-apps-audiocapture \ 208 | cordova-plugin-chrome-apps-bootstrap \ 209 | cordova-plugin-chrome-apps-bluetooth \ 210 | cordova-plugin-chrome-apps-bluetoothlowenergy \ 211 | cordova-plugin-chrome-apps-bluetoothsocket \ 212 | cordova-plugin-chrome-apps-filesystem \ 213 | cordova-plugin-chrome-apps-gcm \ 214 | cordova-plugin-chrome-apps-i18n \ 215 | cordova-plugin-chrome-apps-identity \ 216 | cordova-plugin-chrome-apps-idle \ 217 | cordova-plugin-chrome-apps-navigation \ 218 | cordova-plugin-chrome-apps-notifications \ 219 | cordova-plugin-chrome-apps-power \ 220 | cordova-plugin-chrome-apps-pushmessaging \ 221 | cordova-plugin-chrome-apps-socket \ 222 | cordova-plugin-chrome-apps-sockets-tcp \ 223 | cordova-plugin-chrome-apps-sockets-tcpserver \ 224 | cordova-plugin-chrome-apps-sockets-udp \ 225 | cordova-plugin-chrome-apps-storage \ 226 | cordova-plugin-chrome-apps-system-cpu \ 227 | cordova-plugin-chrome-apps-system-display \ 228 | cordova-plugin-chrome-apps-system-memory \ 229 | cordova-plugin-chrome-apps-system-network \ 230 | cordova-plugin-chrome-apps-system-storage \ 231 | cordova-plugin-chrome-apps-usb \ 232 | cordova-plugin-chrome-apps-videocapture \ 233 | cordova-plugin-blob-constructor-polyfill \ 234 | cordova-plugin-customevent-polyfill \ 235 | cordova-plugin-xhr-blob-polyfill \ 236 | cordova-plugin-statusbar \ 237 | cordova-plugin-network-information \ 238 | cordova-plugin-google-payments \ 239 | --link \ 240 | --searchpath="$PLUGIN_SEARCH_PATH" 241 | 242 | "$CORDOVA" plugin add --link --searchpath "$AH_PATH/node_modules/cca/cordova" cordova-plugin-crosswalk-webview 243 | 244 | if [[ -n "$ENABLE_APK_PACKAGER" ]]; then 245 | "$CORDOVA" plugin add --link "$AH_PATH/apkpackager" 246 | mkdir -p merges/android 247 | ( cd merges/android && ln -s "$AH_PATH/AndroidApkTemplate/apk-template" . ) 248 | fi 249 | 250 | "$CORDOVA" prepare 251 | ln -s "$CORDOVA" . 252 | 253 | set +x 254 | echo "Project successfully created at:" 255 | pwd 256 | 257 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | * 19 | */ 20 | 21 | var gulp = require('gulp'); 22 | var autoprefixer = require('gulp-autoprefixer'); 23 | var gutil = require('gulp-util'); 24 | var jshint = require('gulp-jshint'); 25 | var path = require('path'); 26 | var webpack = require('webpack'); 27 | var webpackConfig = require('./webpack.config.js'); 28 | 29 | gulp.task('default', ['build-dev']); 30 | 31 | gulp.task('watch', ['build-dev'], function() { 32 | gulp.watch([ 33 | path.join('www', '**', '*'), 34 | 'webpack.config.js', 35 | //'node_modules/**/*', // disabled because of https://github.com/gruntjs/grunt-contrib-watch#how-do-i-fix-the-error-emfile-too-many-opened-files 36 | ], ['lint', 'webpack:build-dev']); 37 | gulp.watch([ 38 | path.join('src', '*.css'), 39 | ], ['styles']); 40 | }); 41 | 42 | gulp.task('styles', function() { 43 | return gulp.src(path.join('src', 'style.css')) 44 | .pipe(autoprefixer({ 45 | browsers: ['ios 7', 'android 3'] 46 | })) 47 | .pipe(gulp.dest(path.join('www', 'cdvah', 'generated'))); 48 | 49 | }); 50 | 51 | gulp.task('build', ['lint', 'webpack:build', 'styles']); 52 | 53 | gulp.task('build-dev', ['lint', 'webpack:build-dev', 'styles']); 54 | 55 | gulp.task('lint', ['lint:app', 'lint:harness-push']); 56 | 57 | /******************************************************************************/ 58 | /******************************************************************************/ 59 | 60 | gulp.task('lint:app', function() { 61 | return gulp.src([path.join('www', '**', '*.js'), path.join('!www', 'cdvah', 'js', 'libs', '*.js'), path.join('!www', 'cdvah', 'generated', '*.js')]) 62 | .pipe(jshint()) 63 | .pipe(jshint.reporter('default')) 64 | .pipe(jshint.reporter('fail')); 65 | }); 66 | 67 | gulp.task('lint:harness-push', function() { 68 | return gulp.src([path.join('harness-push', '*.js'), path.join('harness-push', 'node_modules', 'chrome-app-developer-tool-client', '*.js')]) 69 | .pipe(jshint()) 70 | .pipe(jshint.reporter('default')) 71 | .pipe(jshint.reporter('fail')); 72 | }); 73 | 74 | /******************************************************************************/ 75 | /******************************************************************************/ 76 | 77 | function createWebpackResultHandler(taskname, callback) { 78 | return function(err, stats) { 79 | if(err) throw new gutil.PluginError(taskname, err); 80 | gutil.log('[' + taskname + ']', stats.toString({ 81 | colors: true 82 | })); 83 | callback(); 84 | } 85 | } 86 | 87 | gulp.task('webpack:build', function(callback) { 88 | var myConfig = Object.create(webpackConfig); 89 | myConfig.plugins = (myConfig.plugins || []).concat( 90 | new webpack.DefinePlugin({ 91 | 'process.env': { 92 | 'NODE_ENV': JSON.stringify('production') 93 | } 94 | }), 95 | new webpack.optimize.DedupePlugin() 96 | ); 97 | 98 | webpack(myConfig, createWebpackResultHandler('webpack:build', callback)); 99 | }); 100 | 101 | gulp.task('webpack:build-dev', (function() { 102 | var myDevConfig = Object.create(webpackConfig); 103 | myDevConfig.devtool = 'sourcemap'; 104 | myDevConfig.debug = true; 105 | 106 | // create a single instance of the compiler to allow caching 107 | var devCompiler = webpack(myDevConfig); 108 | 109 | return function(callback) { 110 | devCompiler.run(createWebpackResultHandler('webpack:build-dev', callback)); 111 | }; 112 | }())); 113 | -------------------------------------------------------------------------------- /harness-push/README.md: -------------------------------------------------------------------------------- 1 | # cordova-harness-push 2 | 3 | Command-line tool for controlling the Cordova App Harness. 4 | 5 | # Usage: 6 | 7 | For usage, run: 8 | 9 | harness-push.js --help 10 | 11 | ## Port Forwarding (Android) 12 | 13 | No longer required! 14 | 15 | ## Port Forwarding (iOS) 16 | 17 | Download tcprelay.py from: https://github.com/chid/tcprelay 18 | Then run: 19 | 20 | python tcprelay.py 2424:2424 21 | -------------------------------------------------------------------------------- /harness-push/harness-push.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /** 3 | Licensed to the Apache Software Foundation (ASF) under one 4 | or more contributor license agreements. See the NOTICE file 5 | distributed with this work for additional information 6 | regarding copyright ownership. The ASF licenses this file 7 | to you under the Apache License, Version 2.0 (the 8 | "License"); you may not use this file except in compliance 9 | with the License. You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, 14 | software distributed under the License is distributed on an 15 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | KIND, either express or implied. See the License for the 17 | specific language governing permissions and limitations 18 | under the License. 19 | */ 20 | 21 | var path = require('path'); 22 | var fs = require('fs'); 23 | try { 24 | var nopt = require('nopt'); 25 | } catch (e) { 26 | console.log('Please run: ( cd ' + __dirname + ' && npm install )'); 27 | process.exit(1); 28 | } 29 | try { 30 | var HarnessClient = require('chrome-app-developer-tool-client'); 31 | } catch (e) { 32 | console.log('Please run: ( cd ' + 33 | path.join(__dirname, 'node_modules', 'chrome-app-developer-tool-client') + 34 | ' && npm install )'); 35 | process.exit(1); 36 | } 37 | 38 | function parseArgs(argv) { 39 | var opts = { 40 | 'help': Boolean, 41 | 'target': String 42 | }; 43 | var ret = nopt(opts, null, argv); 44 | return ret; 45 | } 46 | 47 | function usage() { 48 | console.log('Usage: harness-push push path/to/app --target=IP_ADDRESS:PORT'); 49 | console.log('Usage: harness-push menu'); 50 | console.log('Usage: harness-push eval "alert(1)"'); 51 | console.log('Usage: harness-push info'); 52 | console.log('Usage: harness-push launch [appId]'); 53 | console.log('Usage: harness-push quit'); 54 | console.log('Usage: harness-push assetmanifest [appId]'); 55 | console.log('Usage: harness-push delete appId'); 56 | console.log('Usage: harness-push deleteall'); 57 | console.log('Usage: harness-push buildapk [appId] [--output=output.apk] [--keyProps=android-release-keys.properties]'); 58 | console.log(); 59 | console.log('--target defaults to localhost:2424'); 60 | console.log(' To deploy to Android over USB: adb forward tcp:2424 tcp:2424'); 61 | console.log(' "adb" comes with the Android SDK'); 62 | console.log(' To deploy to iOS over USB: python tcprelay.py 2424:2424'); 63 | console.log(' "tcprelay.py" is available from https://github.com/chid/tcprelay'); 64 | process.exit(1); 65 | } 66 | 67 | function readPropertiesFile(propsFile) { 68 | var data = fs.readFileSync(propsFile, 'utf8'); 69 | var pattern = /(.*?)=(.*)/g; 70 | var match; 71 | var ret = {}; 72 | while ((match = pattern.exec(data))) { 73 | ret[match[1]] = match[2]; 74 | } 75 | return ret; 76 | } 77 | 78 | function main() { 79 | var args = parseArgs(process.argv); 80 | 81 | function onFailure(err) { 82 | console.error(err); 83 | } 84 | function onSuccess(result) { 85 | if (typeof result.body == 'object') { 86 | console.log(JSON.stringify(result.body, null, 4)); 87 | } else if (result.body) { 88 | console.log(result.body); 89 | } 90 | } 91 | 92 | var client = new HarnessClient(args.target); 93 | 94 | var cmd = args.argv.remain[0]; 95 | if (cmd == 'push') { 96 | if (!args.argv.remain[1]) { 97 | usage(); 98 | } 99 | var pushSession = client.createPushSession(args.argv.remain[1]); 100 | client.quit() 101 | .then(function() { 102 | return pushSession.push(); 103 | }).then(onSuccess, onFailure); 104 | } else if (cmd == 'buildapk') { 105 | var propsFilePath = args.keyProps || 'android-release-keys.properties'; 106 | var props = readPropertiesFile(propsFilePath); 107 | var certificatePath = props['certificateFile']; 108 | var privateKeyPath = props['privateKeyFile']; 109 | var keyStorePath = props['storeFile']; 110 | var appId = args.argv.remain[1]; 111 | var outputPath = args.output || 'output.apk'; 112 | var signingOpts = { 113 | keyPassword: props['keyPassword'] 114 | }; 115 | if (certificatePath && privateKeyPath) { 116 | signingOpts.certificatePath = path.join(path.dirname(propsFilePath), certificatePath); 117 | signingOpts.privateKeyPath = path.join(path.dirname(propsFilePath), privateKeyPath); 118 | } else if (keyStorePath) { 119 | signingOpts.keyStorePath = path.join(path.dirname(propsFilePath), keyStorePath); 120 | signingOpts.storeType = props['storeType']; 121 | signingOpts.storePassword = props['storePassword']; 122 | signingOpts.keyAlias = props['keyAlias']; 123 | } else { 124 | throw new Error('No key signing information within ' + propsFilePath); 125 | } 126 | 127 | client.buildApk(appId, 'chrome', signingOpts, outputPath).then(onSuccess, onFailure); 128 | } else if (cmd == 'deleteall') { 129 | client.deleteAllApps().then(onSuccess, onFailure); 130 | } else if (cmd == 'delete') { 131 | if (!args.argv.remain[1]) { 132 | usage(); 133 | } 134 | client.deleteApp().then(onSuccess, onFailure); 135 | } else if (cmd == 'menu') { 136 | client.menu().then(onSuccess, onFailure); 137 | } else if (cmd == 'eval') { 138 | if (!args.argv.remain[1]) { 139 | usage(); 140 | } 141 | client.evalJs(args.argv.remain[1]).then(onSuccess, onFailure); 142 | } else if (cmd == 'assetmanifest') { 143 | client.assetmanifest(args.appid).then(onSuccess, onFailure); 144 | } else if (cmd == 'info') { 145 | client.info().then(onSuccess, onFailure); 146 | } else if (cmd == 'launch') { 147 | client.launch(args.argv.remain[1]).then(onSuccess, onFailure); 148 | } else if (cmd == 'quit') { 149 | client.quit().then(onSuccess, onFailure); 150 | } else { 151 | usage(); 152 | } 153 | } 154 | 155 | if (require.main === module) { 156 | main(); 157 | } 158 | -------------------------------------------------------------------------------- /harness-push/node_modules/chrome-app-developer-tool-client/README.md: -------------------------------------------------------------------------------- 1 | # chrome-app-developer-tool-client 2 | 3 | A node module for controlling the Chrome App Developer Tool for Mobile. 4 | 5 | Learn more about [Chrome Apps for Mobile](https://github.com/MobileChromeApps/mobile-chrome-apps). 6 | 7 | For example usage of the library, see 8 | [cca](https://github.com/MobileChromeApps/mobile-chrome-apps/blob/master/src/push-to-harness.js) or 9 | [harness-push.js](https://github.com/MobileChromeApps/chrome-app-developer-tool/blob/master/harness-push/harness-push.js). 10 | 11 | For server reference & curl examples, refer to [server implementation](https://github.com/MobileChromeApps/chrome-app-developer-tool/blob/master/www/cdvah/js/HarnessServer.js). 12 | 13 | ## Releasing chrome-app-developer-tool-client 14 | 15 | - Ensure you're up-to-date 16 | - `git pull` 17 | - Update the release notes 18 | - `git log --pretty=format:'* %s' --no-merges $(tail -n1 release_hashes.txt | cut -d':' -f2)..HEAD -- .` 19 | - `vim README.md` 20 | - Update the version in `package.json` 21 | - `vim package.json` 22 | - Commit Changes 23 | - `git commit -am "Releasing chrome-app-developer-tool-client@$(npm ls | head -n1 | sed -E 's:.*@| .*::g')"` 24 | - Publish to npm 25 | - `npm publish` 26 | - Tag release (via `release_hashes.txt` file) 27 | - `echo "v$(npm ls | head -n1 | sed -E 's:.*@| .*::g'): $(git rev-parse HEAD)" >> release_hashes.txt` 28 | - Update the version with `-dev` 29 | - `vim package.json` 30 | - `git commit -am "Set version of chrome-app-developer-tool-client to $(npm ls | head -n1 | sed -E 's:.*@| .*::g')"` 31 | - `git push origin master` 32 | 33 | ## Release Notes 34 | 35 | ### v0.0.5 36 | * Make harnessclient compatible with node 0.12 as well as 0.10 37 | 38 | ### v0.0.4 (November 17, 2014) 39 | * Enhancements to new adbkit integration 40 | * Improved error handling 41 | * Provided a connection to the appropriate port 42 | 43 | ### v0.0.3 44 | * Prevented launching an app that hasn't changed. 45 | * Add a "buildapk" command 46 | 47 | ### v0.0.2 48 | * Adds support for /quit command. Quits before pushing. 49 | 50 | ### v0.0.1 51 | Initial release! 52 | -------------------------------------------------------------------------------- /harness-push/node_modules/chrome-app-developer-tool-client/chromeharnessclient.js: -------------------------------------------------------------------------------- 1 | /** 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | var fs = require('fs'); 20 | var HarnessClient = require('./harnessclient'); 21 | var ChromePushSession = require('./chromepushsession'); 22 | 23 | function ChromeHarnessClient(target) { 24 | HarnessClient.call(this, target); 25 | } 26 | ChromeHarnessClient.detectAdbTargets = HarnessClient.detectAdbTargets; 27 | ChromeHarnessClient.prototype = Object.create(HarnessClient.prototype); 28 | ChromeHarnessClient.prototype.constructor = ChromeHarnessClient; 29 | 30 | ChromeHarnessClient.prototype.createPushSession = function(dir) { 31 | return new ChromePushSession(this, dir); 32 | }; 33 | 34 | ChromeHarnessClient.prototype.buildApk = function(appId, appType, signingOpts, outputPath, /* optional */ manifestEtag) { 35 | var query = {}; 36 | if (manifestEtag) { 37 | query['manifestEtag'] = manifestEtag; 38 | } 39 | var payload = { 40 | 'keyPassword': signingOpts.keyPassword 41 | }; 42 | if (signingOpts.privateKeyPath) { 43 | payload['publicKeyData'] = fs.readFileSync(signingOpts.certificatePath, 'base64'); 44 | payload['privateKeyData'] = fs.readFileSync(signingOpts.privateKeyPath, 'base64'); 45 | } else { 46 | payload['storeData'] = fs.readFileSync(signingOpts.keyStorePath, 'base64'); 47 | payload['storeType'] = signingOpts.storeType; 48 | payload['storePassword'] = signingOpts.storePassword; 49 | payload['keyAlias'] = signingOpts.keyAlias; 50 | } 51 | var opts = { 52 | appId: appId, 53 | appType: appType, 54 | json: payload, 55 | query: query, 56 | saveResponseToFile: outputPath 57 | }; 58 | return this.doRequest('POST', 'buildapk', opts); 59 | }; 60 | 61 | module.exports = ChromeHarnessClient; 62 | 63 | -------------------------------------------------------------------------------- /harness-push/node_modules/chrome-app-developer-tool-client/chromepushsession.js: -------------------------------------------------------------------------------- 1 | /** 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | var fs = require('fs'); 21 | var path = require('path'); 22 | var PushSession = require('./pushsession'); 23 | 24 | function ChromePushSession(harnessClient, dir) { 25 | PushSession.call(this, harnessClient, dir); 26 | } 27 | ChromePushSession.prototype = Object.create(PushSession.prototype); 28 | ChromePushSession.prototype.constructor = ChromePushSession; 29 | 30 | ChromePushSession.prototype.initialize = function(opts) { 31 | var manifestPath = path.join(this.rootDir_, 'manifest.json'); 32 | var manifestMobilePath = path.join(this.rootDir_, 'manifest.mobile.json'); 33 | if (fs.existsSync(manifestPath)) { 34 | opts = opts || {}; 35 | 36 | // jshint evil:true 37 | var manifest = eval('(' + fs.readFileSync(manifestPath, 'utf8') + ')'); 38 | var manifestMobile = fs.existsSync(manifestMobilePath) ? eval('(' + fs.readFileSync(manifestMobilePath, 'utf8') + ')') : {}; 39 | // jshint evil:false 40 | 41 | var appId = manifestMobile['packageId'] || manifest['packageId'] || 'com.company.' + (manifest.name.replace(/[^a-zA-Z]/g, '') || 'YourApp'); 42 | opts.appId = appId; 43 | opts.appType = 'chrome'; 44 | opts.wwwDir = this.rootDir_; 45 | opts.configXmlPath = null; 46 | opts.skipCordovaPlugins = true; 47 | } 48 | return PushSession.prototype.initialize.call(this, opts); 49 | }; 50 | 51 | module.exports = ChromePushSession; 52 | -------------------------------------------------------------------------------- /harness-push/node_modules/chrome-app-developer-tool-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chrome-app-developer-tool-client", 3 | "version": "0.0.6-dev", 4 | "description": "Client library for communicating with Chrome App Developer Tool for Mobile.", 5 | "main": "chromeharnessclient.js", 6 | "author": "The Chrome Team", 7 | "license": "Apache 2.0", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/MobileChromeApps/chrome-app-developer-tool.git" 11 | }, 12 | "keywords": [ 13 | "cordova", 14 | "cordova-app-harness", 15 | "cca", 16 | "chrome-app-developer-tool", 17 | "Chrome", 18 | "chrome-app", 19 | "harness" 20 | ], 21 | "dependencies": { 22 | "adbkit": "^2.1.2", 23 | "agentkeepalive": "^1.2.0", 24 | "jszip": "~2.1", 25 | "old-agentkeepalive": "^0.2.2", 26 | "q": "~0.9", 27 | "shelljs": "0.1.x", 28 | "temp": "~0.7" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /harness-push/node_modules/chrome-app-developer-tool-client/release_hashes.txt: -------------------------------------------------------------------------------- 1 | This file contains version:hash pairs that are a replacement for git tags. 2 | The reason to not use git tags is that they show up in GitHub as releases, 3 | along with tags for chrome-app-developer-tool. 4 | 5 | v0.0.1: 40c31758a6c1bf6e0ca0b3bd81e5a65ff4ab950e 6 | v0.0.2: 1ec1e14044bb43e27ac16adcb621ce2297963d5d 7 | v0.0.3: 9ab9bd9b8b171817bc9c70e7cee9295c0d56a26d 8 | v0.0.4: 1f40896e8e551bff0224e6979a847934e5781f95 9 | v0.0.5: 89b0ad2cb921a7a23ee9fe25b68ef7a238bb2678 10 | -------------------------------------------------------------------------------- /harness-push/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "harness-push", 3 | "version": "0.0.2", 4 | "description": "Command-line utility for communicating with Chrome App Developer Tool.", 5 | "author": "The Chrome Team", 6 | "license": "Apache 2.0", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/MobileChromeApps/chrome-app-harness.git" 10 | }, 11 | "keywords": [ 12 | "cordova", 13 | "cordova-app-harness", 14 | "Chrome", 15 | "chrome-app", 16 | "harness" 17 | ], 18 | "bin": { 19 | "chrome-harness-push": "./harness-push.js" 20 | }, 21 | "dependencies": { 22 | "chrome-app-developer-tool-client": "~0.0", 23 | "nopt": "~2.2" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chrome-app-developer-tool", 3 | "version": "0.13.1-dev", 4 | "description": "The Chrome App Developer Tool for Mobile (\"App Dev Tool\" for short) is a distribution of [Apache Cordova App Harness](https://git-wip-us.apache.org/repos/asf/cordova-app-harness.git) that can run Chrome Apps. It is based on the plugins from the [cca](https://github.com/MobileChomeApps/mobile-chrome-apps) toolkit.", 5 | "repository": { 6 | "type": "git", 7 | "url": "git://github.com/MobileChromeApps/chrome-app-developer-tool.git" 8 | }, 9 | "author": "The Chrome Team", 10 | "license": "Apache version 2.0", 11 | "bugs": { 12 | "url": "https://github.com/MobileChromeApps/chrome-app-developer-tool/issues" 13 | }, 14 | "homepage": "https://github.com/MobileChromeApps/chrome-app-developer-tool", 15 | "devDependencies": { 16 | "base64-arraybuffer": "^0.1.2", 17 | "cca": "latest", 18 | "cca-manifest-logic": "^1.1.1", 19 | "cordova": "latest", 20 | "gulp": "latest", 21 | "gulp-autoprefixer": "^1.0.1", 22 | "gulp-jshint": "~1.6", 23 | "gulp-util": "latest", 24 | "webpack": "latest" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/cca.js: -------------------------------------------------------------------------------- 1 | /* Licensed to the Apache Software Foundation (ASF) under one 2 | * or more contributor license agreements. See the NOTICE file 3 | * distributed with this work for additional information 4 | * regarding copyright ownership. The ASF licenses this file 5 | * to you under the Apache License, Version 2.0 (the 6 | * "License"); you may not use this file except in compliance 7 | * with the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | 20 | module.exports = require('cca-manifest-logic'); 21 | module.exports.decodeBase64 = require('base64-arraybuffer').decode; 22 | -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | @font-face { 21 | font-family: 'Roboto Condensed'; 22 | font-style: normal; 23 | font-weight: 400; 24 | src: local('Roboto Condensed Regular'), local('RobotoCondensed-Regular'), url(../css/Zd2E9abXLFGSr9G3YK2MsNxB8OB85xaNTJvVSB9YUjQ.woff) format('woff'); 25 | } 26 | 27 | * { 28 | -webkit-box-sizing: border-box; 29 | -moz-box-sizing: border-box; 30 | box-sizing: border-box; 31 | } 32 | 33 | img { 34 | border: none; 35 | margin: 0; 36 | } 37 | 38 | a:link, a:visited, a:hover, a:active { 39 | /* text-decoration:none; */ 40 | color: #fff; 41 | } 42 | 43 | h1, h2, h3 { 44 | text-align: center; 45 | width: 100%; /* applies when in an hflex-container */ 46 | } 47 | 48 | ul { 49 | text-align: left; 50 | } 51 | 52 | html { 53 | background: #222; 54 | color: #ddd; 55 | font-family: "Roboto Condensed"; 56 | text-align: center; 57 | -webkit-user-select: none; 58 | -webkit-touch-callout: none; 59 | /* For large screens */ 60 | padding: 30px 30px 0 30px; 61 | font-size: 14pt; 62 | } 63 | 64 | @media all and (max-width: 500px) { 65 | html { 66 | padding: 20px 8px 0 8px; 67 | font-size: 10pt; 68 | } 69 | } 70 | 71 | body { 72 | margin: 0; 73 | } 74 | 75 | html, body { 76 | height: 100%; 77 | position: relative; 78 | max-width: 100%; 79 | overflow-x: hidden; 80 | } 81 | 82 | .vflex-container { 83 | display: flex; 84 | flex-direction: column; 85 | } 86 | 87 | .hflex-container { 88 | display: flex; 89 | flex-wrap: wrap; 90 | align-items: center; 91 | } 92 | 93 | .flex-justify-between { 94 | justify-content: space-between; 95 | } 96 | 97 | .flex-justify-around { 98 | justify-content: space-around; 99 | } 100 | 101 | .flex-1 { 102 | flex-grow: 1; 103 | flex-basis: 0; 104 | } 105 | 106 | .scrollable { 107 | overflow: auto; 108 | -webkit-overflow-scrolling: touch; 109 | } 110 | 111 | .title { 112 | font-size: 1.8rem; 113 | margin-bottom: 10px; 114 | } 115 | 116 | /* iOS title */ 117 | @media all and (max-width: 330px) { 118 | .title { 119 | font-size: 1.5rem; 120 | } 121 | } 122 | 123 | .title-version { 124 | font-size: .6em; 125 | } 126 | 127 | .big-button { 128 | font-size: 1rem; 129 | } 130 | 131 | .listening-container { 132 | margin-bottom: 10pt; 133 | font-size: 1.2rem; 134 | } 135 | 136 | .listening-ip { 137 | color: #AAA; 138 | margin: 0 3pt; 139 | display: inline-block; 140 | } 141 | 142 | .listening-error { 143 | color: #E00; 144 | } 145 | 146 | .listening-port { 147 | color: #0A0; 148 | } 149 | 150 | .app-container { 151 | border: 1px solid #555; 152 | border-radius: 2px; 153 | text-align: left; 154 | position: relative; 155 | padding: .5rem; 156 | background: #444; 157 | width: 80%; 158 | line-height: 1.5rem; 159 | } 160 | 161 | @media all and (max-width: 500px) { 162 | .app-container { 163 | width: 90%; 164 | } 165 | } 166 | 167 | .app-empty-message { 168 | font-size: 1.5rem; 169 | } 170 | 171 | .app-button-x { 172 | position: absolute; 173 | top: -1.2rem; 174 | cursor:pointer; 175 | color: #fff; 176 | border-radius: 1.2rem; 177 | background: #D13D3D; 178 | display: inline-block; 179 | padding: 1.2rem; 180 | left: -1.2rem; 181 | } 182 | 183 | .app-button-x::after { 184 | content: "\d7"; 185 | position: absolute; 186 | top: 0; 187 | left: 0; 188 | right: 0; 189 | line-height: 2.2rem; 190 | font-size: 2rem; 191 | text-align: center; 192 | } 193 | 194 | .app-icon-container { 195 | margin-right: 6px; 196 | } 197 | 198 | .app-icon { 199 | width: 64px; 200 | height: 64px; 201 | line-height: 64px; 202 | display: inline-block; 203 | vertical-align: middle; 204 | } 205 | 206 | .app-icon-missing { 207 | text-align: center; 208 | border: 1px solid #666; 209 | } 210 | 211 | @media all and (max-width: 500px) { 212 | .app-icon { 213 | width: 48px; 214 | height: 48px; 215 | line-height: 48px; 216 | } 217 | } 218 | 219 | @media all and (max-width: 500px) { 220 | .app-text-container-1 { 221 | left: 58px; 222 | } 223 | } 224 | 225 | .app-title { 226 | font-size: 1.3rem; 227 | line-height: 2rem; 228 | } 229 | 230 | .app-version { 231 | } 232 | 233 | .app-last-updated { 234 | color: #ccc; 235 | } 236 | 237 | .app-plugin-status { 238 | color: #ccc; 239 | } 240 | 241 | .app-button-container { 242 | text-align: center; 243 | margin-top: 12px; 244 | } 245 | 246 | .app-button { 247 | background-color: #c32; 248 | border:1px solid #d02718; 249 | display:inline-block; 250 | color: #ddd; 251 | font-size: 15px; 252 | font-weight:bold; 253 | padding: 4px 12px; 254 | -webkit-tap-highlight-color: rgba(0,0,0,0); 255 | } 256 | .app-button:active { 257 | background-color: #a32; 258 | } 259 | 260 | .footer { 261 | margin: 2.2rem 0; 262 | } 263 | 264 | .plugin-newer { 265 | color: #FFCA21; 266 | } 267 | .plugin-older { 268 | color: #FFCA21; 269 | } 270 | .plugin-missing { 271 | color: #1ADAFF; 272 | } 273 | 274 | .plugin-newer>span, .plugin-older>span, .plugin-missing>span { 275 | font-style: italic; 276 | } 277 | -------------------------------------------------------------------------------- /template-overrides/Activity.java: -------------------------------------------------------------------------------- 1 | 2 | @Override 3 | public void onBackPressed() { 4 | // If app is running, quit it. 5 | AppHarnessUI ahui = (AppHarnessUI)appView.getPluginManager().getPlugin("AppHarnessUI"); 6 | if (ahui != null) { 7 | if (ahui.isSlaveCreated()) { 8 | ahui.sendEvent("quitApp"); 9 | return; 10 | } 11 | } 12 | // Otherwise, hide instead of calling .finish(). 13 | moveTaskToBack(true); 14 | } 15 | 16 | @Override 17 | public Object onMessage(String id, Object data) { 18 | // Capture the app calling navigator.app.exitApp(). 19 | if ("exit".equals(id)) { 20 | AppHarnessUI ahui = (AppHarnessUI)appView.getPluginManager().getPlugin("AppHarnessUI"); 21 | if (ahui != null) { 22 | if (ahui.isSlaveCreated()) { 23 | ahui.sendEvent("quitApp"); 24 | return new Object(); 25 | } 26 | } 27 | } 28 | return super.onMessage(id, data); 29 | } 30 | -------------------------------------------------------------------------------- /template-overrides/CCAHarness-debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MobileChromeApps/chrome-app-developer-tool/765ae4e7e31c4c1266e64278a13536bc84f77cfd/template-overrides/CCAHarness-debug.keystore -------------------------------------------------------------------------------- /template-overrides/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleDisplayName 8 | App Dev Tool 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIconFile 12 | icon.png 13 | CFBundleIcons 14 | 15 | CFBundlePrimaryIcon 16 | 17 | CFBundleIconFiles 18 | 19 | icon-40 20 | icon-small 21 | icon-60 22 | icon.png 23 | icon@2x 24 | icon-72 25 | icon-72@2x 26 | 27 | UIPrerenderedIcon 28 | 29 | 30 | 31 | CFBundleIcons~ipad 32 | 33 | CFBundlePrimaryIcon 34 | 35 | CFBundleIconFiles 36 | 37 | icon-small 38 | icon-40 39 | icon-50 40 | icon-76 41 | icon-60 42 | icon 43 | icon@2x 44 | icon-72 45 | icon-72@2x 46 | 47 | UIPrerenderedIcon 48 | 49 | 50 | 51 | CFBundleIdentifier 52 | org.chromium.appharness 53 | CFBundleInfoDictionaryVersion 54 | 6.0 55 | CFBundleName 56 | ${PRODUCT_NAME} 57 | CFBundlePackageType 58 | APPL 59 | CFBundleShortVersionString 60 | 0.5.1 61 | CFBundleSignature 62 | ???? 63 | CFBundleVersion 64 | 0.5.1 65 | LSRequiresIPhoneOS 66 | 67 | NSMainNibFile 68 | 69 | NSMainNibFile~ipad 70 | 71 | UISupportedInterfaceOrientations 72 | 73 | UIInterfaceOrientationPortrait 74 | 75 | UISupportedInterfaceOrientations~ipad 76 | 77 | UIInterfaceOrientationPortrait 78 | UIInterfaceOrientationLandscapeLeft 79 | UIInterfaceOrientationPortraitUpsideDown 80 | UIInterfaceOrientationLandscapeRight 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /template-overrides/after-hook.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | * Licensed to the Apache Software Foundation (ASF) under one 4 | * or more contributor license agreements. See the NOTICE file 5 | * distributed with this work for additional information 6 | * regarding copyright ownership. The ASF licenses this file 7 | * to you under the Apache License, Version 2.0 (the 8 | * "License"); you may not use this file except in compliance 9 | * with the License. You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, 14 | * software distributed under the License is distributed on an 15 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | * KIND, either express or implied. See the License for the 17 | * specific language governing permissions and limitations 18 | * under the License. 19 | */ 20 | 21 | var fs = require('fs'); 22 | var path = require('path'); 23 | 24 | var preparedWwwPathMap = { 25 | 'android': path.join('platforms', 'android', 'assets' , 'www'), 26 | 'ios': path.join('platforms', 'ios', 'www') 27 | }; 28 | 29 | var platforms = process.env['CORDOVA_PLATFORMS'].split(',').filter(function(name) { return name in preparedWwwPathMap; }); 30 | if (platforms.length === 0) { 31 | return; 32 | } 33 | 34 | function generatePluginInfoFile() { 35 | var idToServiceNameMap = {}; 36 | var depsMap = {}; 37 | 38 | function extractServiceNames(contents) { 39 | var foundNames = {}; 40 | var pattern = / ' + path.join(wwwPath, 'cordova_plugins_harness.js')); 92 | }); 93 | } 94 | generatePluginInfoFile(); 95 | renameCordovaPluginsFile(); 96 | -------------------------------------------------------------------------------- /template-overrides/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | {NAME} 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /template-overrides/debug-signing.properties: -------------------------------------------------------------------------------- 1 | keyAlias=AndroidDebugKey 2 | keyPassword=android 3 | storeFile=CCAHarness-debug.keystore 4 | storePassword=android 5 | -------------------------------------------------------------------------------- /template-overrides/icons/android/icon-hdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MobileChromeApps/chrome-app-developer-tool/765ae4e7e31c4c1266e64278a13536bc84f77cfd/template-overrides/icons/android/icon-hdpi.png -------------------------------------------------------------------------------- /template-overrides/icons/android/icon-mdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MobileChromeApps/chrome-app-developer-tool/765ae4e7e31c4c1266e64278a13536bc84f77cfd/template-overrides/icons/android/icon-mdpi.png -------------------------------------------------------------------------------- /template-overrides/icons/android/icon-xdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MobileChromeApps/chrome-app-developer-tool/765ae4e7e31c4c1266e64278a13536bc84f77cfd/template-overrides/icons/android/icon-xdpi.png -------------------------------------------------------------------------------- /template-overrides/icons/android/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MobileChromeApps/chrome-app-developer-tool/765ae4e7e31c4c1266e64278a13536bc84f77cfd/template-overrides/icons/android/icon.png -------------------------------------------------------------------------------- /template-overrides/icons/ios/iTunesArtwork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MobileChromeApps/chrome-app-developer-tool/765ae4e7e31c4c1266e64278a13536bc84f77cfd/template-overrides/icons/ios/iTunesArtwork.png -------------------------------------------------------------------------------- /template-overrides/icons/ios/iTunesArtwork@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MobileChromeApps/chrome-app-developer-tool/765ae4e7e31c4c1266e64278a13536bc84f77cfd/template-overrides/icons/ios/iTunesArtwork@2x.png -------------------------------------------------------------------------------- /template-overrides/icons/ios/icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MobileChromeApps/chrome-app-developer-tool/765ae4e7e31c4c1266e64278a13536bc84f77cfd/template-overrides/icons/ios/icon-40.png -------------------------------------------------------------------------------- /template-overrides/icons/ios/icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MobileChromeApps/chrome-app-developer-tool/765ae4e7e31c4c1266e64278a13536bc84f77cfd/template-overrides/icons/ios/icon-40@2x.png -------------------------------------------------------------------------------- /template-overrides/icons/ios/icon-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MobileChromeApps/chrome-app-developer-tool/765ae4e7e31c4c1266e64278a13536bc84f77cfd/template-overrides/icons/ios/icon-50.png -------------------------------------------------------------------------------- /template-overrides/icons/ios/icon-50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MobileChromeApps/chrome-app-developer-tool/765ae4e7e31c4c1266e64278a13536bc84f77cfd/template-overrides/icons/ios/icon-50@2x.png -------------------------------------------------------------------------------- /template-overrides/icons/ios/icon-57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MobileChromeApps/chrome-app-developer-tool/765ae4e7e31c4c1266e64278a13536bc84f77cfd/template-overrides/icons/ios/icon-57.png -------------------------------------------------------------------------------- /template-overrides/icons/ios/icon-57@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MobileChromeApps/chrome-app-developer-tool/765ae4e7e31c4c1266e64278a13536bc84f77cfd/template-overrides/icons/ios/icon-57@2x.png -------------------------------------------------------------------------------- /template-overrides/icons/ios/icon-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MobileChromeApps/chrome-app-developer-tool/765ae4e7e31c4c1266e64278a13536bc84f77cfd/template-overrides/icons/ios/icon-60.png -------------------------------------------------------------------------------- /template-overrides/icons/ios/icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MobileChromeApps/chrome-app-developer-tool/765ae4e7e31c4c1266e64278a13536bc84f77cfd/template-overrides/icons/ios/icon-60@2x.png -------------------------------------------------------------------------------- /template-overrides/icons/ios/icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MobileChromeApps/chrome-app-developer-tool/765ae4e7e31c4c1266e64278a13536bc84f77cfd/template-overrides/icons/ios/icon-72.png -------------------------------------------------------------------------------- /template-overrides/icons/ios/icon-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MobileChromeApps/chrome-app-developer-tool/765ae4e7e31c4c1266e64278a13536bc84f77cfd/template-overrides/icons/ios/icon-72@2x.png -------------------------------------------------------------------------------- /template-overrides/icons/ios/icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MobileChromeApps/chrome-app-developer-tool/765ae4e7e31c4c1266e64278a13536bc84f77cfd/template-overrides/icons/ios/icon-76.png -------------------------------------------------------------------------------- /template-overrides/icons/ios/icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MobileChromeApps/chrome-app-developer-tool/765ae4e7e31c4c1266e64278a13536bc84f77cfd/template-overrides/icons/ios/icon-76@2x.png -------------------------------------------------------------------------------- /template-overrides/icons/ios/icon-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MobileChromeApps/chrome-app-developer-tool/765ae4e7e31c4c1266e64278a13536bc84f77cfd/template-overrides/icons/ios/icon-small.png -------------------------------------------------------------------------------- /template-overrides/icons/ios/icon-small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MobileChromeApps/chrome-app-developer-tool/765ae4e7e31c4c1266e64278a13536bc84f77cfd/template-overrides/icons/ios/icon-small@2x.png -------------------------------------------------------------------------------- /template-overrides/icons/ios/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MobileChromeApps/chrome-app-developer-tool/765ae4e7e31c4c1266e64278a13536bc84f77cfd/template-overrides/icons/ios/icon.png -------------------------------------------------------------------------------- /template-overrides/icons/ios/icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MobileChromeApps/chrome-app-developer-tool/765ae4e7e31c4c1266e64278a13536bc84f77cfd/template-overrides/icons/ios/icon@2x.png -------------------------------------------------------------------------------- /template-overrides/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Chrome App Developer Tool 5 | 6 | App Dev Tool 7 | 8 | @string/launcher_name 9 | 10 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | * 19 | */ 20 | var path = require('path'); 21 | 22 | module.exports = { 23 | context: __dirname + "/www/cdvah", 24 | entry: { 25 | cca: [ 26 | path.join('..', '..', 'src', 'cca.js'), 27 | ], 28 | }, 29 | output: { 30 | // Make sure to use [name] or [id] in output.filename 31 | // when using multiple entry points 32 | path: path.join(__dirname, 'www', 'cdvah', 'generated'), 33 | 34 | filename: "[name].bundle.js", 35 | chunkFilename: "[id].bundle.js", 36 | 37 | library: "[name]", 38 | libraryTarget: "umd", 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /www/cdvah/css/Zd2E9abXLFGSr9G3YK2MsNxB8OB85xaNTJvVSB9YUjQ.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MobileChromeApps/chrome-app-developer-tool/765ae4e7e31c4c1266e64278a13536bc84f77cfd/www/cdvah/css/Zd2E9abXLFGSr9G3YK2MsNxB8OB85xaNTJvVSB9YUjQ.woff -------------------------------------------------------------------------------- /www/cdvah/harnessmenu.html: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /www/cdvah/js/AboutCtrl.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | (function(){ 20 | 'use strict'; 21 | /* global analytics */ 22 | /* global myApp */ 23 | myApp.controller('AboutCtrl', ['$rootScope', '$scope', '$location', 'PluginMetadata', 'Reporter', function($rootScope, $scope, $location, PluginMetadata, Reporter) { 24 | // Track the page view. 25 | Reporter.sendPageView('about'); 26 | 27 | $scope.plugins = PluginMetadata.availablePlugins().filter(function(p) { 28 | return !/UrlRemap|appharness/.exec(p.id); 29 | }); 30 | 31 | $scope.goBack = function() { 32 | if ($scope.config) { 33 | $scope.config.setTrackingPermitted($scope.formData.reportingPermissionCheckbox); 34 | } 35 | $location.path('/'); 36 | }; 37 | 38 | var getConfigCallback = function(config) { 39 | // Save the config object so we can update it when the user leaves this screen. 40 | $scope.config = config; 41 | 42 | // Initialize the reporting permission checkbox according to the recorded response. 43 | var permitted = config.isTrackingPermitted(); 44 | $scope.formData = { reportingPermissionCheckbox: permitted }; 45 | }; 46 | 47 | // Get the config object. 48 | var service = analytics.getService($rootScope.appTitle); 49 | service.getConfig().addCallback(getConfigCallback); 50 | }]); 51 | })(); 52 | -------------------------------------------------------------------------------- /www/cdvah/js/ApkPackager.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | (function() { 20 | 'use strict'; 21 | /* global myApp */ 22 | /* global cca */ 23 | myApp.factory('ApkPackager', ['$q', 'ResourcesLoader', function($q, ResourcesLoader) { 24 | 25 | function extractAppMetadata(app) { 26 | // TODO: Add to this the list of android permissions. 27 | return { 28 | 'appName': app.getAppName(), 29 | 'packageName': app.getConfigXmlId(), 30 | 'versionName': app.getVersion(), 31 | 'versionCode': app.getAndroidVersionCode() 32 | }; 33 | } 34 | 35 | return { 36 | build: function(app, signingOpts, outputUrl) { 37 | var wwwDir = app.getWwwDir(); 38 | var workDir = ResourcesLoader.createTmpFileUrl('.apkpackager'); 39 | var templateUrl = 'file:///android_asset/www/apk-template'; 40 | var execSigningOpts = { 41 | 'keyPassword': signingOpts.keyPassword 42 | }; 43 | // TODO: Need to do post-prepare logic for locales 44 | return $q.when().then(function() { 45 | if (signingOpts.privateKeyData) { 46 | var privateKeyUrl = workDir + '/privatekey'; 47 | var certificateUrl = workDir + '/certificate'; 48 | execSigningOpts['privateKeyUrl'] = privateKeyUrl; 49 | execSigningOpts['certificateUrl'] = certificateUrl; 50 | return ResourcesLoader.writeFileContents(privateKeyUrl, cca.decodeBase64(signingOpts.privateKeyData)) 51 | .then(function() { 52 | return ResourcesLoader.writeFileContents(certificateUrl, cca.decodeBase64(signingOpts.certificateData)); 53 | }); 54 | } 55 | var keyStoreUrl = workDir + '/keystore'; 56 | execSigningOpts['keyStoreUrl'] = keyStoreUrl; 57 | execSigningOpts['storePassword'] = signingOpts.storePassword; 58 | execSigningOpts['keyAlias'] = signingOpts.keyAlias; 59 | execSigningOpts['storeType'] = signingOpts.storeType; 60 | return ResourcesLoader.writeFileContents(keyStoreUrl, cca.decodeBase64(signingOpts.storeData)); 61 | }).then(function() { 62 | return extractAppMetadata(app); 63 | }).then(function(appMetadata) { 64 | var deferred = $q.defer(); 65 | cordova.exec(deferred.resolve, deferred.reject, 'APKPackager', 'packageAPK', [workDir + '/playground/', templateUrl, wwwDir, outputUrl, execSigningOpts, appMetadata]); 66 | return deferred.promise; 67 | }).finally(function() { 68 | return ResourcesLoader.delete(workDir); 69 | }); 70 | } 71 | }; 72 | }]); 73 | })(); 74 | -------------------------------------------------------------------------------- /www/cdvah/js/AppHarnessUI.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | (function() { 20 | 'use strict'; 21 | /* global myApp */ 22 | myApp.factory('AppHarnessUI', ['$q', 'pluginIdToServiceNames', function($q, pluginIdToServiceNames) { 23 | function createServiceNameWhitelist(pluginMetadata) { 24 | var ret = []; 25 | Object.keys(pluginMetadata).forEach(function(pluginId) { 26 | var serviceNames = pluginIdToServiceNames[pluginId]; 27 | if (serviceNames) { 28 | ret.push.apply(ret, serviceNames); 29 | } 30 | }); 31 | if (cordova.platformId == 'android') { 32 | // This is a plugin bundled with the platform. 33 | ret.push('CoreAndroid'); 34 | // Needed for launching to work. 35 | ret.push('UrlRemap'); 36 | } 37 | return ret; 38 | } 39 | 40 | return { 41 | create: function(startUrl, configXmlUrl, pluginMetadata, webViewType) { 42 | var deferred = $q.defer(); 43 | var serviceNames = createServiceNameWhitelist(pluginMetadata); 44 | cordova.plugins.appharnessui.create(startUrl, configXmlUrl, serviceNames, webViewType, deferred.resolve); 45 | return deferred.promise; 46 | }, 47 | destroy: function() { 48 | var deferred = $q.defer(); 49 | cordova.plugins.appharnessui.destroy(deferred.resolve); 50 | return deferred.promise; 51 | }, 52 | reload: function(startUrl, configXmlUrl, pluginMetadata) { 53 | var deferred = $q.defer(); 54 | var serviceNames = createServiceNameWhitelist(pluginMetadata); 55 | cordova.plugins.appharnessui.reload(startUrl, configXmlUrl, serviceNames, deferred.resolve); 56 | return deferred.promise; 57 | }, 58 | setVisible: function(value) { 59 | var deferred = $q.defer(); 60 | cordova.plugins.appharnessui.setVisible(value, deferred.resolve); 61 | return deferred.promise; 62 | }, 63 | setEventHandler: function(f) { 64 | cordova.plugins.appharnessui.onEvent = f && function(type) { 65 | $q.when().then(function() { 66 | f(type); 67 | }); 68 | }; 69 | }, 70 | fireEvent: function(type) { 71 | if (cordova.plugins.appharnessui.onEvent) { 72 | cordova.plugins.appharnessui.onEvent(type); 73 | } 74 | }, 75 | evalJs: function(code) { 76 | var deferred = $q.defer(); 77 | cordova.plugins.appharnessui.evalJs(code, deferred.resolve); 78 | return deferred.promise; 79 | } 80 | }; 81 | }]); 82 | })(); 83 | -------------------------------------------------------------------------------- /www/cdvah/js/AppsService.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | (function() { 20 | 'use strict'; 21 | /* global myApp */ 22 | myApp.factory('AppsService', ['$q', 'ResourcesLoader', 'INSTALL_DIRECTORY', 'APPS_JSON', 'AppHarnessUI', 'Reporter', function($q, ResourcesLoader, INSTALL_DIRECTORY, APPS_JSON, AppHarnessUI, Reporter) { 23 | // Map of type -> installer. 24 | var _installerFactories = Object.create(null); 25 | // Array of installer objects. 26 | var _installers = null; 27 | // The app that is currently running. 28 | var activeInstaller = null; 29 | var lastAccessedInstaller = null; 30 | var curWebViewType = null; 31 | 32 | function readAppsJson() { 33 | var deferred = $q.defer(); 34 | ResourcesLoader.readJSONFileContents(APPS_JSON) 35 | .then(function(result) { 36 | if (result['fileVersion'] !== 1) { 37 | console.warn('Ignoring old version of apps.json'); 38 | result = {}; 39 | } 40 | deferred.resolve(result); 41 | }, function() { 42 | // Error means first run. 43 | deferred.resolve({}); 44 | }); 45 | return deferred.promise; 46 | } 47 | 48 | function initHandlers() { 49 | if (_installers) { 50 | return $q.when(); 51 | } 52 | 53 | return readAppsJson() 54 | .then(function(json) { 55 | var appList = json['appList'] || []; 56 | _installers = []; 57 | var i = -1; 58 | return $q.when() 59 | .then(function next() { 60 | var entry = appList[++i]; 61 | if (!entry) { 62 | return; 63 | } 64 | var Ctor = _installerFactories[entry['appType']]; 65 | return new Ctor().initFromJson(entry) 66 | .then(function(app) { 67 | _installers.push(app); 68 | return next(); 69 | }, next); 70 | }) 71 | .then(function() { 72 | lastAccessedInstaller = _installers.filter(function(x) { return x.appId === json['lastAccessedAppId']; })[0] || null; 73 | }); 74 | }); 75 | } 76 | 77 | function createAppsJson() { 78 | var appsJson = { 79 | 'fileVersion': 1, 80 | 'lastAccessedAppId': lastAccessedInstaller ? lastAccessedInstaller.appId : null, 81 | 'appList': [] 82 | }; 83 | for (var i = 0; i < _installers.length; ++i) { 84 | var installer = _installers[i]; 85 | appsJson.appList.push(installer.toDiskJson()); 86 | } 87 | return appsJson; 88 | } 89 | 90 | function writeAppsJson() { 91 | if (AppsService.onAppListChange) { 92 | AppsService.onAppListChange(); 93 | } 94 | var appsJson = createAppsJson(); 95 | var stringContents = JSON.stringify(appsJson, null, 4); 96 | return ResourcesLoader.writeFileContents(APPS_JSON, stringContents); 97 | } 98 | 99 | function updateLastAccessed(app) { 100 | if (lastAccessedInstaller != app) { 101 | lastAccessedInstaller = app; 102 | return writeAppsJson(); 103 | } 104 | return $q.when(); 105 | } 106 | 107 | var AppsService = { 108 | // return promise with the array of apps 109 | getAppList : function() { 110 | return initHandlers() 111 | .then(function() { 112 | return _installers.slice(); 113 | }); 114 | }, 115 | 116 | getLastAccessedApp: function() { 117 | return lastAccessedInstaller; 118 | }, 119 | 120 | getActiveApp: function() { 121 | return activeInstaller; 122 | }, 123 | 124 | getAppListAsJson : function() { 125 | return createAppsJson(); 126 | }, 127 | 128 | // If no appId, then return the first app. 129 | // If appId and appType, then create it if it doesn't exist. 130 | // Else: return null. 131 | getAppById : function(appId, /* optional */ appType) { 132 | return initHandlers() 133 | .then(function() { 134 | var matches = _installers; 135 | if (appId) { 136 | matches = _installers.filter(function(x) { 137 | return x.appId == appId; 138 | }); 139 | } 140 | if (matches.length > 0) { 141 | if (appType) { 142 | return updateLastAccessed(matches[0]) 143 | .then(function() { 144 | return matches[0]; 145 | }); 146 | } 147 | return matches[0]; 148 | } 149 | if (appType) { 150 | return AppsService.addApp(appType, appId); 151 | } 152 | return null; 153 | }); 154 | }, 155 | 156 | quitApp : function() { 157 | if (activeInstaller) { 158 | activeInstaller.unlaunch(); 159 | activeInstaller = null; 160 | curWebViewType = null; 161 | return AppHarnessUI.destroy(); 162 | } 163 | return $q.when(); 164 | }, 165 | 166 | launchApp : function(installer) { 167 | // Determine whether we're relaunching the same app as is already active. 168 | var activeAppId = activeInstaller && activeInstaller.appId; 169 | var newAppId = installer && installer.appId; 170 | var relaunch = activeAppId && newAppId && activeAppId === newAppId; 171 | 172 | return $q.when() 173 | .then(function() { 174 | // If we're relaunching the active app, move on. 175 | // Otherwise, quit the active app. 176 | // TODO(maxw): Determine whether we actually ever need to quit the app. 177 | if (relaunch) { 178 | return $q.when(); 179 | } else { 180 | return AppsService.quitApp(); 181 | } 182 | }).then(function() { 183 | activeInstaller = installer; 184 | return updateLastAccessed(installer); 185 | }).then(function() { 186 | return installer.launch(); 187 | }).then(function(launchUrl) { 188 | return installer.getPluginMetadata() 189 | .then(function(pluginMetadata) { 190 | var configXmlUrl = installer.directoryManager.rootURL + 'config.xml'; 191 | var webViewType = installer.getWebViewType(); 192 | if (relaunch) { 193 | if (webViewType != curWebViewType) { 194 | curWebViewType = webViewType; 195 | return AppHarnessUI.destroy() 196 | .then(function() { 197 | return AppHarnessUI.create(launchUrl, configXmlUrl, pluginMetadata, webViewType); 198 | }); 199 | } else { 200 | return AppHarnessUI.reload(launchUrl, configXmlUrl, pluginMetadata); 201 | } 202 | } else { 203 | curWebViewType = webViewType; 204 | return AppHarnessUI.create(launchUrl, configXmlUrl, pluginMetadata, webViewType); 205 | } 206 | }).then(function() { 207 | if (AppsService.onAppListChange) { 208 | AppsService.onAppListChange(); 209 | } 210 | }).then(function() { 211 | Reporter.sendEvent('app', 'launched'); 212 | }); 213 | }); 214 | }, 215 | 216 | addApp : function(appType, /* optional */ appId) { 217 | var installPath = INSTALL_DIRECTORY + 'app' + Math.floor(Math.random() * 0xFFFFFFFF).toString(36) + '/'; 218 | return initHandlers().then(function() { 219 | var Ctor = _installerFactories[appType]; 220 | return new Ctor().init(installPath, appId); 221 | }).then(function(installer) { 222 | _installers.push(installer); 223 | lastAccessedInstaller = installer; 224 | return writeAppsJson() 225 | .then(function() { 226 | return installer; 227 | }); 228 | }); 229 | }, 230 | 231 | uninstallAllApps : function() { 232 | return this.quitApp() 233 | .then(function() { 234 | var deletePromises = []; 235 | for (var i = 0; i < _installers.length; ++i) { 236 | deletePromises.push(AppsService.uninstallApp(_installers[i])); 237 | } 238 | return $q.all(deletePromises); 239 | }); 240 | }, 241 | 242 | uninstallApp : function(installer) { 243 | var ret = $q.when(); 244 | if (lastAccessedInstaller == installer) { 245 | lastAccessedInstaller = null; 246 | } 247 | if (installer == activeInstaller) { 248 | ret = this.quitApp(); 249 | } 250 | return ret.then(function() { 251 | return installer.deleteFiles(); 252 | }).then(function() { 253 | _installers.splice(_installers.indexOf(installer), 1); 254 | return writeAppsJson(); 255 | }); 256 | }, 257 | 258 | triggerAppListChange: function() { 259 | return writeAppsJson(); 260 | }, 261 | 262 | registerInstallerFactory : function(installerFactory) { 263 | _installerFactories[installerFactory.type] = installerFactory; 264 | }, 265 | 266 | onAppListChange: null 267 | }; 268 | return AppsService; 269 | }]); 270 | })(); 271 | -------------------------------------------------------------------------------- /www/cdvah/js/CordovaInstaller.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | (function(){ 20 | 'use strict'; 21 | 22 | /* global myApp */ 23 | myApp.factory('CordovaInstaller', ['$q', 'Installer', 'ResourcesLoader', 'PluginMetadata', function($q, Installer, ResourcesLoader, PluginMetadata) { 24 | 25 | function CordovaInstaller() {} 26 | CordovaInstaller.prototype = Object.create(Installer.prototype); 27 | CordovaInstaller.prototype.constructor = CordovaInstaller; 28 | CordovaInstaller.type = 'cordova'; 29 | 30 | CordovaInstaller.prototype.initFromJson = function(json) { 31 | var self = this; 32 | return Installer.prototype.initFromJson.call(this, json) 33 | .then(function() { 34 | return self.readCordovaPluginsFile(); 35 | }).then(function() { 36 | return self; 37 | }, function(e) { 38 | console.warn('Deleting broken app: ' + json['installPath']); 39 | ResourcesLoader.delete(json['installPath']); 40 | throw e; 41 | }); 42 | }; 43 | 44 | CordovaInstaller.prototype.onFileAdded = function(path, etag) { 45 | var self = this; 46 | return $q.when(Installer.prototype.onFileAdded.call(this, path, etag)) 47 | .then(function() { 48 | if (path == 'orig-cordova_plugins.js') { 49 | return self.readCordovaPluginsFile(); 50 | } 51 | }); 52 | }; 53 | 54 | CordovaInstaller.prototype.getPluginMetadata = function() { 55 | return ResourcesLoader.readFileContents(this.directoryManager.rootURL + 'orig-cordova_plugins.js') 56 | .then(function(contents) { 57 | return PluginMetadata.extractPluginMetadata(contents); 58 | }); 59 | }; 60 | 61 | CordovaInstaller.prototype.readCordovaPluginsFile = function() { 62 | var etag = this.directoryManager.getAssetEtag('orig-cordova_plugins.js'); 63 | return this.updateCordovaPluginsFile(etag); 64 | }; 65 | 66 | return CordovaInstaller; 67 | }]); 68 | 69 | myApp.run(['CordovaInstaller', 'AppsService', function(CordovaInstaller, AppsService) { 70 | AppsService.registerInstallerFactory(CordovaInstaller); 71 | }]); 72 | })(); 73 | -------------------------------------------------------------------------------- /www/cdvah/js/CrxInstaller.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | (function(){ 20 | 'use strict'; 21 | /* global cca */ 22 | 23 | // The only things that matters here at the moment 24 | // are the appId and . 25 | var CONFIG_XML_TEMPLATE = '\n' + 26 | '\n' + 27 | '\n' + 28 | '\n' + 29 | '\n' + 30 | '\n' + 31 | '\n' + 32 | '\n' + 33 | '\n' + 34 | '\n' + 35 | '\n'; 36 | 37 | 38 | function generateConfigXmlData(manifest) { 39 | var template = CONFIG_XML_TEMPLATE; 40 | var analyzedManifest = cca.analyseManifest(manifest); 41 | var configXmlDom = new DOMParser().parseFromString(template, 'text/xml'); 42 | cca.updateConfigXml(manifest, analyzedManifest, configXmlDom); 43 | return new XMLSerializer().serializeToString(configXmlDom); 44 | } 45 | 46 | /* global myApp */ 47 | myApp.factory('CrxInstaller', ['$q', 'Installer', 'AppsService', 'ResourcesLoader', 'pluginDepsMap', function($q, Installer, AppsService, ResourcesLoader, pluginDepsMap) { 48 | 49 | function CrxInstaller() { 50 | this.mergedManifestJson_ = null; 51 | } 52 | CrxInstaller.prototype = Object.create(Installer.prototype); 53 | CrxInstaller.prototype.constructor = CrxInstaller; 54 | CrxInstaller.type = 'chrome'; 55 | 56 | CrxInstaller.prototype.initFromJson = function(json) { 57 | var self = this; 58 | return Installer.prototype.initFromJson.call(this, json) 59 | .then(function() { 60 | return self.readManifest_(); 61 | }).then(function() { 62 | return self.updateDerivedFiles(); 63 | }).then(function() { 64 | return self; 65 | }, function(e) { 66 | console.warn('Deleting broken app: ' + json['installPath']); 67 | ResourcesLoader.delete(json['installPath']); 68 | throw e; 69 | }); 70 | }; 71 | 72 | CrxInstaller.prototype.onFileAdded = function(path, etag) { 73 | var self = this; 74 | return $q.when(Installer.prototype.onFileAdded.call(this, path, etag)) 75 | .then(function() { 76 | if (path == 'www/manifest.json' || (path == 'www/manifest.mobile.json' && self.mergedManifestJson_)) { 77 | return self.readManifest_() 78 | .then(function() { 79 | return self.updateDerivedFiles(); 80 | }); 81 | } 82 | }); 83 | }; 84 | 85 | CrxInstaller.prototype.readManifest_ = function() { 86 | var self = this; 87 | return cca.parseAndMergeManifests(this.directoryManager.rootURL + 'www/manifest.json', 'android', ResourcesLoader.readFileContents, $q) 88 | .then(function(m) { 89 | self.mergedManifestJson_ = m; 90 | }); 91 | }; 92 | 93 | CrxInstaller.prototype.updateDerivedFiles = function() { 94 | var self = this; 95 | var contents = generateConfigXmlData(this.mergedManifestJson_); 96 | var combinedEtag = this.directoryManager.getAssetEtag('www/manifest.json') + this.directoryManager.getAssetEtag('www/manifest.mobile.json'); 97 | return this.directoryManager.writeFile(contents, 'config.xml', combinedEtag) 98 | .then(function() { 99 | return self.updateCordovaPluginsFile(self.directoryManager.getAssetEtag('www/manifest.json')); 100 | }); 101 | }; 102 | 103 | function expandPluginIdsWithDeps(ids) { 104 | var idMap = {}; 105 | function addAll(arr) { 106 | arr.forEach(function(pluginId) { 107 | pluginId = pluginId.replace(/@.*/, ''); 108 | if (!idMap[pluginId]) { 109 | idMap[pluginId] = true; 110 | var deps = pluginDepsMap[pluginId]; 111 | if (deps) { 112 | addAll(deps); 113 | } 114 | } 115 | }); 116 | } 117 | addAll(ids); 118 | return Object.keys(idMap); 119 | } 120 | 121 | CrxInstaller.prototype.getPluginMetadata = function() { 122 | var pluginIds = cca.analyseManifest(this.mergedManifestJson_).pluginsToBeInstalled; 123 | pluginIds = expandPluginIdsWithDeps(pluginIds); 124 | var harnessPluginMetadata = cordova.require('cordova/plugin_list').metadata; 125 | var ret = {}; 126 | // Make all versions match what is installed. 127 | for (var i = 0; i < pluginIds.length; ++i) { 128 | ret[pluginIds[i]] = harnessPluginMetadata[pluginIds[i]] || '0'; 129 | } 130 | return $q.when(ret); 131 | }; 132 | 133 | function injectCsp(htmlPath, cspTag) { 134 | return ResourcesLoader.readFileContents(htmlPath) 135 | .then(function(html) { 136 | html = html.replace(//, cspTag); 137 | return ResourcesLoader.writeFileContents(htmlPath, html); 138 | }); 139 | } 140 | 141 | CrxInstaller.prototype.launch = function() { 142 | var appWwwUrl = this.getWwwDir(); 143 | var cspContent = cca.analyseManifest.createCspString(this.mergedManifestJson_, cordova.platformId); 144 | var cspTag = ''; 145 | return Installer.prototype.launch.call(this) 146 | .then(function(ret) { 147 | return injectCsp(appWwwUrl + 'plugins/cordova-plugin-chrome-apps-bootstrap//chromeapp.html', cspTag) 148 | .then(function() { 149 | return injectCsp(appWwwUrl + 'plugins/cordova-plugin-chrome-apps-bootstrap//chromebgpage.html', cspTag); 150 | }).then(function() { 151 | return ret; 152 | }); 153 | }); 154 | }; 155 | 156 | return CrxInstaller; 157 | }]); 158 | 159 | myApp.run(['CrxInstaller', 'AppsService', function(CrxInstaller, AppsService) { 160 | AppsService.registerInstallerFactory(CrxInstaller); 161 | }]); 162 | })(); 163 | -------------------------------------------------------------------------------- /www/cdvah/js/DetailsCtrl.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | (function(){ 20 | 'use strict'; 21 | 22 | /* global myApp */ 23 | myApp.controller('DetailsCtrl', ['$rootScope', '$scope', '$location', 'AppsService', '$routeParams', 'Reporter', function($rootScope, $scope, $location, AppsService, $routeParams, Reporter) { 24 | $scope.goBack = function() { 25 | $location.path('/'); 26 | }; 27 | AppsService.getAppList().then(function(appsList) { 28 | if ($routeParams.index >= 0) { 29 | $scope.app = appsList[$routeParams.index]; 30 | } else { 31 | $location.path('/'); 32 | } 33 | //$scope.$apply(); 34 | }).then(function() { 35 | // Track the page view. 36 | Reporter.sendPageView('details'); 37 | }); 38 | }]); 39 | })(); 40 | -------------------------------------------------------------------------------- /www/cdvah/js/DirectoryManager.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | (function(){ 20 | 'use strict'; 21 | 22 | /* global myApp */ 23 | myApp.factory('DirectoryManager', ['$q', 'ResourcesLoader', function($q, ResourcesLoader) { 24 | var ASSET_MANIFEST = 'assetmanifest.json'; 25 | 26 | function DirectoryManager() {} 27 | 28 | DirectoryManager.prototype.init = function(rootURL) { 29 | this.rootURL = rootURL; 30 | this.onFileAdded = null; 31 | this._assetManifest = null; 32 | this._assetManifestEtag = null; 33 | this._flushTimerId = null; 34 | var deferred = $q.defer(); 35 | var me = this; 36 | ResourcesLoader.readJSONFileContents(rootURL + ASSET_MANIFEST) 37 | .then(function(json) { 38 | me._assetManifest = json['assetManifest']; 39 | me._assetManifestEtag = json['etag']; 40 | deferred.resolve(); 41 | }, function() { 42 | me._assetManifest = {}; 43 | me._assetManifestEtag = 0; 44 | deferred.resolve(); 45 | }); 46 | return deferred.promise; 47 | }; 48 | 49 | DirectoryManager.prototype.deleteAll = function() { 50 | this._assetManifest = {}; 51 | this._assetManifestEtag = 0; 52 | window.clearTimeout(this._flushTimerId); 53 | return ResourcesLoader.delete(this.rootURL); 54 | }; 55 | 56 | DirectoryManager.prototype.getAssetManifest = function() { 57 | return this._assetManifest; 58 | }; 59 | 60 | DirectoryManager.prototype.getAssetEtag = function(relativePath) { 61 | if (this._assetManifest.hasOwnProperty(relativePath)) { 62 | return this._assetManifest[relativePath]; 63 | } 64 | return ''; 65 | }; 66 | 67 | DirectoryManager.prototype.getAssetManifestEtag = function() { 68 | return (this._assetManifestEtag).toString(36).toUpperCase(); 69 | }; 70 | 71 | DirectoryManager.prototype._lazyWriteAssetManifest = function() { 72 | if (this._flushTimerId === null) { 73 | this._flushTimerId = window.setTimeout(this._writeAssetManifest.bind(this), 1000); 74 | } 75 | }; 76 | 77 | DirectoryManager.prototype._updateManifest = function(relativePath, etag) { 78 | if (etag !== null) { 79 | this._assetManifest[relativePath] = etag; 80 | } else { 81 | delete this._assetManifest[relativePath]; 82 | } 83 | this._assetManifestEtag = Math.floor(Math.random() * 0xFFFFFFFF); 84 | this._lazyWriteAssetManifest(); 85 | var self = this; 86 | if (etag !== null && this.onFileAdded) { 87 | return this.onFileAdded(relativePath, etag) 88 | .then(null, function(err) { 89 | // If there was an error with the file, delete it so that clients will 90 | // send it again. 91 | return self.deleteFile(relativePath) 92 | .then(function() { throw err; }, function() { throw err; }); 93 | }); 94 | } 95 | }; 96 | 97 | DirectoryManager.prototype._writeAssetManifest = function() { 98 | this._flushTimerId = null; 99 | var stringContents = JSON.stringify({ 100 | 'assetManifest': this._assetManifest, 101 | 'etag': this._assetManifestEtag 102 | }); 103 | return ResourcesLoader.writeFileContents(this.rootURL + ASSET_MANIFEST, stringContents); 104 | }; 105 | 106 | DirectoryManager.prototype.bulkAddFile = function(newAssetManifest, srcURL) { 107 | var self = this; 108 | return this.deleteAll() 109 | .then(function() { 110 | return ResourcesLoader.moveFile(srcURL, self.rootURL); 111 | }).then(function() { 112 | var keys = Object.keys(newAssetManifest); 113 | return $q.when().then(function next() { 114 | var key = keys.shift(); 115 | if (!key) { 116 | return; 117 | } 118 | return self._updateManifest(key, newAssetManifest[key]) 119 | .then(next); 120 | }); 121 | }); 122 | }; 123 | 124 | DirectoryManager.prototype.addFile = function(srcURL, relativePath, etag) { 125 | var self = this; 126 | return ResourcesLoader.moveFile(srcURL, this.rootURL + relativePath) 127 | .then(function() { 128 | return self._updateManifest(relativePath, etag); 129 | }); 130 | }; 131 | 132 | DirectoryManager.prototype.writeFile = function(data, relativePath, etag) { 133 | var self = this; 134 | return ResourcesLoader.writeFileContents(this.rootURL + relativePath, data) 135 | .then(function() { 136 | return self._updateManifest(relativePath, etag); 137 | }); 138 | }; 139 | 140 | DirectoryManager.prototype.deleteFile = function(relativePath) { 141 | if (!this._assetManifest[relativePath]) { 142 | console.warn('Tried to delete non-existing file: ' + relativePath); 143 | return $q.when(); 144 | } 145 | this._updateManifest(relativePath, null); 146 | return ResourcesLoader.delete(this.rootURL + relativePath); 147 | }; 148 | 149 | return DirectoryManager; 150 | }]); 151 | 152 | })(); 153 | 154 | -------------------------------------------------------------------------------- /www/cdvah/js/InAppMenuCtrl.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | (function(){ 20 | 'use strict'; 21 | 22 | /* global myApp */ 23 | myApp.controller('InAppMenuCtrl', ['$rootScope', '$scope', '$window', 'AppsService', 'AppHarnessUI', function($rootScope, $scope, $window, AppsService, AppHarnessUI) { 24 | var activeApp = AppsService.getActiveApp(); 25 | if (!activeApp) { 26 | $window.history.back(); 27 | } 28 | 29 | function backButtonListener() { 30 | $scope.$apply($scope.hideMenu); 31 | } 32 | document.addEventListener('backbutton', backButtonListener); 33 | $scope.$on('$destroy', function() { 34 | document.removeEventListener('backbutton', backButtonListener); 35 | }); 36 | 37 | $scope.app = activeApp; 38 | 39 | $scope.hideMenu = function() { 40 | return AppHarnessUI.setVisible(true); 41 | }; 42 | 43 | $scope.restartApp = function() { 44 | return AppsService.launchApp(activeApp); 45 | }; 46 | 47 | $scope.quitApp = function() { 48 | return AppsService.quitApp(); 49 | }; 50 | 51 | AppHarnessUI.setEventHandler(function(eventName) { 52 | if (eventName == 'showMenu') { 53 | AppHarnessUI.setVisible(false); 54 | } else if (eventName == 'hideMenu') { 55 | AppHarnessUI.setVisible(true); 56 | } else if (eventName == 'quitApp') { 57 | return AppsService.quitApp(); 58 | } else if (eventName == 'destroyed') { 59 | $window.history.back(); 60 | } else { 61 | console.warn('Unknown message from AppHarnessUI: ' + eventName); 62 | } 63 | }); 64 | }]); 65 | })(); 66 | -------------------------------------------------------------------------------- /www/cdvah/js/Installer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | (function(){ 20 | 'use strict'; 21 | 22 | /* global myApp, cordova */ 23 | myApp.factory('Installer', ['$q', 'UrlRemap', 'ResourcesLoader', 'PluginMetadata', 'DirectoryManager', function($q, UrlRemap, ResourcesLoader, PluginMetadata, DirectoryManager) { 24 | function Installer() {} 25 | 26 | Installer.prototype.init = function(installPath, /* optional */ appId) { 27 | var ret = this; 28 | ret.appType = ret.constructor.type; 29 | ret.appId = appId || 'default'; // Stored in apps.json. May be different from id within config.xml. 30 | ret.lastUpdated = null; 31 | // Derived values: 32 | ret.updatingStatus = null; 33 | ret.configXmlDom = null; // Read from config.xml 34 | ret.plugins = {}; // Read from orig-cordova_plugins.js 35 | ret.directoryManager = new DirectoryManager(); 36 | return ret.directoryManager.init(installPath) 37 | .then(function() { 38 | ret.directoryManager.onFileAdded = ret.onFileAdded.bind(ret); 39 | return ret; 40 | }); 41 | }; 42 | 43 | Installer.prototype.initFromJson = function(json) { 44 | var self = this; 45 | return this.init(json['installPath'], json['appId']) 46 | .then(function(ret) { 47 | ret.lastUpdated = json['lastUpdated'] && new Date(json['lastUpdated']); 48 | return self.readConfigXml_(); 49 | }); 50 | }; 51 | 52 | Installer.prototype.toDiskJson = function() { 53 | return { 54 | 'appType' : this.appType, 55 | 'appId' : this.appId, 56 | 'lastUpdated': this.lastUpdated && +this.lastUpdated, 57 | 'installPath': this.directoryManager.rootURL 58 | }; 59 | }; 60 | 61 | Installer.prototype.readConfigXml_ = function() { 62 | var self = this; 63 | return ResourcesLoader.readFileContents(this.directoryManager.rootURL + 'config.xml') 64 | .then(function(configStr) { 65 | self.configXmlDom = new DOMParser().parseFromString(configStr, 'text/xml'); 66 | }); 67 | }; 68 | 69 | function lastEl(els) { 70 | return els[els.length - 1]; 71 | } 72 | 73 | Installer.prototype.getAppName = function() { 74 | if (!this.configXmlDom) { 75 | return ''; 76 | } 77 | var el = lastEl(this.configXmlDom.getElementsByTagName('name')); 78 | return el && el.textContent; 79 | }; 80 | 81 | Installer.prototype.getIconUrl = function() { 82 | if (!this.configXmlDom) { 83 | return ''; 84 | } 85 | var el = lastEl(this.configXmlDom.getElementsByTagName('icon')); 86 | var ret = el && el.getAttribute('src'); 87 | // Don't set icon until the file exists. 88 | if (!ret || !this.directoryManager.getAssetEtag(ret)) { 89 | return ''; 90 | } 91 | ret = this.directoryManager.rootURL + ret; 92 | return ret; 93 | }; 94 | 95 | Installer.prototype.getStartPage = function() { 96 | var el = lastEl(this.configXmlDom.getElementsByTagName('content')); 97 | var ret = el ? el.getAttribute('src') : 'index.html'; 98 | return ret; 99 | }; 100 | 101 | Installer.prototype.getVersion = function() { 102 | if (!this.configXmlDom) { 103 | return ''; 104 | } 105 | var widgetEl = this.configXmlDom.lastChild; 106 | return widgetEl.getAttribute('version'); 107 | }; 108 | 109 | Installer.prototype.getConfigXmlId = function() { 110 | if (!this.configXmlDom) { 111 | return ''; 112 | } 113 | var widgetEl = this.configXmlDom.lastChild; 114 | return widgetEl.getAttribute('id'); 115 | }; 116 | 117 | Installer.prototype.getWebViewType = function() { 118 | if (cordova.platformId != 'android') { 119 | return 'system'; 120 | } 121 | var pluginsIds = this.plugins.all.map(function(e) { return e.id; }); 122 | var hasCrosswalk = pluginsIds.indexOf('org.crosswalk.engine') != -1; 123 | return hasCrosswalk ? 'crosswalk' : 'system'; 124 | }; 125 | 126 | Installer.prototype.getAndroidVersionCode = function() { 127 | // copied from android_parser.js 128 | function defaultVersionCode(version) { 129 | var nums = version.split('-')[0].split('.').map(Number); 130 | var versionCode = nums[0] * 10000 + nums[1] * 100 + nums[2]; 131 | return versionCode; 132 | } 133 | 134 | var versionCode = this.configXmlDom.lastChild.getAttribute('android-versionCode'); 135 | if (versionCode) { 136 | return +versionCode; 137 | } 138 | return defaultVersionCode(this.getVersion() || '0.0.1'); 139 | }; 140 | 141 | Installer.prototype.updateCordovaPluginsFile = function(etag) { 142 | var self = this; 143 | return $q.when(self.getPluginMetadata()) 144 | .then(function(metadata) { 145 | self.plugins = PluginMetadata.process(metadata); 146 | var pluginIds = Object.keys(metadata); 147 | var newPluginsFileData = PluginMetadata.createNewPluginListFile(pluginIds); 148 | return self.directoryManager.writeFile(newPluginsFileData, 'www/cordova_plugins.js', etag); 149 | }); 150 | }; 151 | 152 | Installer.prototype.onFileAdded = function(path) { 153 | if (path == 'config.xml') { 154 | return this.readConfigXml_(); 155 | } 156 | }; 157 | 158 | Installer.prototype.getPluginMetadata = function() { 159 | throw new Error('unimplemented.'); 160 | }; 161 | 162 | Installer.prototype.deleteFiles = function() { 163 | this.lastUpdated = null; 164 | return this.directoryManager.deleteAll(); 165 | }; 166 | 167 | Installer.prototype.unlaunch = function() { 168 | return UrlRemap.reset(); 169 | }; 170 | 171 | Installer.prototype._prepareForLaunch = function() { 172 | return $q.when(); 173 | }; 174 | 175 | Installer.prototype.getWwwDir = function() { 176 | return this.directoryManager.rootURL + 'www/'; 177 | }; 178 | 179 | Installer.prototype.launch = function() { 180 | var self = this; 181 | return $q.when(this._prepareForLaunch()) 182 | .then(function() { 183 | var urlutil = cordova.require('cordova/urlutil'); 184 | var harnessWwwUrl = urlutil.makeAbsolute(location.pathname).replace(/\/[^\/]*\/[^\/]*$/, '/'); 185 | var appWwwUrl = self.getWwwDir(); 186 | var startLocation = urlutil.makeAbsolute(self.getStartPage()).replace('/cdvah/', '/'); 187 | var realStartLocation = startLocation.replace(harnessWwwUrl, appWwwUrl); 188 | var useRemapper = false; //platformId == 'android'; 189 | 190 | if (!/^file:/.exec(startLocation)) { 191 | throw new Error('Expected to start with file: ' + startLocation); 192 | } 193 | 194 | if (useRemapper) { 195 | // Override cordova.js, and www/plugins to point at bundled plugins. 196 | // Note: Due to the remapper's inability to remap files that exist, this isn't strictly necessary. 197 | UrlRemap.aliasUri('^(?!app-harness://).*/www/cordova\\.js.*', '.+', 'app-harness:///cordova.js', false /* redirect */, true /* allowFurtherRemapping */); 198 | UrlRemap.aliasUri('^(?!app-harness://).*/www/plugins/.*', '^.*?/www/plugins/' , 'app-harness:///plugins/', false /* redirect */, true /* allowFurtherRemapping */); 199 | 200 | // Make any references to www/ point to the app's install location. 201 | var harnessPrefixPattern = '^' + harnessWwwUrl.replace('file:///', 'file://.*?/'); 202 | UrlRemap.aliasUri(harnessPrefixPattern, harnessPrefixPattern, appWwwUrl, false /* redirect */, true /* allowFurtherRemapping */); 203 | 204 | // Set-up app-harness: scheme to point at the harness. 205 | return UrlRemap.aliasUri('^app-harness:', '^app-harness:///', harnessWwwUrl, false, false) 206 | .then(function() { 207 | return startLocation; 208 | }); 209 | } else { 210 | return ResourcesLoader.delete(appWwwUrl + 'plugins/') 211 | .then(function() { 212 | return ResourcesLoader.delete(appWwwUrl + 'cordova.js'); 213 | }).then(function() { 214 | return ResourcesLoader.delete(appWwwUrl + 'plugins/'); 215 | }).then(function() { 216 | return ResourcesLoader.copy(harnessWwwUrl + 'cordova.js', appWwwUrl + 'cordova.js'); 217 | }).then(function() { 218 | return ResourcesLoader.copy(harnessWwwUrl + 'plugins/', appWwwUrl + 'plugins/'); 219 | }).then(function() { 220 | return realStartLocation; 221 | }); 222 | } 223 | }); 224 | }; 225 | 226 | return Installer; 227 | }]); 228 | })(); 229 | 230 | -------------------------------------------------------------------------------- /www/cdvah/js/ListCtrl.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | (function(){ 20 | 'use strict'; 21 | /* global myApp */ 22 | /* global chrome */ 23 | myApp.controller('ListCtrl', ['$location', '$rootScope', '$scope', '$routeParams', '$q', 'AppsService', 'HarnessServer', 'AppHarnessUI', 'Reporter', 'APP_NAME', function ($location, $rootScope, $scope, $routeParams, $q, AppsService, HarnessServer, AppHarnessUI, Reporter, APP_NAME) { 24 | $scope.app = null; 25 | $scope.ipAddresses = null; 26 | $scope.port = null; 27 | 28 | function initialise() { 29 | $rootScope.appTitle = APP_NAME; 30 | $scope.$on('$destroy', function() { 31 | AppsService.onAppListChange = null; 32 | }); 33 | $scope.port = 2424; 34 | AppsService.onAppListChange = loadAppsList; 35 | 36 | AppHarnessUI.setEventHandler(function(eventName) { 37 | if (eventName == 'showMenu') { // two-finger double tap 38 | return AppHarnessUI.setVisible(false); 39 | } else if (eventName == 'hideMenu') { 40 | return AppHarnessUI.setVisible(true); 41 | } else if (eventName == 'quitApp') { 42 | return AppsService.quitApp(); 43 | } else if (eventName == 'destroyed') { 44 | return loadAppsList(); 45 | } else { 46 | console.warn('Unknown message from AppHarnessUI: ' + eventName); 47 | } 48 | }); 49 | 50 | return loadAppsList() 51 | .then(function() { 52 | return HarnessServer.start(); 53 | }).then(function() { 54 | var getInfoCallback = function() { 55 | HarnessServer.getListenAddress(/* skipCache */ true) 56 | .then(function(value) { 57 | // No IP addresses happens when wifi is not connected. 58 | $scope.ipAddresses = value ? value.split(', ') : ['127.0.0.1']; 59 | }); 60 | }; 61 | 62 | // When getInfo is called, the callback is retained and called every time network info changes. 63 | // The callback updates the IP. 64 | navigator.connection.getInfo(getInfoCallback); 65 | }, function() { 66 | $scope.ipAddresses = []; 67 | }).then(function() { 68 | var deferred = $q.defer(); 69 | 70 | // If this is the app's first run, show the first-launch screen. 71 | var hasRunDefault = { hasRun: 'empty' }; 72 | var getHasRunCallback = function(data) { 73 | var isFirstRun = (data.hasRun === 'empty'); 74 | if (isFirstRun) { 75 | // This is the app's first run, so ask for permission! 76 | $location.path('/permission'); 77 | } 78 | // If this the first run, we don't want to send any events yet, since we want to ask permission first. 79 | deferred.resolve(/* shouldSendEvents */ !isFirstRun); 80 | }; 81 | 82 | // Check local storage for the first run key. 83 | chrome.storage.local.get(hasRunDefault, getHasRunCallback); 84 | 85 | return deferred.promise; 86 | }).then(function(shouldSendEvents) { 87 | if (shouldSendEvents) { 88 | // Track the page view. 89 | Reporter.sendPageView('list'); 90 | 91 | // Send a "CADT has launched" event, if we haven't already. 92 | if (!$rootScope.appLaunchReported) { 93 | Reporter.sendEvent('CADT', 'launched'); 94 | $rootScope.appLaunchReported = true; 95 | } 96 | } 97 | }); 98 | } 99 | 100 | function loadAppsList() { 101 | return AppsService.getAppList() 102 | .then(function(){ 103 | $scope.app = AppsService.getLastAccessedApp(); 104 | $scope.isRunning = !!AppsService.getActiveApp(); 105 | }, function(error){ 106 | console.error(error); 107 | }); 108 | } 109 | 110 | $scope.launchApp = function(app){ 111 | return AppsService.launchApp(app) 112 | .then(null, function(error){ 113 | console.error(error); 114 | }); 115 | }; 116 | 117 | $scope.resumeApp = function(){ 118 | return AppHarnessUI.setVisible(true); 119 | }; 120 | 121 | $scope.stopApp = function(){ 122 | return AppsService.quitApp(); 123 | }; 124 | 125 | $scope.removeApp = function() { 126 | // Uninstall all apps, since we only show the latest one, this most closely matches their intention. 127 | return AppsService.uninstallAllApps(); 128 | }; 129 | 130 | $scope.showDetails = function(index, ev) { 131 | ev.preventDefault(); 132 | $location.path('/details/' + index); 133 | }; 134 | 135 | return initialise(); 136 | }]); 137 | })(); 138 | 139 | 140 | -------------------------------------------------------------------------------- /www/cdvah/js/PermissionCtrl.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | (function(){ 20 | 'use strict'; 21 | /* global analytics */ 22 | /* global chrome */ 23 | /* global myApp */ 24 | myApp.controller('PermissionCtrl', ['$rootScope', '$scope', '$location', 'Reporter', function($rootScope, $scope, $location, Reporter) { 25 | // By default, the checkbox should be checked. 26 | $scope.formData = { reportingPermissionCheckbox: true }; 27 | 28 | $scope.saveReportingPermission = function() { 29 | var getConfigCallback = function(config) { 30 | // Set tracking according to the user's response. 31 | var permitted = $scope.formData.reportingPermissionCheckbox; 32 | config.setTrackingPermitted(permitted); 33 | 34 | // Track the page view. 35 | Reporter.sendPageView('permission'); 36 | }; 37 | 38 | // Get the config object so we can update tracking permission. 39 | var service = analytics.getService($rootScope.appTitle); 40 | service.getConfig().addCallback(getConfigCallback); 41 | 42 | // Note that the app has run, so that we don't show this page again. 43 | chrome.storage.local.set({ hasRun: true }); 44 | $location.path('/'); 45 | }; 46 | }]); 47 | })(); 48 | -------------------------------------------------------------------------------- /www/cdvah/js/PluginMetadata.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | (function() { 20 | 'use strict'; 21 | /* global myApp */ 22 | myApp.factory('PluginMetadata', function() { 23 | var harnessPluginList = cordova.require('cordova/plugin_list'); 24 | var harnessPluginMetadata = harnessPluginList.metadata; 25 | 26 | // Returns -1, (a > b), 0 (a = b), or 1 (a < b). 27 | function semverCompare(a, b) { 28 | var regex = /^(\d+)\.(\d+)\.(\d+)/; 29 | var aComps = a.match(regex); 30 | var bComps = b.match(regex); 31 | 32 | for(var i = 1; i <= 3; i++) { 33 | if (+aComps[i] != +bComps[i]) { 34 | return +aComps[i] < +bComps[i] ? 1 : -1; 35 | } 36 | } 37 | 38 | return 0; 39 | } 40 | 41 | return { 42 | availablePlugins: function() { 43 | var plugins = []; 44 | var pluginIds = Object.keys(harnessPluginMetadata); 45 | pluginIds.sort(); 46 | for (var i=0; i < pluginIds.length; ++i) { 47 | plugins.push({ 48 | id: pluginIds[i], 49 | version: harnessPluginMetadata[pluginIds[i]] 50 | }); 51 | } 52 | return plugins; 53 | }, 54 | extractPluginMetadata: function(pluginListFileContents) { 55 | if (!pluginListFileContents) { 56 | throw new Error('cordova_plugins.js file is empty. Something has gone wrong with "cordova prepare".'); 57 | } 58 | 59 | // Extract the JSON data from inside the JS file. 60 | // It's between two magic comments created by Plugman. 61 | var startIndex = pluginListFileContents.indexOf('TOP OF METADATA') + 15; 62 | var endIndex = pluginListFileContents.indexOf('// BOTTOM OF METADATA'); 63 | var target = pluginListFileContents.substring(startIndex, endIndex); 64 | var metadata = JSON.parse(target); 65 | return metadata; 66 | }, 67 | 68 | // Returns an object with plugin matching data. 69 | process: function(childPlugins) { 70 | var results = { 71 | raw: childPlugins, 72 | matched: [], 73 | missing: [], 74 | newer: [], // Those dependencies which are newer in the child than the harness. 75 | older: [], // And those which are older in the child than the harness. 76 | all: [] 77 | }; 78 | 79 | Object.keys(childPlugins).forEach(function(plugin) { 80 | var version = childPlugins[plugin]; 81 | results.all.push({ id: plugin, version: version }); 82 | if (!harnessPluginMetadata[plugin]) { 83 | results.missing.push({ id: plugin, version: version }); 84 | } else { 85 | switch(semverCompare(harnessPluginMetadata[plugin], version)) { 86 | case -1: // Child older. 87 | results.older.push({ id: plugin, versions: { harness: harnessPluginMetadata[plugin], child: version } }); 88 | break; 89 | case 1: // Child newer. 90 | results.newer.push({ id: plugin, versions: { harness: harnessPluginMetadata[plugin], child: version } }); 91 | break; 92 | case 0: // Match! 93 | results.matched.push({ id: plugin, version: harnessPluginMetadata[plugin] }); 94 | break; 95 | } 96 | } 97 | }); 98 | 99 | return results; 100 | }, 101 | // This creates the contents for the app's cordova_plugins.js file. 102 | // Right now, it contains the harness's plugins with all plugins not listed 103 | // in the target app removed. 104 | // TODO: is to also add in plugin .js that is exists in the app but *not* 105 | // in the harness. This will allow for JS-only plugins to work. 106 | createNewPluginListFile: function(appPluginIds) { 107 | function startsWith(a, b) { 108 | return a.lastIndexOf(b, 0) === 0; 109 | } 110 | 111 | function isPluginIdEnabled(id) { 112 | for (var i = 0; i < appPluginIds.length; ++i) { 113 | if (startsWith(id, appPluginIds[i])) { 114 | return true; 115 | } 116 | } 117 | return false; 118 | } 119 | var newPluginList = harnessPluginList.filter(function(entry) { 120 | return isPluginIdEnabled(entry.id); 121 | }); 122 | var newMetadata = {}; 123 | for (var i = 0; i < appPluginIds.length; ++i) { 124 | var pluginId = appPluginIds[i]; 125 | if (pluginId in harnessPluginMetadata) { 126 | newMetadata[pluginId] = harnessPluginMetadata[pluginId]; 127 | } 128 | } 129 | var ret = 'cordova.define("cordova/plugin_list", function(require, exports, module) {\n' + 130 | 'module.exports = ' + JSON.stringify(newPluginList, null, 4) + ';\n' + 131 | 'module.exports.metadata =\n' + 132 | '// TOP OF METADATA\n' + 133 | JSON.stringify(newMetadata, null, 4) + '\n' + 134 | '// BOTTOM OF METADATA\n' + 135 | '});\n'; 136 | 137 | return ret; 138 | } 139 | }; 140 | }); 141 | })(); 142 | 143 | -------------------------------------------------------------------------------- /www/cdvah/js/Reporter.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | (function() { 20 | 'use strict'; 21 | /* global analytics */ 22 | /* global chrome */ 23 | /* global myApp */ 24 | myApp.factory('Reporter', ['$rootScope', 'APP_VERSION', function($rootScope, APP_VERSION) { 25 | // This tracking ID identifies our app for Google Analytics. 26 | var trackingId = 'UA-52080037-1'; 27 | 28 | // We rewrite chrome.runtime.getManifest(), since CADT doesn't have a manifest. 29 | chrome.runtime.getManifest = function() { 30 | return { version: APP_VERSION }; 31 | }; 32 | 33 | // Chrome Platform Analytics objects. 34 | var service = analytics.getService($rootScope.appTitle); 35 | var tracker = service.getTracker(trackingId); 36 | 37 | return { 38 | sendEvent: function(eventCategory, eventAction) { 39 | tracker.sendEvent(eventCategory, eventAction); 40 | }, 41 | 42 | sendPageView: function(pageName) { 43 | tracker.sendAppView(pageName); 44 | } 45 | }; 46 | }]); 47 | })(); 48 | -------------------------------------------------------------------------------- /www/cdvah/js/UrlRemap.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | (function() { 20 | 'use strict'; 21 | /* global myApp */ 22 | myApp.factory('UrlRemap', ['$q', function($q) { 23 | 24 | // URI aliasing : the ability to launch an app in the harness, query the document.location and get the same location as would have been got if you run the app separately 25 | // Without URI aliasing, document.location in the harness would give something like file:///APP_HARNESS_INSTALLED_APPS_LOCATION/www/index.html 26 | 27 | function aliasUri(sourceUriMatchRegex, sourceUriReplaceRegex, replaceString, redirectToReplacedUrl, allowFurtherRemapping) { 28 | var deferred = $q.defer(); 29 | cordova.plugins.urlremap.addAlias(sourceUriMatchRegex, sourceUriReplaceRegex, replaceString, redirectToReplacedUrl, !!allowFurtherRemapping, function(succeded) { 30 | if (succeded){ 31 | deferred.resolve(); 32 | } else { 33 | deferred.reject(new Error('Unable to set up uri aliasing')); 34 | } 35 | }); 36 | return deferred.promise; 37 | } 38 | 39 | function setResetUrl(url) { 40 | var deferred = $q.defer(); 41 | cordova.plugins.urlremap.setResetUrl(url, deferred.resolve); 42 | return deferred.promise; 43 | } 44 | 45 | function injectJsForUrl(url, js) { 46 | var deferred = $q.defer(); 47 | cordova.plugins.urlremap.injectJsForUrl(url, js, deferred.resolve); 48 | return deferred.promise; 49 | } 50 | 51 | function reset() { 52 | var deferred = $q.defer(); 53 | cordova.plugins.urlremap.clearAllAliases(deferred.resolve); 54 | return deferred.promise; 55 | } 56 | 57 | function escapeRegExp(str) { 58 | return str.replace(/[-\[\]\/{}()*+?.\\^$|]/g, '\\$&'); 59 | } 60 | 61 | return { 62 | aliasUri: aliasUri, 63 | reset: reset, 64 | setResetUrl: setResetUrl, 65 | injectJsForUrl: injectJsForUrl, 66 | escapeRegExp: escapeRegExp 67 | }; 68 | 69 | }]); 70 | })(); 71 | -------------------------------------------------------------------------------- /www/cdvah/js/app.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | var myApp = angular.module('ChromeAppDeveloperTool', ['ngRoute', 'angularMoment']); 21 | 22 | myApp.value('APP_NAME', 'Chrome App Developer Tool'); 23 | myApp.value('APP_VERSION', '0.13.1-dev'); 24 | 25 | myApp.config(['$routeProvider', function($routeProvider){ 26 | $routeProvider.when('/', { 27 | templateUrl: 'views/list.html', 28 | controller: 'ListCtrl' 29 | }); 30 | $routeProvider.when('/details/:index', { 31 | templateUrl: 'views/details.html', 32 | controller: 'DetailsCtrl' 33 | }); 34 | $routeProvider.when('/about', { 35 | templateUrl: 'views/about.html', 36 | controller: 'AboutCtrl' 37 | }); 38 | $routeProvider.when('/permission', { 39 | templateUrl: 'views/permission.html', 40 | controller: 'PermissionCtrl' 41 | }); 42 | }]); 43 | 44 | myApp.run(['$rootScope', 'APP_NAME', 'APP_VERSION', function($rootScope, APP_NAME, APP_VERSION){ 45 | $rootScope.appTitle = APP_NAME; 46 | $rootScope.appVersion = APP_VERSION; 47 | $rootScope.appLaunchReported = false; 48 | document.title = APP_NAME + ' v' + APP_VERSION; 49 | }]); 50 | 51 | document.addEventListener('deviceready', function() { 52 | myApp.value('INSTALL_DIRECTORY', cordova.file.dataDirectory + 'apps/'); 53 | myApp.value('APPS_JSON', cordova.file.dataDirectory + 'apps.json'); 54 | myApp.value('TEMP_DIR', cordova.file.tempDirectory || cordova.file.cacheDirectory); 55 | angular.bootstrap(document, ['ChromeAppDeveloperTool']); 56 | }, false); 57 | -------------------------------------------------------------------------------- /www/cdvah/js/libs/angular-moment.js: -------------------------------------------------------------------------------- 1 | /* angular-moment.js / v0.7.1 / (c) 2013, 2014 Uri Shaked / MIT Licence */ 2 | 3 | /* global define */ 4 | 5 | (function () { 6 | 'use strict'; 7 | 8 | function angularMoment(angular, moment) { 9 | 10 | /** 11 | * @ngdoc overview 12 | * @name angularMoment 13 | * 14 | * @description 15 | * angularMoment module provides moment.js functionality for angular.js apps. 16 | */ 17 | return angular.module('angularMoment', []) 18 | 19 | /** 20 | * @ngdoc object 21 | * @name angularMoment.config:angularMomentConfig 22 | * 23 | * @description 24 | * Common configuration of the angularMoment module 25 | */ 26 | .constant('angularMomentConfig', { 27 | /** 28 | * @ngdoc property 29 | * @name angularMoment.config.angularMomentConfig#preprocess 30 | * @propertyOf angularMoment.config:angularMomentConfig 31 | * @returns {string} The default preprocessor to apply 32 | * 33 | * @description 34 | * Defines a default preprocessor to apply (e.g. 'unix', 'etc', ...). The default value is null, 35 | * i.e. no preprocessor will be applied. 36 | */ 37 | preprocess: null, // e.g. 'unix', 'utc', ... 38 | 39 | /** 40 | * @ngdoc property 41 | * @name angularMoment.config.angularMomentConfig#timezone 42 | * @propertyOf angularMoment.config:angularMomentConfig 43 | * @returns {string} The default timezone 44 | * 45 | * @description 46 | * The default timezone (e.g. 'Europe/London'). Empty string by default (does not apply 47 | * any timezone shift). 48 | */ 49 | timezone: '' 50 | }) 51 | 52 | /** 53 | * @ngdoc object 54 | * @name angularMoment.object:moment 55 | * 56 | * @description 57 | * moment global (as provided by the moment.js library) 58 | */ 59 | .constant('moment', moment) 60 | 61 | /** 62 | * @ngdoc object 63 | * @name angularMoment.config:amTimeAgoConfig 64 | * @module angularMoment 65 | * 66 | * @description 67 | * configuration specific to the amTimeAgo directive 68 | */ 69 | .constant('amTimeAgoConfig', { 70 | /** 71 | * @ngdoc property 72 | * @name angularMoment.config.amTimeAgoConfig#withoutSuffix 73 | * @propertyOf angularMoment.config:amTimeAgoConfig 74 | * @returns {boolean} Whether to include a suffix in am-time-ago directive 75 | * 76 | * @description 77 | * Defaults to false. 78 | */ 79 | withoutSuffix: false 80 | }) 81 | 82 | /** 83 | * @ngdoc directive 84 | * @name angularMoment.directive:amTimeAgo 85 | * @module angularMoment 86 | * 87 | * @restrict A 88 | */ 89 | .directive('amTimeAgo', ['$window', 'moment', 'amMoment', 'amTimeAgoConfig', 'angularMomentConfig', function ($window, moment, amMoment, amTimeAgoConfig, angularMomentConfig) { 90 | 91 | return function (scope, element, attr) { 92 | var activeTimeout = null; 93 | var currentValue; 94 | var currentFormat; 95 | var withoutSuffix = amTimeAgoConfig.withoutSuffix; 96 | var preprocess = angularMomentConfig.preprocess; 97 | 98 | function cancelTimer() { 99 | if (activeTimeout) { 100 | $window.clearTimeout(activeTimeout); 101 | activeTimeout = null; 102 | } 103 | } 104 | 105 | function updateTime(momentInstance) { 106 | element.text(momentInstance.fromNow(withoutSuffix)); 107 | var howOld = moment().diff(momentInstance, 'minute'); 108 | var secondsUntilUpdate = 3600; 109 | if (howOld < 1) { 110 | secondsUntilUpdate = 1; 111 | } else if (howOld < 60) { 112 | secondsUntilUpdate = 30; 113 | } else if (howOld < 180) { 114 | secondsUntilUpdate = 300; 115 | } 116 | 117 | activeTimeout = $window.setTimeout(function () { 118 | updateTime(momentInstance); 119 | }, secondsUntilUpdate * 1000); 120 | } 121 | 122 | function updateMoment() { 123 | cancelTimer(); 124 | if (currentValue) { 125 | updateTime(amMoment.preprocessDate(currentValue, preprocess, currentFormat)); 126 | } 127 | } 128 | 129 | scope.$watch(attr.amTimeAgo, function (value) { 130 | if ((typeof value === 'undefined') || (value === null) || (value === '')) { 131 | cancelTimer(); 132 | if (currentValue) { 133 | element.text(''); 134 | currentValue = null; 135 | } 136 | return; 137 | } 138 | 139 | currentValue = value; 140 | updateMoment(); 141 | }); 142 | 143 | if (angular.isDefined(attr.amWithoutSuffix)) { 144 | scope.$watch(attr.amWithoutSuffix, function (value) { 145 | if (typeof value === 'boolean') { 146 | withoutSuffix = value; 147 | updateMoment(); 148 | } else { 149 | withoutSuffix = amTimeAgoConfig.withoutSuffix; 150 | } 151 | }); 152 | } 153 | 154 | attr.$observe('amFormat', function (format) { 155 | currentFormat = format; 156 | updateMoment(); 157 | }); 158 | 159 | attr.$observe('amPreprocess', function (newValue) { 160 | preprocess = newValue; 161 | updateMoment(); 162 | }); 163 | 164 | scope.$on('$destroy', function () { 165 | cancelTimer(); 166 | }); 167 | 168 | scope.$on('amMoment:languageChange', function () { 169 | updateMoment(); 170 | }); 171 | }; 172 | }]) 173 | 174 | /** 175 | * @ngdoc service 176 | * @name angularMoment.service.amMoment 177 | * @module angularMoment 178 | */ 179 | .service('amMoment', ['moment', '$rootScope', '$log', 'angularMomentConfig', function (moment, $rootScope, $log, angularMomentConfig) { 180 | /** 181 | * @ngdoc property 182 | * @name angularMoment:amMoment#preprocessors 183 | * @module angularMoment 184 | * 185 | * @description 186 | * Defines the preprocessors for the preprocessDate method. By default, the following preprocessors 187 | * are defined: utc, unix. 188 | */ 189 | this.preprocessors = { 190 | utc: moment.utc, 191 | unix: moment.unix 192 | }; 193 | 194 | /** 195 | * @ngdoc function 196 | * @name angularMoment.service.amMoment#changeLanguage 197 | * @methodOf angularMoment.service.amMoment 198 | * 199 | * @description 200 | * Changes the language for moment.js and updates all the am-time-ago directive instances 201 | * with the new language. 202 | * 203 | * @param {string} lang 2-letter language code (e.g. en, es, ru, etc.) 204 | */ 205 | this.changeLanguage = function (lang) { 206 | var result = moment.lang(lang); 207 | if (angular.isDefined(lang)) { 208 | $rootScope.$broadcast('amMoment:languageChange'); 209 | } 210 | return result; 211 | }; 212 | 213 | /** 214 | * @ngdoc function 215 | * @name angularMoment.service.amMoment#preprocessDate 216 | * @methodOf angularMoment.service.amMoment 217 | * 218 | * @description 219 | * Preprocess a given value and convert it into a Moment instance appropriate for use in the 220 | * am-time-ago directive and the filters. 221 | * 222 | * @param {*} value The value to be preprocessed 223 | * @param {string} preprocess The name of the preprocessor the apply (e.g. utc, unix) 224 | * @param {string=} format Specifies how to parse the value (see {@link http://momentjs.com/docs/#/parsing/string-format/}) 225 | * @return {Moment} A value that can be parsed by the moment library 226 | */ 227 | this.preprocessDate = function (value, preprocess, format) { 228 | if (angular.isUndefined(preprocess)) { 229 | preprocess = angularMomentConfig.preprocess; 230 | } 231 | if (this.preprocessors[preprocess]) { 232 | return this.preprocessors[preprocess](value, format); 233 | } 234 | if (preprocess) { 235 | $log.warn('angular-moment: Ignoring unsupported value for preprocess: ' + preprocess); 236 | } 237 | if (!isNaN(parseFloat(value)) && isFinite(value)) { 238 | // Milliseconds since the epoch 239 | return moment(parseInt(value, 10)); 240 | } 241 | // else just returns the value as-is. 242 | return moment(value, format); 243 | }; 244 | 245 | /** 246 | * @ngdoc function 247 | * @name angularMoment.service.amMoment#applyTimezone 248 | * @methodOf angularMoment.service.amMoment 249 | * 250 | * @description 251 | * Apply a timezone onto a given moment object - if moment-timezone.js is included 252 | * Otherwise, it'll not apply any timezone shift. 253 | * 254 | * @param {Moment} aMoment a moment() instance to apply the timezone shift to 255 | * @returns {Moment} The given moment with the timezone shift applied 256 | */ 257 | this.applyTimezone = function (aMoment) { 258 | var timezone = angularMomentConfig.timezone; 259 | if (aMoment && timezone) { 260 | if (aMoment.tz) { 261 | aMoment = aMoment.tz(timezone); 262 | } else { 263 | $log.warn('angular-moment: timezone specified but moment.tz() is undefined. Did you forget to include moment-timezone.js?'); 264 | } 265 | } 266 | return aMoment; 267 | }; 268 | }]) 269 | 270 | /** 271 | * @ngdoc filter 272 | * @name angularMoment.filter:amCalendar 273 | * @module angularMoment 274 | */ 275 | .filter('amCalendar', ['moment', 'amMoment', function (moment, amMoment) { 276 | return function (value, preprocess) { 277 | if (typeof value === 'undefined' || value === null) { 278 | return ''; 279 | } 280 | 281 | value = amMoment.preprocessDate(value, preprocess); 282 | var date = moment(value); 283 | if (!date.isValid()) { 284 | return ''; 285 | } 286 | 287 | return amMoment.applyTimezone(date).calendar(); 288 | }; 289 | }]) 290 | 291 | /** 292 | * @ngdoc filter 293 | * @name angularMoment.filter:amDateFormat 294 | * @module angularMoment 295 | * @function 296 | */ 297 | .filter('amDateFormat', ['moment', 'amMoment', function (moment, amMoment) { 298 | return function (value, format, preprocess) { 299 | if (typeof value === 'undefined' || value === null) { 300 | return ''; 301 | } 302 | 303 | value = amMoment.preprocessDate(value, preprocess); 304 | var date = moment(value); 305 | if (!date.isValid()) { 306 | return ''; 307 | } 308 | 309 | return amMoment.applyTimezone(date).format(format); 310 | }; 311 | }]) 312 | 313 | /** 314 | * @ngdoc filter 315 | * @name angularMoment.filter:amDurationFormat 316 | * @module angularMoment 317 | * @function 318 | */ 319 | .filter('amDurationFormat', ['moment', function (moment) { 320 | return function (value, format, suffix) { 321 | if (typeof value === 'undefined' || value === null) { 322 | return ''; 323 | } 324 | 325 | return moment.duration(value, format).humanize(suffix); 326 | }; 327 | }]); 328 | } 329 | 330 | if (typeof define === 'function' && define.amd) { 331 | define('angular-moment', ['angular', 'moment'], angularMoment); 332 | } else { 333 | angularMoment(angular, window.moment); 334 | } 335 | })(); 336 | -------------------------------------------------------------------------------- /www/cdvah/js/libs/slice.js: -------------------------------------------------------------------------------- 1 | // https://github.com/ttaubert/node-arraybuffer-slice 2 | // (c) 2014 Tim Taubert 3 | // arraybuffer-slice may be freely distributed under the MIT license. 4 | 5 | (function (undefined) { 6 | "use strict"; 7 | 8 | function clamp(val, length) { 9 | val = (val|0) || 0; 10 | 11 | if (val < 0) { 12 | return Math.max(val + length, 0); 13 | } 14 | 15 | return Math.min(val, length); 16 | } 17 | 18 | if (!ArrayBuffer.prototype.slice) { 19 | ArrayBuffer.prototype.slice = function (from, to) { 20 | var length = this.byteLength; 21 | var begin = clamp(from, length); 22 | var end = length; 23 | 24 | if (to !== undefined) { 25 | end = clamp(to, length); 26 | } 27 | 28 | if (begin > end) { 29 | return new ArrayBuffer(0); 30 | } 31 | 32 | var num = end - begin; 33 | var target = new ArrayBuffer(num); 34 | var targetArray = new Uint8Array(target); 35 | 36 | var sourceArray = new Uint8Array(this, begin, num); 37 | targetArray.set(sourceArray); 38 | 39 | return target; 40 | }; 41 | } 42 | })(); 43 | -------------------------------------------------------------------------------- /www/cdvah/views/about.html: -------------------------------------------------------------------------------- 1 | 19 | 20 |
{{appTitle}} v{{appVersion}}
21 |
22 |
23 |

24 | The Chrome App Developer Tool aids in the development of Chrome Apps for Mobile. It can be used to run Chrome Apps for Mobile with the Chrome Dev Editor, or with the cca tool, for faster development and easier debugging. 25 |

26 |

27 | For more information, see the source and documentation here. 28 |

29 |
30 |
31 |

Licences

32 |

{{appTitle}} is open source software, released under the Apache License, Version 2.0

33 |

This product contains other open source software. See the License files in the source tree for full details.

34 |

This version of {{appTitle}} uses the Crosswalk project, which is released under a BSD license, and uses Chromium, also released under a BSD license.

35 |
36 | 37 |
38 |

Available Plugins

39 |

This version of Chrome App Developer Tool has support for the following plugins:

40 |
    41 |
  • 42 |
    {{ plugin.id }}: {{ plugin.version }}
    43 |
  • 44 |
45 |
46 | 47 |
48 |

Usage Statistics and Crash Reports

49 | 50 |
Help make {{appTitle}} better by automatically sending usage statistics and crash reports to Google. This information will be used in accordance with the Google Privacy Policy. 51 |
52 |
53 |
54 |
55 | 56 |
57 | -------------------------------------------------------------------------------- /www/cdvah/views/details.html: -------------------------------------------------------------------------------- 1 | 19 |
{{appTitle}} v{{appVersion}}
20 |

{{ app.appId }}

21 |
22 |

Plugins in Use: {{ app.plugins.matched.length + app.plugins.older.length + app.plugins.newer.length }}

23 |
    24 |
  • 25 | {{ plugin.id }}: {{ plugin.version }} 26 |
  • 27 |
  • 28 | {{ plugin.id }}: {{ plugin.versions.harness}} 29 | (wants {{ plugin.versions.child }}) 30 |
  • 31 |
  • 32 | {{ plugin.id }}: {{ plugin.versions.harness}} 33 | (wants {{ plugin.versions.child }}) 34 |
  • 35 |
  • 36 | {{ plugin.id }} 37 | (missing) 38 |
  • 39 |
40 |
41 | Missing plugins require that you build {{appTitle}} from source with these extra plugins added. See the README for details. 42 |
43 |
44 | Plugin problems can sometimes be resolved by updating {{appTitle}}, or by it from source. See the README for details. 45 |
46 |
47 |
48 | 49 |
50 | -------------------------------------------------------------------------------- /www/cdvah/views/list.html: -------------------------------------------------------------------------------- 1 | 19 |
{{appTitle}} v{{appVersion}}
20 |
21 | {{ipAddress}}:{{port}} 22 | Could not bind to port {{port}} 23 |
24 |
25 |
26 | Use the cca or Chrome Dev Editor to launch an app. 27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | 35 |
No Icon
36 |
37 |
38 |
{{app.getAppName() || app.getConfigXmlId() || app.appId}} {{app.getVersion()}}
39 |
Last updated:
40 |
Update in progress: {{app.updatingStatus}}%
41 |
Plugins: 42 | {{app.plugins.all.length}} total 43 | {{ app.plugins.missing.length }} missing, 44 | {{ app.plugins.older.length + app.plugins.newer.length }} mismatched 45 | (details) 46 |
47 |
48 |
49 |
50 | 51 | 52 |
53 |
54 |
55 | 58 | 59 | -------------------------------------------------------------------------------- /www/cdvah/views/permission.html: -------------------------------------------------------------------------------- 1 | 19 | 20 |
{{appTitle}} v{{appVersion}}
21 |
22 |
23 |

Welcome!

24 |

The Chrome App Developer Tool aids in the development of Chrome Apps for Mobile. It can be used to run Chrome Apps for Mobile with the Chrome Dev Editor, or with the cca tool, for faster development and easier debugging.

25 |
26 |
27 |

Usage Statistics and Crash Reports

28 | 29 |
Help make {{appTitle}} better by automatically sending usage statistics and crash reports to Google. This information will be used in accordance with the Google Privacy Policy. 30 |
31 |
32 |
33 |
34 | 35 |
36 | --------------------------------------------------------------------------------