Jump to content

Qt for HarmonyOS/qtohosextras doc/Application Continuation Example - main.cpp

From Qt Wiki
Revision as of 05:19, 17 September 2026 by Shawn Luo (talk | contribs) (Drop manual breadcrumb, MediaWiki renders it automatically (QTFOROH-2307))
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Application Continuation Example - main.cpp

 // Copyright (C) 2025 The Qt Company Ltd.
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

 #include <QApplication>
 #include <QCheckBox>
 #include <QDataStream>
 #include <QDir>
 #include <QFile>
 #include <QHBoxLayout>
 #include <QJsonArray>
 #include <QJsonDocument>
 #include <QJsonObject>
 #include <QJsonValue>
 #include <QLabel>
 #include <QLayout>
 #include <QLineEdit>
 #include <QPlainTextEdit>
 #include <QPushButton>
 #include <QStandardPaths>
 #include <QVBoxLayout>
 #include <QWidget>
 #include <QtOhosExtras>

 struct Note
 {
     int priority = 0;
 };

 struct Fields
 {
     QLineEdit *number = nullptr;
     QLineEdit *text = nullptr;
     QLineEdit *tags = nullptr;
     QLineEdit *pairFirst = nullptr;
     QLineEdit *pairSecond = nullptr;
     QLineEdit *notePriority = nullptr;
     QLineEdit *numbers = nullptr;
     QPlainTextEdit *assetContent = nullptr;
     QLabel *syncProgress = nullptr;
 };

 QByteArray serializeNote(const Note &note)
 {
     QByteArray bytes;
     QDataStream stream(&bytes, QIODevice::WriteOnly);
     stream << note.priority;

     return bytes;
 }

 Note deserializeNote(const QByteArray &bytes)
 {
     Note note;
     QDataStream stream(bytes);
     stream >> note.priority;

     return note;
 }

 QLineEdit *addFieldRow(QVBoxLayout *layout, const QString &label, const QString &value)
 {
     auto *rowLayout = new QHBoxLayout();
     rowLayout->addWidget(new QLabel(label));
     auto *edit = new QLineEdit(value);
     rowLayout->addWidget(edit);
     layout->addLayout(rowLayout);

     return edit;
 }

 QStringList splitList(const QString &text)
 {
     QStringList values;
     for (const auto &token : text.split(QLatin1Char(','))) {
         const auto trimmedToken = token.trimmed();
         if (!trimmedToken.isEmpty())
             values.append(trimmedToken);
     }

     return values;
 }

 QJsonArray toNumberArray(const QStringList &tokens)
 {
     QJsonArray numbers;
     for (const auto &token : tokens)
         numbers.append(token.toInt());

     return numbers;
 }

 QSharedPointer<QFile> tryWriteAssetFile(const QString &fileName, const QString &content)
 {
     const auto dirPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
     QDir().mkpath(dirPath);

     auto file = QSharedPointer<QFile>::create(QDir(dirPath).filePath(fileName));
     if (!file->open(QIODevice::WriteOnly | QIODevice::Truncate)) {
         qWarning(
             "failed to open '%s': %s",
             qUtf8Printable(file->fileName()), qUtf8Printable(file->errorString()));
         return nullptr;
     }

     file->write(content.toUtf8());
     file->close();

     return file;
 }

 QSharedPointer<QtOhosExtras::QOhosDistributedDataObject> createObjectToMigrate(const Fields &fields)
 {
     const auto tags = splitList(fields.tags->text());
     const Note note{fields.notePriority->text().toInt()};
     const auto encodedNote = QString::fromLatin1(serializeNote(note).toBase64());

     const QJsonObject pair{
         {QStringLiteral("first"), fields.pairFirst->text()},
         {QStringLiteral("second"), fields.pairSecond->text()},
     };

     auto object = QtOhosExtras::QOhosDistributedDataObject::create(QJsonObject{
         {QStringLiteral("number"), fields.number->text().toInt()},
         {QStringLiteral("text"), fields.text->text()},
         {QStringLiteral("tags"), QJsonArray::fromStringList(tags)},
         {QStringLiteral("pair"), pair},
         {QStringLiteral("note"), encodedNote},
         {QStringLiteral("numbers"), toNumberArray(splitList(fields.numbers->text()))},
     });

     const QString assetContent = QStringList{
         QStringLiteral("number: ") + fields.number->text(),
         QStringLiteral("text: ") + fields.text->text(),
         QStringLiteral("tags: ") + fields.tags->text(),
         QStringLiteral("pair.first: ") + fields.pairFirst->text(),
         QStringLiteral("pair.second: ") + fields.pairSecond->text(),
         QStringLiteral("note.priority: ") + fields.notePriority->text(),
         QStringLiteral("numbers: ") + fields.numbers->text(),
     }.join(QLatin1Char('\n'));

     const auto assetFile = tryWriteAssetFile(QStringLiteral("fields.txt"), assetContent);
     if (assetFile)
         object->addAssets({assetFile});

     return object;
 }

 QString readAssetFile(const QString &path)
 {
     if (path.isEmpty())
         return {};
     QFile file(path);
     if (!file.open(QIODevice::ReadOnly))
         return {};
     return QString::fromUtf8(file.readAll());
 }

 void applyObjectFields(const Fields &fields, QtOhosExtras::QOhosDistributedDataObject *object)
 {
     if (!object)
         return;

     const auto has = [&](const QString &key) {
         const auto value = object->get(key);
         return !value.isNull() && !value.isUndefined();
     };

     if (has(QStringLiteral("number")))
         fields.number->setText(QString::number(object->get(QStringLiteral("number")).toInt()));

     if (has(QStringLiteral("text")))
         fields.text->setText(object->get(QStringLiteral("text")).toString());

     if (has(QStringLiteral("tags"))) {
         QStringList tags;
         for (const auto &tag : object->get(QStringLiteral("tags")).toArray())
             tags.append(tag.toString());
         fields.tags->setText(tags.join(QLatin1Char(',')));
     }

     if (has(QStringLiteral("pair"))) {
         const auto pair = object->get(QStringLiteral("pair")).toObject();
         fields.pairFirst->setText(pair.value(QStringLiteral("first")).toString());
         fields.pairSecond->setText(pair.value(QStringLiteral("second")).toString());
     }

     if (has(QStringLiteral("note"))) {
         const auto note = deserializeNote(
             QByteArray::fromBase64(object->get(QStringLiteral("note")).toString().toLatin1()));
         fields.notePriority->setText(QString::number(note.priority));
     }

     if (has(QStringLiteral("numbers"))) {
         QStringList numbers;
         for (const auto &value : object->get(QStringLiteral("numbers")).toArray())
             numbers.append(QString::number(value.toInt()));
         fields.numbers->setText(numbers.join(QLatin1Char(',')));
     }

     const auto assets = object->assets();
     if (!assets.isEmpty()) {
         const auto assetPath = assets.first().toObject().value(QStringLiteral("path")).toString();
         fields.assetContent->setPlainText(readAssetFile(assetPath));
     }
 }

 QSharedPointer<QtOhosExtras::QOhosDistributedDataObject> restoreFirstObject(
     const QList<QSharedPointer<QtOhosExtras::QOhosDistributedDataObject>> &restored, const Fields &fields)
 {
     if (restored.isEmpty())
         return {};

     const auto object = restored.first();
     auto *objectPtr = object.get();

     QObject::connect(
         objectPtr, &QtOhosExtras::QOhosDistributedDataObject::statusChanged,
         objectPtr,
         [fields, objectPtr](const QString &, const QString &, auto status) {
             if (status != QtOhosExtras::QOhosDistributedDataObject::Status::Offline)
                 applyObjectFields(fields, objectPtr);
         });

     QObject::connect(
         objectPtr, &QtOhosExtras::QOhosDistributedDataObject::dataChanged,
         objectPtr,
         [fields, objectPtr](const QString &, const QStringList &) {
             applyObjectFields(fields, objectPtr);
         });

     QObject::connect(
         objectPtr, &QtOhosExtras::QOhosDistributedDataObject::progressChanged,
         objectPtr,
         [fields](const QString &, int progress) {
             fields.syncProgress->setText(QStringLiteral("sync progress: %1%").arg(progress));
         });

     applyObjectFields(fields, objectPtr);

     return object;
 }

 QLayout *createContinuationStateButtonsLayout(
     QSharedPointer<QtOhosExtras::QOhosAbilityContext> ability)
 {
     auto *buttonsLayout = new QHBoxLayout();

     auto *activateButton = new QPushButton(QLatin1String("Set Continuation Active"));
     QObject::connect(
         activateButton, &QPushButton::clicked,
         [ability]() {
             ability->setContinuationActive(true);
         });
     buttonsLayout->addWidget(activateButton);

     auto *deactivateButton = new QPushButton(QLatin1String("Set Continuation Inactive"));
     QObject::connect(
         deactivateButton, &QPushButton::clicked,
         [ability]() {
             ability->setContinuationActive(false);
         });
     buttonsLayout->addWidget(deactivateButton);

     return buttonsLayout;
 }

 int main(int argc, char *argv[])
 {
     QApplication app(argc, argv);

     QWidget window;

     auto *mainLayout = new QVBoxLayout(&window);

     auto *dataLayout = new QHBoxLayout();
     dataLayout->addWidget(new QLabel(QLatin1String("Received On Continue Data:")));
     auto *valueLabel = new QLabel(QLatin1String(""));
     dataLayout->addWidget(valueLabel);

     mainLayout->addLayout(dataLayout);

     Fields fields;
     fields.number = addFieldRow(mainLayout, QLatin1String("number"), QLatin1String("42"));
     fields.text = addFieldRow(mainLayout, QLatin1String("text"), QLatin1String("hello"));
     fields.tags = addFieldRow(mainLayout, QLatin1String("tags (a,b,c)"), QLatin1String("red,green,blue"));
     fields.pairFirst = addFieldRow(mainLayout, QLatin1String("pair.first"), QLatin1String("left"));
     fields.pairSecond = addFieldRow(mainLayout, QLatin1String("pair.second"), QLatin1String("right"));
     fields.notePriority = addFieldRow(mainLayout, QLatin1String("note.priority"), QLatin1String("7"));
     fields.numbers = addFieldRow(mainLayout, QLatin1String("numbers (1,2,3)"), QLatin1String("1,2,3"));

     mainLayout->addWidget(new QLabel(QLatin1String("asset: fields.txt")));
     fields.assetContent = new QPlainTextEdit();
     fields.assetContent->setReadOnly(true);
     mainLayout->addWidget(fields.assetContent);

     fields.syncProgress = new QLabel(QLatin1String("sync progress: -"));
     mainLayout->addWidget(fields.syncProgress);

     auto *exitAfterMigrationCheckBox =
         new QCheckBox(QLatin1String("Exit app on source device after migration"));
     exitAfterMigrationCheckBox->setChecked(true);
     mainLayout->addWidget(exitAfterMigrationCheckBox);

     window.show();

     auto launchWant = QtOhosExtras::QOhosAppContext::getAppLaunchWant();
     auto onContinueData = QtOhosExtras::tryGetOnContinueData(launchWant);
     if (onContinueData) {
         valueLabel->setText(*onContinueData.get());
     } else {
         valueLabel->setText(QLatin1String(""));
     }

     auto ability = QtOhosExtras::QOhosAbilityContext::getInstanceForMainWindow(window.windowHandle());

     mainLayout->addLayout(createContinuationStateButtonsLayout(ability));

     QObject::connect(
         ability.get(), &QtOhosExtras::QOhosUiAbilityContext::continueRequestReceived,
         ability.get(),
         [fields, exitAfterMigrationCheckBox, abilityPtr = ability.get()](auto context) {
             auto *appContext = QtOhosExtras::QOhosAppContext::instance();
             auto appVersionCode = appContext->getBundleInfo()->versionCode();

             context->setExitAppOnSourceDeviceAfterMigration(exitAfterMigrationCheckBox->isChecked());

             auto object = createObjectToMigrate(fields);
             context->setResponseOnDistributedDataObjectsSaveResults(
                 {object},
                 abilityPtr,
                 [context, appVersionCode](const QList<bool> &results) {
                     const bool saved = !results.isEmpty() && !results.contains(false);
                     if (saved && context->sourceApplicationVersionCode() == appVersionCode) {
                         context->setAgreeResponse(QByteArray("Migration Test Data"));
                     } else {
                         context->setMismatchResponse();
                     }
                 });
         });

     QObject::connect(
         ability.get(), &QtOhosExtras::QOhosUiAbilityContext::newWantInfoReceived,
         valueLabel,
         [fields, valueLabel, continuationObjectHandle = restoreFirstObject(QtOhosExtras::tryRestoreOnContinueDistributedDataObjects(launchWant), fields)](auto wantInfo) mutable {
             auto onContinueData = QtOhosExtras::tryGetOnContinueData(wantInfo->want());
             if (onContinueData) {
                 valueLabel->setText(*onContinueData.get());
             } else {
                 valueLabel->setText(QLatin1String(""));
             }

             auto newWantContinuationObjectHandle = restoreFirstObject(
                 QtOhosExtras::tryRestoreOnContinueDistributedDataObjects(wantInfo->want()), fields);
             if (newWantContinuationObjectHandle)
                 continuationObjectHandle = newWantContinuationObjectHandle;
         });

     return app.exec();
 }