Merge pull request #5352 from CharlieHess/hands-on-hand-off

OS X Handoff Feature
This commit is contained in:
Cheng Zhao 2016-05-05 12:06:56 +09:00
commit 3f2a25d07c
15 changed files with 222 additions and 26 deletions

View file

@ -20,6 +20,7 @@
#include "atom/common/native_mate_converters/image_converter.h"
#include "atom/common/native_mate_converters/net_converter.h"
#include "atom/common/native_mate_converters/value_converter.h"
#include "atom/common/native_mate_converters/string_map_converter.h"
#include "atom/common/node_includes.h"
#include "atom/common/options_switches.h"
#include "base/command_line.h"
@ -249,6 +250,12 @@ void App::OnFinishLaunching() {
Emit("ready");
}
void App::OnContinueUserActivity(bool* prevent_default,
const std::string& type,
const std::map<std::string, std::string>& user_info) {
*prevent_default = Emit("continue-activity", type, user_info);
}
void App::OnLogin(LoginHandler* login_handler) {
v8::Locker locker(isolate());
v8::HandleScope handle_scope(isolate());
@ -460,6 +467,10 @@ void App::BuildPrototype(
#if defined(OS_MACOSX)
.SetMethod("hide", base::Bind(&Browser::Hide, browser))
.SetMethod("show", base::Bind(&Browser::Show, browser))
.SetMethod("setUserActivity",
base::Bind(&Browser::SetUserActivity, browser))
.SetMethod("getCurrentActivityType",
base::Bind(&Browser::GetCurrentActivityType, browser))
#endif
#if defined(OS_WIN)
.SetMethod("setUserTasks",

View file

@ -6,6 +6,7 @@
#define ATOM_BROWSER_API_ATOM_API_APP_H_
#include <string>
#include <map>
#include "atom/browser/api/event_emitter.h"
#include "atom/browser/atom_browser_client.h"
@ -71,6 +72,9 @@ class App : public AtomBrowserClient::Delegate,
void OnWillFinishLaunching() override;
void OnFinishLaunching() override;
void OnLogin(LoginHandler* login_handler) override;
void OnContinueUserActivity(bool* prevent_default,
const std::string& type,
const std::map<std::string, std::string>& user_info) override;
// content::ContentBrowserClient:
void AllowCertificateError(

View file

@ -137,6 +137,19 @@ void Browser::Activate(bool has_visible_windows) {
OnActivate(has_visible_windows));
}
#if defined(OS_MACOSX)
bool Browser::ContinueUserActivity(const std::string& type,
const std::map<std::string,
std::string>& user_info) {
bool prevent_default = false;
FOR_EACH_OBSERVER(BrowserObserver,
observers_,
OnContinueUserActivity(&prevent_default, type, user_info));
return prevent_default;
}
#endif
void Browser::WillFinishLaunching() {
FOR_EACH_OBSERVER(BrowserObserver, observers_, OnWillFinishLaunching());
}

View file

@ -7,6 +7,7 @@
#include <string>
#include <vector>
#include <map>
#include "base/macros.h"
#include "base/compiler_specific.h"
@ -92,6 +93,17 @@ class Browser : public WindowListObserver {
// Show the application.
void Show();
// Creates an activity and sets it as the one currently in use.
void SetUserActivity(const std::string& type,
const std::map<std::string, std::string>& user_info);
// Returns the type name of the current user activity.
std::string GetCurrentActivityType();
// Resumes an activity via hand-off.
bool ContinueUserActivity(const std::string& type,
const std::map<std::string, std::string>& user_info);
// Bounce the dock icon.
enum BounceType {
BOUNCE_CRITICAL = 0,

View file

@ -69,7 +69,7 @@ bool Browser::IsDefaultProtocolClient(const std::string& protocol) {
return false;
NSString* protocol_ns = [NSString stringWithUTF8String:protocol.c_str()];
CFStringRef bundle =
LSCopyDefaultHandlerForURLScheme(base::mac::NSToCFCast(protocol_ns));
NSString* bundleId = static_cast<NSString*>(
@ -77,7 +77,7 @@ bool Browser::IsDefaultProtocolClient(const std::string& protocol) {
if (!bundleId)
return false;
// Ensure the comparison is case-insensitive
// Ensure the comparison is case-insensitive
// as LS does not persist the case of the bundle id.
NSComparisonResult result =
[bundleId caseInsensitiveCompare:identifier];
@ -87,6 +87,30 @@ bool Browser::IsDefaultProtocolClient(const std::string& protocol) {
void Browser::SetAppUserModelID(const base::string16& name) {
}
void Browser::SetUserActivity(const std::string& type, const std::map<std::string, std::string>& user_info) {
NSString* type_ns = [NSString stringWithUTF8String:type.c_str()];
NSUserActivity* user_activity = [[NSUserActivity alloc] initWithActivityType:type_ns];
base::scoped_nsobject<NSMutableDictionary> user_info_args([[NSMutableDictionary alloc] init]);
for (auto const &pair : user_info) {
NSString* value_ns = [NSString stringWithUTF8String:pair.second.c_str()];
NSString* key_ns = [NSString stringWithUTF8String:pair.first.c_str()];
[user_info_args.get() setObject:value_ns
forKey:key_ns];
}
user_activity.userInfo = user_info_args.get();
[user_activity becomeCurrent];
[[AtomApplication sharedApplication] setCurrentActivity:user_activity];
}
std::string Browser::GetCurrentActivityType() {
NSUserActivity* user_activity = [[AtomApplication sharedApplication] getCurrentActivity];
return base::SysNSStringToUTF8(user_activity.activityType);
}
std::string Browser::GetExecutableFileVersion() const {
return brightray::GetApplicationVersion();
}

View file

@ -6,6 +6,7 @@
#define ATOM_BROWSER_BROWSER_OBSERVER_H_
#include <string>
#include <map>
namespace atom {
@ -45,6 +46,11 @@ class BrowserObserver {
// The browser requests HTTP login.
virtual void OnLogin(LoginHandler* login_handler) {}
// The browser wants to resume a user activity via handoff. (OS X only)
virtual void OnContinueUserActivity(bool* prevent_default,
const std::string& type,
const std::map<std::string, std::string>& user_info) {}
protected:
virtual ~BrowserObserver() {}
};

View file

@ -3,11 +3,13 @@
// found in the LICENSE file.
#import "base/mac/scoped_sending_event.h"
#import "base/mac/scoped_nsobject.h"
@interface AtomApplication : NSApplication<CrAppProtocol,
CrAppControlProtocol> {
@private
BOOL handlingSendEvent_;
base::scoped_nsobject<NSUserActivity> currentActivity_;
}
+ (AtomApplication*)sharedApplication;
@ -18,4 +20,8 @@
// CrAppControlProtocol:
- (void)setHandlingSendEvent:(BOOL)handlingSendEvent;
- (NSUserActivity*)getCurrentActivity;
- (void)setCurrentActivity:(NSUserActivity*)userActivity;
@end

View file

@ -28,6 +28,14 @@
handlingSendEvent_ = handlingSendEvent;
}
- (void)setCurrentActivity:(NSUserActivity*)userActivity {
currentActivity_ = base::scoped_nsobject<NSUserActivity>(userActivity);
}
- (NSUserActivity*)getCurrentActivity {
return currentActivity_.get();
}
- (void)awakeFromNib {
[[NSAppleEventManager sharedAppleEventManager]
setEventHandler:self

View file

@ -59,4 +59,23 @@
return flag;
}
- (BOOL)application:(NSApplication *)sender
continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:(void (^)(NSArray *restorableObjects))restorationHandler {
std::string activity_type(base::SysNSStringToUTF8(userActivity.activityType));
std::map<std::string, std::string> user_info;
base::scoped_nsobject<NSArray> keys([userActivity.userInfo allKeys]);
for (NSString* key in keys.get()) {
NSString* value = [userActivity.userInfo objectForKey:key];
std::string key_str(base::SysNSStringToUTF8(key));
std::string value_str(base::SysNSStringToUTF8(value));
user_info[key_str] = value_str;
}
atom::Browser* browser = atom::Browser::Get();
return browser->ContinueUserActivity(activity_type, user_info) ? YES : NO;
}
@end

View file

@ -6,6 +6,7 @@
#include <string>
#include "atom/common/crash_reporter/crash_reporter.h"
#include "atom/common/native_mate_converters/string_map_converter.h"
#include "base/bind.h"
#include "native_mate/dictionary.h"
@ -15,24 +16,6 @@ using crash_reporter::CrashReporter;
namespace mate {
template<>
struct Converter<std::map<std::string, std::string> > {
static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
std::map<std::string, std::string>* out) {
if (!val->IsObject())
return false;
v8::Local<v8::Object> dict = val->ToObject();
v8::Local<v8::Array> keys = dict->GetOwnPropertyNames();
for (uint32_t i = 0; i < keys->Length(); ++i) {
v8::Local<v8::Value> key = keys->Get(i);
(*out)[V8ToString(key)] = V8ToString(dict->Get(key));
}
return true;
}
};
template<>
struct Converter<CrashReporter::UploadReportResult> {
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,

View file

@ -0,0 +1,36 @@
// Copyright (c) 2014 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/common/native_mate_converters/string_map_converter.h"
namespace mate {
bool Converter<std::map<std::string, std::string>>::FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
std::map<std::string, std::string>* out) {
if (!val->IsObject())
return false;
v8::Local<v8::Object> dict = val->ToObject();
v8::Local<v8::Array> keys = dict->GetOwnPropertyNames();
for (uint32_t i = 0; i < keys->Length(); ++i) {
v8::Local<v8::Value> key = keys->Get(i);
(*out)[V8ToString(key)] = V8ToString(dict->Get(key));
}
return true;
}
v8::Local<v8::Value> Converter<std::map<std::string, std::string>>::ToV8(
v8::Isolate* isolate,
const std::map<std::string, std::string>& in) {
mate::Dictionary dict(isolate, v8::Object::New(isolate));
for (auto const &pair : in) {
dict.Set(pair.first, pair.second);
}
return dict.GetHandle();
}
} // namespace mate

View file

@ -0,0 +1,28 @@
// Copyright (c) 2014 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_COMMON_NATIVE_MATE_CONVERTERS_STRING_MAP_CONVERTER_H_
#define ATOM_COMMON_NATIVE_MATE_CONVERTERS_STRING_MAP_CONVERTER_H_
#include <map>
#include <string>
#include "native_mate/converter.h"
#include "native_mate/dictionary.h"
namespace mate {
template<>
struct Converter<std::map<std::string, std::string>> {
static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
std::map<std::string, std::string>* out);
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
const std::map<std::string, std::string>& in);
};
} // namespace mate
#endif // ATOM_COMMON_NATIVE_MATE_CONVERTERS_STRING_MAP_CONVERTER_H_

View file

@ -108,8 +108,26 @@ Returns:
* `event` Event
* `hasVisibleWindows` Boolean
Emitted when the application is activated, which usually happens when clicks on
the applications's dock icon.
Emitted when the application is activated, which usually happens when the user clicks on
the application's dock icon.
### Event: 'continue-activity' _OS X_
Returns:
* `event` Event
* `type` String - A string identifying the event. Maps to [`NSUserActivity.activityType`](https://developer.apple.com/library/ios/documentation/Foundation/Reference/NSUserActivity_Class/index.html#//apple_ref/occ/instp/NSUserActivity/activityType).
* `userInfo` Object - Contains app-specific state stored by the activity on
another device. Currently only string data is supported.
Emitted during [handoff](https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/Handoff/HandoffFundamentals/HandoffFundamentals.html) when an activity from a different device wants to be
resumed. You should call `event.preventDefault()` if you want to handle this
event.
A user activity can be continued only in an app that has the same developer
Team ID as the activity's source app and that supports the activity's type.
Supported activity types are specified in the app's Info.plist under the
`NSUserActivityTypes` key.
### Event: 'browser-window-blur'
@ -386,12 +404,12 @@ default protocol handler.
### `app.isDefaultProtocolClient(protocol)` _OS X_ _Windows_
* `protocol` String - The name of your protocol, without `://`.
* `protocol` String - The name of your protocol, without `://`.
This method checks if the current executable is the default handler for a protocol
(aka URI scheme). If so, it will return true. Otherwise, it will return false.
(aka URI scheme). If so, it will return true. Otherwise, it will return false.
**Note:** On OS X, you can use this method to check if the app has been registered as the default protocol handler for a protocol. You can also verify this by checking `~/Library/Preferences/com.apple.LaunchServices.plist` on the OS X machine.
**Note:** On OS X, you can use this method to check if the app has been registered as the default protocol handler for a protocol. You can also verify this by checking `~/Library/Preferences/com.apple.LaunchServices.plist` on the OS X machine.
Please refer to [Apple's documentation][LSCopyDefaultHandlerForURLScheme] for details.
The API uses the Windows Registry and LSCopyDefaultHandlerForURLScheme internally.
@ -482,6 +500,20 @@ app.on('ready', function() {
});
```
### `app.setUserActivity(type, userInfo)` _OS X_
* `type` String - Uniquely identifies the activity. It's recommended to use a
reverse-DNS string.
* `userInfo` Object - App-specific state to store for use by another device.
Currently only string data is supported.
Creates an `NSUserActivity` and sets it as the current activity. The activity
is eligible for [handoff](https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/Handoff/HandoffFundamentals/HandoffFundamentals.html) to another device afterward.
### `app.getCurrentActivityType()` _OS X_
Returns the type of the currently running activity.
### `app.setAppUserModelId(id)` _Windows_
* `id` String
@ -568,5 +600,5 @@ Sets the `image` associated with this dock icon.
[tasks]:http://msdn.microsoft.com/en-us/library/windows/desktop/dd378460(v=vs.85).aspx#tasks
[app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx
[CFBundleURLTypes]: https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/TP40009249-102207-TPXREF115
[LSCopyDefaultHandlerForURLScheme]:
[LSCopyDefaultHandlerForURLScheme]:
https://developer.apple.com/library/mac/documentation/Carbon/Reference/LaunchServicesReference/#//apple_ref/c/func/LSCopyDefaultHandlerForURLScheme

View file

@ -361,6 +361,8 @@
'atom/common/native_mate_converters/image_converter.h',
'atom/common/native_mate_converters/net_converter.cc',
'atom/common/native_mate_converters/net_converter.h',
'atom/common/native_mate_converters/string_map_converter.cc',
'atom/common/native_mate_converters/string_map_converter.h',
'atom/common/native_mate_converters/string16_converter.h',
'atom/common/native_mate_converters/ui_base_types_converter.h',
'atom/common/native_mate_converters/v8_value_converter.cc',

View file

@ -7,6 +7,7 @@ const remote = require('electron').remote
const app = remote.require('electron').app
const BrowserWindow = remote.require('electron').BrowserWindow
const isCI = remote.getGlobal('isCi')
describe('electron module', function () {
it('allows old style require by default', function () {
@ -89,6 +90,17 @@ describe('app module', function () {
})
})
describe('app.setUserActivity(type, userInfo)', function () {
if (isCI && process.platform !== 'darwin') {
return
}
it('sets the current activity', function () {
app.setUserActivity('com.electron.testActivity', {testData: '123'});
assert.equal(app.getCurrentActivityType(), 'com.electron.testActivity');
})
})
describe('app.importCertificate', function () {
if (process.platform !== 'linux')
return