/ocr/ocrservice/src/com/googlecode/eyesfree/ocr/client/Ocr.java

http://eyes-free.googlecode.com/ · Java · 857 lines · 444 code · 141 blank · 272 comment · 53 complexity · fe71e606a7675711a449ed490e35c556 MD5 · raw file

  1. /*
  2. * Copyright (C) 2011 Google Inc.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * 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, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.googlecode.eyesfree.ocr.client;
  17. import android.content.ComponentName;
  18. import android.content.Context;
  19. import android.content.DialogInterface;
  20. import android.content.DialogInterface.OnClickListener;
  21. import android.content.Intent;
  22. import android.content.ServiceConnection;
  23. import android.content.pm.PackageManager;
  24. import android.content.pm.ResolveInfo;
  25. import android.graphics.Bitmap;
  26. import android.graphics.Bitmap.CompressFormat;
  27. import android.os.Bundle;
  28. import android.os.DeadObjectException;
  29. import android.os.Environment;
  30. import android.os.IBinder;
  31. import android.os.Parcel;
  32. import android.os.Parcelable;
  33. import android.os.RemoteException;
  34. import android.util.Log;
  35. import java.io.ByteArrayOutputStream;
  36. import java.io.File;
  37. import java.io.FileOutputStream;
  38. import java.io.IOException;
  39. import java.lang.ref.WeakReference;
  40. import java.util.List;
  41. import java.util.Set;
  42. /**
  43. * Recognizes text in images. This abstracts away the complexities of using the
  44. * OCR service such as setting up the IBinder connection and handling
  45. * RemoteExceptions, etc. Specifically, this class initializes the OCR service
  46. * and pushes recognization requests across IPC for processing in the service
  47. * thread.
  48. *
  49. * @author alanv@google.com (Alan Viverette)
  50. */
  51. public class Ocr {
  52. private static final String TAG = "Ocr";
  53. // This is the minimum version of the Ocr service that is needed by this
  54. // version of the library stub.
  55. private static final int MIN_VER = 1;
  56. public static final int STATUS_SUCCESS = 0;
  57. public static final int STATUS_FAILURE = 1;
  58. public static final int STATUS_MISSING = 2;
  59. public static final int ERROR = -1;
  60. public static final int SUCCESS = 1;
  61. public static final long INVALID_TOKEN = -1;
  62. private static final int BINDER_SIZE_LIMIT = 40000;
  63. private int mVersion = -1;
  64. private IOcr mIOcr;
  65. private ServiceConnection mServiceConnection;
  66. private boolean mStorageAvailable;
  67. private boolean mSuppressAlerts;
  68. private WeakReference<Context> mContext;
  69. private ResultCallback mOnResult;
  70. private CompletionCallback mOnCompleted;
  71. private Parameters mParameters;
  72. /**
  73. * The constructor for the OCR service client. Initializes the service if
  74. * necessary and calls the supplied InitCallback when it's ready.
  75. *
  76. * @param context the context of the parent activity
  77. * @param init the callback to call on initialization
  78. */
  79. public Ocr(Context context, InitCallback init) {
  80. this(context, init, false);
  81. }
  82. /**
  83. * The constructor for the OCR service client. Initializes the service if
  84. * necessary and calls the supplied InitCallback when it's ready.
  85. * <p>
  86. * You may optionally set suppressAlerts to true to turn off alert dialogs.
  87. *
  88. * @param context the context of the parent activity
  89. * @param init the callback to call on initialization
  90. * @param suppressAlerts <code>true</code> to suppress alert dialogs
  91. */
  92. public Ocr(Context context, InitCallback init, boolean suppressAlerts) {
  93. if (context == null) {
  94. throw new IllegalArgumentException("Context must not be null");
  95. }
  96. mContext = new WeakReference<Context>(context);
  97. mSuppressAlerts = suppressAlerts;
  98. mParameters = new Parameters();
  99. connectOcrService(init);
  100. }
  101. /**
  102. * Sets the result callback. If text detection is enabled, this will be
  103. * called once for each individual box before the completion callback
  104. * occurs.
  105. *
  106. * @param callback
  107. */
  108. public void setResultCallback(ResultCallback callback) {
  109. mOnResult = callback;
  110. }
  111. /**
  112. * Sets the completion callback. This is called when recognition is complete
  113. * and receives an ArrayList of results.
  114. *
  115. * @param callback
  116. */
  117. public void setCompletionCallback(CompletionCallback callback) {
  118. mOnCompleted = callback;
  119. }
  120. /**
  121. * Enqueues an image represented as a Bitmap for OCR.
  122. *
  123. * @param bitmap The bitmap on which to perform OCR.
  124. * @return A Job representing the queued OCR job.
  125. */
  126. public Job enqueue(Bitmap bitmap) {
  127. if (bitmap == null) {
  128. throw new IllegalArgumentException("Bitmap must be non-null");
  129. }
  130. // TODO(alanv): Replace this with native Bitmap conversion
  131. ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
  132. bitmap.compress(CompressFormat.JPEG, 85, byteStream);
  133. byte[] jpegData = byteStream.toByteArray();
  134. return enqueue(jpegData);
  135. }
  136. /**
  137. * Enqueues an image represented as JPEG-compressed bytes for OCR.
  138. *
  139. * @param jpegData The JPEG-compressed image on which to perform OCR.
  140. * @return A Job representing the queued OCR job.
  141. */
  142. public Job enqueue(byte[] jpegData) {
  143. if (jpegData == null) {
  144. throw new IllegalArgumentException("JPEG data must be non-null");
  145. }
  146. // If we're over the binder size limit, write to disk.
  147. if (jpegData.length > BINDER_SIZE_LIMIT) {
  148. return cacheAndEnqueue(jpegData);
  149. }
  150. try {
  151. long taskId = mIOcr.enqueueData(jpegData, mParameters);
  152. return new Job(taskId);
  153. } catch (DeadObjectException e) {
  154. e.printStackTrace();
  155. } catch (RemoteException e) {
  156. e.printStackTrace();
  157. }
  158. return null;
  159. }
  160. /**
  161. * Internal method that writes image bytes to disk when they exceed the
  162. * binder transaction limit.
  163. *
  164. * @param data The bytes to write to disk.
  165. * @return A Job representing the queued OCR job.
  166. */
  167. private Job cacheAndEnqueue(byte[] data) {
  168. Job job = null;
  169. try {
  170. File cacheDir = mContext.get().getExternalCacheDir();
  171. File cached = File.createTempFile("ocr", ".jpg", cacheDir);
  172. FileOutputStream output = new FileOutputStream(cached);
  173. output.write(data);
  174. output.close();
  175. job = enqueue(cached);
  176. if (job != null) {
  177. job.mCached = cached;
  178. }
  179. } catch (IOException e) {
  180. e.printStackTrace();
  181. }
  182. return job;
  183. }
  184. /**
  185. * Enqueues an image represented as an encoded file. The file extension must
  186. * match the encoding and must be one of the following formats:
  187. * <ol>
  188. * <li>JPEG</li>
  189. * <li>BMP</li>
  190. * </ol>
  191. *
  192. * @param file An encoded file containing the image to OCR.
  193. * @return A Job representing the queued OCR job.
  194. */
  195. public Job enqueue(File file) {
  196. if (file == null) {
  197. throw new IllegalArgumentException("File must be non-null");
  198. }
  199. try {
  200. long taskId = mIOcr.enqueueFile(file.getAbsolutePath(), mParameters);
  201. return new Job(taskId);
  202. } catch (DeadObjectException e) {
  203. e.printStackTrace();
  204. } catch (RemoteException e) {
  205. e.printStackTrace();
  206. }
  207. return null;
  208. }
  209. /**
  210. * Returns the OCR parameters that will be used to process new enqueue
  211. * requests. If changes are made, you must call setParameters to commit
  212. * them.
  213. *
  214. * @return The parameters used when processing new OCR requests.
  215. */
  216. public Parameters getParameters() {
  217. return mParameters;
  218. }
  219. /**
  220. * Sets the OCR parameters that will be used to process new enqueue
  221. * requests.
  222. *
  223. * @param parameters The parameters to use when processing new OCR requests.
  224. */
  225. public void setParameters(Parameters parameters) {
  226. mParameters = parameters;
  227. }
  228. /**
  229. * Returns the absolute path of the OCR service's language data folder.
  230. * Typically this is on the user's SD card.
  231. *
  232. * @return the absolute path of the OCR service's language data folder
  233. */
  234. public File getTessdata() {
  235. if (mIOcr == null) {
  236. Log.e(TAG, "getTessdata() without a connection to Ocr service.");
  237. return null;
  238. }
  239. File tessdata = null;
  240. try {
  241. String tessstr = mIOcr.getTessdata();
  242. tessdata = tessstr == null ? null : new File(tessstr);
  243. } catch (DeadObjectException e) {
  244. e.printStackTrace();
  245. } catch (RemoteException e) {
  246. e.printStackTrace();
  247. }
  248. return tessdata;
  249. }
  250. /**
  251. * Forces the Ocr service to refresh the list of available languages.
  252. */
  253. public void reloadLanguages() {
  254. if (mIOcr == null) {
  255. Log.e(TAG, "reloadLanguages() without a connection to Ocr service.");
  256. }
  257. try {
  258. mIOcr.reloadLanguages();
  259. } catch (DeadObjectException e) {
  260. e.printStackTrace();
  261. } catch (RemoteException e) {
  262. e.printStackTrace();
  263. }
  264. }
  265. /**
  266. * Returns the list of available languages.
  267. *
  268. * @return a sorted list of available languages
  269. */
  270. public List<Language> getAvailableLanguages() {
  271. if (mIOcr == null) {
  272. Log.e(TAG, "getAvailableLanguages() without a connection to Ocr service.");
  273. return null;
  274. }
  275. List<Language> available = null;
  276. try {
  277. available = mIOcr.getAvailableLanguages();
  278. } catch (DeadObjectException e) {
  279. e.printStackTrace();
  280. } catch (RemoteException e) {
  281. e.printStackTrace();
  282. }
  283. return available;
  284. }
  285. /**
  286. * Disconnects from the Ocr service.
  287. * <p>
  288. * It is recommended that you call this as soon as you're done with the Ocr
  289. * object. After this call the receiving Ocr object will be unusable.
  290. */
  291. public synchronized void release() {
  292. mOnCompleted = null;
  293. mOnResult = null;
  294. try {
  295. Context context = mContext.get();
  296. if (context != null) {
  297. context.unbindService(mServiceConnection);
  298. }
  299. } catch (IllegalArgumentException e) {
  300. // Do nothing and fail silently since an error here indicates that
  301. // binding never succeeded in the first place.
  302. }
  303. mIOcr = null;
  304. mContext = null;
  305. }
  306. /**
  307. * Internal method used to connect to the OCR service.
  308. *
  309. * @param init Initialization callback.
  310. */
  311. private void connectOcrService(final InitCallback init) {
  312. // Initialize the OCR service, run the callback after the binding is
  313. // successful
  314. mServiceConnection = new ServiceConnection() {
  315. @Override
  316. public void onServiceConnected(ComponentName name, IBinder service) {
  317. mIOcr = IOcr.Stub.asInterface(service);
  318. try {
  319. mVersion = mIOcr.getVersion();
  320. // The Ocr service must be at least the min version needed
  321. // by the library stub. Do not try to run the older Ocr with
  322. // the newer library stub as the newer library may reference
  323. // methods which are unavailable and cause a crash.
  324. if (mVersion < MIN_VER) {
  325. Log.e(TAG, "OCR service too old (version " + mVersion + " < " + MIN_VER
  326. + ")");
  327. if (!mSuppressAlerts) {
  328. OnClickListener onClick = new OnClickListener() {
  329. @Override
  330. public void onClick(DialogInterface dialog, int which) {
  331. postInitialized(init, STATUS_MISSING);
  332. }
  333. };
  334. VersionAlert.createUpdateAlert(mContext.get(), null).show();
  335. } else {
  336. postInitialized(init, STATUS_MISSING);
  337. }
  338. return;
  339. }
  340. mStorageAvailable = Environment.getExternalStorageDirectory().exists();
  341. if (!mStorageAvailable) {
  342. Log.e(TAG, "External storage is not available");
  343. if (!mSuppressAlerts) {
  344. OnClickListener onClick = new OnClickListener() {
  345. @Override
  346. public void onClick(DialogInterface dialog, int which) {
  347. postInitialized(init, STATUS_MISSING);
  348. }
  349. };
  350. VersionAlert.createStorageAlert(mContext.get(), onClick).show();
  351. } else {
  352. postInitialized(init, STATUS_MISSING);
  353. }
  354. return;
  355. }
  356. List<Language> languages = mIOcr.getAvailableLanguages();
  357. if (languages == null || languages.isEmpty()) {
  358. Log.e(TAG, "No languages are installed");
  359. if (!mSuppressAlerts) {
  360. OnClickListener onClick = new OnClickListener() {
  361. @Override
  362. public void onClick(DialogInterface dialog, int which) {
  363. postInitialized(init, STATUS_MISSING);
  364. }
  365. };
  366. VersionAlert.createLanguagesAlert(mContext.get(), onClick, onClick)
  367. .show();
  368. } else {
  369. postInitialized(init, STATUS_MISSING);
  370. }
  371. return;
  372. }
  373. // Set the callback so that we can receive completion events
  374. mIOcr.setCallback(mCallback);
  375. } catch (RemoteException e) {
  376. Log.e(TAG, "Exception caught in onServiceConnected(): " + e.toString());
  377. postInitialized(init, STATUS_FAILURE);
  378. return;
  379. }
  380. postInitialized(init, STATUS_SUCCESS);
  381. }
  382. @Override
  383. public void onServiceDisconnected(ComponentName name) {
  384. mIOcr = null;
  385. }
  386. };
  387. Intent intent = new Intent(Intents.Service.ACTION);
  388. intent.addCategory(Intent.CATEGORY_DEFAULT);
  389. // Binding will fail only if the Ocr doesn't exist;
  390. // the OcrVersionAlert will give users a chance to install
  391. // the needed Ocr.
  392. Context context = mContext.get();
  393. if (!context.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE)) {
  394. Log.e(TAG, "Cannot bind to OCR service, assuming not installed");
  395. OnClickListener onClick = new OnClickListener() {
  396. @Override
  397. public void onClick(DialogInterface dialog, int which) {
  398. postInitialized(init, STATUS_MISSING);
  399. }
  400. };
  401. if (!mSuppressAlerts) {
  402. VersionAlert.createInstallAlert(context, onClick).show();
  403. }
  404. return;
  405. }
  406. }
  407. /**
  408. * Passes the initialization status to the InitCallback.
  409. *
  410. * @param init The initialization callback.
  411. * @param status The initialization status.
  412. */
  413. private void postInitialized(final InitCallback init, final int status) {
  414. if (init != null) {
  415. init.onInitialized(status);
  416. }
  417. }
  418. /**
  419. * Cancels all active and pending OCR jobs.
  420. */
  421. public void stop() {
  422. if (mIOcr == null) {
  423. Log.e(TAG, "Attempted to call stop() without a connection to Ocr service.");
  424. return;
  425. }
  426. try {
  427. mIOcr.stop();
  428. } catch (DeadObjectException e) {
  429. e.printStackTrace();
  430. } catch (RemoteException e) {
  431. e.printStackTrace();
  432. }
  433. }
  434. /**
  435. * Returns the version number of the Ocr library that the user has
  436. * installed.
  437. *
  438. * @return te version number of the Ocr library that the user has installed
  439. */
  440. public int getVersion() {
  441. return mVersion;
  442. }
  443. /**
  444. * Checks if the Ocr service is installed or not
  445. *
  446. * @return a boolean that indicates whether the Ocr service is installed
  447. */
  448. public static boolean isInstalled(Context ctx) {
  449. Intent intent = new Intent(Intents.Service.ACTION);
  450. PackageManager pm = ctx.getPackageManager();
  451. ResolveInfo info = pm.resolveService(intent, 0);
  452. if (info == null) {
  453. return false;
  454. } else {
  455. return true;
  456. }
  457. }
  458. /**
  459. * Handles the callback when the Ocr service has initialized.
  460. */
  461. public static interface InitCallback {
  462. public void onInitialized(int status);
  463. }
  464. /**
  465. * Handles the callback for when recognition is completed.
  466. */
  467. public static interface CompletionCallback {
  468. public void onCompleted(List<OcrResult> results);
  469. }
  470. /**
  471. * Handles the callback for a single mid-recognition result.
  472. */
  473. public static interface ResultCallback {
  474. public void onResult(OcrResult result);
  475. }
  476. private final IOcrCallback mCallback = new IOcrCallback.Stub() {
  477. @Override
  478. public void onCompleted(final long token, final List<OcrResult> results) {
  479. if (mOnCompleted != null) {
  480. mOnCompleted.onCompleted(results);
  481. }
  482. }
  483. @Override
  484. public void onResult(final long token, final OcrResult result) {
  485. if (mOnResult != null) {
  486. mOnResult.onResult(result);
  487. }
  488. }
  489. };
  490. /**
  491. * Represents a single OCR job.
  492. *
  493. * @author alanv@google.com (Alan Viverette)
  494. */
  495. public class Job {
  496. long mTaskId;
  497. File mCached;
  498. Job(long taskId) {
  499. mTaskId = taskId;
  500. mCached = null;
  501. }
  502. @Override
  503. protected void finalize() throws Throwable {
  504. // If we have a cached file, delete it when we're done.
  505. try {
  506. if (mCached != null) {
  507. mCached.delete();
  508. }
  509. } finally {
  510. super.finalize();
  511. }
  512. }
  513. /**
  514. * Cancels this OCR job.
  515. */
  516. public void cancel() {
  517. try {
  518. mIOcr.cancel(mTaskId);
  519. } catch (DeadObjectException e) {
  520. e.printStackTrace();
  521. } catch (RemoteException e) {
  522. e.printStackTrace();
  523. }
  524. }
  525. }
  526. /**
  527. * Represents a set of OCR processing parameters.
  528. *
  529. * @author alanv@google.com (Alan Viverette)
  530. */
  531. public static class Parameters implements Parcelable {
  532. /** Whitelist of characters to recognize */
  533. public static final String VAR_CHAR_WHITELIST = "tessedit_char_whitelist";
  534. /** Blacklist of characters to not recognize */
  535. public static final String VAR_CHAR_BLACKLIST = "tessedit_char_blacklist";
  536. /** Detect text in image using TextDetect */
  537. public static final String FLAG_DETECT_TEXT = "detect_text";
  538. /** Aligns horizontal text in an image */
  539. public static final String FLAG_ALIGN_TEXT = "align_text";
  540. /** Perform spell-checking on results */
  541. public static final String FLAG_SPELLCHECK = "spellcheck";
  542. /** Write intermediate files to external storage */
  543. public static final String FLAG_DEBUG_MODE = "debug_mode";
  544. /** Fully automatic page segmentation. */
  545. public static final int PSM_AUTO = 0;
  546. /** Assume a single column of text of variable sizes. */
  547. public static final int PSM_SINGLE_COLUMN = 1;
  548. /** Assume a single uniform block of text. */
  549. public static final int PSM_SINGLE_BLOCK = 2;
  550. /** Treat the image as a single text line. (Default) */
  551. public static final int PSM_SINGLE_LINE = 3;
  552. /** Treat the image as a single word. */
  553. public static final int PSM_SINGLE_WORD = 4;
  554. /** Treat the image as a single character. */
  555. public static final int PSM_SINGLE_CHAR = 5;
  556. private static final int PSM_MODE_COUNT = 6;
  557. private Bundle mVariables;
  558. private Bundle mFlags;
  559. private String mLanguage;
  560. private int mPageSegMode;
  561. /**
  562. * Constructs a new Parameters object using the default values.
  563. */
  564. public Parameters() {
  565. mVariables = new Bundle();
  566. mFlags = new Bundle();
  567. mPageSegMode = PSM_SINGLE_LINE;
  568. mLanguage = "eng";
  569. }
  570. /**
  571. * Sets the value of the variable identified by <code>key</code>. If the
  572. * value is null, removes the variable.
  573. *
  574. * @param key The key that identifies the variable to set.
  575. * @param value The String value to assign to the variable.
  576. */
  577. public void setVariable(String key, String value) {
  578. if (value == null) {
  579. mVariables.remove(key);
  580. } else {
  581. mVariables.putString(key, value);
  582. }
  583. }
  584. /**
  585. * Returns the value of the variable identified by <code>key</code>, or
  586. * <code>null</code> if it has not been set.
  587. *
  588. * @param key The key that identifies the variable to retrieve.
  589. * @return The value of the variable or <code>null</code> if it has not
  590. * been set.
  591. */
  592. public String getVariable(String key) {
  593. return mVariables.getString(key);
  594. }
  595. /**
  596. * Returns the list of keys identifying variables that have been set.
  597. *
  598. * @return A set of Strings representing the variable keys that have
  599. * been set.
  600. */
  601. public Set<String> getVariableKeys() {
  602. return mVariables.keySet();
  603. }
  604. /**
  605. * Sets the value of the flag identified by <code>key</code>. If the
  606. * value is <code>null</code>, removes the flag.
  607. *
  608. * @param key The key that identifies the flag to set.
  609. * @param value The boolean value to assign to the flag.
  610. */
  611. public void setFlag(String key, boolean value) {
  612. mFlags.putBoolean(key, value);
  613. }
  614. /**
  615. * Returns the value of the flag identified by <code>key</code>. If
  616. * <code>key</code> has not been set, returns <code>false</code>.
  617. *
  618. * @param key The key that identifies the flag to retrieve.
  619. * @return The value of the flag or <code>false</code> if it has not
  620. * been set.
  621. */
  622. public boolean getFlag(String key) {
  623. if (!mFlags.containsKey(key)) {
  624. return false;
  625. } else {
  626. return mFlags.getBoolean(key);
  627. }
  628. }
  629. /**
  630. * Sets the language used by the OCR engine. Use
  631. * Ocr.getAvailableLanguages() to retrieve the list of available
  632. * languages.
  633. *
  634. * @param language A language present in Ocr.getAvailableLanguages().
  635. */
  636. public void setLanguage(Language language) {
  637. mLanguage = language.iso_639_2;
  638. }
  639. /**
  640. * Sets the language (as an ISO 639-2 code) used by the OCR engine. Use
  641. * Ocr.getAvailableLanguages() to retrieve the list of available
  642. * languages and Language.iso_639_2 to retrieve the ISO 639-2 code. If
  643. * the specified language is not available, the OCR engine will default
  644. * to English.
  645. *
  646. * @param language An ISO 639-2 code representing a supported language.
  647. */
  648. public void setLanguage(String language) {
  649. mLanguage = language;
  650. }
  651. /**
  652. * Returns the ISO 639-2 code representing the current language that
  653. * will be used by the OCR engine.
  654. *
  655. * @return The ISO 639-2 code representing the current languages used
  656. * for OCR.
  657. */
  658. public String getLanguage() {
  659. return mLanguage;
  660. }
  661. /**
  662. * Sets the page segmentation mode, which is used by the OCR engine to
  663. * detect and group areas of text. See the Parameters.PSM_* constants
  664. * for available values.
  665. *
  666. * @param pageSegMode A page segmentation mode from Parameters.PSM_*
  667. * constants.
  668. */
  669. public void setPageSegMode(int pageSegMode) {
  670. if (pageSegMode < 0 || pageSegMode > PSM_MODE_COUNT) {
  671. throw new IllegalArgumentException("Invalid page segmentation mode");
  672. }
  673. mPageSegMode = pageSegMode;
  674. }
  675. /**
  676. * Returns the current page segmentation mode as defined in
  677. * Parameters.PSM_* constants.
  678. *
  679. * @return The current page segmentation mode.
  680. */
  681. public int getPageSegMode() {
  682. return mPageSegMode;
  683. }
  684. // ************************
  685. // * Parcelable functions *
  686. // ************************
  687. private Parameters(Parcel src) {
  688. readFromParcel(src);
  689. }
  690. @Override
  691. public int describeContents() {
  692. return 0;
  693. }
  694. @Override
  695. public void writeToParcel(Parcel dest, int flags) {
  696. dest.writeBundle(mVariables);
  697. dest.writeBundle(mFlags);
  698. dest.writeString(mLanguage);
  699. }
  700. private void readFromParcel(Parcel src) {
  701. mVariables = src.readBundle();
  702. mFlags = src.readBundle();
  703. mLanguage = src.readString();
  704. }
  705. public static final Parcelable.Creator<Parameters> CREATOR = new Parcelable.Creator<Parameters>() {
  706. @Override
  707. public Parameters createFromParcel(Parcel in) {
  708. return new Parameters(in);
  709. }
  710. @Override
  711. public Parameters[] newArray(int size) {
  712. return new Parameters[size];
  713. }
  714. };
  715. }
  716. }