121bb0deb36af32339521038cdbd827f74468df4aFred Quintana/*
221bb0deb36af32339521038cdbd827f74468df4aFred Quintana * Copyright (C) 2009 The Android Open Source Project
321bb0deb36af32339521038cdbd827f74468df4aFred Quintana *
421bb0deb36af32339521038cdbd827f74468df4aFred Quintana * Licensed under the Apache License, Version 2.0 (the "License");
521bb0deb36af32339521038cdbd827f74468df4aFred Quintana * you may not use this file except in compliance with the License.
621bb0deb36af32339521038cdbd827f74468df4aFred Quintana * You may obtain a copy of the License at
721bb0deb36af32339521038cdbd827f74468df4aFred Quintana *
821bb0deb36af32339521038cdbd827f74468df4aFred Quintana *      http://www.apache.org/licenses/LICENSE-2.0
921bb0deb36af32339521038cdbd827f74468df4aFred Quintana *
1021bb0deb36af32339521038cdbd827f74468df4aFred Quintana * Unless required by applicable law or agreed to in writing, software
1121bb0deb36af32339521038cdbd827f74468df4aFred Quintana * distributed under the License is distributed on an "AS IS" BASIS,
1221bb0deb36af32339521038cdbd827f74468df4aFred Quintana * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1321bb0deb36af32339521038cdbd827f74468df4aFred Quintana * See the License for the specific language governing permissions and
1421bb0deb36af32339521038cdbd827f74468df4aFred Quintana * limitations under the License.
1521bb0deb36af32339521038cdbd827f74468df4aFred Quintana */
1621bb0deb36af32339521038cdbd827f74468df4aFred Quintana
1721bb0deb36af32339521038cdbd827f74468df4aFred Quintanapackage android.content;
1821bb0deb36af32339521038cdbd827f74468df4aFred Quintana
1921bb0deb36af32339521038cdbd827f74468df4aFred Quintanaimport android.accounts.Account;
2021bb0deb36af32339521038cdbd827f74468df4aFred Quintanaimport android.os.Bundle;
21f038004f4a5e4fab18df9c87573ba1e82790c30fFred Quintanaimport android.os.IBinder;
221719a39a4c0ff3afbf9c9e5f03f20ba50f490902Ken Shirriffimport android.os.Process;
23e7424ffdafb0c18f753f383ebfb121ea5ebf582bFred Quintanaimport android.os.RemoteException;
2421bb0deb36af32339521038cdbd827f74468df4aFred Quintana
2521bb0deb36af32339521038cdbd827f74468df4aFred Quintanaimport java.util.concurrent.atomic.AtomicInteger;
2621bb0deb36af32339521038cdbd827f74468df4aFred Quintana
2721bb0deb36af32339521038cdbd827f74468df4aFred Quintana/**
2821bb0deb36af32339521038cdbd827f74468df4aFred Quintana * An abstract implementation of a SyncAdapter that spawns a thread to invoke a sync operation.
2921bb0deb36af32339521038cdbd827f74468df4aFred Quintana * If a sync operation is already in progress when a startSync() request is received then an error
3021bb0deb36af32339521038cdbd827f74468df4aFred Quintana * will be returned to the new request and the existing request will be allowed to continue.
3121bb0deb36af32339521038cdbd827f74468df4aFred Quintana * When a startSync() is received and there is no sync operation in progress then a thread
32f038004f4a5e4fab18df9c87573ba1e82790c30fFred Quintana * will be started to run the operation and {@link #onPerformSync} will be invoked on that thread.
3321bb0deb36af32339521038cdbd827f74468df4aFred Quintana * If a cancelSync() is received that matches an existing sync operation then the thread
3421bb0deb36af32339521038cdbd827f74468df4aFred Quintana * that is running that sync operation will be interrupted, which will indicate to the thread
3521bb0deb36af32339521038cdbd827f74468df4aFred Quintana * that the sync has been canceled.
3621bb0deb36af32339521038cdbd827f74468df4aFred Quintana */
3721bb0deb36af32339521038cdbd827f74468df4aFred Quintanapublic abstract class AbstractThreadedSyncAdapter {
3897ef7637c6799e72956db8e08192539f1b1942f6Fred Quintana    /**
3997ef7637c6799e72956db8e08192539f1b1942f6Fred Quintana     * Kernel event log tag.  Also listed in data/etc/event-log-tags.
4097ef7637c6799e72956db8e08192539f1b1942f6Fred Quintana     * @Deprecated
4197ef7637c6799e72956db8e08192539f1b1942f6Fred Quintana     */
4297ef7637c6799e72956db8e08192539f1b1942f6Fred Quintana    public static final int LOG_SYNC_DETAILS = 2743;
4397ef7637c6799e72956db8e08192539f1b1942f6Fred Quintana
4421bb0deb36af32339521038cdbd827f74468df4aFred Quintana    private final Context mContext;
4521bb0deb36af32339521038cdbd827f74468df4aFred Quintana    private final AtomicInteger mNumSyncStarts;
4621bb0deb36af32339521038cdbd827f74468df4aFred Quintana    private final ISyncAdapterImpl mISyncAdapterImpl;
4721bb0deb36af32339521038cdbd827f74468df4aFred Quintana
483cff76aaa893049d02467a231d477e86a0f80daaFred Quintana    // all accesses to this member variable must be synchronized on mSyncThreadLock
4921bb0deb36af32339521038cdbd827f74468df4aFred Quintana    private SyncThread mSyncThread;
503cff76aaa893049d02467a231d477e86a0f80daaFred Quintana    private final Object mSyncThreadLock = new Object();
5121bb0deb36af32339521038cdbd827f74468df4aFred Quintana
524a6679b97e0285c5b65ec5c0d9080ff90d3e9e81Fred Quintana    private final boolean mAutoInitialize;
5321bb0deb36af32339521038cdbd827f74468df4aFred Quintana
5421bb0deb36af32339521038cdbd827f74468df4aFred Quintana    /**
5521bb0deb36af32339521038cdbd827f74468df4aFred Quintana     * Creates an {@link AbstractThreadedSyncAdapter}.
564a6679b97e0285c5b65ec5c0d9080ff90d3e9e81Fred Quintana     * @param context the {@link android.content.Context} that this is running within.
574a6679b97e0285c5b65ec5c0d9080ff90d3e9e81Fred Quintana     * @param autoInitialize if true then sync requests that have
584a6679b97e0285c5b65ec5c0d9080ff90d3e9e81Fred Quintana     * {@link ContentResolver#SYNC_EXTRAS_INITIALIZE} set will be internally handled by
594a6679b97e0285c5b65ec5c0d9080ff90d3e9e81Fred Quintana     * {@link AbstractThreadedSyncAdapter} by calling
604a6679b97e0285c5b65ec5c0d9080ff90d3e9e81Fred Quintana     * {@link ContentResolver#setIsSyncable(android.accounts.Account, String, int)} with 1 if it
614a6679b97e0285c5b65ec5c0d9080ff90d3e9e81Fred Quintana     * is currently set to <0.
6221bb0deb36af32339521038cdbd827f74468df4aFred Quintana     */
634a6679b97e0285c5b65ec5c0d9080ff90d3e9e81Fred Quintana    public AbstractThreadedSyncAdapter(Context context, boolean autoInitialize) {
6421bb0deb36af32339521038cdbd827f74468df4aFred Quintana        mContext = context;
6521bb0deb36af32339521038cdbd827f74468df4aFred Quintana        mISyncAdapterImpl = new ISyncAdapterImpl();
6621bb0deb36af32339521038cdbd827f74468df4aFred Quintana        mNumSyncStarts = new AtomicInteger(0);
6721bb0deb36af32339521038cdbd827f74468df4aFred Quintana        mSyncThread = null;
684a6679b97e0285c5b65ec5c0d9080ff90d3e9e81Fred Quintana        mAutoInitialize = autoInitialize;
6921bb0deb36af32339521038cdbd827f74468df4aFred Quintana    }
7021bb0deb36af32339521038cdbd827f74468df4aFred Quintana
71c298a8518a8fd73a303132c7db241f10eb46c5b6Fred Quintana    public Context getContext() {
72c298a8518a8fd73a303132c7db241f10eb46c5b6Fred Quintana        return mContext;
73c298a8518a8fd73a303132c7db241f10eb46c5b6Fred Quintana    }
74c298a8518a8fd73a303132c7db241f10eb46c5b6Fred Quintana
75f038004f4a5e4fab18df9c87573ba1e82790c30fFred Quintana    private class ISyncAdapterImpl extends ISyncAdapter.Stub {
7621bb0deb36af32339521038cdbd827f74468df4aFred Quintana        public void startSync(ISyncContext syncContext, String authority, Account account,
7721bb0deb36af32339521038cdbd827f74468df4aFred Quintana                Bundle extras) {
7821bb0deb36af32339521038cdbd827f74468df4aFred Quintana            final SyncContext syncContextClient = new SyncContext(syncContext);
7921bb0deb36af32339521038cdbd827f74468df4aFred Quintana
8021bb0deb36af32339521038cdbd827f74468df4aFred Quintana            boolean alreadyInProgress;
8121bb0deb36af32339521038cdbd827f74468df4aFred Quintana            // synchronize to make sure that mSyncThread doesn't change between when we
8221bb0deb36af32339521038cdbd827f74468df4aFred Quintana            // check it and when we use it
833cff76aaa893049d02467a231d477e86a0f80daaFred Quintana            synchronized (mSyncThreadLock) {
8421bb0deb36af32339521038cdbd827f74468df4aFred Quintana                if (mSyncThread == null) {
854a6679b97e0285c5b65ec5c0d9080ff90d3e9e81Fred Quintana                    if (mAutoInitialize
864a6679b97e0285c5b65ec5c0d9080ff90d3e9e81Fred Quintana                            && extras != null
874a6679b97e0285c5b65ec5c0d9080ff90d3e9e81Fred Quintana                            && extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)) {
884a6679b97e0285c5b65ec5c0d9080ff90d3e9e81Fred Quintana                        if (ContentResolver.getIsSyncable(account, authority) < 0) {
894a6679b97e0285c5b65ec5c0d9080ff90d3e9e81Fred Quintana                            ContentResolver.setIsSyncable(account, authority, 1);
904a6679b97e0285c5b65ec5c0d9080ff90d3e9e81Fred Quintana                        }
914a6679b97e0285c5b65ec5c0d9080ff90d3e9e81Fred Quintana                        syncContextClient.onFinished(new SyncResult());
924a6679b97e0285c5b65ec5c0d9080ff90d3e9e81Fred Quintana                        return;
934a6679b97e0285c5b65ec5c0d9080ff90d3e9e81Fred Quintana                    }
9421bb0deb36af32339521038cdbd827f74468df4aFred Quintana                    mSyncThread = new SyncThread(
9521bb0deb36af32339521038cdbd827f74468df4aFred Quintana                            "SyncAdapterThread-" + mNumSyncStarts.incrementAndGet(),
9621bb0deb36af32339521038cdbd827f74468df4aFred Quintana                            syncContextClient, authority, account, extras);
9721bb0deb36af32339521038cdbd827f74468df4aFred Quintana                    mSyncThread.start();
9821bb0deb36af32339521038cdbd827f74468df4aFred Quintana                    alreadyInProgress = false;
9921bb0deb36af32339521038cdbd827f74468df4aFred Quintana                } else {
10021bb0deb36af32339521038cdbd827f74468df4aFred Quintana                    alreadyInProgress = true;
10121bb0deb36af32339521038cdbd827f74468df4aFred Quintana                }
10221bb0deb36af32339521038cdbd827f74468df4aFred Quintana            }
10321bb0deb36af32339521038cdbd827f74468df4aFred Quintana
10421bb0deb36af32339521038cdbd827f74468df4aFred Quintana            // do this outside since we don't want to call back into the syncContext while
10521bb0deb36af32339521038cdbd827f74468df4aFred Quintana            // holding the synchronization lock
10621bb0deb36af32339521038cdbd827f74468df4aFred Quintana            if (alreadyInProgress) {
10721bb0deb36af32339521038cdbd827f74468df4aFred Quintana                syncContextClient.onFinished(SyncResult.ALREADY_IN_PROGRESS);
10821bb0deb36af32339521038cdbd827f74468df4aFred Quintana            }
10921bb0deb36af32339521038cdbd827f74468df4aFred Quintana        }
11021bb0deb36af32339521038cdbd827f74468df4aFred Quintana
11121bb0deb36af32339521038cdbd827f74468df4aFred Quintana        public void cancelSync(ISyncContext syncContext) {
11221bb0deb36af32339521038cdbd827f74468df4aFred Quintana            // synchronize to make sure that mSyncThread doesn't change between when we
11321bb0deb36af32339521038cdbd827f74468df4aFred Quintana            // check it and when we use it
114d5e4fdc8a4743abc0d9fe3cb952a78f9ad078c6bFred Quintana            final SyncThread syncThread;
1153cff76aaa893049d02467a231d477e86a0f80daaFred Quintana            synchronized (mSyncThreadLock) {
116d5e4fdc8a4743abc0d9fe3cb952a78f9ad078c6bFred Quintana                syncThread = mSyncThread;
117d5e4fdc8a4743abc0d9fe3cb952a78f9ad078c6bFred Quintana            }
118d5e4fdc8a4743abc0d9fe3cb952a78f9ad078c6bFred Quintana            if (syncThread != null
119d5e4fdc8a4743abc0d9fe3cb952a78f9ad078c6bFred Quintana                    && syncThread.mSyncContext.getSyncContextBinder() == syncContext.asBinder()) {
120d5e4fdc8a4743abc0d9fe3cb952a78f9ad078c6bFred Quintana                onSyncCanceled();
12121bb0deb36af32339521038cdbd827f74468df4aFred Quintana            }
12221bb0deb36af32339521038cdbd827f74468df4aFred Quintana        }
123e7424ffdafb0c18f753f383ebfb121ea5ebf582bFred Quintana
124e7424ffdafb0c18f753f383ebfb121ea5ebf582bFred Quintana        public void initialize(Account account, String authority) throws RemoteException {
125e7424ffdafb0c18f753f383ebfb121ea5ebf582bFred Quintana            Bundle extras = new Bundle();
126e7424ffdafb0c18f753f383ebfb121ea5ebf582bFred Quintana            extras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
127e7424ffdafb0c18f753f383ebfb121ea5ebf582bFred Quintana            startSync(null, authority, account, extras);
128e7424ffdafb0c18f753f383ebfb121ea5ebf582bFred Quintana        }
12921bb0deb36af32339521038cdbd827f74468df4aFred Quintana    }
13021bb0deb36af32339521038cdbd827f74468df4aFred Quintana
13121bb0deb36af32339521038cdbd827f74468df4aFred Quintana    /**
132f038004f4a5e4fab18df9c87573ba1e82790c30fFred Quintana     * The thread that invokes {@link AbstractThreadedSyncAdapter#onPerformSync}. It also acquires
133f038004f4a5e4fab18df9c87573ba1e82790c30fFred Quintana     * the provider for this sync before calling onPerformSync and releases it afterwards. Cancel
134f038004f4a5e4fab18df9c87573ba1e82790c30fFred Quintana     * this thread in order to cancel the sync.
13521bb0deb36af32339521038cdbd827f74468df4aFred Quintana     */
13621bb0deb36af32339521038cdbd827f74468df4aFred Quintana    private class SyncThread extends Thread {
13721bb0deb36af32339521038cdbd827f74468df4aFred Quintana        private final SyncContext mSyncContext;
13821bb0deb36af32339521038cdbd827f74468df4aFred Quintana        private final String mAuthority;
13921bb0deb36af32339521038cdbd827f74468df4aFred Quintana        private final Account mAccount;
14021bb0deb36af32339521038cdbd827f74468df4aFred Quintana        private final Bundle mExtras;
14121bb0deb36af32339521038cdbd827f74468df4aFred Quintana
14221bb0deb36af32339521038cdbd827f74468df4aFred Quintana        private SyncThread(String name, SyncContext syncContext, String authority,
14321bb0deb36af32339521038cdbd827f74468df4aFred Quintana                Account account, Bundle extras) {
14421bb0deb36af32339521038cdbd827f74468df4aFred Quintana            super(name);
14521bb0deb36af32339521038cdbd827f74468df4aFred Quintana            mSyncContext = syncContext;
14621bb0deb36af32339521038cdbd827f74468df4aFred Quintana            mAuthority = authority;
14721bb0deb36af32339521038cdbd827f74468df4aFred Quintana            mAccount = account;
14821bb0deb36af32339521038cdbd827f74468df4aFred Quintana            mExtras = extras;
14921bb0deb36af32339521038cdbd827f74468df4aFred Quintana        }
15021bb0deb36af32339521038cdbd827f74468df4aFred Quintana
15121bb0deb36af32339521038cdbd827f74468df4aFred Quintana        public void run() {
15275d797c2e78d53f49d05b518adb14fd57e0c785cFred Quintana            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
15375d797c2e78d53f49d05b518adb14fd57e0c785cFred Quintana
15421bb0deb36af32339521038cdbd827f74468df4aFred Quintana            if (isCanceled()) {
15521bb0deb36af32339521038cdbd827f74468df4aFred Quintana                return;
15621bb0deb36af32339521038cdbd827f74468df4aFred Quintana            }
15721bb0deb36af32339521038cdbd827f74468df4aFred Quintana
15821bb0deb36af32339521038cdbd827f74468df4aFred Quintana            SyncResult syncResult = new SyncResult();
15921bb0deb36af32339521038cdbd827f74468df4aFred Quintana            ContentProviderClient provider = null;
16021bb0deb36af32339521038cdbd827f74468df4aFred Quintana            try {
16121bb0deb36af32339521038cdbd827f74468df4aFred Quintana                provider = mContext.getContentResolver().acquireContentProviderClient(mAuthority);
16221bb0deb36af32339521038cdbd827f74468df4aFred Quintana                if (provider != null) {
163f038004f4a5e4fab18df9c87573ba1e82790c30fFred Quintana                    AbstractThreadedSyncAdapter.this.onPerformSync(mAccount, mExtras,
16421bb0deb36af32339521038cdbd827f74468df4aFred Quintana                            mAuthority, provider, syncResult);
16521bb0deb36af32339521038cdbd827f74468df4aFred Quintana                } else {
166f038004f4a5e4fab18df9c87573ba1e82790c30fFred Quintana                    syncResult.databaseError = true;
16721bb0deb36af32339521038cdbd827f74468df4aFred Quintana                }
16821bb0deb36af32339521038cdbd827f74468df4aFred Quintana            } finally {
16921bb0deb36af32339521038cdbd827f74468df4aFred Quintana                if (provider != null) {
17021bb0deb36af32339521038cdbd827f74468df4aFred Quintana                    provider.release();
17121bb0deb36af32339521038cdbd827f74468df4aFred Quintana                }
17221bb0deb36af32339521038cdbd827f74468df4aFred Quintana                if (!isCanceled()) {
17321bb0deb36af32339521038cdbd827f74468df4aFred Quintana                    mSyncContext.onFinished(syncResult);
17421bb0deb36af32339521038cdbd827f74468df4aFred Quintana                }
17521bb0deb36af32339521038cdbd827f74468df4aFred Quintana                // synchronize so that the assignment will be seen by other threads
17621bb0deb36af32339521038cdbd827f74468df4aFred Quintana                // that also synchronize accesses to mSyncThread
1773cff76aaa893049d02467a231d477e86a0f80daaFred Quintana                synchronized (mSyncThreadLock) {
17821bb0deb36af32339521038cdbd827f74468df4aFred Quintana                    mSyncThread = null;
17921bb0deb36af32339521038cdbd827f74468df4aFred Quintana                }
18021bb0deb36af32339521038cdbd827f74468df4aFred Quintana            }
18121bb0deb36af32339521038cdbd827f74468df4aFred Quintana        }
18221bb0deb36af32339521038cdbd827f74468df4aFred Quintana
18321bb0deb36af32339521038cdbd827f74468df4aFred Quintana        private boolean isCanceled() {
18421bb0deb36af32339521038cdbd827f74468df4aFred Quintana            return Thread.currentThread().isInterrupted();
18521bb0deb36af32339521038cdbd827f74468df4aFred Quintana        }
18621bb0deb36af32339521038cdbd827f74468df4aFred Quintana    }
18721bb0deb36af32339521038cdbd827f74468df4aFred Quintana
18821bb0deb36af32339521038cdbd827f74468df4aFred Quintana    /**
189f038004f4a5e4fab18df9c87573ba1e82790c30fFred Quintana     * @return a reference to the IBinder of the SyncAdapter service.
19021bb0deb36af32339521038cdbd827f74468df4aFred Quintana     */
191f038004f4a5e4fab18df9c87573ba1e82790c30fFred Quintana    public final IBinder getSyncAdapterBinder() {
192f038004f4a5e4fab18df9c87573ba1e82790c30fFred Quintana        return mISyncAdapterImpl.asBinder();
19321bb0deb36af32339521038cdbd827f74468df4aFred Quintana    }
19421bb0deb36af32339521038cdbd827f74468df4aFred Quintana
19521bb0deb36af32339521038cdbd827f74468df4aFred Quintana    /**
19621bb0deb36af32339521038cdbd827f74468df4aFred Quintana     * Perform a sync for this account. SyncAdapter-specific parameters may
19721bb0deb36af32339521038cdbd827f74468df4aFred Quintana     * be specified in extras, which is guaranteed to not be null. Invocations
19821bb0deb36af32339521038cdbd827f74468df4aFred Quintana     * of this method are guaranteed to be serialized.
19921bb0deb36af32339521038cdbd827f74468df4aFred Quintana     *
20021bb0deb36af32339521038cdbd827f74468df4aFred Quintana     * @param account the account that should be synced
20121bb0deb36af32339521038cdbd827f74468df4aFred Quintana     * @param extras SyncAdapter-specific parameters
20221bb0deb36af32339521038cdbd827f74468df4aFred Quintana     * @param authority the authority of this sync request
20321bb0deb36af32339521038cdbd827f74468df4aFred Quintana     * @param provider a ContentProviderClient that points to the ContentProvider for this
20421bb0deb36af32339521038cdbd827f74468df4aFred Quintana     *   authority
20521bb0deb36af32339521038cdbd827f74468df4aFred Quintana     * @param syncResult SyncAdapter-specific parameters
20621bb0deb36af32339521038cdbd827f74468df4aFred Quintana     */
207f038004f4a5e4fab18df9c87573ba1e82790c30fFred Quintana    public abstract void onPerformSync(Account account, Bundle extras,
20821bb0deb36af32339521038cdbd827f74468df4aFred Quintana            String authority, ContentProviderClient provider, SyncResult syncResult);
209274dc9d35fdf5d0464f74071a9a8f14e497d4d5fFred Quintana
210274dc9d35fdf5d0464f74071a9a8f14e497d4d5fFred Quintana    /**
211274dc9d35fdf5d0464f74071a9a8f14e497d4d5fFred Quintana     * Indicates that a sync operation has been canceled. This will be invoked on a separate
212274dc9d35fdf5d0464f74071a9a8f14e497d4d5fFred Quintana     * thread than the sync thread and so you must consider the multi-threaded implications
213274dc9d35fdf5d0464f74071a9a8f14e497d4d5fFred Quintana     * of the work that you do in this method.
214274dc9d35fdf5d0464f74071a9a8f14e497d4d5fFred Quintana     *
215274dc9d35fdf5d0464f74071a9a8f14e497d4d5fFred Quintana     */
216d5e4fdc8a4743abc0d9fe3cb952a78f9ad078c6bFred Quintana    public void onSyncCanceled() {
217d5e4fdc8a4743abc0d9fe3cb952a78f9ad078c6bFred Quintana        final SyncThread syncThread;
218d5e4fdc8a4743abc0d9fe3cb952a78f9ad078c6bFred Quintana        synchronized (mSyncThreadLock) {
219d5e4fdc8a4743abc0d9fe3cb952a78f9ad078c6bFred Quintana            syncThread = mSyncThread;
220d5e4fdc8a4743abc0d9fe3cb952a78f9ad078c6bFred Quintana        }
221d5e4fdc8a4743abc0d9fe3cb952a78f9ad078c6bFred Quintana        if (syncThread != null) {
222d5e4fdc8a4743abc0d9fe3cb952a78f9ad078c6bFred Quintana            syncThread.interrupt();
223d5e4fdc8a4743abc0d9fe3cb952a78f9ad078c6bFred Quintana        }
224274dc9d35fdf5d0464f74071a9a8f14e497d4d5fFred Quintana    }
225f038004f4a5e4fab18df9c87573ba1e82790c30fFred Quintana}
226