1c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay/* 2c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay * Copyright (C) 2017 The Android Open Source Project 3c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay * 4c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay * Licensed under the Apache License, Version 2.0 (the "License"); 5c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay * you may not use this file except in compliance with the License. 6c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay * You may obtain a copy of the License at 7c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay * 8c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay * http://www.apache.org/licenses/LICENSE-2.0 9c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay * 10c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay * Unless required by applicable law or agreed to in writing, software 11c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay * distributed under the License is distributed on an "AS IS" BASIS, 12c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay * See the License for the specific language governing permissions and 14c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay * limitations under the License. 15c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay */ 16c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 17c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKaypackage com.example.android.support.content.demos; 18c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 19c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKayimport android.database.Cursor; 20c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKayimport android.net.Uri; 21c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKayimport android.os.Bundle; 22def582a5836579a3fadabfdbe4413cb1652bf098Aurimas Liutikasimport android.view.Menu; 23def582a5836579a3fadabfdbe4413cb1652bf098Aurimas Liutikasimport android.view.MenuItem; 24def582a5836579a3fadabfdbe4413cb1652bf098Aurimas Liutikasimport android.view.ViewGroup; 25def582a5836579a3fadabfdbe4413cb1652bf098Aurimas Liutikasimport android.widget.TextView; 26def582a5836579a3fadabfdbe4413cb1652bf098Aurimas Liutikas 27ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.annotation.NonNull; 28def582a5836579a3fadabfdbe4413cb1652bf098Aurimas Liutikasimport androidx.appcompat.app.AppCompatActivity; 29def582a5836579a3fadabfdbe4413cb1652bf098Aurimas Liutikasimport androidx.appcompat.widget.Toolbar; 30ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.contentpager.content.ContentPager; 31ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.contentpager.content.ContentPager.ContentCallback; 32ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.contentpager.content.LoaderQueryRunner; 33ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.contentpager.content.Query; 34ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.recyclerview.widget.LinearLayoutManager; 35ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.recyclerview.widget.RecyclerView; 36c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 37f87e4dfc7bcd2583e7908ca3cda65cec6c697080Aurimas Liutikasimport com.google.android.material.floatingactionbutton.FloatingActionButton; 38f87e4dfc7bcd2583e7908ca3cda65cec6c697080Aurimas Liutikasimport com.google.android.material.snackbar.BaseTransientBottomBar; 39f87e4dfc7bcd2583e7908ca3cda65cec6c697080Aurimas Liutikasimport com.google.android.material.snackbar.Snackbar; 40f87e4dfc7bcd2583e7908ca3cda65cec6c697080Aurimas Liutikas 419bf9bbbedf8aa2a71961e366d8f214cf61c8f296Aurimas Liutikasimport java.util.Locale; 429bf9bbbedf8aa2a71961e366d8f214cf61c8f296Aurimas Liutikas 43c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay/** 44c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay * ContentPager demo activity. 45c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay */ 46c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKaypublic class ContentPagerDemoActivity extends AppCompatActivity { 47c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 48c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay private static final int PAGE_SIZE = 20; 49c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 50c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay private RecyclerView mRecycler; 51c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay private ContentPager mPager; 52c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay private Adapter mAdapter; 53c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay private FloatingActionButton mFab; 54c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay private Menu mMenu; 55c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay private int mCurrentPage = -1; 56c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 57c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay @Override 58c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay protected void onCreate(Bundle savedInstanceState) { 59c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay super.onCreate(savedInstanceState); 60c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay setContentView(R.layout.activity_demo); 61c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 62c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay ContentPager.QueryRunner runner = new LoaderQueryRunner(this, getLoaderManager()); 63c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay mPager = new ContentPager(getContentResolver(), runner); 64c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay mAdapter = new Adapter(mPager, PAGE_SIZE); 65c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 66c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay mRecycler = (RecyclerView) findViewById(R.id.list); 67c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay mRecycler.setLayoutManager(new LinearLayoutManager(this)); 68c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay mRecycler.setAdapter(mAdapter); 69c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 70c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay setSupportActionBar(toolbar); 71c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay } 72c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 73c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay @Override 74c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay protected void onDestroy() { 75c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay super.onDestroy(); 76c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay mPager.reset(); 77c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay } 78c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 79c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay @Override 80c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay public boolean onCreateOptionsMenu(Menu menu) { 81c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay // Inflate the menu; this adds items to the action bar if it is present. 82c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay getMenuInflater().inflate(R.menu.menu_demo, menu); 83c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay mMenu = menu; 84c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay return true; 85c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay } 86c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 87c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay @Override 88c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay public boolean onOptionsItemSelected(MenuItem item) { 89c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 90c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay switch (item.getItemId()) { 91c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay case R.id.action_load: 92c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay onLoadContent(); 93c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay break; 94c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay case R.id.action_previous: 95c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay onLoadPreviousPage(); 96c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay break; 97c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay case R.id.action_next: 98c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay onLoadNextPage(); 99c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay break; 100c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay } 101c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay // Handle action bar item clicks here. The action bar will 102c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay // automatically handle clicks on the Home/Up button, so long 103c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay // as you specify a parent activity in AndroidManifest.xml. 104c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay int id = item.getItemId(); 105c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 106c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay //noinspection SimplifiableIfStatement 107c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay if (id == R.id.action_load) { 108c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay return true; 109c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay } 110c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 111c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay return super.onOptionsItemSelected(item); 112c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay } 113c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 114c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay private void onLoadContent() { 115c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay mAdapter.reset(UnpagedDemoDataProvider.URI); 116c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay mCurrentPage = 0; 117c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay mAdapter.loadPage(mCurrentPage); 118c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 119c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay updateOptionsMenu(); 120c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay } 121c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 122c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay private void onLoadNextPage() { 123c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay mAdapter.loadPage(mCurrentPage + 1); 124c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 125c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay updateOptionsMenu(); 126c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay } 127c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 128c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay private void onLoadPreviousPage() { 129c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay mAdapter.loadPage(mCurrentPage - 1); 130c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 131c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay updateOptionsMenu(); 132c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay } 133c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 134c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay private void updateOptionsMenu() { 135c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay MenuItem prev = mMenu.findItem(R.id.action_previous); 136c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay MenuItem next = mMenu.findItem(R.id.action_next); 137c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 138c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay int lastPage = (UnpagedDemoDataProvider.TOTAL_SIZE / PAGE_SIZE) - 1; 139c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay prev.setEnabled(mCurrentPage > 0); 140c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay next.setEnabled(mCurrentPage < lastPage); 141c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay } 142c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 143c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay private void msg(String msg) { 144c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay Snackbar.make( 145c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay mRecycler, 146f87e4dfc7bcd2583e7908ca3cda65cec6c697080Aurimas Liutikas msg, BaseTransientBottomBar.LENGTH_LONG) 147c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay .setAction("Action", null).show(); 148c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay } 149c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 150c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay private final class Adapter extends RecyclerView.Adapter<Holder> implements ContentCallback { 151c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 152c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay private final ContentPager mPager; 153c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay private final int mPageSize; 154c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 155c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay private Uri mUri; 156c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay private Cursor mCursor; 157c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 158c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay private Adapter(ContentPager pager, int pageSize) { 159c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay mPager = pager; 160c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay mPageSize = pageSize; 161c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay } 162c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 163c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay private void reset(Uri uri) { 164c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay mUri = uri; 165c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay mCursor = null; 166c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay } 167c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 168c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay void loadPage(int page) { 1699bf9bbbedf8aa2a71961e366d8f214cf61c8f296Aurimas Liutikas if (page < 0 || page >= (UnpagedDemoDataProvider.TOTAL_SIZE / PAGE_SIZE)) { 1709bf9bbbedf8aa2a71961e366d8f214cf61c8f296Aurimas Liutikas throw new IndexOutOfBoundsException(); 1719bf9bbbedf8aa2a71961e366d8f214cf61c8f296Aurimas Liutikas } 172c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 173c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay mCurrentPage = page; 174c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay int offset = mCurrentPage * mPageSize; 175c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay mPager.query(mUri, null, ContentPager.createArgs(offset, mPageSize), null, this); 176c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay } 177c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 178c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay @Override 179c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay public Holder onCreateViewHolder(ViewGroup parent, int viewType) { 180c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay return new Holder(new TextView(ContentPagerDemoActivity.this)); 181c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay } 182c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 183c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay @Override 184c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay public void onBindViewHolder(Holder holder, int position) { 185c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay if (!mCursor.moveToPosition(position)) { 186c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay holder.view.setText("Nope, couldn't position cursor to: " + position); 187c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay return; 188c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay } 189c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 1909bf9bbbedf8aa2a71961e366d8f214cf61c8f296Aurimas Liutikas holder.view.setText(String.format(Locale.US, 191c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay "%d.%d (%d): %s", 192c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay mCurrentPage, 193c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay mCursor.getInt(0), 194c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay mCursor.getLong(2), 195c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay mCursor.getString(1))); 196c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay } 197c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 198c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay @Override 199c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay public int getItemCount() { 200c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay return mCursor == null ? 0 : mCursor.getCount(); 201c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay } 202c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 203c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay @Override 2049bf9bbbedf8aa2a71961e366d8f214cf61c8f296Aurimas Liutikas public void onCursorReady(@NonNull Query query, Cursor cursor) { 205c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay if (cursor == null) { 206c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay msg("Content query returned a null cursor: " + query.getUri()); 207c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay } 208c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 209c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay mCurrentPage = query.getOffset() / mPageSize; 210c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay mCursor = cursor; 211c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay notifyDataSetChanged(); 212c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay } 213c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 214c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay private int getCorpusSize(Bundle extras) { 215c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay return extras.getInt(ContentPager.EXTRA_TOTAL_COUNT, -1); 216c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay } 217c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay } 218c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 219c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay private class Holder extends RecyclerView.ViewHolder { 220c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 221c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay public final TextView view; 222c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay 223c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay private Holder(TextView view) { 224c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay super(view); 225c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay this.view = view; 226c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay } 227c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay } 228c01ec9664a26a603d90b067d1ca7ca39950eb025Steve McKay} 229