1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.os;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.SystemApi;
22 import android.annotation.WorkerThread;
23 import android.content.res.AssetFileDescriptor;
24 import android.os.IUpdateEngine;
25 import android.os.IUpdateEngineCallback;
26 import android.os.RemoteException;
27 
28 /**
29  * UpdateEngine handles calls to the update engine which takes care of A/B OTA
30  * updates. It wraps up the update engine Binder APIs and exposes them as
31  * SystemApis, which will be called by the system app responsible for OTAs.
32  * On a Google device, this will be GmsCore.
33  *
34  * The minimal flow is:
35  * <ol>
36  * <li>Create a new UpdateEngine instance.
37  * <li>Call {@link #bind}, optionally providing callbacks.
38  * <li>Call {@link #applyPayload}.
39  * </ol>
40  *
41  * In addition, methods are provided to {@link #cancel} or
42  * {@link #suspend}/{@link #resume} application of an update.
43  *
44  * The APIs defined in this class and UpdateEngineCallback class must be in
45  * sync with the ones in
46  * {@code system/update_engine/binder_bindings/android/os/IUpdateEngine.aidl}
47  * and
48  * {@code system/update_engine/binder_bindings/android/os/IUpdateEngineCallback.aidl}.
49  *
50  * {@hide}
51  */
52 @SystemApi
53 public class UpdateEngine {
54     private static final String TAG = "UpdateEngine";
55 
56     private static final String UPDATE_ENGINE_SERVICE = "android.os.UpdateEngineService";
57 
58     /**
59      * Error codes from update engine upon finishing a call to
60      * {@link applyPayload}. Values will be passed via the callback function
61      * {@link UpdateEngineCallback#onPayloadApplicationComplete}. Values must
62      * agree with the ones in {@code system/update_engine/common/error_code.h}.
63      */
64     public static final class ErrorCodeConstants {
65         /**
66          * Error code: a request finished successfully.
67          */
68         public static final int SUCCESS = 0;
69         /**
70          * Error code: a request failed due to a generic error.
71          */
72         public static final int ERROR = 1;
73         /**
74          * Error code: an update failed to apply due to filesystem copier
75          * error.
76          */
77         public static final int FILESYSTEM_COPIER_ERROR = 4;
78         /**
79          * Error code: an update failed to apply due to an error in running
80          * post-install hooks.
81          */
82         public static final int POST_INSTALL_RUNNER_ERROR = 5;
83         /**
84          * Error code: an update failed to apply due to a mismatching payload.
85          *
86          * <p>For example, the given payload uses a feature that's not
87          * supported by the current update engine.
88          */
89         public static final int PAYLOAD_MISMATCHED_TYPE_ERROR = 6;
90         /**
91          * Error code: an update failed to apply due to an error in opening
92          * devices.
93          */
94         public static final int INSTALL_DEVICE_OPEN_ERROR = 7;
95         /**
96          * Error code: an update failed to apply due to an error in opening
97          * kernel device.
98          */
99         public static final int KERNEL_DEVICE_OPEN_ERROR = 8;
100         /**
101          * Error code: an update failed to apply due to an error in fetching
102          * the payload.
103          *
104          * <p>For example, this could be a result of bad network connection
105          * when streaming an update.
106          */
107         public static final int DOWNLOAD_TRANSFER_ERROR = 9;
108         /**
109          * Error code: an update failed to apply due to a mismatch in payload
110          * hash.
111          *
112          * <p>Update engine does validity checks for the given payload and its
113          * metadata.
114          */
115         public static final int PAYLOAD_HASH_MISMATCH_ERROR = 10;
116 
117         /**
118          * Error code: an update failed to apply due to a mismatch in payload
119          * size.
120          */
121         public static final int PAYLOAD_SIZE_MISMATCH_ERROR = 11;
122 
123         /**
124          * Error code: an update failed to apply due to failing to verify
125          * payload signatures.
126          */
127         public static final int DOWNLOAD_PAYLOAD_VERIFICATION_ERROR = 12;
128 
129         /**
130          * Error code: an update failed to apply due to a downgrade in payload
131          * timestamp.
132          *
133          * <p>The timestamp of a build is encoded into the payload, which will
134          * be enforced during install to prevent downgrading a device.
135          */
136         public static final int PAYLOAD_TIMESTAMP_ERROR = 51;
137 
138         /**
139          * Error code: an update has been applied successfully but the new slot
140          * hasn't been set to active.
141          *
142          * <p>It indicates a successful finish of calling {@link #applyPayload} with
143          * {@code SWITCH_SLOT_ON_REBOOT=0}. See {@link #applyPayload}.
144          */
145         public static final int UPDATED_BUT_NOT_ACTIVE = 52;
146 
147         /**
148          * Error code: there is not enough space on the device to apply the update. User should
149          * be prompted to free up space and re-try the update.
150          *
151          * <p>See {@link UpdateEngine#allocateSpace}.
152          */
153         public static final int NOT_ENOUGH_SPACE = 60;
154 
155         /**
156          * Error code: the device is corrupted and no further updates may be applied.
157          *
158          * <p>See {@link UpdateEngine#cleanupAppliedPayload}.
159          */
160         public static final int DEVICE_CORRUPTED = 61;
161     }
162 
163     /** @hide */
164     @IntDef(value = {
165             ErrorCodeConstants.SUCCESS,
166             ErrorCodeConstants.ERROR,
167             ErrorCodeConstants.FILESYSTEM_COPIER_ERROR,
168             ErrorCodeConstants.POST_INSTALL_RUNNER_ERROR,
169             ErrorCodeConstants.PAYLOAD_MISMATCHED_TYPE_ERROR,
170             ErrorCodeConstants.INSTALL_DEVICE_OPEN_ERROR,
171             ErrorCodeConstants.KERNEL_DEVICE_OPEN_ERROR,
172             ErrorCodeConstants.DOWNLOAD_TRANSFER_ERROR,
173             ErrorCodeConstants.PAYLOAD_HASH_MISMATCH_ERROR,
174             ErrorCodeConstants.PAYLOAD_SIZE_MISMATCH_ERROR,
175             ErrorCodeConstants.DOWNLOAD_PAYLOAD_VERIFICATION_ERROR,
176             ErrorCodeConstants.PAYLOAD_TIMESTAMP_ERROR,
177             ErrorCodeConstants.UPDATED_BUT_NOT_ACTIVE,
178             ErrorCodeConstants.NOT_ENOUGH_SPACE,
179             ErrorCodeConstants.DEVICE_CORRUPTED,
180     })
181     public @interface ErrorCode {}
182 
183     /**
184      * Status codes for update engine. Values must agree with the ones in
185      * {@code system/update_engine/client_library/include/update_engine/update_status.h}.
186      */
187     public static final class UpdateStatusConstants {
188         /**
189          * Update status code: update engine is in idle state.
190          */
191         public static final int IDLE = 0;
192 
193         /**
194          * Update status code: update engine is checking for update.
195          */
196         public static final int CHECKING_FOR_UPDATE = 1;
197 
198         /**
199          * Update status code: an update is available.
200          */
201         public static final int UPDATE_AVAILABLE = 2;
202 
203         /**
204          * Update status code: update engine is downloading an update.
205          */
206         public static final int DOWNLOADING = 3;
207 
208         /**
209          * Update status code: update engine is verifying an update.
210          */
211         public static final int VERIFYING = 4;
212 
213         /**
214          * Update status code: update engine is finalizing an update.
215          */
216         public static final int FINALIZING = 5;
217 
218         /**
219          * Update status code: an update has been applied and is pending for
220          * reboot.
221          */
222         public static final int UPDATED_NEED_REBOOT = 6;
223 
224         /**
225          * Update status code: update engine is reporting an error event.
226          */
227         public static final int REPORTING_ERROR_EVENT = 7;
228 
229         /**
230          * Update status code: update engine is attempting to rollback an
231          * update.
232          */
233         public static final int ATTEMPTING_ROLLBACK = 8;
234 
235         /**
236          * Update status code: update engine is in disabled state.
237          */
238         public static final int DISABLED = 9;
239     }
240 
241     private final IUpdateEngine mUpdateEngine;
242     private IUpdateEngineCallback mUpdateEngineCallback = null;
243     private final Object mUpdateEngineCallbackLock = new Object();
244 
245     /**
246      * Creates a new instance.
247      */
UpdateEngine()248     public UpdateEngine() {
249         mUpdateEngine = IUpdateEngine.Stub.asInterface(
250                 ServiceManager.getService(UPDATE_ENGINE_SERVICE));
251         if (mUpdateEngine == null) {
252             throw new IllegalStateException("Failed to find update_engine");
253         }
254     }
255 
256     /**
257      * Prepares this instance for use. The callback will be notified on any
258      * status change, and when the update completes. A handler can be supplied
259      * to control which thread runs the callback, or null.
260      */
bind(final UpdateEngineCallback callback, final Handler handler)261     public boolean bind(final UpdateEngineCallback callback, final Handler handler) {
262         synchronized (mUpdateEngineCallbackLock) {
263             mUpdateEngineCallback = new IUpdateEngineCallback.Stub() {
264                 @Override
265                 public void onStatusUpdate(final int status, final float percent) {
266                     if (handler != null) {
267                         handler.post(new Runnable() {
268                             @Override
269                             public void run() {
270                                 callback.onStatusUpdate(status, percent);
271                             }
272                         });
273                     } else {
274                         callback.onStatusUpdate(status, percent);
275                     }
276                 }
277 
278                 @Override
279                 public void onPayloadApplicationComplete(final int errorCode) {
280                     if (handler != null) {
281                         handler.post(new Runnable() {
282                             @Override
283                             public void run() {
284                                 callback.onPayloadApplicationComplete(errorCode);
285                             }
286                         });
287                     } else {
288                         callback.onPayloadApplicationComplete(errorCode);
289                     }
290                 }
291             };
292 
293             try {
294                 return mUpdateEngine.bind(mUpdateEngineCallback);
295             } catch (RemoteException e) {
296                 throw e.rethrowFromSystemServer();
297             }
298         }
299     }
300 
301     /**
302      * Equivalent to {@code bind(callback, null)}.
303      */
bind(final UpdateEngineCallback callback)304     public boolean bind(final UpdateEngineCallback callback) {
305         return bind(callback, null);
306     }
307 
308     /**
309      * Applies the payload found at the given {@code url}. For non-streaming
310      * updates, the URL can be a local file using the {@code file://} scheme.
311      *
312      * <p>The {@code offset} and {@code size} parameters specify the location
313      * of the payload within the file represented by the URL. This is useful
314      * if the downloadable package at the URL contains more than just the
315      * update_engine payload (such as extra metadata). This is true for
316      * Google's OTA system, where the URL points to a zip file in which the
317      * payload is stored uncompressed within the zip file alongside other
318      * data.
319      *
320      * <p>The {@code headerKeyValuePairs} parameter is used to pass metadata
321      * to update_engine. In Google's implementation, this is stored as
322      * {@code payload_properties.txt} in the zip file. It's generated by the
323      * script {@code system/update_engine/scripts/brillo_update_payload}.
324      * The complete list of keys and their documentation is in
325      * {@code system/update_engine/common/constants.cc}, but an example
326      * might be:
327      * <pre>
328      * String[] pairs = {
329      *   "FILE_HASH=lURPCIkIAjtMOyB/EjQcl8zDzqtD6Ta3tJef6G/+z2k=",
330      *   "FILE_SIZE=871903868",
331      *   "METADATA_HASH=tBvj43QOB0Jn++JojcpVdbRLz0qdAuL+uTkSy7hokaw=",
332      *   "METADATA_SIZE=70604"
333      * };
334      * </pre>
335      *
336      * <p>The callback functions registered via {@code #bind} will be called
337      * during and at the end of the payload application.
338      *
339      * <p>By default the newly updated slot will be set active upon
340      * successfully finishing an update. Device will attempt to boot into the
341      * new slot on next reboot. This behavior can be customized by specifying
342      * {@code SWITCH_SLOT_ON_REBOOT=0} in {@code headerKeyValuePairs}, which
343      * allows the caller to later determine a good time to boot into the new
344      * slot. Calling {@code applyPayload} again with the same payload but with
345      * {@code SWITCH_SLOT_ON_REBOOT=1} will do the minimal work to set the new
346      * slot active, after verifying its integrity.
347      */
applyPayload(String url, long offset, long size, String[] headerKeyValuePairs)348     public void applyPayload(String url, long offset, long size, String[] headerKeyValuePairs) {
349         try {
350             mUpdateEngine.applyPayload(url, offset, size, headerKeyValuePairs);
351         } catch (RemoteException e) {
352             throw e.rethrowFromSystemServer();
353         }
354     }
355 
356     /**
357      * Applies the payload passed as AssetFileDescriptor {@code assetFd}
358      * instead of using the {@code file://} scheme.
359      *
360      * <p>See {@link #applyPayload(String)} for {@code offset}, {@code size} and
361      * {@code headerKeyValuePairs} parameters.
362      */
applyPayload(@onNull AssetFileDescriptor assetFd, @NonNull String[] headerKeyValuePairs)363     public void applyPayload(@NonNull AssetFileDescriptor assetFd,
364             @NonNull String[] headerKeyValuePairs) {
365         try {
366             mUpdateEngine.applyPayloadFd(assetFd.getParcelFileDescriptor(),
367                     assetFd.getStartOffset(), assetFd.getLength(), headerKeyValuePairs);
368         } catch (RemoteException e) {
369             throw e.rethrowFromSystemServer();
370         }
371     }
372 
373     /**
374      * Permanently cancels an in-progress update.
375      *
376      * <p>See {@link #resetStatus} to undo a finshed update (only available
377      * before the updated system has been rebooted).
378      *
379      * <p>See {@link #suspend} for a way to temporarily stop an in-progress
380      * update with the ability to resume it later.
381      */
cancel()382     public void cancel() {
383         try {
384             mUpdateEngine.cancel();
385         } catch (RemoteException e) {
386             throw e.rethrowFromSystemServer();
387         }
388     }
389 
390     /**
391      * Suspends an in-progress update. This can be undone by calling
392      * {@link #resume}.
393      */
suspend()394     public void suspend() {
395         try {
396             mUpdateEngine.suspend();
397         } catch (RemoteException e) {
398             throw e.rethrowFromSystemServer();
399         }
400     }
401 
402     /**
403      * Resumes a suspended update.
404      */
resume()405     public void resume() {
406         try {
407             mUpdateEngine.resume();
408         } catch (RemoteException e) {
409             throw e.rethrowFromSystemServer();
410         }
411     }
412 
413     /**
414      * Resets the bootable flag on the non-current partition and all internal
415      * update_engine state. Note this call will clear the entire update
416      * progress. So a subsequent {@link #applyPayload} will apply the update
417      * from scratch.
418      *
419      * <p>After this call completes, update_engine will no longer report
420      * {@code UPDATED_NEED_REBOOT}, so your callback can remove any outstanding
421      * notification that rebooting into the new system is possible.
422      */
resetStatus()423     public void resetStatus() {
424         try {
425             mUpdateEngine.resetStatus();
426         } catch (RemoteException e) {
427             throw e.rethrowFromSystemServer();
428         }
429     }
430 
431     /**
432      * Sets the A/B slot switch for the next boot after applying an ota update. If
433      * {@link #applyPayload} hasn't switched the slot, the updater APP can call
434      * this API to switch the slot and apply the update on next boot.
435      *
436      * @param payloadMetadataFilename the location of the metadata without the
437      * {@code file://} prefix.
438      */
setShouldSwitchSlotOnReboot(@onNull String payloadMetadataFilename)439     public void setShouldSwitchSlotOnReboot(@NonNull String payloadMetadataFilename) {
440         try {
441             mUpdateEngine.setShouldSwitchSlotOnReboot(payloadMetadataFilename);
442         } catch (RemoteException e) {
443             throw e.rethrowFromSystemServer();
444         }
445     }
446 
447    /**
448     * Resets the boot slot to the source/current slot, without cancelling the
449     * update progress. This can be called after the update is installed, and to
450     * prevent the device from accidentally taking the update when it reboots.
451     *
452     * This is useful when users don't want to take the update immediately; or
453     * the updater determines some condition hasn't met, e.g. insufficient space
454     * for boot.
455     */
resetShouldSwitchSlotOnReboot()456     public void resetShouldSwitchSlotOnReboot() {
457         try {
458             mUpdateEngine.resetShouldSwitchSlotOnReboot();
459         } catch (RemoteException e) {
460             throw e.rethrowFromSystemServer();
461         }
462     }
463 
464     /**
465      * Unbinds the last bound callback function.
466      */
unbind()467     public boolean unbind() {
468         synchronized (mUpdateEngineCallbackLock) {
469             if (mUpdateEngineCallback == null) {
470                 return true;
471             }
472             try {
473                 boolean result = mUpdateEngine.unbind(mUpdateEngineCallback);
474                 mUpdateEngineCallback = null;
475                 return result;
476             } catch (RemoteException e) {
477                 throw e.rethrowFromSystemServer();
478             }
479         }
480     }
481 
482     /**
483      * Verifies that a payload associated with the given payload metadata
484      * {@code payloadMetadataFilename} can be safely applied to ths device.
485      * Returns {@code true} if the update can successfully be applied and
486      * returns {@code false} otherwise.
487      *
488      * @param payloadMetadataFilename the location of the metadata without the
489      * {@code file://} prefix.
490      */
verifyPayloadMetadata(String payloadMetadataFilename)491     public boolean verifyPayloadMetadata(String payloadMetadataFilename) {
492         try {
493             return mUpdateEngine.verifyPayloadApplicable(payloadMetadataFilename);
494         } catch (RemoteException e) {
495             throw e.rethrowFromSystemServer();
496         }
497     }
498 
499     /**
500      * Return value of {@link #allocateSpace.}
501      */
502     public static final class AllocateSpaceResult {
503         private @ErrorCode int mErrorCode = ErrorCodeConstants.SUCCESS;
504         private long mFreeSpaceRequired = 0;
AllocateSpaceResult()505         private AllocateSpaceResult() {}
506         /**
507          * Error code.
508          *
509          * @return The following error codes:
510          * <ul>
511          * <li>{@link ErrorCodeConstants#SUCCESS} if space has been allocated
512          *         successfully.</li>
513          * <li>{@link ErrorCodeConstants#NOT_ENOUGH_SPACE} if insufficient
514          *         space.</li>
515          * <li>Other {@link ErrorCodeConstants} for other errors.</li>
516          * </ul>
517          */
518         @ErrorCode
getErrorCode()519         public int getErrorCode() {
520             return mErrorCode;
521         }
522 
523         /**
524          * Estimated total space that needs to be available on the userdata partition to apply the
525          * payload (in bytes).
526          *
527          * <p>
528          * Note that in practice, more space needs to be made available before applying the payload
529          * to keep the device working.
530          *
531          * @return The following values:
532          * <ul>
533          * <li>zero if {@link #getErrorCode} returns {@link ErrorCodeConstants#SUCCESS}</li>
534          * <li>non-zero if {@link #getErrorCode} returns
535          * {@link ErrorCodeConstants#NOT_ENOUGH_SPACE}.
536          * Value is the estimated total space required on userdata partition.</li>
537          * </ul>
538          * @throws IllegalStateException if {@link #getErrorCode} is not one of the above.
539          *
540          */
getFreeSpaceRequired()541         public long getFreeSpaceRequired() {
542             if (mErrorCode == ErrorCodeConstants.SUCCESS) {
543                 return 0;
544             }
545             if (mErrorCode == ErrorCodeConstants.NOT_ENOUGH_SPACE) {
546                 return mFreeSpaceRequired;
547             }
548             throw new IllegalStateException(String.format(
549                     "getFreeSpaceRequired() is not available when error code is %d", mErrorCode));
550         }
551     }
552 
553     /**
554      * Initialize partitions for a payload associated with the given payload
555      * metadata {@code payloadMetadataFilename} by preallocating required space.
556      *
557      * <p>This function should be called after payload has been verified after
558      * {@link #verifyPayloadMetadata}. This function does not verify whether
559      * the given payload is applicable or not.
560      *
561      * <p>Implementation of {@code allocateSpace} uses
562      * {@code headerKeyValuePairs} to determine whether space has been allocated
563      * for a different or same payload previously. If space has been allocated
564      * for a different payload before, space will be reallocated for the given
565      * payload. If space has been allocated for the same payload, no actions to
566      * storage devices are taken.
567      *
568      * <p>This function is synchronous and may take a non-trivial amount of
569      * time. Callers should call this function in a background thread.
570      *
571      * @param payloadMetadataFilename See {@link #verifyPayloadMetadata}.
572      * @param headerKeyValuePairs See {@link #applyPayload}.
573      * @return See {@link AllocateSpaceResult#getErrorCode} and
574      *             {@link AllocateSpaceResult#getFreeSpaceRequired}.
575      */
576     @WorkerThread
577     @NonNull
allocateSpace( @onNull String payloadMetadataFilename, @NonNull String[] headerKeyValuePairs)578     public AllocateSpaceResult allocateSpace(
579                 @NonNull String payloadMetadataFilename,
580                 @NonNull String[] headerKeyValuePairs) {
581         AllocateSpaceResult result = new AllocateSpaceResult();
582         try {
583             result.mFreeSpaceRequired = mUpdateEngine.allocateSpaceForPayload(
584                     payloadMetadataFilename,
585                     headerKeyValuePairs);
586             result.mErrorCode = result.mFreeSpaceRequired == 0
587                     ? ErrorCodeConstants.SUCCESS
588                     : ErrorCodeConstants.NOT_ENOUGH_SPACE;
589             return result;
590         } catch (ServiceSpecificException e) {
591             result.mErrorCode = e.errorCode;
592             result.mFreeSpaceRequired = 0;
593             return result;
594         } catch (RemoteException e) {
595             throw e.rethrowFromSystemServer();
596         }
597     }
598 
599     private static class CleanupAppliedPayloadCallback extends IUpdateEngineCallback.Stub {
600         private int mErrorCode = ErrorCodeConstants.ERROR;
601         private boolean mCompleted = false;
602         private Object mLock = new Object();
getResult()603         private int getResult() {
604             synchronized (mLock) {
605                 while (!mCompleted) {
606                     try {
607                         mLock.wait();
608                     } catch (InterruptedException ex) {
609                         // do nothing, just wait again.
610                     }
611                 }
612                 return mErrorCode;
613             }
614         }
615 
616         @Override
onStatusUpdate(int status, float percent)617         public void onStatusUpdate(int status, float percent) {
618         }
619 
620         @Override
onPayloadApplicationComplete(int errorCode)621         public void onPayloadApplicationComplete(int errorCode) {
622             synchronized (mLock) {
623                 mErrorCode = errorCode;
624                 mCompleted = true;
625                 mLock.notifyAll();
626             }
627         }
628     }
629 
630     /**
631      * Cleanup files used by the previous update and free up space after the
632      * device has been booted successfully into the new build.
633      *
634      * <p>In particular, this function waits until delta files for snapshots for
635      * Virtual A/B update are merged to OS partitions, then delete these delta
636      * files.
637      *
638      * <p>This function is synchronous and may take a non-trivial amount of
639      * time. Callers should call this function in a background thread.
640      *
641      * <p>This function does not delete payload binaries downloaded for a
642      * non-streaming OTA update.
643      *
644      * @return One of the following:
645      * <ul>
646      * <li>{@link ErrorCodeConstants#SUCCESS} if execution is successful.</li>
647      * <li>{@link ErrorCodeConstants#ERROR} if a transient error has occurred.
648      * The device should be able to recover after a reboot. The function should
649      * be retried after the reboot.</li>
650      * <li>{@link ErrorCodeConstants#DEVICE_CORRUPTED} if a permanent error is
651      * encountered. Device is corrupted, and future updates must not be applied.
652      * The device cannot recover without flashing and factory resets.
653      * </ul>
654      */
655     @WorkerThread
656     @ErrorCode
cleanupAppliedPayload()657     public int cleanupAppliedPayload() {
658         CleanupAppliedPayloadCallback callback = new CleanupAppliedPayloadCallback();
659         try {
660             mUpdateEngine.cleanupSuccessfulUpdate(callback);
661             return callback.getResult();
662         } catch (RemoteException e) {
663             throw e.rethrowFromSystemServer();
664         }
665     }
666 }
667