1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the QtGui module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file.  Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 
42 
43 #include <qcoreapplication.h>
44 #include <qdir.h>
45 #include <qurl.h>
46 #include <private/qcore_symbian_p.h>
47 
48 #include <f32file.h>                // TDriveUnit etc
49 #include <pathinfo.h>               // PathInfo
50 
51 #ifndef USE_SCHEMEHANDLER
52 #ifdef Q_WS_S60
53 // This flag changes the implementation to use S60 CDcoumentHandler
54 // instead of apparc when opening the files
55 #define USE_DOCUMENTHANDLER
56 #endif
57 
58 #include <txtrich.h>                // CRichText
59 #include <eikenv.h>                 // CEikonEnv
60 #include <apgcli.h>                 // RApaLsSession
61 #include <apgtask.h>                // TApaTaskList, TApaTask
62 #include <rsendas.h>                // RSendAs
63 #include <rsendasmessage.h>         // RSendAsMessage
64 
65 #ifdef USE_DOCUMENTHANDLER
66 #include <DocumentHandler.h>        // CDocumentHandler
67 #include <AknServerApp.h>
68 #endif
69 #else // USE_SCHEMEHANDLER
70 #include <schemehandler.h>
71 #endif
72 
73 #ifdef Q_WS_S60
74 #include <CDirectoryLocalizer.h>     // CDirectoryLocalizer
75 #endif
76 
77 QT_BEGIN_NAMESPACE
78 
79 _LIT(KCacheSubDir, "Cache\\");
80 _LIT(KSysBin, "\\Sys\\Bin\\");
81 _LIT(KBrowserPrefix, "4 " );
82 _LIT(KFontsDir, "z:\\resource\\Fonts\\");
83 
84 #ifndef USE_SCHEMEHANDLER
85 // copied from miutset.h, so we don't get a dependency into the app layer
86 const TUid KUidMsgTypeSMTP = {0x10001028}; // 268439592
87 const TUid KUidBrowser = { 0x10008D39 };
88 
89 template<class R>
90 class QAutoClose
91 {
92 public:
QAutoClose(R & aObj)93     QAutoClose(R& aObj) : mPtr(&aObj) {}
~QAutoClose()94     ~QAutoClose()
95     {
96         if (mPtr)
97             mPtr->Close();
98     }
Forget()99     void Forget()
100     {
101         mPtr = 0;
102     }
103 private:
104     QAutoClose(const QAutoClose&);
105     QAutoClose& operator=(const QAutoClose&);
106 private:
107     R* mPtr;
108 };
109 
110 #ifdef USE_DOCUMENTHANDLER
111 class QS60DocumentHandler : public MAknServerAppExitObserver
112 {
113 public:
QS60DocumentHandler()114     QS60DocumentHandler() :docHandler(0) {}
115 
~QS60DocumentHandler()116     ~QS60DocumentHandler() {
117         delete docHandler;
118     }
119 
documentHandler()120     CDocumentHandler& documentHandler() {
121         // In case user calls openUrl twice subsequently, before the first embedded app is closed
122         // we use the same CDocumentHandler instance. Using same instance makes sure the first
123         // launched embedded app is closed and latter one gets embedded to our app.
124         // Using different instance would help only theoretically since user cannot interact with
125         // several embedded apps at the same time.
126         if(!docHandler) {
127             QT_TRAP_THROWING(docHandler = CDocumentHandler::NewL());
128             docHandler->SetExitObserver(this);
129         }
130         return *docHandler;
131     }
132 
133 private: // From MAknServerAppExitObserver
HandleServerAppExit(TInt)134     void HandleServerAppExit(TInt /*aReason*/) {
135         delete docHandler;
136         docHandler = 0;
137     }
138 
139 private:
140     CDocumentHandler* docHandler;
141 };
142 Q_GLOBAL_STATIC(QS60DocumentHandler, qt_s60_documenthandler);
143 #endif
144 
handleMailtoSchemeLX(const QUrl & url)145 static void handleMailtoSchemeLX(const QUrl &url)
146 {
147     // this function has many intermingled leaves and throws. Qt and Symbian objects do not have
148     // destructor dependencies, and cleanup object is used to prevent cleanup stack dependency on stack.
149     QString recipient = url.path();
150     QString subject = url.queryItemValue(QLatin1String("subject"));
151     QString body = url.queryItemValue(QLatin1String("body"));
152     QString to = url.queryItemValue(QLatin1String("to"));
153     QString cc = url.queryItemValue(QLatin1String("cc"));
154     QString bcc = url.queryItemValue(QLatin1String("bcc"));
155 
156     // these fields might have comma separated addresses
157     QStringList recipients = recipient.split(QLatin1String(","), QString::SkipEmptyParts);
158     QStringList tos = to.split(QLatin1String(","), QString::SkipEmptyParts);
159     QStringList ccs = cc.split(QLatin1String(","), QString::SkipEmptyParts);
160     QStringList bccs = bcc.split(QLatin1String(","), QString::SkipEmptyParts);
161 
162     RSendAs sendAs;
163     User::LeaveIfError(sendAs.Connect());
164     QAutoClose<RSendAs> sendAsCleanup(sendAs);
165 
166     CSendAsAccounts* accounts = CSendAsAccounts::NewL();
167     CleanupStack::PushL(accounts);
168     sendAs.AvailableAccountsL(KUidMsgTypeSMTP, *accounts);
169     TInt count = accounts->Count();
170     CleanupStack::PopAndDestroy(accounts);
171 
172     if(!count) {
173         // TODO: Task 259192: We should try to create account if count == 0
174         // CSendUi would provide account creation service for us, but it requires ridicilous
175         // capabilities: LocalServices NetworkServices ReadDeviceData ReadUserData WriteDeviceData WriteUserData
176         User::Leave(KErrNotSupported);
177     } else {
178         RSendAsMessage sendAsMessage;
179         sendAsMessage.CreateL(sendAs, KUidMsgTypeSMTP);
180         QAutoClose<RSendAsMessage> sendAsMessageCleanup(sendAsMessage);
181 
182 
183         // Subject
184         sendAsMessage.SetSubjectL(qt_QString2TPtrC(subject));
185 
186         // Body
187         sendAsMessage.SetBodyTextL(qt_QString2TPtrC(body));
188 
189         // To
190         foreach(QString item, recipients)
191          sendAsMessage.AddRecipientL(qt_QString2TPtrC(item), RSendAsMessage::ESendAsRecipientTo);
192 
193         foreach(QString item, tos)
194          sendAsMessage.AddRecipientL(qt_QString2TPtrC(item), RSendAsMessage::ESendAsRecipientTo);
195 
196         // Cc
197         foreach(QString item, ccs)
198          sendAsMessage.AddRecipientL(qt_QString2TPtrC(item), RSendAsMessage::ESendAsRecipientCc);
199 
200         // Bcc
201         foreach(QString item, bccs)
202          sendAsMessage.AddRecipientL(qt_QString2TPtrC(item), RSendAsMessage::ESendAsRecipientBcc);
203 
204         // send the message
205         sendAsMessage.LaunchEditorAndCloseL();
206         // sendAsMessage is already closed
207         sendAsMessageCleanup.Forget();
208     }
209 }
210 
handleMailtoScheme(const QUrl & url)211 static bool handleMailtoScheme(const QUrl &url)
212 {
213     TRAPD(err, QT_TRYCATCH_LEAVING(handleMailtoSchemeLX(url)));
214     return err ? false : true;
215 }
216 
handleOtherSchemesL(const TDesC & aUrl)217 static void handleOtherSchemesL(const TDesC& aUrl)
218 {
219     // Other schemes are at the moment passed to WEB browser
220     HBufC* buf16 = HBufC::NewLC(aUrl.Length() + KBrowserPrefix.iTypeLength);
221     buf16->Des().Copy(KBrowserPrefix); // Prefix used to launch correct browser view
222     buf16->Des().Append(aUrl);
223 
224     TApaTaskList taskList(CEikonEnv::Static()->WsSession());
225     TApaTask task = taskList.FindApp(KUidBrowser);
226     if (task.Exists()){
227         // Switch to existing browser instance
228         task.BringToForeground();
229         HBufC8* param8 = HBufC8::NewLC(buf16->Length());
230         param8->Des().Append(buf16->Des());
231         task.SendMessage(TUid::Uid( 0 ), *param8); // Uid is not used
232         CleanupStack::PopAndDestroy(param8);
233     } else {
234         // Start a new browser instance
235         RApaLsSession appArcSession;
236         User::LeaveIfError(appArcSession.Connect());
237         CleanupClosePushL<RApaLsSession>(appArcSession);
238         TThreadId id;
239         appArcSession.StartDocument(*buf16, KUidBrowser, id);
240         CleanupStack::PopAndDestroy(); // appArcSession
241     }
242 
243     CleanupStack::PopAndDestroy(buf16);
244 }
245 
handleOtherSchemes(const QUrl & url)246 static bool handleOtherSchemes(const QUrl &url)
247 {
248     QString encUrl(QString::fromUtf8(url.toEncoded()));
249     TPtrC urlPtr(qt_QString2TPtrC(encUrl));
250     TRAPD( err, handleOtherSchemesL(urlPtr));
251     return err ? false : true;
252 }
253 
254 
openDocumentL(const TDesC & aUrl)255 static void openDocumentL(const TDesC& aUrl)
256 {
257 #ifndef USE_DOCUMENTHANDLER
258     // Start app associated to file MIME type by using RApaLsSession
259     // Apparc base method cannot be used to open app in embedded mode,
260     // but seems to be most stable way at the moment
261     RApaLsSession appArcSession;
262     User::LeaveIfError(appArcSession.Connect());
263     CleanupClosePushL<RApaLsSession>(appArcSession);
264     TThreadId id;
265     // ESwitchFiles means do not start another instance
266     // Leaves if file does not exist, leave is trapped in openDocument and false returned to user.
267     User::LeaveIfError(appArcSession.StartDocument(aUrl, id,
268             RApaLsSession::ESwitchFiles)); // ELaunchNewApp
269     CleanupStack::PopAndDestroy(); // appArcSession
270 #else
271     // This is an alternative way to launch app associated to MIME type
272     // CDocumentHandler also supports opening apps in embedded mode.
273     TDataType temp;
274     qt_s60_documenthandler()->documentHandler().OpenFileEmbeddedL(aUrl, temp);
275 #endif
276 }
277 
launchWebBrowser(const QUrl & url)278 static bool launchWebBrowser(const QUrl &url)
279 {
280     if (!url.isValid())
281         return false;
282 
283     if (url.scheme() == QLatin1String("mailto")) {
284         return handleMailtoScheme(url);
285     }
286     return handleOtherSchemes( url );
287 }
288 
openDocument(const QUrl & file)289 static bool openDocument(const QUrl &file)
290 {
291     if (!file.isValid())
292         return false;
293 
294     QString filePath = file.toLocalFile();
295     filePath = QDir::toNativeSeparators(filePath);
296     TPtrC filePathPtr(qt_QString2TPtrC(filePath));
297     TRAPD(err, openDocumentL(filePathPtr));
298     return err ? false : true;
299 }
300 
301 #else //USE_SCHEMEHANDLER
302 // The schemehandler component only exist in private SDK. This implementation
303 // exist here just for convenience in case that we need to use it later on
304 // The schemehandle based implementation is not yet tested.
305 
306 // The biggest advantage of schemehandler is that it can handle
307 // wide range of schemes and is extensible by plugins
handleUrlL(const TDesC & aUrl)308 static void handleUrlL(const TDesC& aUrl)
309 {
310     CSchemeHandler* schemeHandler = CSchemeHandler::NewL(aUrl);
311     CleanupStack::PushL(schemeHandler);
312     schemeHandler->HandleUrlStandaloneL(); // Process the Url in standalone mode
313     CleanupStack::PopAndDestroy();
314 }
315 
handleUrl(const QUrl & url,bool useEncodedUrl)316 static bool handleUrl(const QUrl &url, bool useEncodedUrl)
317 {
318     if (!url.isValid())
319         return false;
320 
321     QString urlString(useEncodedUrl ? url.toEncoded() : url.toString());
322     TPtrC urlPtr(qt_QString2TPtrC(urlString));
323     TRAPD( err, handleUrlL(urlPtr));
324     return err ? false : true;
325 }
326 
launchWebBrowser(const QUrl & url)327 static bool launchWebBrowser(const QUrl &url)
328 {
329     return handleUrl(url, true);
330 }
331 
openDocument(const QUrl & file)332 static bool openDocument(const QUrl &file)
333 {
334     return handleUrl(file, false);
335 }
336 
337 #endif //USE_SCHEMEHANDLER
338 
339 // Common functions to all implementations
340 
exeDrive()341 static TDriveUnit exeDrive()
342 {
343     RProcess me;
344     TFileName processFileName = me.FileName();
345     TDriveUnit drive(processFileName);
346     return drive;
347 }
348 
writableExeDrive()349 static TDriveUnit writableExeDrive()
350 {
351     TDriveUnit drive = exeDrive();
352     if (drive.operator TInt() == EDriveZ)
353         return TDriveUnit(EDriveC);
354     return drive;
355 }
356 
writableDataRoot()357 static TPtrC writableDataRoot()
358 {
359     TDriveUnit drive = exeDrive();
360     switch (drive.operator TInt()){
361         case EDriveC:
362             return PathInfo::PhoneMemoryRootPath();
363             break;
364         case EDriveE:
365             return PathInfo::MemoryCardRootPath();
366             break;
367         case EDriveZ:
368             // It is not possible to write on ROM drive ->
369             // return phone mem root path instead
370             return PathInfo::PhoneMemoryRootPath();
371             break;
372         default:
373             return PathInfo::PhoneMemoryRootPath();
374             break;
375     }
376 }
377 
storageLocation(StandardLocation type)378 QString QDesktopServices::storageLocation(StandardLocation type)
379 {
380     TFileName path;
381 
382     switch (type) {
383     case DesktopLocation:
384         qWarning("No desktop concept in Symbian OS");
385         // But lets still use some feasible default
386         path.Append(writableDataRoot());
387         break;
388     case DocumentsLocation:
389         path.Append(writableDataRoot());
390         break;
391     case FontsLocation:
392         path.Append(KFontsDir);
393         break;
394     case ApplicationsLocation:
395         path.Append(exeDrive().Name());
396         path.Append(KSysBin);
397         break;
398     case MusicLocation:
399         path.Append(writableDataRoot());
400         path.Append(PathInfo::SoundsPath());
401         break;
402     case MoviesLocation:
403         path.Append(writableDataRoot());
404         path.Append(PathInfo::VideosPath());
405         break;
406     case PicturesLocation:
407         path.Append(writableDataRoot());
408         path.Append(PathInfo::ImagesPath());
409         break;
410     case TempLocation:
411         return QDir::tempPath();
412         break;
413     case HomeLocation:
414         path.Append(writableDataRoot());
415         //return QDir::homePath(); break;
416         break;
417     case DataLocation:
418         qt_s60GetRFs().PrivatePath(path);
419         path.Insert(0, writableExeDrive().Name());
420         break;
421     case CacheLocation:
422         qt_s60GetRFs().PrivatePath(path);
423         path.Insert(0, writableExeDrive().Name());
424         path.Append(KCacheSubDir);
425         break;
426     default:
427         // Lets use feasible default
428         path.Append(writableDataRoot());
429         break;
430     }
431 
432     // Convert to cross-platform format and clean the path
433     QString nativePath = QString::fromUtf16(path.Ptr(), path.Length());
434     QString qtPath = QDir::fromNativeSeparators(nativePath);
435     qtPath = QDir::cleanPath(qtPath);
436 
437     // Note: The storage location returned can be a directory that does not exist;
438     // i.e., it may need to be created by the system or the user.
439     return  qtPath;
440 }
441 
442 typedef QString (*LocalizerFunc)(QString&);
443 
defaultLocalizedDirectoryName(QString &)444 static QString defaultLocalizedDirectoryName(QString&)
445 {
446     return QString();
447 }
448 
displayName(StandardLocation type)449 QString QDesktopServices::displayName(StandardLocation type)
450 {
451     QString ret;
452 
453 #ifdef Q_WS_S60
454     QString rawPath = storageLocation(type);
455 
456     TRAPD(err,
457         QT_TRYCATCH_LEAVING(
458             CDirectoryLocalizer* localizer = CDirectoryLocalizer::NewL();
459             CleanupStack::PushL(localizer);
460             localizer->SetFullPath(qt_QString2TPtrC(QDir::toNativeSeparators(rawPath)));
461             if (localizer->IsLocalized()) {
462                 TPtrC locName(localizer->LocalizedName());
463                 ret = qt_TDesC2QString(locName);
464             }
465             CleanupStack::PopAndDestroy(localizer);
466         )
467     )
468 
469     if (err != KErrNone)
470         ret = QString();
471 #else
472     qWarning("QDesktopServices::displayName() not implemented for this platform version");
473 #endif
474 
475     return ret;
476 }
477 
478 QT_END_NAMESPACE
479