people_result.cc revision 8bcbed890bc3ce4d7a057a8f32cab53fa534672e
158537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)// Copyright 2013 The Chromium Authors. All rights reserved. 258537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 358537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)// found in the LICENSE file. 458537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) 558537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)#include "chrome/browser/ui/app_list/search/people/people_result.h" 658537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) 758537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)#include <vector> 858537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) 958537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)#include "base/bind.h" 1058537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)#include "base/memory/ref_counted.h" 1158537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)#include "base/strings/utf_string_conversions.h" 124e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include "chrome/browser/extensions/event_router.h" 134e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include "chrome/browser/extensions/extension_system.h" 1458537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)#include "chrome/browser/profiles/profile.h" 154e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include "chrome/browser/signin/profile_oauth2_token_service.h" 164e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include "chrome/browser/signin/profile_oauth2_token_service_factory.h" 1758537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)#include "chrome/browser/ui/app_list/search/common/url_icon_source.h" 184e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include "chrome/browser/ui/app_list/search/people/person.h" 1968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)#include "chrome/browser/ui/browser_navigator.h" 204e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include "chrome/common/extensions/api/hangouts_private.h" 2168043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)#include "grit/generated_resources.h" 2258537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)#include "grit/theme_resources.h" 2358537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)#include "ui/base/l10n/l10n_util.h" 2468043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)#include "ui/base/resource/resource_bundle.h" 2558537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) 264e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)namespace OnHangoutRequested = 274e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) extensions::api::hangouts_private::OnHangoutRequested; 284e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) 294e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)using extensions::api::hangouts_private::User; 304e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)using extensions::api::hangouts_private::HangoutRequest; 314e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) 3258537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)namespace { 3358537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) 3458537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)const int kIconSize = 32; 3558537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)const char kImageSizePath[] = "s32-p/"; 3668043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)const char kEmailUrlPrefix[] = "mailto:"; 3758537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) 388bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)const char* const kHangoutsExtensionIds[] = { 398bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) "nckgahadagoaajjgafhacjanaoiihapd", 408bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) "ljclpkphhpbpinifbeabbhlfddcpfdde", 418bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) "ppleadejekpmccmnpjdimmlfljlkdfej", 428bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) "eggnbpckecmjlblplehfpjjdhhidfdoj" 438bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)}; 444e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) 4558537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)// Add a query parameter to specify the size to fetch the image in. The 4658537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)// original profile image can be of an arbitrary size, we ask the server to 4758537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)// crop it to a square 32x32 using its smart cropping algorithm. 4858537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)GURL GetImageUrl(const GURL& url) { 4958537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) std::string image_filename = url.ExtractFileName(); 5058537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) if (image_filename.empty()) 5158537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) return url; 5258537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) 5358537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) return url.Resolve(kImageSizePath + image_filename); 5458537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)} 5558537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) 5658537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)} // namespace 5758537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) 5858537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)namespace app_list { 5958537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) 604e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)PeopleResult::PeopleResult(Profile* profile, scoped_ptr<Person> person) 614e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) : profile_(profile), person_(person.Pass()), weak_factory_(this) { 624e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) set_id(person_->id); 634e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) set_title(UTF8ToUTF16(person_->display_name)); 644e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) set_relevance(person_->interaction_rank); 654e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) set_details(UTF8ToUTF16(person_->email)); 6668043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles) 678bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) RefreshHangoutsExtensionId(); 6868043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles) SetDefaultActions(); 6958537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) 7058537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) image_ = gfx::ImageSkia( 7158537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) new UrlIconSource(base::Bind(&PeopleResult::OnIconLoaded, 7258537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) weak_factory_.GetWeakPtr()), 7358537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) profile_->GetRequestContext(), 744e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) GetImageUrl(person_->image_url), 7558537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) kIconSize, 7658537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) IDR_PROFILE_PICTURE_LOADING), 7758537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) gfx::Size(kIconSize, kIconSize)); 7858537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) SetIcon(image_); 7958537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)} 8058537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) 8158537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)PeopleResult::~PeopleResult() { 8258537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)} 8358537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) 8458537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)void PeopleResult::Open(int event_flags) { 854e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) // Action 0 will always be our default action. 8668043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles) InvokeAction(0, event_flags); 8758537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)} 8858537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) 8958537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)void PeopleResult::InvokeAction(int action_index, int event_flags) { 908bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) if (hangouts_extension_id_.empty()) { 914e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) // If the hangouts app is not available, the only option we are showing 924e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) // to the user is 'Send Email'. 934e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) SendEmail(); 944e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) } else { 954e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) switch (action_index) { 964e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) case 0: 974e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) OpenChat(); 984e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) break; 994e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) case 1: 1004e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) SendEmail(); 1014e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) break; 1024e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) default: 1034e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) LOG(ERROR) << "Invalid people search action: " << action_index; 1044e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) } 1054e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) } 10658537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)} 10758537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) 10858537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)scoped_ptr<ChromeSearchResult> PeopleResult::Duplicate() { 1094e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) return scoped_ptr<ChromeSearchResult>( 1104e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) new PeopleResult(profile_, person_->Duplicate().Pass())).Pass(); 11158537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)} 11258537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) 11358537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)void PeopleResult::OnIconLoaded() { 11458537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) // Remove the existing image reps since the icon data is loaded and they 11558537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) // need to be re-created. 11658537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) const std::vector<gfx::ImageSkiaRep>& image_reps = image_.image_reps(); 11758537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) for (size_t i = 0; i < image_reps.size(); ++i) 11868043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles) image_.RemoveRepresentation(image_reps[i].scale()); 11958537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) 12058537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) SetIcon(image_); 12158537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)} 12258537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) 12368043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)void PeopleResult::SetDefaultActions() { 12468043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles) Actions actions; 12568043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles) 12668043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles) ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); 1278bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) if (!hangouts_extension_id_.empty()) { 1284e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) actions.push_back(Action( 1294e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) *bundle.GetImageSkiaNamed(IDR_PEOPLE_SEARCH_ACTION_CHAT), 1304e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) *bundle.GetImageSkiaNamed(IDR_PEOPLE_SEARCH_ACTION_CHAT_HOVER), 1314e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) *bundle.GetImageSkiaNamed(IDR_PEOPLE_SEARCH_ACTION_CHAT_PRESSED), 1324e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) l10n_util::GetStringUTF16(IDS_PEOPLE_SEARCH_ACTION_CHAT_TOOLTIP))); 1334e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) } 13468043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles) actions.push_back(Action( 13568043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles) *bundle.GetImageSkiaNamed(IDR_PEOPLE_SEARCH_ACTION_EMAIL), 13668043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles) *bundle.GetImageSkiaNamed(IDR_PEOPLE_SEARCH_ACTION_EMAIL_HOVER), 13768043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles) *bundle.GetImageSkiaNamed(IDR_PEOPLE_SEARCH_ACTION_EMAIL_PRESSED), 13868043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles) l10n_util::GetStringUTF16(IDS_PEOPLE_SEARCH_ACTION_EMAIL_TOOLTIP))); 13968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles) SetActions(actions); 14068043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)} 14168043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles) 1424e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)void PeopleResult::OpenChat() { 1434e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) HangoutRequest request; 1444e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) 1454e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) request.type = extensions::api::hangouts_private::HANGOUT_TYPE_CHAT; 1464e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) 1474e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) // from: the user this chat request is originating from. 1484e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) ProfileOAuth2TokenService* token_service = 1494e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) ProfileOAuth2TokenServiceFactory::GetForProfile(profile_); 1504e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) DCHECK(token_service); 1514e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) request.from = token_service->GetPrimaryAccountId(); 1524e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) 1534e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) // to: list of users with whom to start this hangout is with. 1544e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) linked_ptr<User> target(new User()); 1554e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) target->id = person_->owner_id; 1564e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) request.to.push_back(target); 1574e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) 1584e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) scoped_ptr<extensions::Event> event( 1594e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) new extensions::Event(OnHangoutRequested::kEventName, 1604e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) OnHangoutRequested::Create(request))); 1614e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) 1624e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) // TODO(rkc): Change this once we remove the hangoutsPrivate API. 1634e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) // See crbug.com/306672 1644e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) extensions::ExtensionSystem::Get( 1654e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) profile_)->event_router()->DispatchEventToExtension( 1668bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) hangouts_extension_id_, event.Pass()); 1674e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)} 1684e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) 16968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)void PeopleResult::SendEmail() { 17068043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles) chrome::NavigateParams params(profile_, 1714e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) GURL(kEmailUrlPrefix + person_->email), 17268043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles) content::PAGE_TRANSITION_LINK); 17368043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles) // If no window exists, this will open a new window this one tab. 17468043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles) params.disposition = NEW_FOREGROUND_TAB; 17568043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles) chrome::Navigate(¶ms); 17668043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)} 17768043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles) 1788bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)void PeopleResult::RefreshHangoutsExtensionId() { 1794e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) // TODO(rkc): Change this once we remove the hangoutsPrivate API. 1804e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) // See crbug.com/306672 1818bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) for (size_t i = 0; i < arraysize(kHangoutsExtensionIds); ++i) { 1828bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) if (extensions::ExtensionSystem::Get( 1838bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) profile_)->event_router()->ExtensionHasEventListener( 1848bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) kHangoutsExtensionIds[i], OnHangoutRequested::kEventName)) { 1858bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) hangouts_extension_id_ = kHangoutsExtensionIds[i]; 1868bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) return; 1878bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) } 1888bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) } 1898bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) hangouts_extension_id_.clear(); 1904e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)} 1914e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) 19258537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)ChromeSearchResultType PeopleResult::GetType() { 19358537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) return SEARCH_PEOPLE_SEARCH_RESULT; 19458537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)} 19558537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) 19658537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)} // namespace app_list 197