1 //*********************************************************
3 // Copyright (c) Microsoft. All rights reserved.
4 // THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
5 // ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
6 // IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
7 // PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
9 //*********************************************************
12 // SuspensionManager.cpp
13 // Implementation of the SuspensionManager class
17 #include "SuspensionManager.h"
19 #include <collection.h>
22 using namespace SDKSample::Common;
24 using namespace Concurrency;
25 using namespace Platform;
26 using namespace Platform::Collections;
27 using namespace Windows::Foundation;
28 using namespace Windows::Foundation::Collections;
29 using namespace Windows::Storage;
30 using namespace Windows::Storage::FileProperties;
31 using namespace Windows::Storage::Streams;
32 using namespace Windows::UI::Xaml;
33 using namespace Windows::UI::Xaml::Controls;
34 using namespace Windows::UI::Xaml::Interop;
38 Map<String^, Object^>^ _sessionState = ref new Map<String^, Object^>();
39 String^ sessionStateFilename = "_sessionState.dat";
41 // Forward declarations for object object read / write support
42 void WriteObject(Windows::Storage::Streams::DataWriter^ writer, Platform::Object^ object);
43 Platform::Object^ ReadObject(Windows::Storage::Streams::DataReader^ reader);
47 /// Provides access to global session state for the current session. This state is serialized by
48 /// <see cref="SaveAsync"/> and restored by <see cref="RestoreAsync"/> which require values to be
49 /// one of the following: boxed values including integers, floating-point singles and doubles,
50 /// wide characters, boolean, Strings and Guids, or Map<String^, Object^> where map values are
51 /// subject to the same constraints. Session state should be as compact as possible.
53 IMap<String^, Object^>^ SuspensionManager::SessionState::get(void)
59 /// Wrap a WeakReference as a reference object for use in a collection.
61 private ref class WeakFrame sealed
64 WeakReference _frameReference;
67 WeakFrame(Frame^ frame) { _frameReference = frame; }
68 property Frame^ ResolvedFrame
70 Frame^ get(void) { return _frameReference.Resolve<Frame>(); }
76 std::vector<WeakFrame^> _registeredFrames;
77 DependencyProperty^ FrameSessionStateKeyProperty =
78 DependencyProperty::RegisterAttached("_FrameSessionStateKeyProperty",
79 TypeName(String::typeid), TypeName(SuspensionManager::typeid), nullptr);
80 DependencyProperty^ FrameSessionStateProperty =
81 DependencyProperty::RegisterAttached("_FrameSessionStateProperty",
82 TypeName(IMap<String^, Object^>::typeid), TypeName(SuspensionManager::typeid), nullptr);
86 /// Registers a <see cref="Frame"/> instance to allow its navigation history to be saved to
87 /// and restored from <see cref="SessionState"/>. Frames should be registered once
88 /// immediately after creation if they will participate in session state management. Upon
89 /// registration if state has already been restored for the specified key
90 /// the navigation history will immediately be restored. Subsequent invocations of
91 /// <see cref="RestoreAsync(String)"/> will also restore navigation history.
93 /// <param name="frame">An instance whose navigation history should be managed by
94 /// <see cref="SuspensionManager"/></param>
95 /// <param name="sessionStateKey">A unique key into <see cref="SessionState"/> used to
96 /// store navigation-related information.</param>
97 void SuspensionManager::RegisterFrame(Frame^ frame, String^ sessionStateKey)
99 if (frame->GetValue(FrameSessionStateKeyProperty) != nullptr)
101 throw ref new FailureException("Frames can only be registered to one session state key");
104 if (frame->GetValue(FrameSessionStateProperty) != nullptr)
106 throw ref new FailureException("Frames must be either be registered before accessing frame session state, or not registered at all");
109 // Use a dependency property to associate the session key with a frame, and keep a list of frames whose
110 // navigation state should be managed
111 frame->SetValue(FrameSessionStateKeyProperty, sessionStateKey);
112 _registeredFrames.insert(_registeredFrames.begin(), ref new WeakFrame(frame));
114 // Check to see if navigation state can be restored
115 RestoreFrameNavigationState(frame);
119 /// Disassociates a <see cref="Frame"/> previously registered by <see cref="RegisterFrame"/>
120 /// from <see cref="SessionState"/>. Any navigation state previously captured will be
123 /// <param name="frame">An instance whose navigation history should no longer be
125 void SuspensionManager::UnregisterFrame(Frame^ frame)
127 // Remove session state and remove the frame from the list of frames whose navigation
128 // state will be saved (along with any weak references that are no longer reachable)
129 auto key = safe_cast<String^>(frame->GetValue(FrameSessionStateKeyProperty));
130 if (SessionState->HasKey(key)) SessionState->Remove(key);
131 _registeredFrames.erase(
132 std::remove_if(_registeredFrames.begin(), _registeredFrames.end(), [=](WeakFrame^& e)
134 auto testFrame = e->ResolvedFrame;
135 return testFrame == nullptr || testFrame == frame;
137 _registeredFrames.end()
142 /// Provides storage for session state associated with the specified <see cref="Frame"/>.
143 /// Frames that have been previously registered with <see cref="RegisterFrame"/> have
144 /// their session state saved and restored automatically as a part of the global
145 /// <see cref="SessionState"/>. Frames that are not registered have transient state
146 /// that can still be useful when restoring pages that have been discarded from the
147 /// navigation cache.
149 /// <remarks>Apps may choose to rely on <see cref="LayoutAwarePage"/> to manage
150 /// page-specific state instead of working with frame session state directly.</remarks>
151 /// <param name="frame">The instance for which session state is desired.</param>
152 /// <returns>A collection of state subject to the same serialization mechanism as
153 /// <see cref="SessionState"/>.</returns>
154 IMap<String^, Object^>^ SuspensionManager::SessionStateForFrame(Frame^ frame)
156 auto frameState = safe_cast<IMap<String^, Object^>^>(frame->GetValue(FrameSessionStateProperty));
158 if (frameState == nullptr)
160 auto frameSessionKey = safe_cast<String^>(frame->GetValue(FrameSessionStateKeyProperty));
161 if (frameSessionKey != nullptr)
163 // Registered frames reflect the corresponding session state
164 if (!_sessionState->HasKey(frameSessionKey))
166 _sessionState->Insert(frameSessionKey, ref new Map<String^, Object^>());
168 frameState = safe_cast<IMap<String^, Object^>^>(_sessionState->Lookup(frameSessionKey));
172 // Frames that aren't registered have transient state
173 frameState = ref new Map<String^, Object^>();
175 frame->SetValue(FrameSessionStateProperty, frameState);
180 void SuspensionManager::RestoreFrameNavigationState(Frame^ frame)
182 auto frameState = SessionStateForFrame(frame);
183 if (frameState->HasKey("Navigation"))
185 frame->SetNavigationState(safe_cast<String^>(frameState->Lookup("Navigation")));
189 void SuspensionManager::SaveFrameNavigationState(Frame^ frame)
191 auto frameState = SessionStateForFrame(frame);
192 frameState->Insert("Navigation", frame->GetNavigationState());
196 /// Save the current <see cref="SessionState"/>. Any <see cref="Frame"/> instances
197 /// registered with <see cref="RegisterFrame"/> will also preserve their current
198 /// navigation stack, which in turn gives their active <see cref="Page"/> an opportunity
199 /// to save its state.
201 /// <returns>An asynchronous task that reflects when session state has been saved.</returns>
202 task<void> SuspensionManager::SaveAsync(void)
204 // Save the navigation state for all registered frames
205 for (auto&& weakFrame : _registeredFrames)
207 auto frame = weakFrame->ResolvedFrame;
208 if (frame != nullptr) SaveFrameNavigationState(frame);
211 // Serialize the session state synchronously to avoid asynchronous access to shared
213 auto sessionData = ref new InMemoryRandomAccessStream();
214 auto sessionDataWriter = ref new DataWriter(sessionData->GetOutputStreamAt(0));
215 WriteObject(sessionDataWriter, _sessionState);
217 // Once session state has been captured synchronously, begin the asynchronous process
218 // of writing the result to disk
219 return task<unsigned int>(sessionDataWriter->StoreAsync()).then([=](unsigned int)
221 return sessionDataWriter->FlushAsync();
222 }).then([=](bool flushSucceeded)
224 (void)flushSucceeded; // Unused parameter
225 return ApplicationData::Current->LocalFolder->CreateFileAsync(sessionStateFilename,
226 CreationCollisionOption::ReplaceExisting);
227 }).then([=](StorageFile^ createdFile)
229 return createdFile->OpenAsync(FileAccessMode::ReadWrite);
230 }).then([=](IRandomAccessStream^ newStream)
232 return RandomAccessStream::CopyAndCloseAsync(
233 sessionData->GetInputStreamAt(0), newStream->GetOutputStreamAt(0));
234 }).then([=](UINT64 copiedBytes)
236 (void)copiedBytes; // Unused parameter
242 /// Restores previously saved <see cref="SessionState"/>. Any <see cref="Frame"/> instances
243 /// registered with <see cref="RegisterFrame"/> will also restore their prior navigation
244 /// state, which in turn gives their active <see cref="Page"/> an opportunity restore its
247 /// <param name="version">A version identifer compared to the session state to prevent
248 /// incompatible versions of session state from reaching app code. Saved state with a
249 /// different version will be ignored, resulting in an empty <see cref="SessionState"/>
250 /// dictionary.</param>
251 /// <returns>An asynchronous task that reflects when session state has been read. The
252 /// content of <see cref="SessionState"/> should not be relied upon until this task
253 /// completes.</returns>
254 task<void> SuspensionManager::RestoreAsync(void)
256 _sessionState->Clear();
258 task<StorageFile^> getFileTask(ApplicationData::Current->LocalFolder->GetFileAsync(sessionStateFilename));
259 return getFileTask.then([=](StorageFile^ stateFile)
261 task<BasicProperties^> getBasicPropertiesTask(stateFile->GetBasicPropertiesAsync());
262 return getBasicPropertiesTask.then([=](BasicProperties^ stateFileProperties)
264 auto size = unsigned int(stateFileProperties->Size);
265 if (size != stateFileProperties->Size) throw ref new FailureException("Session state larger than 4GB");
266 task<IRandomAccessStreamWithContentType^> openReadTask(stateFile->OpenReadAsync());
267 return openReadTask.then([=](IRandomAccessStreamWithContentType^ stateFileStream)
269 auto stateReader = ref new DataReader(stateFileStream);
270 return task<unsigned int>(stateReader->LoadAsync(size)).then([=](unsigned int bytesRead)
272 (void)bytesRead; // Unused parameter
273 // Deserialize the Session State
274 Object^ content = ReadObject(stateReader);
275 _sessionState = (Map<String^, Object^>^)content;
277 // Restore any registered frames to their saved state
278 for (auto&& weakFrame : _registeredFrames)
280 auto frame = weakFrame->ResolvedFrame;
281 if (frame != nullptr)
283 frame->ClearValue(FrameSessionStateProperty);
284 RestoreFrameNavigationState(frame);
287 }, task_continuation_context::use_current());
293 #pragma region Object serialization for a known set of types
297 // Codes used for identifying serialized types
301 // Supported IPropertyValue types
302 UInt8Type, UInt16Type, UInt32Type, UInt64Type, Int16Type, Int32Type, Int64Type,
303 SingleType, DoubleType, BooleanType, Char16Type, GuidType, StringType,
305 // Additional supported types
306 StringToObjectMapType,
308 // Marker values used to ensure stream integrity
312 void WriteString(DataWriter^ writer, String^ string)
314 writer->WriteByte(StringType);
315 writer->WriteUInt32(writer->MeasureString(string));
316 writer->WriteString(string);
319 void WriteProperty(DataWriter^ writer, IPropertyValue^ propertyValue)
321 switch (propertyValue->Type)
323 case PropertyType::UInt8:
324 writer->WriteByte(UInt8Type);
325 writer->WriteByte(propertyValue->GetUInt8());
327 case PropertyType::UInt16:
328 writer->WriteByte(UInt16Type);
329 writer->WriteUInt16(propertyValue->GetUInt16());
331 case PropertyType::UInt32:
332 writer->WriteByte(UInt32Type);
333 writer->WriteUInt32(propertyValue->GetUInt32());
335 case PropertyType::UInt64:
336 writer->WriteByte(UInt64Type);
337 writer->WriteUInt64(propertyValue->GetUInt64());
339 case PropertyType::Int16:
340 writer->WriteByte(Int16Type);
341 writer->WriteUInt16(propertyValue->GetInt16());
343 case PropertyType::Int32:
344 writer->WriteByte(Int32Type);
345 writer->WriteUInt32(propertyValue->GetInt32());
347 case PropertyType::Int64:
348 writer->WriteByte(Int64Type);
349 writer->WriteUInt64(propertyValue->GetInt64());
351 case PropertyType::Single:
352 writer->WriteByte(SingleType);
353 writer->WriteSingle(propertyValue->GetSingle());
355 case PropertyType::Double:
356 writer->WriteByte(DoubleType);
357 writer->WriteDouble(propertyValue->GetDouble());
359 case PropertyType::Boolean:
360 writer->WriteByte(BooleanType);
361 writer->WriteBoolean(propertyValue->GetBoolean());
363 case PropertyType::Char16:
364 writer->WriteByte(Char16Type);
365 writer->WriteUInt16(propertyValue->GetChar16());
367 case PropertyType::Guid:
368 writer->WriteByte(GuidType);
369 writer->WriteGuid(propertyValue->GetGuid());
371 case PropertyType::String:
372 WriteString(writer, propertyValue->GetString());
375 throw ref new InvalidArgumentException("Unsupported property type");
379 void WriteStringToObjectMap(DataWriter^ writer, IMap<String^, Object^>^ map)
381 writer->WriteByte(StringToObjectMapType);
382 writer->WriteUInt32(map->Size);
383 for (auto&& pair : map)
385 WriteObject(writer, pair->Key);
386 WriteObject(writer, pair->Value);
388 writer->WriteByte(MapEndMarker);
391 void WriteObject(DataWriter^ writer, Object^ object)
393 if (object == nullptr)
395 writer->WriteByte(NullPtrType);
399 auto propertyObject = dynamic_cast<IPropertyValue^>(object);
400 if (propertyObject != nullptr)
402 WriteProperty(writer, propertyObject);
406 auto mapObject = dynamic_cast<IMap<String^, Object^>^>(object);
407 if (mapObject != nullptr)
409 WriteStringToObjectMap(writer, mapObject);
413 throw ref new InvalidArgumentException("Unsupported data type");
416 String^ ReadString(DataReader^ reader)
418 int length = reader->ReadUInt32();
419 String^ string = reader->ReadString(length);
423 IMap<String^, Object^>^ ReadStringToObjectMap(DataReader^ reader)
425 auto map = ref new Map<String^, Object^>();
426 auto size = reader->ReadUInt32();
427 for (unsigned int index = 0; index < size; index++)
429 auto key = safe_cast<String^>(ReadObject(reader));
430 auto value = ReadObject(reader);
431 map->Insert(key, value);
433 if (reader->ReadByte() != MapEndMarker)
435 throw ref new InvalidArgumentException("Invalid stream");
440 Object^ ReadObject(DataReader^ reader)
442 auto type = reader->ReadByte();
448 return reader->ReadByte();
450 return reader->ReadUInt16();
452 return reader->ReadUInt32();
454 return reader->ReadUInt64();
456 return reader->ReadInt16();
458 return reader->ReadInt32();
460 return reader->ReadInt64();
462 return reader->ReadSingle();
464 return reader->ReadDouble();
466 return reader->ReadBoolean();
468 return (char16_t)reader->ReadUInt16();
470 return reader->ReadGuid();
472 return ReadString(reader);
473 case StringToObjectMapType:
474 return ReadStringToObjectMap(reader);
476 throw ref new InvalidArgumentException("Unsupported property type");