I am using the following code to send an SMS from my app;
void App::sendSms(const QString &messageText, const QStringList &phoneNumbers) {
bb::pim::account::AccountService accountService;
bb::pim::message::MessageService messageService;
QList<Account> accountListy = accountService.accounts(bb::pim::account::Service::Messages,"sms-mms");
bb::pim::account::AccountKey smsAccountId = 0;
if(!accountListy.isEmpty()) {
smsAccountId = accountListy.first().id();
qDebug() << "SMS-MMS account ID:" << smsAccountId;
}
else {
qWarning() << "Could not find SMS account";
return;
}
QList<bb::pim::message::MessageContact> participants;
foreach(const QString &phoneNumber, phoneNumbers) {
bb::pim::message::MessageContact recipient = bb::pim::message::MessageContact(
-1, bb::pim::message::MessageContact::To,
phoneNumber, phoneNumber);
participants.append(recipient);
}
bb::pim::message::ConversationBuilder *conversationBuilder =
bb::pim::message::ConversationBuilder::create();
conversationBuilder->accountId(smsAccountId);
conversationBuilder->participants(participants);
bb::pim::message::Conversation conversation = *conversationBuilder;
bb::pim::message::ConversationKey conversationId = messageService.save(smsAccountId, conversation);
bb::pim::message::MessageBuilder *builder =
bb::pim::message::MessageBuilder::create(smsAccountId);
builder->conversationId(conversationId);
builder->addAttachment(bb::pim::message::Attachment("text/plain", "", messageText.toUtf8()));
foreach(const bb::pim::message::MessageContact recipient, participants) {
builder->addRecipient(recipient);
}
bb::pim::message::Message message = *builder;
messageService.send(smsAccountId, message);
delete builder;
delete conversationBuilder;
}
However everytime it sends a new SMS, it creates a new thread in the Text Messages UI. I was wondering if there was a way to add the new message to the thread that already exists for the number it is going to send to?
Thanks!
The aspect of your code which causes this bug is
// at top of file
using namespace bb::pim::messages;
ConversationBuilder *conversationBuilder = ConversationBuilder::create();
conversationBuilder->accountId(smsAccountId);
conversationBuilder->participants(participants);
Conversation conversation = *conversationBuilder;
ConversationKey conversationId = messageService.save(smsAccountId, conversation);
This piece—following its preceeding lines, would create a new conversation for the participants regardless of previous existing conversations in the Hub.
To work around this, the BlackBerry Cascades PIM MessageService provides a MessageSearchFilter with which you can use to filter the conversations by any SearchFilterCriteria. Use it thus…
//Setup a filter
MessageFilter filter;
//Set our filter to filter conversations with id of the contact
filter.insert(MessageFilter::ContactId, contactId);
//Run filter
filterdConvosKeys = messageService.conversationKeys(smsAccountId, filter);
ConversationKey conversation_id;
//Vars for conversation builder
conversationBuilder->accountId(smsAccountId);
conversationBuilder->participants(participants);
//Get conversation ID for existing conversation, else create new
if (filterdConvosKeys.count() > 1) {
// go through all conversations for this contact and choose
// the conversation in which this contact is the sole participant
else if (filterdConvosKeys.count() == 1) {
conversation_id = filterdConvosKeys[0].toAscii();
} else {
conversation_id = messageService.save(smsAccountId, conversation);
}
Edit
Despite what the original Source says, I find it a bit buggy. If you use it exactly as it says, you will always end up with a new conversation if there is no conversation with the contact as the only participant. I tried doing a search in the BlackBerry Hub on my STL100-3 with the phone number of a contact, I ended up with many messages which were in the same conversation. But this means there is a possibility that many conversations are being returned if you filter by MessageFilter::Participants. It is better to filter using MessageFilter::ContactId.
p.s: I namespaced the code blocks so bb::pim::messages:: is not repeated.
Related
I have an app with contextual commands. After triggered a contextual command, it will make a HTTP request with a link and post the result on the card, something like, "Completed!". I want this card to be closed by itself after one second so that the user need not to tap to close it. Once the result card is closed, it will go back to contextual command lists with "Ok, glass" at footer and ready for next command.
May i know how to do that?
private class HTTPRequest extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... arg0) {
try {
if (mWhat.equalsIgnoreCase("GET")) {
// get json via YouTube API
URL url = new URL("http://example.com");
mUrlConnection = (HttpURLConnection)
url.openConnection();
InputStream in = new BufferedInputStream(
mUrlConnection.getInputStream());
int ch;
StringBuffer b = new StringBuffer();
while ((ch = in.read()) != -1) {
b.append((char) ch);
}
mResult = new String(b);
}
} catch (Exception e) {}
return null;
}
#Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
mTvInfo.setText(mResult);
}
You can use an Android Dialog for this:
Use CardBuilder to create the "Completed" card using the MENU layout.
Create a new instance of Dialog and set its content view to be the view returned by CardBuilder.getView.
Show the dialog.
Use Handler.postDelayed (or some similar mechanism) to automatically dismiss the dialog after the desired amount of time has passed.
I'm sending a bundle of cards to Glass with the Mirror API (c# library)
I know that you can use the default delete menu item on single cards, but is there a way to provide delete functionality for an entire bundle, ideally the result of one action by the users?
I have successfully used the DELETE action on a menu item with the code below
MenuItem mi = new MenuItem();
mi.Action = "DELETE";
TimelineItem tli = new TimelineItem()
{
Html = itemHtml.ToString(),
Notification = new NotificationConfig() { Level = "DEFAULT" },
MenuItems = new List<MenuItem>() { mi }
};
Is there a way to add this delete menu item to a bundle cover? I know this may be tricky because clicking the bundle cover causes you to navigate into the child cards thus no menu is present like on single cards. I'm looking for something (which I did try but it just ignored the menu item) like this:
MenuItem mi = new MenuItem();
mi.Action = "DELETE";
TimelineItem tli = new TimelineItem()
{
Html = itemHtml.ToString(),
Notification = new NotificationConfig() { Level = "DEFAULT" },
IsBundleCover = true,
BundleId = bundleId,
MenuItems = new List<MenuItem>() { mi }
};
If not possible on a cover card, is there a way to do this for a bundle by adding delete menu items to the child cards?
Any suggestions would be appreciated
You can use customized menu to do this. The code below is using Java but C# should be similar:
Add customized menu item to the card:
List<MenuValue> menuValueList = new ArrayList<MenuValue>();
menuValueList.add(new MenuValue().setIconUrl(iconUrl).setDisplayName("Delete All"));
MenuItem menuItem = new MenuItem();
menuItem.setValues(menuValueList).setId("delete_bundle_A").setAction("CUSTOM");
List<MenuItem> menuItemList = new ArrayList<MenuItem>();
menuItemList.add(menuItem);
timelineItem.setMenuItems(menuItemList);
Define the controller which handles the callback request of Mirror server notification:
if (notification.getCollection().equals("timeline") && notification.getUserActions().contains(new UserAction().setType("CUSTOM").setPayload("delete_bundle_A"))) {
deleteCards(credential, bundleId);
}
The delete card function:
// if bundleId is null or "", delete all cards
public static void deleteCards(Credential credential, String bundleId) throws IOException {
if (bundleId == null) {
bundleId = "";
}
Mirror.Timeline timelineItems = MirrorClient.getMirror(credential).timeline();
Mirror.Timeline.List list = timelineItems.list();
List<TimelineItem> timelineItemList = null;
do {
TimelineListResponse response = list.execute();
timelineItemList = response.getItems();
if (timelineItemList != null && timelineItemList.size() > 0) {
for (TimelineItem item : timelineItemList) {
if (bundleId == "" || bundleId.equalsIgnoreCase(item.getBundleId())) {
LOG.info("Deleting card " + item.getId());
MirrorClient.deleteTimelineItem(credential, item.getId());
}
}
list.setPageToken(response.getNextPageToken());
} else {
break;
}
} while (list.getPageToken() != null && list.getPageToken().length() > 0);
}
Finally, don't forget to subscribe timeline notification when application starts up:
String notifyUrl = "https://mirrornotifications.appspot.com/forward?url=" + "http://yourServer.com/notify";
Subscription subscription = MirrorClient.insertSubscription(credential, notifyUrl, userId, "timeline");
It isn't clear if you're asking how to create the menu items to delete the entire bundle at once, or if you're looking for code to do the actual delete.
Yuan provides some very good answers to both (not least of which because he actually provides code, which I won't), but there are three things you might also want to consider.
1) You can't have a menu on the bundle cover, but if you don't explicitly specify a bundle cover, then the most recent card will be shown as the cover and will also be shown as the first card in the bundle. You'd be able to get to the menu this way. (The default messaging app works this way, for example, but the first card has the same menu as the rest.)
2) You don't need to create a new menu item. You can leverage the DELETE menu item, if you wish. You'll get a delete notification for one of the cards in the bundle and you can then read the bundleId and delete the rest.
3) You don't need to loop through all the cards you've inserted just to find ones that have that bundleId. That is horribly inefficient. I am not fluent in C#, but from reading the documentation at https://developers.google.com/resources/api-libraries/documentation/mirror/v1/csharp/latest/classGoogle_1_1Apis_1_1Mirror_1_1v1_1_1TimelineResource_1_1ListRequest.html, I get the sense that you can create a ListRequest and then set the bundleId before executing the query and get the results.
So I think you can change Yuan's code to something like:
Mirror.Timeline.List list = timelineItems.list();
list.BundleId = bundleId;
List<TimelineItem> timelineItemList = null;
do {
TimelineListResponse response = list.execute();
timelineItemList = response.getItems();
if (timelineItemList != null && timelineItemList.size() > 0) {
for (TimelineItem item : timelineItemList) {
LOG.info("Deleting card " + item.getId());
MirrorClient.deleteTimelineItem(credential, item.getId());
}
list.setPageToken(response.getNextPageToken());
} else {
break;
}
} while (list.getPageToken() != null && list.getPageToken().length() > 0);
(this should be treated as pseudo-code, at best)
If you're confident how many items you've put into a bundle, you might also be able to just set list.MaxResults and not have to iterate over the pages of results. So perhaps something more like
Mirror.Timeline.List list = timelineItems.list();
list.BundleId = bundleId;
list.MaxResults = 20; // Set to more than the max number of items in a bundle
TimelineListResponse response = list.execute();
List<TimelineItem> timelineItemList = response.getItems();
if (timelineItemList != null && timelineItemList.size() > 0) {
for (TimelineItem item : timelineItemList) {
LOG.info("Deleting card " + item.getId());
MirrorClient.deleteTimelineItem(credential, item.getId());
}
}
There doesn't appear to be a way to delete a bundle in one step but it's still possible...
You can do a GET on /Timeline to get a list of items your app has pushed to the users timeline. Filter that out to find the entries with the bundleId you want to delete. For each of those items, call DELETE /Timeline/{itemid}
How do I start a fragment in my Android application from a notification in the notification bar?
I've tried to implement this answer of creating my own action and then setting the action to the intent, but I'm unsure how to use it and what is required additionally - like adding something to the Manifest.
I've got a notification class that receives a context, a message and then an action. I then want to filter on that action to determine which fragment to launch, but I don't know how to launch a fragment as opposed to launching an activity.
Here is my Notifications.java class (incomplete):
public class Notifications {
private Context mContext;
public Notifications(Context context) {
this.mContext = context;
}
public static void notify(Context context, String message, String action) {
//Action you invent should include the application package as a prefix — for example: "com.example.project.SHOW_COLOR".
action = "my.package.name.here.frag."+action;
//Construct a user message.
String appName = context.getResources().getString(R.string.app_name);
// Use the Notification manager to send notification
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
// Create a notification using android stat_notify_chat icon.
Notification notification = new Notification(R.drawable.ic_stat_notification, message, 0);
//Sound, lights, vibration.
//REMEMBER PERMISSIONS.
notification.defaults |= Notification.DEFAULT_SOUND;
notification.defaults |= Notification.DEFAULT_VIBRATE;
notification.defaults |= Notification.DEFAULT_LIGHTS;
// Create a pending intent to open the application when the notification is clicked.
//Restart the app.
Intent launchIntent = null;
//Get the action and based on what the action is, launch the application displaying the appropriate fragment.
if (action.equalsIgnoreCase("friend")){
//New friend notification
//Launch application displaying the list of friends
}
if (action.equalsIgnoreCase("article")){
//New article has been posted
//Launch application displaying the news feed fragment
}
if (action.equalsIgnoreCase("points")){
//Points scored notification
//Launch application displaying the user's profile
}
if (action.equalsIgnoreCase("redeemable")){
//New redeemable is offered
//Launch application displaying the list of redeemables
}
if (!action.equalsIgnoreCase("friend")
&& !action.equalsIgnoreCase("article")
&& !action.equalsIgnoreCase("points")
&& !action.equalsIgnoreCase("redeemable")){
//Not specific, so launch the application from scratch displaying the activity feed
launchIntent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
}
if(action != null && launchIntent != null){
launchIntent.setAction(action);
}
// Set the notification and register the pending intent to it
notification.setLatestEventInfo(context, appName, message, pendingIntent);
// Trigger the notification
notificationManager.notify(0, notification);
}
}
So this was actually pretty easy. Hopefully I can help someone else see this too.
I send an action to this notify function. The I add that action to my intent to launch an activity. In my case I open the launching activity, because all the fragments are loaded from within that activity based on what the user does. So I set the action using setAction and the I use that action in the activity as below.
My Notifications.java class changed to this:
public static void notify(Context context, String message, String action) {
action = action.toUpperCase();
// Create a pending intent to open the the application when the notification is clicked.
//Restart the app.
Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
if(action != null && launchIntent != null){
launchIntent.setAction(action);
}
PendingIntent pendingIntent = PendingIntent.getActivity(context, -1, launchIntent, PendingIntent.FLAG_UPDATE_CURRENT);
notification.when = System.currentTimeMillis();
notification.flags |= Notification.FLAG_AUTO_CANCEL;
// Set the notification and register the pending intent to it
notification.setLatestEventInfo(context, appName, message, pendingIntent);
// Trigger the notification
notificationManager.notify(0, notification);
}
And then in my activity from where I load the fragments, I get the action and filter it:
Intent intent = getIntent();
try{
String action = intent.getAction().toUpperCase();
if(action != null){
if(action.equalsIgnoreCase(getResources().getString(R.string.notification_action_friend))){
goFrag(getResources().getInteger(R.integer.FRAG_A_INT));
}
if(action.equalsIgnoreCase(getResources().getString(R.string.notification_action_article))){
goFrag(getResources().getInteger(R.integer.FRAG_B_INT));
}
if(action.equalsIgnoreCase(getResources().getString(R.string.notification_action_points))){
goFrag(getResources().getInteger(R.integer.FRAG_C_INT));
}
if(action.equalsIgnoreCase(getResources().getString(R.string.notification_action_redeemable))){
goFrag(getResources().getInteger(R.integer.FRAG_D_INT));
}
if(action.equalsIgnoreCase(getResources().getString(R.string.notification_action_dance))){
goFrag(getResources().getInteger(R.integer.FRAG_E_INT));
}
}else{
Log.d(TAG, "Intent was null");
}
}catch(Exception e){
Log.e(TAG, "Problem consuming action from intent", e);
}
In my goFrag function I replace the fragment if the required fragment is still in memory (meaning the user was there earlier and it hasn't been destroyed yet), or I create a new fragment required.
I'm currently facing a strange problem in Qt.
I have an application that handles multiple clients using the QTcpSocket.
The clients provide the server with login details and the server is doing a check on the MySQL database to verify the credentials on a separate thread.
I have 2 different client programs that log-in to this server
1. A simple client using Qt to connect there
2. A client that is coded in C-Sharp (.net)
However
When the second client tries to log-in to the server, i get an error on the server telling me this:
QObject: Cannot create children for a parent that is in a different thread. Parent is QNativeSocketEngine(0x1231e00), parent's thread is
QThread(0xc625f0), current thread is CMySQLHandler(0xc67188)
I'm guessing this is happening on the MySQL handler thread
But the weird part is, it is not happening at all on the client that is coded in Qt
here is the code:
void CMySQLHandler::onAuthRequest(ulong clientID, QString email, QString psw, QString HWID, QString ip)
{
string pass = "", salt = "", name = "", avatar = "";
uint UserID = 0;
CUserData *extra = new CUserData();
bool success = false;
QString failReason = "Success";
string Query = "SELECT password, username, avatar, userid FROM jos_users LEFT JOIN jos_community_users ON jos_users.id = jos_community_users.userid WHERE email = '"+email.toStdString()+"'";
SQLRow Row = GetRow(Query);
if(!Row.empty())
{
QString hashedPass = QString(Row[0].c_str());
pass = hashedPass.split(':').at(0).toStdString();
salt = hashedPass.split(':').at(1).toStdString();
name = Row[1];
avatar = Row[2];
UserID = QString(Row[3].c_str()).toUInt();
QByteArray data;
data.append((psw + QString(salt.c_str())));
QString blah = QString(QCryptographicHash::hash(data,QCryptographicHash::Md5).toHex());
if(QString(pass.c_str()) == blah )
success = true;
else
failReason = "Invalid password";
}
else
{
failReason = "E-Mail not found";
emit Handle->onAuthCompleted(clientID, false, failReason, extra);
return;
}
extra->Avatar += avatar.c_str();
extra->UserID = UserID;
extra->Username = QString(name.c_str());
emit Handle->onAuthCompleted(clientID, true, QString(), extra);
}
here's the signal definitions
void CMySQLHandler :: onTimerInit()
{
//Authentication
connect(this, SIGNAL(doAuthCompleted(ulong,bool,QString,CUserData*)), Handle, SLOT(onAuthCompleted(ulong,bool,QString,CUserData*)));
connect(Handle, SIGNAL(doRequestAuth(ulong,QString,QString,QString,QString)), this, SLOT(onAuthRequest(ulong,QString,QString,QString,QString)));
//end
CMySQL::Init(Handle);
}
I am new to the Qt framework.
Please excuse the lack of knowledge.
Thank you for your time reading this.
Qt uses an concept of ownership of objects by threads. Thread own objects to do the signal/slot mechanism. The problem is that you are trying to create an new object in Thread CMySQLHandler(0xc67188) and assign it the parent QNativeSocketEngine(0x1231e00). But QNativeSocketEngine is owned by QThread(0xc625f0) which is not allowed. I can't see the error in your code so you may need to do some more debugging to find the line that cause the error and edit your question.
This kind of different-thread problems happen all the time with Qt when you use multiple threads. The canonical way to solve this problem is to use signals and slots.
The error doesn't happen in onAuthRequest, it is probably earlier, where you try to call this function. Maybe you are creating a CMySQLHandler object in a one thread, and try to execute member functions of it in another.
If you for some reason need to call a function of a object which belongs to a different threat, create a slot in the object which wraps the function and call it with an emit.
See the similar question QObject: Cannot create children for a parent that is in a different thread
I am looking for a way to allow visitors to select what content they want displayed on the site.
Is there a way to programatically trigger a profile in Sitecore DMS?
I've looked at relevant documentation on SDN (http://sdn.sitecore.net/Reference/Sitecore 6/DMS Documentation.aspx), but so far haven't found a way.
EDIT: Raised this on Sitecore Support Portal - will post an answer once I find out more.
I have done something similar on my project. Check out this code sample and let me know if you have any questions. Also, make sure you add profiles to content items too. Call FilterItemByBehavior on a collection of items and it will filter them based on user's past browsing behavior.
private static Dictionary<string, List<string>> AnalyticsFilter()
{
Dictionary<string, List<string>> filter = new Dictionary<string, List<string>>();
if (Tracker.CurrentVisit.Profiles.Count() > 0)
{
foreach (VisitorDataSet.ProfilesRow row in Tracker.CurrentVisit.Profiles)
{
List<string> keys = new List<string>();
foreach (var key in row.Values)
{
if (key.Value >= ResourceHelper.GetInt(new ID(Resources.Settings.AnalyticsProfileSetMinValGuid)))
keys.Add(key.Key);
}
filter.Add(row.ProfileName, keys);
}
}
if(ResourceHelper.IsTurnedOn(new ID(Resources.Settings.AnalyticsUserProfileEnableSwitch)))
filter = ApplyUserProfile(filter);
return filter;
}
public static List<Item> FilterItemByBehavior(List<Item> items, int count)
{
try
{
var filter = AnalyticsFilter();
foreach (var profile in filter)
{
int counter = ResourceHelper.GetInt(new ID(Resources.Settings.AnalyticsProfileTagsFilterMaxGuid));
if (items.Count <= count) break;
foreach (string key in profile.Value)
{
if (items.Count <= count || counter == 0) break;
items = items.Where(i => (((MultilistField)i.Fields[profile.Key]).GetItems().ToList().Select(x => x.Name).Contains(key))).ToList();
counter--;
}
}
return items.Count <= count ? items : items.Take(count).ToList();
}
catch (System.Exception ex)
{
Sitecore.Diagnostics.Log.Error(ex.Message, ex, new AnalyticsHelper());
return items.Count <= count ? items : items.Take(count).ToList();
}
}
I have received a response from Sitecore support on this question. Here it is:
"If you are using pattern cards for personalzation, then you can use the following code as the event handler for "item selected" event for the dropdown list:"
var profile = Sitecore.Analytics.Tracker.CurrentVisit.GetOrCreateProfile("<Profile Name>");
profile.BeginEdit();
profile.Score("<profile key>",<profile key value you want to set>);
profile.Score("<profile key>",<profile key value you want to set>);
profile.UpdatePattern(); //sets the appropriate pattern based on the current profile keys values you have just set.
profile.EndEdit();
This interferes with automatic profile matching, so I am not sure I want to use this approach.