How to get last location on Glass reliably? - google-glass

Method getLastLocation() from LocationManager often return null and it's quite tricky to select best provider. The documentation says:
Warning: Do not use the LocationManager.getBestProvider() method or the constants GPS_PROVIDER or NETWORK_PROVIDER to listen for location updates. Glass uses a dynamic set of providers and listening only to a single provider may cause your application to miss location updates.
How to get best last location?

Because Glass uses dynamic set of providers, you need to get location from all of them and select the location with best accuracy:
public static Location getLastLocation(Context context) {
LocationManager manager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
Criteria criteria = new Criteria();
criteria.setAccuracy(Criteria.NO_REQUIREMENT);
List<String> providers = manager.getProviders(criteria, true);
List<Location> locations = new ArrayList<Location>();
for (String provider : providers) {
Location location = manager.getLastKnownLocation(provider);
if (location != null && location.getAccuracy()!=0.0) {
locations.add(location);
}
}
Collections.sort(locations, new Comparator<Location>() {
#Override
public int compare(Location location, Location location2) {
return (int) (location.getAccuracy() - location2.getAccuracy());
}
});
if (locations.size() > 0) {
return locations.get(0);
}
return null;
}

Destil's answer above correctly handles the case where at least one provider returns a valid location for getLastKnownLocation().
However, I've also seen Glass return null for getLastKnownLocation() for all providers (in XE16 in particular).
In this case, your only option is to register a LocationListener and wait for a new location update.
For example, in context of getting a location when creating a new Activity, it would look like the following:
public class MyActivity extends Activity implements LocationListener {
...
LocationManager mLocationManager;
Location mLastKnownLocation;
#Override
protected void onCreate(Bundle savedInstanceState) {
// Activity setup
...
// Use Destil's answer to get last known location, using all providers
mLastKnownLocation = getLastLocation(this);
if (mLastKnownLocation != null) {
// Do something with location
doSomethingWithLocation(mLastKnownLocation);
} else {
// All providers returned null - start a LocationListener to force a refresh of location
mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
List<String> providers = mLocationManager.getProviders(true);
for (Iterator<String> i = providers.iterator(); i.hasNext(); ) {
mLocationManager.requestLocationUpdates(i.next(), 0, 0, this);
}
}
...
}
...
}
You'll then need to handle the LocationListener callbacks:
#Override
public void onLocationChanged(Location location) {
if (mLastKnownLocation == null) {
// At least one location should be available now
// Use Destil's answer to get last known location again, using all providers
mLastKnownLocation = getLastLocation(this);
if (mLastKnownLocation == null) {
// This shouldn't happen if LocationManager is saving locations correctly, but if it does, use the location that was just passed in
mLastKnownLocation = location;
}
// Stop listening for updates
mLocationManager.removeUpdates(this);
// Do something with location
doSomethingWithLocation(mLastKnownLocation);
}
}
#Override
public void onStatusChanged(String provider, int status, Bundle extras) {}
#Override
public void onProviderEnabled(String provider) {}
#Override
public void onProviderDisabled(String provider) {}
It can be a little tricky to change to the asynchronous model to avoid blocking the UI thread while waiting for the update, and it may require moving some of your app logic around.

The code in the answer should be adjusted to handle an accuracy of "0.0" which represents "NO Accuracy" known!
Here is an alternative that includes this adjustment
public static Location getLastLocation(Context context) {
Location result = null;
LocationManager locationManager;
Criteria locationCriteria;
List<String> providers;
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
locationCriteria = new Criteria();
locationCriteria.setAccuracy(Criteria.NO_REQUIREMENT);
providers = locationManager.getProviders(locationCriteria, true);
// Note that providers = locatoinManager.getAllProviders(); is not used because the
// list might contain disabled providers or providers that are not allowed to be called.
//Note that getAccuracy can return 0, indicating that there is no known accuracy.
for (String provider : providers) {
Location location = locationManager.getLastKnownLocation(provider);
if (result == null) {
result = location;
}
else if (result.getAccuracy() == 0.0) {
if (location.getAccuracy() != 0.0) {
result = location;
break;
} else {
if (result.getAccuracy() > location.getAccuracy()) {
result = location;
}
}
}
}
return result;
}

Related

how to collect sitecore information for anonymous

I need to get sitecore information that collected from anonymous users to give him availability to export it or opt out - [GDPR]
any idea about contact ID for anonymous !
The way of doing it is dependent on the sitecore version.
Sitcore 9 you can use right to be forgotten
Sitecore 8+ you have to implement the feature from scratch.
Regarding Anonymous user - If the user is really anonymous, then you done need to worry about the GDPR (my view). But sometimes we map user email and sensitive personal info to anonymous user by using forms or WFFM. You can use email address of that user to query xDB (Contact Identifiers) to get the contact and contactID. Then reset informations.
Also: please note that based on WFFFM save action config, anonymous user will store in Core DB and Contact List.
To forget a user, you can use the following code. It will execute the ExecuteRightToBeForgotten function on the contact and scrub their data.
Forget User
public bool ForgetUser()
{
var id = _contactIdentificationRepository.GetContactId();
if (id == null)
{
return false;
}
var contactReference = new IdentifiedContactReference(id.Source, id.Identifier);
using (var client = _contactIdentificationRepository.CreateContext())
{
var contact = client.Get(contactReference, new ContactExpandOptions());
if (contact != null)
{
client.ExecuteRightToBeForgotten(contact);
client.Submit();
}
}
return false;
}
Fake up some data
public void FakeUserInfo()
{
var contactReference = _contactIdentificationRepository.GetContactReference();
using (var client = SitecoreXConnectClientConfiguration.GetClient())
{
// we can have 1 to many facets
// PersonalInformation.DefaultFacetKey
// EmailAddressList.DefaultFacetKey
// Avatar.DefaultFacetKey
// PhoneNumberList.DefaultFacetKey
// AddressList.DefaultFacetKey
// plus custom ones
var facets = new List<string> { PersonalInformation.DefaultFacetKey };
// get the contact
var contact = client.Get(contactReference, new ContactExpandOptions(facets.ToArray()));
// pull the facet from the contact (if it exists)
var facet = contact.GetFacet<PersonalInformation>(PersonalInformation.DefaultFacetKey);
// if it exists, change it, else make a new one
if (facet != null)
{
facet.FirstName = $"Myrtle-{DateTime.Now.Date.ToString(CultureInfo.InvariantCulture)}";
facet.LastName = $"McSitecore-{DateTime.Now.Date.ToString(CultureInfo.InvariantCulture)}";
// set the facet on the client connection
client.SetFacet(contact, PersonalInformation.DefaultFacetKey, facet);
}
else
{
// make a new one
var personalInfoFacet = new PersonalInformation()
{
FirstName = "Myrtle",
LastName = "McSitecore"
};
// set the facet on the client connection
client.SetFacet(contact, PersonalInformation.DefaultFacetKey, personalInfoFacet);
}
if (contact != null)
{
// submit the changes to xConnect
client.Submit();
// reset the contact
_contactIdentificationRepository.Manager.RemoveFromSession(Analytics.Tracker.Current.Contact.ContactId);
Analytics.Tracker.Current.Session.Contact = _contactIdentificationRepository.Manager.LoadContact(Analytics.Tracker.Current.Contact.ContactId);
}
}
}
ContactIdentificationRepository
using System.Linq;
using Sitecore.Analytics;
using Sitecore.Analytics.Model;
using Sitecore.Analytics.Tracking;
using Sitecore.Configuration;
using Sitecore.XConnect;
using Sitecore.XConnect.Client.Configuration;
namespace Sitecore.Foundation.Accounts.Repositories
{
public class ContactIdentificationRepository
{
private readonly ContactManager contactManager;
public ContactManager Manager => contactManager;
public ContactIdentificationRepository()
{
contactManager = Factory.CreateObject("tracking/contactManager", true) as ContactManager;
}
public IdentifiedContactReference GetContactReference()
{
// get the contact id from the current contact
var id = GetContactId();
// if the contact is new or has no identifiers
var anon = Tracker.Current.Contact.IsNew || Tracker.Current.Contact.Identifiers.Count == 0;
// if the user is anon, get the xD.Tracker identifier, else get the one we found
return anon
? new IdentifiedContactReference(Sitecore.Analytics.XConnect.DataAccess.Constants.IdentifierSource, Tracker.Current.Contact.ContactId.ToString("N"))
: new IdentifiedContactReference(id.Source, id.Identifier);
}
public Analytics.Model.Entities.ContactIdentifier GetContactId()
{
if (Tracker.Current?.Contact == null)
{
return null;
}
if (Tracker.Current.Contact.IsNew)
{
// write the contact to xConnect so we can work with it
this.SaveContact();
}
return Tracker.Current.Contact.Identifiers.FirstOrDefault();
}
public void SaveContact()
{
// we need the contract to be saved to xConnect. It is only in session now
Tracker.Current.Contact.ContactSaveMode = ContactSaveMode.AlwaysSave;
this.contactManager.SaveContactToCollectionDb(Tracker.Current.Contact);
}
public IXdbContext CreateContext()
{
return SitecoreXConnectClientConfiguration.GetClient();
}
}
}

jetty 9.4 share sessions among different contexts

I recently upgraded from jetty 9.3.11 to 9.4.6 . Since 9.4.x does not support HashSessionManager, I created my own custom SessionHandler. But when i attach this SessionHandler to the WebAppContext then the context becomes null when trying to access from servlets. there are no errors thrown in the logs.
Relevant section of code:
MyCustomSessionHandler sessionHandler = new MyCustomSessionHandler();
HandlerCollection handlers_ = new HandlerCollection(true);
COntextHandlerCollection chc_ = new ContextHandlerCollection();
for(WebAppConfig wap: webAppConfigs) //webappconfig a POJO from where I am getting webapp configs
{
String path = wap.getPath();
String warFile = wap.getWarFile();
WebAppContext context =
new WebAppContext(chc_, warFile, path);
// context.setSessionHandler(new SessionHandler()); // this one works.
context.setSessionHandler(sessionHandler); // this one doesnt work.
for (ServletConfig servletConfig: wap.getServletConfigs()) //ServletConfig is another POJO to get servlet configs
{
String servletName = servletConfig.getName();
String servletPath = servletConfig.getPath();
Servlet servlet = servletConfig.getServlet();
ServletHolder servletHolder = new ServletHolder(servlet);
context.addServlet(servletHolder, servletPath);
}
}
handlers_.setHandlers(new Handler[] { chc_, new DefaultHandler()});
server_.setHandler(handlers_);
Sample of my custom Session handler
public class MyCUstomSessionHandler extends SessionHandler
{
public MyCustomSessionHandler()
{
super();
}
public void setSecureCookies(boolean secureCookies)
{
getSessionCookieConfig().setSecure(secureCookies);
}
public void setHttpOnly(boolean httpOnly)
{
getSessionCookieConfig().setHttpOnly(httpOnly);
}
public void setMaxCookieAge(int age)
{
getSessionCookieConfig().setMaxAge(age);
}
}
Further clarification: It happens because I create a singleton sessionhandler and share it across different WepAppContext as a way of sharing sessions among them. This method seemed work fine without issues in 9.3 but doesn't work with new session management in 9.4.
Any help to solve this problem is appreciated.
I solved it by
setting cookie path to root ("/")
extending the getSession() function of SessionHandler to loop through all the contexts to check if session is created for the cookie in any other context.
/* check all contexts for sessions*/
public Session getSession(String id)
{
Session session = getLocalSession(id);
if (session == null)
{
for (SessionHandler manager: getSessionIdManager().getSessionHandlers())
{
if (manager.equals(this) ||
!(manager instanceof CustomSessionHandler))
{
continue;
}
session = ((CustomSessionHandler)manager).getLocalSession(id);
if (session != null)
{
break;
}
}
// should we duplicate sessions in each context?
// will we end up with inconsistent sessions?
/*
if (externalSession != null)
{
try
{
getSessionCache().put(id, externalSession);
}
catch (Exception e)
{
LOG.warn("Unable to save session to local cache.");
}
}
*/
}
return session;
}
/* ------------------------------------------------------------ */
/**
* Get a known existing session
* #param id The session ID stripped of any worker name.
* #return A Session or null if none exists.
*/
public Session getLocalSession(String id)
{
return super.getSession(id);
}

How to enable VersionCountDisabler for Glass Mapper in Sitecore for SitecoreQuery and SitecoreChildren attributes

The glass mapper will return null object or (no items) for SitecoreQuery and SitecoreChildren attribute that are placed on the GlassModels. These attributes don't take any such parameter where I can specify them to return items if they don't exist in the the context lanaguge. The items e.g. exist in EN but don't exist in en-ES. I need to put a lot of null check in my views to avoid Null exception and makes the views or controller very messy. It is lot of boiler plate code that one has to write to make it work.
In Page Editor the SitecoreChildren returns item and content authors can create items in that langauge version by editing any field on the item. This automatically creates the item in that langauge. However the same code will fail in Preview mode as SitecoreChidren will return null and you see null pointer exception.
SitecoreQuery doesn't return any items in page editor and then Content Authors wont be able to create items in Page editor.
To make the experience good if we can pass a parameter to SiteocreQuery attribute so it disable VsersionCount and returns the items if they dont exist in that langauge.
This is actually not possible. There is an issue on GitHub which would make it easy to create a custom attribute to handle this very easy. Currently you need to create a new type mapper and copy all the code from the SitecoreQueryMapper. I have written a blog post here about how you can create a custom type mapper. You need to create the following classes (example for the SitecoreQuery).
New configuration:
public class SitecoreSharedQueryConfiguration : SitecoreQueryConfiguration
{
}
New attribute:
public class SitecoreSharedQueryAttribute : SitecoreQueryAttribute
{
public SitecoreSharedQueryAttribute(string query) : base(query)
{
}
public override AbstractPropertyConfiguration Configure(PropertyInfo propertyInfo)
{
var config = new SitecoreSharedQueryConfiguration();
this.Configure(propertyInfo, config);
return config;
}
}
New type mapper:
public class SitecoreSharedQueryTypeMapper : SitecoreQueryMapper
{
public SitecoreSharedQueryTypeMapper(IEnumerable<ISitecoreQueryParameter> parameters)
: base(parameters)
{
}
public override object MapToProperty(AbstractDataMappingContext mappingContext)
{
var scConfig = Configuration as SitecoreQueryConfiguration;
var scContext = mappingContext as SitecoreDataMappingContext;
using (new VersionCountDisabler())
{
if (scConfig != null && scContext != null)
{
string query = this.ParseQuery(scConfig.Query, scContext.Item);
if (scConfig.PropertyInfo.PropertyType.IsGenericType)
{
Type outerType = Glass.Mapper.Sc.Utilities.GetGenericOuter(scConfig.PropertyInfo.PropertyType);
if (typeof(IEnumerable<>) == outerType)
{
Type genericType = Utilities.GetGenericArgument(scConfig.PropertyInfo.PropertyType);
Func<IEnumerable<Item>> getItems;
if (scConfig.IsRelative)
{
getItems = () =>
{
try
{
return scContext.Item.Axes.SelectItems(query);
}
catch (Exception ex)
{
throw new MapperException("Failed to perform query {0}".Formatted(query), ex);
}
};
}
else
{
getItems = () =>
{
if (scConfig.UseQueryContext)
{
var conQuery = new Query(query);
var queryContext = new QueryContext(scContext.Item.Database.DataManager);
object obj = conQuery.Execute(queryContext);
var contextArray = obj as QueryContext[];
var context = obj as QueryContext;
if (contextArray == null)
contextArray = new[] { context };
return contextArray.Select(x => scContext.Item.Database.GetItem(x.ID));
}
return scContext.Item.Database.SelectItems(query);
};
}
return Glass.Mapper.Sc.Utilities.CreateGenericType(typeof(ItemEnumerable<>), new[] { genericType }, getItems, scConfig.IsLazy, scConfig.InferType, scContext.Service);
}
throw new NotSupportedException("Generic type not supported {0}. Must be IEnumerable<>.".Formatted(outerType.FullName));
}
{
Item result;
if (scConfig.IsRelative)
{
result = scContext.Item.Axes.SelectSingleItem(query);
}
else
{
result = scContext.Item.Database.SelectSingleItem(query);
}
return scContext.Service.CreateType(scConfig.PropertyInfo.PropertyType, result, scConfig.IsLazy, scConfig.InferType, null);
}
}
}
return null;
}
public override bool CanHandle(AbstractPropertyConfiguration configuration, Context context)
{
return configuration is SitecoreSharedQueryConfiguration;
}
}
And configure the new type mapper in your glass config (mapper and parameters for the constructor):
container.Register(Component.For<AbstractDataMapper>().ImplementedBy<SitecoreSharedQueryTypeMapper>().LifeStyle.Transient);
container.Register(Component.For<IEnumerable<ISitecoreQueryParameter>>().ImplementedBy<List<ItemPathParameter>>().LifeStyle.Transient);
container.Register(Component.For<IEnumerable<ISitecoreQueryParameter>>().ImplementedBy<List<ItemIdParameter>>().LifeStyle.Transient);
container.Register(Component.For<IEnumerable<ISitecoreQueryParameter>>().ImplementedBy<List<ItemIdNoBracketsParameter>>().LifeStyle.Transient);
container.Register(Component.For<IEnumerable<ISitecoreQueryParameter>>().ImplementedBy<List<ItemEscapedPathParameter>>().LifeStyle.Transient);
container.Register(Component.For<IEnumerable<ISitecoreQueryParameter>>().ImplementedBy<List<ItemDateNowParameter>>().LifeStyle.Transient);
You can then simply change the SitecoreQuery attribute on your model to SitecoreSharedQuery:
[SitecoreSharedQuery("./*")]
public virtual IEnumerable<YourModel> YourItems { get; set; }
For the children you could either use the shared query mapper and querying the children or create the same classes for a new SitecoreSharedChildren query.
Edit: Added bindings for IEnumerable<ISitecoreQueryParameter> as they are missing and therefor it threw an error.

Change Activity into Fragment

I am quite new at Android.
So I am a bit confused of working with fragments.
I have found a very great tutorial.
So I have working code. But it is the layout oft a normal activity.
Then I tried to include it into a navigation drawer.
So the list view with data will only be shown when the menu item has been selected.
On the fragment View there is a never ending loading Dialog.
While debugging I have figured out that the code loads still the data and inserts it into feedItems.
So feedItems is filled correctly.
Now after listAdapter.notifyDataSetChanged() there happens nothing.
So here that is my code:
public class FragmentNews extends ListFragment {
private static final String TAG = FragmentNews.class.getSimpleName();
private ListView listView;
private FeedListAdapter listAdapter;
private List<FeedItem> feedItems;
private String URL_FEED = "http://address.com";
public FragmentNews(){}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onActivityCreated(savedInstanceState);
loadDataForNews();
}
private void loadDataForNews(){
listView = this.getListView();
feedItems = new ArrayList<FeedItem>();
listAdapter = new FeedListAdapter(getActivity(), feedItems);
listView.setAdapter(listAdapter);
// We first check for cached request
Cache cache = AppController.getInstance().getRequestQueue().getCache();
Entry entry = cache.get(URL_FEED);
if (entry != null) {
// fetch the data from cache
try {
String data = new String(entry.data, "UTF-8");
try {
parseJsonFeed(new JSONObject(data));
} catch (JSONException e) {
e.printStackTrace();
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
} else {
// making fresh volley request and getting json
JsonObjectRequest jsonReq = new JsonObjectRequest(Method.GET,
URL_FEED, null, new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
VolleyLog.d(TAG, "Response: " + response.toString());
if (response != null) {
parseJsonFeed(response);
}
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
VolleyLog.d(TAG, "Error: " + error.getMessage());
}
});
// Adding request to volley request queue
AppController.getInstance().addToRequestQueue(jsonReq);
}
}
// List View Feed
private void parseJsonFeed(JSONObject response) {
try {
JSONArray feedArray = response.getJSONArray("feed");
for (int i = 0; i < feedArray.length(); i++) {
JSONObject feedObj = (JSONObject) feedArray.get(i);
FeedItem item = new FeedItem();
item.setId(feedObj.getInt("id"));
item.setName(feedObj.getString("name"));
// Image might be null sometimes
String image = feedObj.isNull("image") ? null : feedObj
.getString("image");
item.setImge(image);
item.setStatus(feedObj.getString("status"));
item.setProfilePic(feedObj.getString("profilePic"));
item.setTimeStamp(feedObj.getString("timeStamp"));
// url might be null sometimes
String feedUrl = feedObj.isNull("url") ? null : feedObj
.getString("url");
item.setUrl(feedUrl);
feedItems.add(item);
}
// notify data changes to list adapater
listAdapter.notifyDataSetChanged();
} catch (JSONException e) {
e.printStackTrace();
}
}
}
Can the problem be that the inflater of listAdapter is null?
Thanks for help!
Sometimes listAdapter.notifyDataSetChanged() does not work properly.
Try removing
listAdapter = new FeedListAdapter(getActivity(), feedItems);
listView.setAdapter(listAdapter);
from loadDataForNews() and adding in
place of listAdapter.notifyDataSetChanged();

determine roles that have access to an item in Sitecore - in code

Sitecore 6.5 system:
In code, is there a way to determine which roles have access to a specific item?
I have an extranet set up, some items are "protected" - meaning the anonymous account has had the inheritance broken, and certain roles have been granted read access. I already have a custom pipeline built, so I can determine when user attempts to view a protected item, but I need to determine what role(s) has access to view the item so I can direct them appropriately.
Thanks,
Thad
edit:
Here is the relevant code - there may be a better way of doing this (I inherited the system & code), but I am attempting to utilize what was already in place. The problem with the code below is that Sitecore.Context.Item is null when the user doesn't have permission.
public class NotFoundProcessor : Sitecore.Pipelines.HttpRequest.HttpRequestProcessor
{
public override void Process(Sitecore.Pipelines.HttpRequest.HttpRequestArgs args)
{
if (args.PermissionDenied)
{
//determine what role would give the user access
foreach(Sitecore.Security.Accounts.Role role in Sitecore.Security.Accounts.RolesInRolesManager.GetAllRoles())
{
bool roleCanRead = Sitecore.Context.Item.Security.CanRead(role);
//... do stuff here
}
}
}
}
Just from the top of my head, you can check all the roles if they have read access to chosen item:
foreach (Role role in RolesInRolesManager.GetAllRoles())
{
bool roleCanRead = item.Security.CanRead(role);
}
EDIT after code sample provided:
You need to try to resolve the item in the same way as ItemResolver does but wrapped it with SecurityDisabler and without setting it to Sitecore Context afterwards. This will allow you to find the requested item despite the fact the user doesn't have access to it:
public class NotFoundProcessor : HttpRequestProcessor
{
public override void Process(HttpRequestArgs args)
{
if (args.PermissionDenied)
{
Item item = GetItemUsingSecurityDisabler(args);
//determine what role would give the user access
foreach(Role role in RolesInRolesManager.GetAllRoles())
{
bool roleCanRead = item.Security.CanRead(role);
//... do stuff here
}
}
}
}
private Item GetItemUsingSecurityDisabler(HttpRequestArgs args)
{
using (new SecurityDisabler())
{
string path = MainUtil.DecodeName(args.Url.ItemPath);
Item item = args.GetItem(path);
if (item == null)
{
path = args.Url.ItemPath;
item = args.GetItem(path);
}
if (item == null)
{
path = args.LocalPath;
item = args.GetItem(path);
}
if (item == null)
{
path = MainUtil.DecodeName(args.LocalPath);
item = args.GetItem(path);
}
SiteContext site = Sitecore.Context.Site;
string rootPath = site != null ? site.RootPath : string.Empty;
if (item == null)
{
path = FileUtil.MakePath(rootPath, args.LocalPath, '/');
item = args.GetItem(path);
}
if (item == null)
{
path = MainUtil.DecodeName(FileUtil.MakePath(rootPath, args.LocalPath, '/'));
item = args.GetItem(path);
}
// I've ommited resolving item using DisplayName but you can add if necessary here
return item;
}
}